Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions .github/aw/schemas/agentic-workflow.json
Original file line number Diff line number Diff line change
Expand Up @@ -3102,13 +3102,20 @@
"type": "object",
"description": "Repo-memory configuration object",
"properties": {
"branch-prefix": {
"type": "string",
"minLength": 4,
"maxLength": 32,
"pattern": "^[a-zA-Z0-9_-]+$",
"description": "Branch prefix for memory storage (default: 'memory'). Must be 4-32 characters, alphanumeric with hyphens/underscores, and cannot be 'copilot'. Branch will be named {branch-prefix}/{id}"
},
"target-repo": {
"type": "string",
"description": "Target repository for memory storage (default: current repository). Format: owner/repo"
},
"branch-name": {
"type": "string",
"description": "Git branch name for memory storage (default: memory/default)"
"description": "Git branch name for memory storage (default: {branch-prefix}/default or memory/default if branch-prefix not set)"
},
"file-glob": {
"oneOf": [
Expand Down Expand Up @@ -3172,13 +3179,20 @@
"type": "string",
"description": "Memory identifier (required for array notation, default: 'default')"
},
"branch-prefix": {
"type": "string",
"minLength": 4,
"maxLength": 32,
"pattern": "^[a-zA-Z0-9_-]+$",
"description": "Branch prefix for memory storage (default: 'memory'). Must be 4-32 characters, alphanumeric with hyphens/underscores, and cannot be 'copilot'. Applied to all entries in the array. Branch will be named {branch-prefix}/{id}"
},
"target-repo": {
"type": "string",
"description": "Target repository for memory storage (default: current repository). Format: owner/repo"
},
"branch-name": {
"type": "string",
"description": "Git branch name for memory storage (default: memory/{id})"
"description": "Git branch name for memory storage (default: {branch-prefix}/{id} or memory/{id} if branch-prefix not set)"
},
"file-glob": {
"oneOf": [
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/daily-code-metrics.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/daily-code-metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ tracker-id: daily-code-metrics
engine: claude
tools:
repo-memory:
branch-name: memory/code-metrics
branch-prefix: daily
description: "Historical code quality and health metrics"
file-glob: ["*.json", "*.jsonl", "*.csv", "*.md"]
max-file-size: 102400 # 100KB
Expand Down
18 changes: 16 additions & 2 deletions pkg/parser/schemas/included_file_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -284,13 +284,20 @@
"type": "object",
"description": "Repo-memory configuration object",
"properties": {
"branch-prefix": {
"type": "string",
"minLength": 4,
"maxLength": 32,
"pattern": "^[a-zA-Z0-9_-]+$",
"description": "Branch prefix for memory storage (default: 'memory'). Must be 4-32 characters, alphanumeric with hyphens/underscores, and cannot be 'copilot'. Branch will be named {branch-prefix}/{id}"
},
"target-repo": {
"type": "string",
"description": "Target repository for memory storage (default: current repository). Format: owner/repo"
},
"branch-name": {
"type": "string",
"description": "Git branch name for memory storage (default: memory/default)"
"description": "Git branch name for memory storage (default: {branch-prefix}/default or memory/default if branch-prefix not set)"
},
"file-glob": {
"oneOf": [
Expand Down Expand Up @@ -350,13 +357,20 @@
"type": "string",
"description": "Memory identifier (required for array notation, default: 'default')"
},
"branch-prefix": {
"type": "string",
"minLength": 4,
"maxLength": 32,
"pattern": "^[a-zA-Z0-9_-]+$",
"description": "Branch prefix for memory storage (default: 'memory'). Must be 4-32 characters, alphanumeric with hyphens/underscores, and cannot be 'copilot'. Applied to all entries in the array. Branch will be named {branch-prefix}/{id}"
},
"target-repo": {
"type": "string",
"description": "Target repository for memory storage (default: current repository). Format: owner/repo"
},
"branch-name": {
"type": "string",
"description": "Git branch name for memory storage (default: memory/{id})"
"description": "Git branch name for memory storage (default: {branch-prefix}/{id} or memory/{id} if branch-prefix not set)"
},
"file-glob": {
"oneOf": [
Expand Down
18 changes: 16 additions & 2 deletions pkg/parser/schemas/main_workflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3102,13 +3102,20 @@
"type": "object",
"description": "Repo-memory configuration object",
"properties": {
"branch-prefix": {
"type": "string",
"minLength": 4,
"maxLength": 32,
"pattern": "^[a-zA-Z0-9_-]+$",
"description": "Branch prefix for memory storage (default: 'memory'). Must be 4-32 characters, alphanumeric with hyphens/underscores, and cannot be 'copilot'. Branch will be named {branch-prefix}/{id}"
},
"target-repo": {
"type": "string",
"description": "Target repository for memory storage (default: current repository). Format: owner/repo"
},
"branch-name": {
"type": "string",
"description": "Git branch name for memory storage (default: memory/default)"
"description": "Git branch name for memory storage (default: {branch-prefix}/default or memory/default if branch-prefix not set)"
},
"file-glob": {
"oneOf": [
Expand Down Expand Up @@ -3172,13 +3179,20 @@
"type": "string",
"description": "Memory identifier (required for array notation, default: 'default')"
},
"branch-prefix": {
"type": "string",
"minLength": 4,
"maxLength": 32,
"pattern": "^[a-zA-Z0-9_-]+$",
"description": "Branch prefix for memory storage (default: 'memory'). Must be 4-32 characters, alphanumeric with hyphens/underscores, and cannot be 'copilot'. Applied to all entries in the array. Branch will be named {branch-prefix}/{id}"
},
"target-repo": {
"type": "string",
"description": "Target repository for memory storage (default: current repository). Format: owner/repo"
},
"branch-name": {
"type": "string",
"description": "Git branch name for memory storage (default: memory/{id})"
"description": "Git branch name for memory storage (default: {branch-prefix}/{id} or memory/{id} if branch-prefix not set)"
},
"file-glob": {
"oneOf": [
Expand Down
83 changes: 72 additions & 11 deletions pkg/workflow/repo_memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package workflow

import (
"fmt"
"regexp"
"strings"

"github.com/githubnext/gh-aw/pkg/logger"
Expand All @@ -27,7 +28,8 @@ var repoMemoryLog = logger.New("workflow:repo_memory")

// RepoMemoryConfig holds configuration for repo-memory functionality
type RepoMemoryConfig struct {
Memories []RepoMemoryEntry `yaml:"memories,omitempty"` // repo-memory configurations
BranchPrefix string `yaml:"branch-prefix,omitempty"` // branch prefix (default: "memory")
Memories []RepoMemoryEntry `yaml:"memories,omitempty"` // repo-memory configurations
}

// RepoMemoryEntry represents a single repo-memory configuration
Expand All @@ -49,12 +51,40 @@ type RepoMemoryToolConfig struct {
Raw any `yaml:"-"`
}

// generateDefaultBranchName generates a default branch name for a given memory ID
func generateDefaultBranchName(memoryID string) string {
if memoryID == "default" {
return "memory/default"
// generateDefaultBranchName generates a default branch name for a given memory ID and prefix
func generateDefaultBranchName(memoryID string, branchPrefix string) string {
if branchPrefix == "" {
branchPrefix = "memory"
}
return fmt.Sprintf("memory/%s", memoryID)
return fmt.Sprintf("%s/%s", branchPrefix, memoryID)
}

// validateBranchPrefix validates that the branch prefix meets requirements
func validateBranchPrefix(prefix string) error {
if prefix == "" {
return nil // Empty means use default
}

// Check length (4-32 characters)
if len(prefix) < 4 {
return fmt.Errorf("branch-prefix must be at least 4 characters long, got %d", len(prefix))
}
if len(prefix) > 32 {
return fmt.Errorf("branch-prefix must be at most 32 characters long, got %d", len(prefix))
}

// Check for alphanumeric and branch-friendly characters (alphanumeric, hyphens, underscores)
validPattern := regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
if !validPattern.MatchString(prefix) {
return fmt.Errorf("branch-prefix must contain only alphanumeric characters, hyphens, and underscores, got '%s'", prefix)
}

// Cannot be "copilot"
if strings.ToLower(prefix) == "copilot" {
return fmt.Errorf("branch-prefix cannot be 'copilot' (reserved)")
}

return nil
}

// extractRepoMemoryConfig extracts repo-memory configuration from tools section
Expand All @@ -66,7 +96,9 @@ func (c *Compiler) extractRepoMemoryConfig(toolsConfig *ToolsConfig) (*RepoMemor

repoMemoryLog.Print("Extracting repo-memory configuration from ToolsConfig")

config := &RepoMemoryConfig{}
config := &RepoMemoryConfig{
BranchPrefix: "memory", // Default branch prefix
}
repoMemoryValue := toolsConfig.RepoMemory.Raw

// Handle nil value (simple enable with defaults) - same as true
Expand All @@ -75,7 +107,7 @@ func (c *Compiler) extractRepoMemoryConfig(toolsConfig *ToolsConfig) (*RepoMemor
config.Memories = []RepoMemoryEntry{
{
ID: "default",
BranchName: generateDefaultBranchName("default"),
BranchName: generateDefaultBranchName("default", config.BranchPrefix),
MaxFileSize: 10240, // 10KB
MaxFileCount: 100,
CreateOrphan: true,
Expand All @@ -92,7 +124,7 @@ func (c *Compiler) extractRepoMemoryConfig(toolsConfig *ToolsConfig) (*RepoMemor
config.Memories = []RepoMemoryEntry{
{
ID: "default",
BranchName: generateDefaultBranchName("default"),
BranchName: generateDefaultBranchName("default", config.BranchPrefix),
MaxFileSize: 10240, // 10KB
MaxFileCount: 100,
CreateOrphan: true,
Expand All @@ -109,6 +141,23 @@ func (c *Compiler) extractRepoMemoryConfig(toolsConfig *ToolsConfig) (*RepoMemor
if memoryArray, ok := repoMemoryValue.([]any); ok {
repoMemoryLog.Printf("Processing memory array with %d entries", len(memoryArray))
config.Memories = make([]RepoMemoryEntry, 0, len(memoryArray))

// Parse branch-prefix from first item if it's a map with branch-prefix key
// This allows branch-prefix to be set at the top level for all memories
if len(memoryArray) > 0 {
if firstItem, ok := memoryArray[0].(map[string]any); ok {
if branchPrefix, exists := firstItem["branch-prefix"]; exists {
if prefixStr, ok := branchPrefix.(string); ok {
if err := validateBranchPrefix(prefixStr); err != nil {
return nil, err
}
config.BranchPrefix = prefixStr
repoMemoryLog.Printf("Using custom branch-prefix: %s", prefixStr)
}
}
}
}

for _, item := range memoryArray {
if memoryMap, ok := item.(map[string]any); ok {
entry := RepoMemoryEntry{
Expand Down Expand Up @@ -143,7 +192,7 @@ func (c *Compiler) extractRepoMemoryConfig(toolsConfig *ToolsConfig) (*RepoMemor
}
// Set default branch name if not specified
if entry.BranchName == "" {
entry.BranchName = generateDefaultBranchName(entry.ID)
entry.BranchName = generateDefaultBranchName(entry.ID, config.BranchPrefix)
}

// Parse file-glob
Expand Down Expand Up @@ -228,9 +277,21 @@ func (c *Compiler) extractRepoMemoryConfig(toolsConfig *ToolsConfig) (*RepoMemor
// Convert to array with single entry
if configMap, ok := repoMemoryValue.(map[string]any); ok {
repoMemoryLog.Print("Processing object-style repo-memory configuration (backward compatible)")

// Parse branch-prefix if provided
if branchPrefix, exists := configMap["branch-prefix"]; exists {
if prefixStr, ok := branchPrefix.(string); ok {
if err := validateBranchPrefix(prefixStr); err != nil {
return nil, err
}
config.BranchPrefix = prefixStr
repoMemoryLog.Printf("Using custom branch-prefix: %s", prefixStr)
}
}

entry := RepoMemoryEntry{
ID: "default",
BranchName: generateDefaultBranchName("default"),
BranchName: generateDefaultBranchName("default", config.BranchPrefix),
MaxFileSize: 10240, // 10KB default
MaxFileCount: 100, // 100 files default
CreateOrphan: true, // create orphan by default
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/repo_memory_path_consistency_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ func TestRepoMemoryBranchNameGeneration(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
branchName := generateDefaultBranchName(tt.memoryID)
branchName := generateDefaultBranchName(tt.memoryID, "memory")
assert.Equal(t, tt.expectedBranchName, branchName,
"Generated branch name should match expected pattern")
})
Expand Down
Loading
Loading