Skip to content

Commit c4c684b

Browse files
authored
Download agent file from GitHub on demand with version-aware URL patching (#13612)
1 parent cc46afd commit c4c684b

20 files changed

+538
-5200
lines changed

.github/agents/agentic-workflows.agent.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Workflows may optionally include:
2727
- Workflow files: `.github/workflows/*.md` and `.github/workflows/**/*.md`
2828
- Workflow lock files: `.github/workflows/*.lock.yml`
2929
- Shared components: `.github/workflows/shared/*.md`
30-
- Configuration: `.github/aw/github-agentic-workflows.md`
30+
- Configuration: https://github.com/github/gh-aw/blob/main/.github/aw/github-agentic-workflows.md
3131

3232
## Problems This Solves
3333

@@ -49,7 +49,7 @@ When you interact with this agent, it will:
4949
### Create New Workflow
5050
**Load when**: User wants to create a new workflow from scratch, add automation, or design a workflow that doesn't exist yet
5151

52-
**Prompt file**: `.github/aw/create-agentic-workflow.md`
52+
**Prompt file**: https://github.com/github/gh-aw/blob/main/.github/aw/create-agentic-workflow.md
5353

5454
**Use cases**:
5555
- "Create a workflow that triages issues"
@@ -59,7 +59,7 @@ When you interact with this agent, it will:
5959
### Update Existing Workflow
6060
**Load when**: User wants to modify, improve, or refactor an existing workflow
6161

62-
**Prompt file**: `.github/aw/update-agentic-workflow.md`
62+
**Prompt file**: https://github.com/github/gh-aw/blob/main/.github/aw/update-agentic-workflow.md
6363

6464
**Use cases**:
6565
- "Add web-fetch tool to the issue-classifier workflow"
@@ -69,7 +69,7 @@ When you interact with this agent, it will:
6969
### Debug Workflow
7070
**Load when**: User needs to investigate, audit, debug, or understand a workflow, troubleshoot issues, analyze logs, or fix errors
7171

72-
**Prompt file**: `.github/aw/debug-agentic-workflow.md`
72+
**Prompt file**: https://github.com/github/gh-aw/blob/main/.github/aw/debug-agentic-workflow.md
7373

7474
**Use cases**:
7575
- "Why is this workflow failing?"
@@ -79,7 +79,7 @@ When you interact with this agent, it will:
7979
### Upgrade Agentic Workflows
8080
**Load when**: User wants to upgrade workflows to a new gh-aw version or fix deprecations
8181

82-
**Prompt file**: `.github/aw/upgrade-agentic-workflows.md`
82+
**Prompt file**: https://github.com/github/gh-aw/blob/main/.github/aw/upgrade-agentic-workflows.md
8383

8484
**Use cases**:
8585
- "Upgrade all workflows to the latest version"
@@ -89,7 +89,7 @@ When you interact with this agent, it will:
8989
### Create Shared Agentic Workflow
9090
**Load when**: User wants to create a reusable workflow component or wrap an MCP server
9191

92-
**Prompt file**: `.github/aw/create-shared-agentic-workflow.md`
92+
**Prompt file**: https://github.com/github/gh-aw/blob/main/.github/aw/create-shared-agentic-workflow.md
9393

9494
**Use cases**:
9595
- "Create a shared component for Notion integration"
@@ -101,7 +101,7 @@ When you interact with this agent, it will:
101101
When a user interacts with you:
102102

103103
1. **Identify the task type** from the user's request
104-
2. **Load the appropriate prompt** using `.github/aw/<prompt-name>.md`
104+
2. **Load the appropriate prompt** from the GitHub repository URLs listed above
105105
3. **Follow the loaded prompt's instructions** exactly
106106
4. **If uncertain**, ask clarifying questions to determine the right prompt
107107

@@ -138,7 +138,7 @@ gh aw compile --validate
138138

139139
## Important Notes
140140

141-
- Always reference the instructions file at `.github/aw/github-agentic-workflows.md` for complete documentation
141+
- Always reference the instructions file at https://github.com/github/gh-aw/blob/main/.github/aw/github-agentic-workflows.md for complete documentation
142142
- Use the MCP tool `agentic-workflows` when running in GitHub Copilot Cloud
143143
- Workflows must be compiled to `.lock.yml` files before running in GitHub Actions
144144
- Follow security best practices: minimal permissions, explicit network access, no template injection

Makefile

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ all: build
1919

2020
# Build the binary, run make deps before this
2121
.PHONY: build
22-
build: sync-templates sync-action-pins sync-action-scripts
22+
build: sync-action-pins sync-action-scripts
2323
go build $(LDFLAGS) -o $(BINARY_NAME) ./cmd/gh-aw
2424

2525
# Build for all platforms
@@ -579,26 +579,6 @@ clean-docs:
579579
@echo "✓ Documentation artifacts cleaned"
580580

581581
# Sync templates from .github to pkg/cli/templates
582-
.PHONY: sync-templates
583-
sync-templates:
584-
@echo "Syncing templates from .github to pkg/cli/templates..."
585-
@mkdir -p pkg/cli/templates
586-
587-
# Workflow management templates
588-
@cp .github/aw/github-agentic-workflows.md pkg/cli/templates/
589-
@cp .github/aw/create-agentic-workflow.md pkg/cli/templates/
590-
@cp .github/aw/create-shared-agentic-workflow.md pkg/cli/templates/
591-
@cp .github/aw/debug-agentic-workflow.md pkg/cli/templates/
592-
@cp .github/aw/update-agentic-workflow.md pkg/cli/templates/
593-
@cp .github/aw/upgrade-agentic-workflows.md pkg/cli/templates/
594-
595-
# Agent templates
596-
@cp .github/agents/agentic-workflows.agent.md pkg/cli/templates/
597-
598-
@echo "✓ Templates synced successfully"
599-
600-
601-
602582
# Sync action pins from .github/aw to pkg/workflow/data
603583
.PHONY: sync-action-pins
604584
sync-action-pins:
@@ -620,7 +600,7 @@ sync-action-scripts:
620600

621601
# Recompile all workflow files
622602
.PHONY: recompile
623-
recompile: sync-templates build
603+
recompile: build
624604
./$(BINARY_NAME) init --codespaces
625605
./$(BINARY_NAME) compile --validate --verbose --purge --stats
626606
# ./$(BINARY_NAME) compile --dir pkg/cli/workflows --validate --verbose --purge
@@ -736,7 +716,6 @@ help:
736716
@echo " actionlint - Validate workflows with actionlint (depends on build)"
737717
@echo " validate-workflows - Validate compiled workflow lock files (depends on build)"
738718
@echo " install - Install binary locally"
739-
@echo " sync-templates - Sync templates from .github to pkg/cli/templates (runs automatically during build)"
740719
@echo " sync-action-pins - Sync actions-lock.json from .github/aw to pkg/workflow/data (runs automatically during build)"
741720
@echo " sync-action-scripts - Sync install-gh-aw.sh to actions/setup-cli/install.sh (runs automatically during build)"
742721
@echo " update - Update GitHub Actions and workflows, sync action pins, and rebuild binary"

pkg/cli/agentic_workflow_agent_test.go

Lines changed: 20 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"os"
77
"os/exec"
88
"path/filepath"
9-
"strings"
109
"testing"
1110

1211
"github.com/github/gh-aw/pkg/testutil"
@@ -16,22 +15,17 @@ func TestEnsureCreateWorkflowPrompt(t *testing.T) {
1615
tests := []struct {
1716
name string
1817
existingContent string
19-
expectedContent string
18+
expectExists bool
2019
}{
2120
{
22-
name: "creates new workflow creation prompt file",
21+
name: "reports missing file without error",
2322
existingContent: "",
24-
expectedContent: strings.TrimSpace(createWorkflowPromptTemplate),
23+
expectExists: false,
2524
},
2625
{
27-
name: "does not modify existing correct file",
28-
existingContent: createWorkflowPromptTemplate,
29-
expectedContent: strings.TrimSpace(createWorkflowPromptTemplate),
30-
},
31-
{
32-
name: "updates modified file",
33-
existingContent: "# Modified Workflow Creation Prompt\n\nThis is a modified version.",
34-
expectedContent: strings.TrimSpace(createWorkflowPromptTemplate),
26+
name: "reports existing file",
27+
existingContent: "# Test content",
28+
expectExists: true,
3529
},
3630
}
3731

@@ -74,24 +68,16 @@ func TestEnsureCreateWorkflowPrompt(t *testing.T) {
7468
t.Fatalf("ensureCreateWorkflowPrompt() returned error: %v", err)
7569
}
7670

77-
// Check that file exists
78-
if _, err := os.Stat(promptPath); os.IsNotExist(err) {
79-
t.Fatalf("Expected prompt file to exist")
80-
}
81-
82-
// Check content
83-
content, err := os.ReadFile(promptPath)
84-
if err != nil {
85-
t.Fatalf("Failed to read prompt: %v", err)
86-
}
71+
// Check that file exists or not based on test expectation
72+
_, statErr := os.Stat(promptPath)
73+
fileExists := statErr == nil
8774

88-
contentStr := strings.TrimSpace(string(content))
89-
expectedStr := strings.TrimSpace(tt.expectedContent)
90-
91-
if contentStr != expectedStr {
92-
t.Errorf("Expected content does not match.\nExpected first 100 chars: %q\nActual first 100 chars: %q",
93-
expectedStr[:min(100, len(expectedStr))],
94-
contentStr[:min(100, len(contentStr))])
75+
if fileExists != tt.expectExists {
76+
if tt.expectExists {
77+
t.Errorf("Expected prompt file to exist, but it doesn't")
78+
} else {
79+
t.Errorf("Expected prompt file to not exist, but it does")
80+
}
9581
}
9682
})
9783
}
@@ -130,7 +116,7 @@ func TestEnsureCreateWorkflowPrompt_WithSkipInstructionsTrue(t *testing.T) {
130116
}
131117
}
132118

