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
41 changes: 0 additions & 41 deletions .github/agents/agentic-campaigns.agent.md

This file was deleted.

1,452 changes: 1,452 additions & 0 deletions .github/workflows/agentic-campaign-generator.lock.yml

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,6 @@ sync-templates:
@cp .github/aw/debug-agentic-workflow.md pkg/cli/templates/
@cp .github/aw/upgrade-agentic-workflows.md pkg/cli/templates/
@cp .github/agents/agentic-workflows.agent.md pkg/cli/templates/
@cp .github/agents/agentic-campaigns.agent.md pkg/cli/templates/
@cp .github/aw/orchestrate-agentic-campaign.md pkg/cli/templates/
@cp .github/aw/update-agentic-campaign-project.md pkg/cli/templates/
@cp .github/aw/execute-agentic-campaign-workflow.md pkg/cli/templates/
Expand Down
3 changes: 0 additions & 3 deletions pkg/cli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ var debugWorkflowPromptTemplate string
//go:embed templates/upgrade-agentic-workflows.md
var upgradeAgenticWorkflowsPromptTemplate string

//go:embed templates/agentic-campaigns.agent.md
var agenticCampaignsDispatcherTemplate string

//go:embed templates/create-agentic-campaign.md
var campaignCreationInstructionsTemplate string

Expand Down
39 changes: 39 additions & 0 deletions pkg/cli/compile_purge_campaign_lock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,37 @@ jobs:
t.Fatalf("Failed to create orphaned lock file: %v", err)
}

// Create agentic-campaign-generator source in .github/workflows and its lock file.
// This file should NOT be purged when --purge is used.
generatorMd := filepath.Join(workflowsDir, "agentic-campaign-generator.md")
generatorContent := `---
name: "Agentic Campaign Generator"
on:
issues:
types: [labeled]
engine: copilot
---

# Agentic Campaign Generator
`
if err := os.WriteFile(generatorMd, []byte(generatorContent), 0644); err != nil {
t.Fatalf("Failed to create generator source file: %v", err)
}

generatorLockYml := filepath.Join(workflowsDir, "agentic-campaign-generator.lock.yml")
generatorLockContent := `name: Agentic Campaign Generator
on:
issues:
types: [labeled]
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo "generator"`
if err := os.WriteFile(generatorLockYml, []byte(generatorLockContent), 0644); err != nil {
t.Fatalf("Failed to create generator lock file: %v", err)
}

// Verify files exist before purge
if _, err := os.Stat(campaignLockYml); os.IsNotExist(err) {
t.Fatal("Campaign lock file should exist before purge")
Expand All @@ -134,6 +165,9 @@ jobs:
if _, err := os.Stat(orphanedLockYml); os.IsNotExist(err) {
t.Fatal("Orphaned lock file should exist before purge")
}
if _, err := os.Stat(generatorLockYml); os.IsNotExist(err) {
t.Fatal("Generator lock file should exist before purge")
}

// Run compilation with purge flag
config := CompileConfig{
Expand Down Expand Up @@ -170,6 +204,11 @@ jobs:
t.Error("Orphaned lock file should have been purged")
}

// Verify agentic-campaign-generator lock file was NOT purged (source exists)
if _, err := os.Stat(generatorLockYml); os.IsNotExist(err) {
t.Error("agentic-campaign-generator.lock.yml should NOT be purged when source exists")
}

// Verify source files still exist
if _, err := os.Stat(campaignMd); os.IsNotExist(err) {
t.Error("Campaign .md file should still exist")
Expand Down
5 changes: 0 additions & 5 deletions pkg/cli/copilot-agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,11 +211,6 @@ func ensureUpgradeAgenticWorkflowsPrompt(verbose bool, skipInstructions bool) er
)
}

// ensureAgenticCampaignsDispatcher ensures that .github/agents/agentic-campaigns.agent.md contains the campaigns dispatcher agent
func ensureAgenticCampaignsDispatcher(verbose bool, skipInstructions bool) error {
return ensureAgentFromTemplate("agentic-campaigns.agent.md", agenticCampaignsDispatcherTemplate, verbose, skipInstructions)
}

