diff --git a/.github/workflows/issue-arborist.lock.yml b/.github/workflows/issue-arborist.lock.yml index 3273c4371f..857827468f 100644 --- a/.github/workflows/issue-arborist.lock.yml +++ b/.github/workflows/issue-arborist.lock.yml @@ -115,7 +115,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} name: Fetch issues data - run: "# Create output directory\nmkdir -p /tmp/gh-aw/issues-data\n\necho \"⬇ Downloading the last 100 open issues (excluding sub-issues)...\"\n\n# Fetch the last 100 open issues that don't have a parent issue\n# Using search filter to exclude issues that are already sub-issues\ngh issue list --repo ${{ github.repository }} \\\n --search \"no:parent-issue\" \\\n --state open \\\n --json number,title,author,createdAt,state,url,body,labels,updatedAt,closedAt,milestone,assignees \\\n --limit 100 \\\n > /tmp/gh-aw/issues-data/issues.json\n\n# Generate schema for reference using jqschema\n/tmp/gh-aw/jqschema.sh < /tmp/gh-aw/issues-data/issues.json > /tmp/gh-aw/issues-data/issues-schema.json\n\necho \"✓ Issues data saved to /tmp/gh-aw/issues-data/issues.json\"\necho \"✓ Schema saved to /tmp/gh-aw/issues-data/issues-schema.json\"\necho \"Total issues fetched: $(jq 'length' /tmp/gh-aw/issues-data/issues.json)\"\necho \"\"\necho \"Schema of the issues data:\"\ncat /tmp/gh-aw/issues-data/issues-schema.json | jq .\n" + run: "# Create output directory\nmkdir -p /tmp/gh-aw/issues-data\n\necho \"⬇ Downloading the last 100 open issues (excluding sub-issues)...\"\n\n# Fetch the last 100 open issues that don't have a parent issue\n# Using search filter to exclude issues that are already sub-issues\ngh issue list --repo ${{ github.repository }} \\\n --search \"-parent-issue:*\" \\\n --state open \\\n --json number,title,author,createdAt,state,url,body,labels,updatedAt,closedAt,milestone,assignees \\\n --limit 100 \\\n > /tmp/gh-aw/issues-data/issues.json\n\n# Generate schema for reference using jqschema\n/tmp/gh-aw/jqschema.sh < /tmp/gh-aw/issues-data/issues.json > /tmp/gh-aw/issues-data/issues-schema.json\n\necho \"✓ Issues data saved to /tmp/gh-aw/issues-data/issues.json\"\necho \"✓ Schema saved to /tmp/gh-aw/issues-data/issues-schema.json\"\necho \"Total issues fetched: $(jq 'length' /tmp/gh-aw/issues-data/issues.json)\"\necho \"\"\necho \"Schema of the issues data:\"\ncat /tmp/gh-aw/issues-data/issues-schema.json | jq .\n" - name: Configure Git credentials env: diff --git a/.github/workflows/issue-arborist.md b/.github/workflows/issue-arborist.md index b4be948a09..e93edaa832 100644 --- a/.github/workflows/issue-arborist.md +++ b/.github/workflows/issue-arborist.md @@ -37,7 +37,7 @@ steps: # Fetch the last 100 open issues that don't have a parent issue # Using search filter to exclude issues that are already sub-issues gh issue list --repo ${{ github.repository }} \ - --search "no:parent-issue" \ + --search "-parent-issue:*" \ --state open \ --json number,title,author,createdAt,state,url,body,labels,updatedAt,closedAt,milestone,assignees \ --limit 100 \ diff --git a/pkg/parser/import_processor.go b/pkg/parser/import_processor.go index e85b976047..03229b7b73 100644 --- a/pkg/parser/import_processor.go +++ b/pkg/parser/import_processor.go @@ -172,12 +172,12 @@ func processImportsFromFrontmatterWithManifestAndSource(frontmatter map[string]a var engines []string var safeOutputs []string var safeInputs []string - var bots []string // Track unique bot names - botsSet := make(map[string]bool) // Set for deduplicating bots - var labels []string // Track unique labels - labelsSet := make(map[string]bool) // Set for deduplicating labels - var caches []string // Track cache configurations (appended in order) - var agentFile string // Track custom agent file + var bots []string // Track unique bot names + botsSet := make(map[string]bool) // Set for deduplicating bots + var labels []string // Track unique labels + labelsSet := make(map[string]bool) // Set for deduplicating labels + var caches []string // Track cache configurations (appended in order) + var agentFile string // Track custom agent file importInputs := make(map[string]any) // Aggregated input values from all imports // Seed the queue with initial imports diff --git a/pkg/parser/schema_validation_test.go b/pkg/parser/schema_validation_test.go index b8f575b513..7629778eb2 100644 --- a/pkg/parser/schema_validation_test.go +++ b/pkg/parser/schema_validation_test.go @@ -15,8 +15,8 @@ func TestForbiddenFieldsInSharedWorkflows(t *testing.T) { for _, field := range forbiddenFields { t.Run("reject_"+field, func(t *testing.T) { frontmatter := map[string]any{ - field: "test-value", - "tools": map[string]any{"bash": true}, + field: "test-value", + "tools": map[string]any{"bash": true}, } err := ValidateIncludedFileFrontmatterWithSchema(frontmatter) @@ -33,40 +33,40 @@ func TestForbiddenFieldsInSharedWorkflows(t *testing.T) { // TestAllowedFieldsInSharedWorkflows verifies allowed fields work correctly func TestAllowedFieldsInSharedWorkflows(t *testing.T) { -allowedFields := map[string]any{ -"tools": map[string]any{"bash": true}, -"engine": "copilot", -"network": map[string]any{"allowed": []string{"defaults"}}, -"mcp-servers": map[string]any{}, -"permissions": "read-all", -"runtimes": map[string]any{"node": map[string]any{"version": "20"}}, -"safe-outputs": map[string]any{}, -"safe-inputs": map[string]any{}, -"services": map[string]any{}, -"steps": []any{}, -"secret-masking": true, -"jobs": map[string]any{"test": map[string]any{"runs-on": "ubuntu-latest", "steps": []any{map[string]any{"run": "echo test"}}}}, -"description": "test", -"metadata": map[string]any{}, -"inputs": map[string]any{}, -"bots": []string{"copilot"}, -"post-steps": []any{map[string]any{"run": "echo cleanup"}}, -"labels": []string{"automation", "testing"}, -"imports": []string{"./shared.md"}, -"cache": map[string]any{"key": "test-key", "path": "node_modules"}, -"source": "githubnext/agentics/workflows/ci-doctor.md@v1.0.0", -} + allowedFields := map[string]any{ + "tools": map[string]any{"bash": true}, + "engine": "copilot", + "network": map[string]any{"allowed": []string{"defaults"}}, + "mcp-servers": map[string]any{}, + "permissions": "read-all", + "runtimes": map[string]any{"node": map[string]any{"version": "20"}}, + "safe-outputs": map[string]any{}, + "safe-inputs": map[string]any{}, + "services": map[string]any{}, + "steps": []any{}, + "secret-masking": true, + "jobs": map[string]any{"test": map[string]any{"runs-on": "ubuntu-latest", "steps": []any{map[string]any{"run": "echo test"}}}}, + "description": "test", + "metadata": map[string]any{}, + "inputs": map[string]any{}, + "bots": []string{"copilot"}, + "post-steps": []any{map[string]any{"run": "echo cleanup"}}, + "labels": []string{"automation", "testing"}, + "imports": []string{"./shared.md"}, + "cache": map[string]any{"key": "test-key", "path": "node_modules"}, + "source": "githubnext/agentics/workflows/ci-doctor.md@v1.0.0", + } -for field, value := range allowedFields { -t.Run("allow_"+field, func(t *testing.T) { -frontmatter := map[string]any{ -field: value, -} + for field, value := range allowedFields { + t.Run("allow_"+field, func(t *testing.T) { + frontmatter := map[string]any{ + field: value, + } -err := ValidateIncludedFileFrontmatterWithSchema(frontmatter) -if err != nil && strings.Contains(err.Error(), "cannot be used in shared workflows") { -t.Errorf("Field '%s' should be allowed in shared workflows, got error: %v", field, err) -} -}) -} + err := ValidateIncludedFileFrontmatterWithSchema(frontmatter) + if err != nil && strings.Contains(err.Error(), "cannot be used in shared workflows") { + t.Errorf("Field '%s' should be allowed in shared workflows, got error: %v", field, err) + } + }) + } } diff --git a/pkg/workflow/create_issue_group_test.go b/pkg/workflow/create_issue_group_test.go index 6764b58580..bc3194d242 100644 --- a/pkg/workflow/create_issue_group_test.go +++ b/pkg/workflow/create_issue_group_test.go @@ -3,7 +3,6 @@ package workflow import ( "os" "path/filepath" - "strings" "testing" "github.com/githubnext/gh-aw/pkg/testutil" @@ -205,8 +204,8 @@ Test grouping with title prefix. compiledStr := string(compiledContent) // Verify both group and title_prefix are in the handler config - assert.True(t, strings.Contains(compiledStr, `"group":true`), "Expected group:true in compiled workflow") - assert.True(t, strings.Contains(compiledStr, `title_prefix`), "Expected title_prefix in compiled workflow") + assert.Contains(t, compiledStr, `"group":true`, "Expected group:true in compiled workflow") + assert.Contains(t, compiledStr, `title_prefix`, "Expected title_prefix in compiled workflow") } // TestCreateIssueGroupInMCPConfig verifies group flag is passed to MCP config diff --git a/pkg/workflow/forbidden_fields_import_test.go b/pkg/workflow/forbidden_fields_import_test.go index aa4c248ae5..7d1e29b706 100644 --- a/pkg/workflow/forbidden_fields_import_test.go +++ b/pkg/workflow/forbidden_fields_import_test.go @@ -81,7 +81,7 @@ This workflow imports a shared workflow with forbidden field. // Should get error about forbidden field require.Error(t, err, "Expected error for forbidden field '%s'", field) - assert.Contains(t, err.Error(), "cannot be used in shared workflows", + assert.Contains(t, err.Error(), "cannot be used in shared workflows", "Error should mention forbidden field, got: %v", err) }) } @@ -168,12 +168,12 @@ This workflow imports a shared workflow with allowed field. // TestImportsFieldAllowedInSharedWorkflows tests that the "imports" field is allowed in shared workflows // and that nested imports work correctly func TestImportsFieldAllowedInSharedWorkflows(t *testing.T) { -tempDir := testutil.TempDir(t, "test-allowed-imports-*") -workflowsDir := filepath.Join(tempDir, ".github", "workflows") -require.NoError(t, os.MkdirAll(workflowsDir, 0755)) + tempDir := testutil.TempDir(t, "test-allowed-imports-*") + workflowsDir := filepath.Join(tempDir, ".github", "workflows") + require.NoError(t, os.MkdirAll(workflowsDir, 0755)) -// Create a base shared workflow (level 2) -baseSharedContent := `--- + // Create a base shared workflow (level 2) + baseSharedContent := `--- tools: bash: true labels: ["base"] @@ -183,11 +183,11 @@ labels: ["base"] This is the base shared workflow. ` -baseSharedPath := filepath.Join(workflowsDir, "base.md") -require.NoError(t, os.WriteFile(baseSharedPath, []byte(baseSharedContent), 0644)) + baseSharedPath := filepath.Join(workflowsDir, "base.md") + require.NoError(t, os.WriteFile(baseSharedPath, []byte(baseSharedContent), 0644)) -// Create intermediate shared workflow with "imports" field (level 1) -intermediateSharedContent := `--- + // Create intermediate shared workflow with "imports" field (level 1) + intermediateSharedContent := `--- imports: - ./base.md tools: @@ -199,11 +199,11 @@ labels: ["intermediate"] This shared workflow imports another shared workflow (nested imports). ` -intermediateSharedPath := filepath.Join(workflowsDir, "intermediate.md") -require.NoError(t, os.WriteFile(intermediateSharedPath, []byte(intermediateSharedContent), 0644)) + intermediateSharedPath := filepath.Join(workflowsDir, "intermediate.md") + require.NoError(t, os.WriteFile(intermediateSharedPath, []byte(intermediateSharedContent), 0644)) -// Create main workflow that imports the intermediate shared workflow -mainContent := `--- + // Create main workflow that imports the intermediate shared workflow + mainContent := `--- on: issues imports: - ./intermediate.md @@ -213,15 +213,15 @@ imports: This workflow imports a shared workflow that itself has imports (nested). ` -mainPath := filepath.Join(workflowsDir, "main.md") -require.NoError(t, os.WriteFile(mainPath, []byte(mainContent), 0644)) + mainPath := filepath.Join(workflowsDir, "main.md") + require.NoError(t, os.WriteFile(mainPath, []byte(mainContent), 0644)) -// Compile - should succeed because shared workflows can have imports (nested imports are supported) -compiler := NewCompiler(false, tempDir, "test") -err := compiler.CompileWorkflow(mainPath) + // Compile - should succeed because shared workflows can have imports (nested imports are supported) + compiler := NewCompiler(false, tempDir, "test") + err := compiler.CompileWorkflow(mainPath) -// Should NOT get error about forbidden field -if err != nil && strings.Contains(err.Error(), "cannot be used in shared workflows") { -t.Errorf("Field 'imports' should be allowed in shared workflows, got error: %v", err) -} + // Should NOT get error about forbidden field + if err != nil && strings.Contains(err.Error(), "cannot be used in shared workflows") { + t.Errorf("Field 'imports' should be allowed in shared workflows, got error: %v", err) + } }