133-
func TestEnsureCreateWorkflowPrompt_CreatesNewFile(t *testing.T) {
119+
func TestEnsureCreateWorkflowPrompt_ReportsNonExistent(t *testing.T) {
134120
// Create a temporary directory for testing
135121
tempDir := testutil.TempDir(t, "test-*")
136122

@@ -149,16 +135,16 @@ func TestEnsureCreateWorkflowPrompt_CreatesNewFile(t *testing.T) {
149135
t.Fatalf("Failed to init git repo: %v", err)
150136
}
151137

152-
// Call the function to create prompt
138+
// Call the function - it should not error even if file doesn't exist
153139
err = ensureCreateWorkflowPrompt(false, false)
154140
if err != nil {
155141
t.Fatalf("ensureCreateWorkflowPrompt() returned error: %v", err)
156142
}
157143

158-
// Check that new prompt file was created in .github/aw/
144+
// Check that new prompt file was NOT created (files are source of truth in .github/aw/)
159145
awDir := filepath.Join(tempDir, ".github", "aw")
160146
newPromptPath := filepath.Join(awDir, "create-agentic-workflow.md")
161-
if _, err := os.Stat(newPromptPath); os.IsNotExist(err) {
162-
t.Fatalf("Expected new prompt file to be created in .github/aw/")
147+
if _, err := os.Stat(newPromptPath); !os.IsNotExist(err) {
148+
t.Fatalf("Expected new prompt file to NOT be created (files are source of truth)")
163149
}
164150
}

pkg/cli/commands.go

Lines changed: 78 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package cli
22

33
import (
4-
_ "embed"
54
"errors"
65
"fmt"
6+
"io"
7+
"net/http"
78
"os"
89
"os/exec"
910
"path/filepath"
1011
"strings"
12+
"time"
1113

1214
"github.com/github/gh-aw/pkg/console"
1315
"github.com/github/gh-aw/pkg/constants"
@@ -28,39 +30,91 @@ func init() {
2830
workflow.SetDefaultVersion(version)
2931
}
3032

31-
//go:embed templates/github-agentic-workflows.md
32-
var copilotInstructionsTemplate string
33+
// SetVersionInfo sets the version information for the CLI and workflow package
34+
func SetVersionInfo(v string) {
35+
version = v
36+
workflow.SetDefaultVersion(v) // Keep workflow package in sync
37+
}
3338

34-
//go:embed templates/agentic-workflows.agent.md
35-
var agenticWorkflowsDispatcherTemplate string
39+
// GetVersion returns the current version
40+
func GetVersion() string {
41+
return version
42+
}
3643

37-
//go:embed templates/create-agentic-workflow.md
38-
var createWorkflowPromptTemplate string
44+
// downloadAgentFileFromGitHub downloads the agentic-workflows.agent.md file from GitHub
45+
func downloadAgentFileFromGitHub(verbose bool) (string, error) {
46+
commandsLog.Print("Downloading agentic-workflows.agent.md from GitHub")
47+
48+
// Determine the ref to use (tag for releases, main for dev builds)
49+
ref := "main"
50+
currentVersion := GetVersion()
51+
52+
// If version looks like a release tag (starts with v and contains dots), use it
53+
isRelease := strings.HasPrefix(currentVersion, "v") && strings.Contains(currentVersion, ".")
54+
if isRelease {
55+
ref = currentVersion
56+
commandsLog.Printf("Using release tag: %s", ref)
57+
if verbose {
58+
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Using release version: %s", ref)))
59+
}
60+
} else {
61+
commandsLog.Print("Using main branch for dev build")
62+
if verbose {
63+
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Using main branch (dev build)"))
64+
}
65+
}
3966

40-
//go:embed templates/update-agentic-workflow.md
41-
var updateWorkflowPromptTemplate string
67+
// Construct the raw GitHub URL
68+
url := fmt.Sprintf("https://raw.githubusercontent.com/github/gh-aw/%s/.github/agents/agentic-workflows.agent.md", ref)
69+
commandsLog.Printf("Downloading from URL: %s", url)
4270

43-
//go:embed templates/create-shared-agentic-workflow.md
44-
var createSharedAgenticWorkflowPromptTemplate string
71+
// Create HTTP client with timeout
72+
client := &http.Client{
73+
Timeout: 30 * time.Second,
74+
}
4575

46-
//go:embed templates/debug-agentic-workflow.md
47-
var debugWorkflowPromptTemplate string
76+
// Download the file
77+
resp, err := client.Get(url)
78+
if err != nil {
79+
return "", fmt.Errorf("failed to download agent file: %w", err)
80+
}
81+
defer resp.Body.Close()
4882

49-
//go:embed templates/upgrade-agentic-workflows.md
50-
var upgradeAgenticWorkflowsPromptTemplate string
83+
if resp.StatusCode != http.StatusOK {
84+
return "", fmt.Errorf("failed to download agent file: HTTP %d", resp.StatusCode)
85+
}
5186

52-
//go:embed templates/serena-tool.md
53-
var serenaToolTemplate string
87+
// Read the content
88+
content, err := io.ReadAll(resp.Body)
89+
if err != nil {
90+
return "", fmt.Errorf("failed to read agent file content: %w", err)
91+
}
5492

55-
// SetVersionInfo sets the version information for the CLI and workflow package
56-
func SetVersionInfo(v string) {
57-
version = v
58-
workflow.SetDefaultVersion(v) // Keep workflow package in sync
93+
contentStr := string(content)
94+
95+
// Patch URLs to match the current version/ref
96+
patchedContent := patchAgentFileURLs(contentStr, ref)
97+
if patchedContent != contentStr && verbose {
98+
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Patched URLs to use ref: %s", ref)))
99+
}
100+
101+
commandsLog.Printf("Successfully downloaded agent file (%d bytes)", len(patchedContent))
102+
return patchedContent, nil
59103
}
60104

61-
// GetVersion returns the current version
62-
func GetVersion() string {
63-
return version
105+
// patchAgentFileURLs patches URLs in the agent file to use the correct ref
106+
func patchAgentFileURLs(content, ref string) string {
107+
// Pattern 1: Convert local paths to GitHub URLs
108+
// `.github/aw/file.md` -> `https://github.com/github/gh-aw/blob/{ref}/.github/aw/file.md`
109+
content = strings.ReplaceAll(content, "`.github/aw/", fmt.Sprintf("`https://github.com/github/gh-aw/blob/%s/.github/aw/", ref))
110+
111+
// Pattern 2: Update existing GitHub URLs to use the correct ref
112+
// https://github.com/github/gh-aw/blob/main/ -> https://github.com/github/gh-aw/blob/{ref}/
113+
if ref != "main" {
114+
content = strings.ReplaceAll(content, "/blob/main/", fmt.Sprintf("/blob/%s/", ref))
115+
}
116+
117+
return content
64118
}
65119

66120
func isGHCLIAvailable() bool {

0 commit comments

Comments
 (0)