// ensureCampaignOrchestratorInstructions ensures that .github/aw/orchestrate-agentic-campaign.md exists
func ensureCampaignOrchestratorInstructions(verbose bool, skipInstructions bool) error {
return ensureFileMatchesTemplate(
Expand Down
21 changes: 2 additions & 19 deletions pkg/cli/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,15 +125,6 @@ func InitRepository(verbose bool, mcp bool, campaign bool, tokens bool, engine s

// Write campaign dispatcher agent if requested
if campaign {
initLog.Print("Writing campaign dispatcher agent")
if err := ensureAgenticCampaignsDispatcher(verbose, false); err != nil {
initLog.Printf("Failed to write campaign dispatcher agent: %v", err)
return fmt.Errorf("failed to write campaign dispatcher agent: %w", err)
}
if verbose {
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Created campaign dispatcher agent"))
}

// Write campaign instruction files
initLog.Print("Writing campaign instruction files")
campaignEnsureFuncs := []struct {
Expand Down Expand Up @@ -298,22 +289,16 @@ func addCampaignGeneratorWorkflow(verbose bool) error {
return fmt.Errorf("failed to find git root: %w", err)
}

// The runnable artifact is the compiled lock file, which must be in .github/workflows.
// Keep the markdown source in .github/aw to match other gh-aw prompts.
// Keep the campaign generator source next to its lock file for consistency.
workflowsDir := filepath.Join(gitRoot, ".github", "workflows")
awDir := filepath.Join(gitRoot, ".github", "aw")
if err := os.MkdirAll(workflowsDir, 0755); err != nil {
initLog.Printf("Failed to create workflows directory: %v", err)
return fmt.Errorf("failed to create workflows directory: %w", err)
}
if err := os.MkdirAll(awDir, 0755); err != nil {
initLog.Printf("Failed to create .github/aw directory: %v", err)
return fmt.Errorf("failed to create .github/aw directory: %w", err)
}

// Build the agentic-campaign-generator workflow
data := campaign.BuildCampaignGenerator()
workflowPath := filepath.Join(awDir, "agentic-campaign-generator.md")
workflowPath := filepath.Join(workflowsDir, "agentic-campaign-generator.md")

// Render the workflow to markdown
content := renderCampaignGeneratorMarkdown(data)
Expand All @@ -329,8 +314,6 @@ func addCampaignGeneratorWorkflow(verbose bool) error {
}

// Compile to lock file using the standard compiler.
// agentic-campaign-generator.md lives in .github/aw, but MarkdownToLockFile is
// intentionally mapped to emit the runnable lock file into .github/workflows.
compiler := workflow.NewCompiler(verbose, "", GetVersion())
if err := CompileWorkflowWithValidation(compiler, workflowPath, verbose, false, false, false, false, false); err != nil {
initLog.Printf("Failed to compile agentic-campaign-generator: %v", err)
Expand Down
3 changes: 1 addition & 2 deletions pkg/cli/init_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ With --codespaces flag:
- Use without value (--codespaces) for current repo only, or with comma-separated repos (--codespaces repo1,repo2)

With --campaign flag:
- Creates .github/agents/agentic-campaigns.agent.md with the Campaigns dispatcher agent
- Adds (or reuses) .github/aw/agentic-campaign-generator.md source and compiles .github/workflows/agentic-campaign-generator.lock.yml for creating campaigns from issues
- Adds (or reuses) .github/workflows/agentic-campaign-generator.md source and compiles .github/workflows/agentic-campaign-generator.lock.yml for creating campaigns from issues
- Creates a 'create-agentic-campaign' label in your repository for triggering campaign workflows
- Enables campaign-related prompts and functionality for multi-workflow coordination

Expand Down
8 changes: 1 addition & 7 deletions pkg/cli/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,14 +273,8 @@ func TestInitRepository_Campaign(t *testing.T) {
t.Fatalf("InitRepository with campaign flag returned error: %v", err)
}

// Verify campaign dispatcher agent was created
campaignAgentPath := filepath.Join(tempDir, ".github", "agents", "agentic-campaigns.agent.md")
if _, err := os.Stat(campaignAgentPath); os.IsNotExist(err) {
t.Errorf("Expected campaign dispatcher agent to exist at %s", campaignAgentPath)
}

// Verify agentic-campaign-generator source markdown was generated
campaignWorkflowPath := filepath.Join(tempDir, ".github", "aw", "agentic-campaign-generator.md")
campaignWorkflowPath := filepath.Join(tempDir, ".github", "workflows", "agentic-campaign-generator.md")
if _, err := os.Stat(campaignWorkflowPath); os.IsNotExist(err) {
t.Errorf("Expected agentic-campaign-generator workflow to exist at %s", campaignWorkflowPath)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/templates/agentic-campaigns.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ This is a **dispatcher agent**. It routes your request to the right campaign pro

## Files This Applies To

- Campaign generator workflow source: `.github/aw/agentic-campaign-generator.md`
- Campaign generator workflow source: `.github/workflows/agentic-campaign-generator.md`
- Generator lock file: `.github/workflows/agentic-campaign-generator.lock.yml`
- Campaign specs: `.github/workflows/*.campaign.md`
- Campaign orchestrators: `.github/workflows/*.campaign.g.md` and `.github/workflows/*.campaign.lock.yml`
Expand Down
34 changes: 2 additions & 32 deletions pkg/stringutil/identifiers.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,24 +75,7 @@ func MarkdownToLockFile(mdPath string) string {
}

cleaned := filepath.Clean(mdPath)

// Special case: agentic-campaign-generator lives in .github/aw as markdown source,
// but its runnable compiled lock file must be in .github/workflows.
//
// This keeps the repo convention (markdown prompts in .github/aw) while
// satisfying GitHub Actions' requirement that runnable workflows live in
// .github/workflows.
if filepath.Base(cleaned) == "agentic-campaign-generator.md" {
dir := filepath.Dir(cleaned)
if filepath.Base(dir) == "aw" {
githubDir := filepath.Dir(dir)
if filepath.Base(githubDir) == ".github" {
return filepath.Join(githubDir, "workflows", "agentic-campaign-generator.lock.yml")
}
}
}

return strings.TrimSuffix(mdPath, ".md") + ".lock.yml"
return strings.TrimSuffix(cleaned, ".md") + ".lock.yml"
}

// LockFileToMarkdown converts a compiled lock file path back to its markdown source path.
Expand All @@ -114,20 +97,7 @@ func LockFileToMarkdown(lockPath string) string {
}

cleaned := filepath.Clean(lockPath)

// Special case: agentic-campaign-generator lock file lives in .github/workflows,
// but its markdown source lives in .github/aw.
if filepath.Base(cleaned) == "agentic-campaign-generator.lock.yml" {
dir := filepath.Dir(cleaned)
if filepath.Base(dir) == "workflows" {
githubDir := filepath.Dir(dir)
if filepath.Base(githubDir) == ".github" {
return filepath.Join(githubDir, "aw", "agentic-campaign-generator.md")
}
}
}

return strings.TrimSuffix(lockPath, ".lock.yml") + ".md"
return strings.TrimSuffix(cleaned, ".lock.yml") + ".md"
}

// CampaignSpecToOrchestrator converts a campaign specification file to its generated orchestrator file.
Expand Down
8 changes: 4 additions & 4 deletions pkg/stringutil/identifiers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,8 @@ func TestMarkdownToLockFile(t *testing.T) {
expected: "test.campaign.lock.yml",
},
{
name: "agentic-campaign-generator special-case mapping",
input: ".github/aw/agentic-campaign-generator.md",
name: "agentic-campaign-generator in workflows directory",
input: ".github/workflows/agentic-campaign-generator.md",
expected: ".github/workflows/agentic-campaign-generator.lock.yml",
},
}
Expand Down Expand Up @@ -257,9 +257,9 @@ func TestLockFileToMarkdown(t *testing.T) {
expected: "test.campaign.md",
},
{
name: "agentic-campaign-generator special-case mapping",
name: "agentic-campaign-generator in workflows directory",
input: ".github/workflows/agentic-campaign-generator.lock.yml",
expected: ".github/aw/agentic-campaign-generator.md",
expected: ".github/workflows/agentic-campaign-generator.md",
},
}

Expand Down
52 changes: 26 additions & 26 deletions pkg/workflow/action_pins_logging_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,44 +16,44 @@ func TestActionPinResolutionWithMismatchedVersions(t *testing.T) {
// version v2, but the comment still shows v1 (the requested version)

tests := []struct {
name string
repo string
requestedVer string
name string
repo string
requestedVer string
expectedCommentVer string // The version that should appear in the comment
fallbackPinVer string // The actual pin version used (for warning message)
expectMismatch bool
fallbackPinVer string // The actual pin version used (for warning message)
expectMismatch bool
}{
{
name: "ai-inference v1 resolves to v2 pin but comment shows v1",
repo: "actions/ai-inference",
requestedVer: "v1",
name: "ai-inference v1 resolves to v2 pin but comment shows v1",
repo: "actions/ai-inference",
requestedVer: "v1",
expectedCommentVer: "v1", // Comment shows requested version
fallbackPinVer: "v2", // Falls back to semver-compatible v2
expectMismatch: true,
fallbackPinVer: "v2", // Falls back to semver-compatible v2
expectMismatch: true,
},
{
name: "setup-dotnet v4 resolves to v4.3.1 pin but comment shows v4",
repo: "actions/setup-dotnet",
requestedVer: "v4",
name: "setup-dotnet v4 resolves to v4.3.1 pin but comment shows v4",
repo: "actions/setup-dotnet",
requestedVer: "v4",
expectedCommentVer: "v4", // Comment shows requested version
fallbackPinVer: "v4.3.1",
expectMismatch: true,
fallbackPinVer: "v4.3.1",
expectMismatch: true,
},
{
name: "github-script v7 resolves to v7 pin (exact match)",
repo: "actions/github-script",
requestedVer: "v7",
expectedCommentVer: "v7", // Exact match exists in hardcoded pins
fallbackPinVer: "v7",
expectMismatch: false, // No mismatch since exact match found
name: "github-script v7 resolves to v7 pin (exact match)",
repo: "actions/github-script",
requestedVer: "v7",
expectedCommentVer: "v7", // Exact match exists in hardcoded pins
fallbackPinVer: "v7",
expectMismatch: false, // No mismatch since exact match found
},
{
name: "checkout v5.0.1 exact match",
repo: "actions/checkout",
requestedVer: "v5.0.1",
name: "checkout v5.0.1 exact match",
repo: "actions/checkout",
requestedVer: "v5.0.1",
expectedCommentVer: "v5.0.1",
fallbackPinVer: "v5.0.1",
expectMismatch: false,
fallbackPinVer: "v5.0.1",
expectMismatch: false,
},
}

Expand Down
Loading
Loading