From 7ad7b2a2186fd77e3e54315099873cf2dfd2c96c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:45:05 +0000 Subject: [PATCH 1/6] Initial plan From 27def2cacc2a4434096d8977d9bdadab4481f3c4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:49:19 +0000 Subject: [PATCH 2/6] Add support for bootstrap in frontmatter (preferred over file-based) Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- integration_test.go | 85 +++++++++++++++++++++++ pkg/codingcontext/context.go | 16 ++++- pkg/codingcontext/context_test.go | 53 ++++++++++++++ pkg/codingcontext/markdown/frontmatter.go | 4 ++ 4 files changed, 156 insertions(+), 2 deletions(-) diff --git a/integration_test.go b/integration_test.go index 00d768a..e87ce34 100644 --- a/integration_test.go +++ b/integration_test.go @@ -155,6 +155,91 @@ General information about the project. } } +func TestBootstrapFromFrontmatter(t *testing.T) { + dirs := setupTestDirs(t) + + // Create a rule file with bootstrap in frontmatter + ruleFile := filepath.Join(dirs.rulesDir, "setup.md") + ruleContent := `--- +bootstrap: | + echo "Bootstrap from frontmatter" +--- +# Setup + +This is a setup guide with frontmatter bootstrap. +` + if err := os.WriteFile(ruleFile, []byte(ruleContent), 0o644); err != nil { + t.Fatalf("failed to write rule file: %v", err) + } + + createStandardTask(t, dirs.tasksDir, "test-task") + + // Run the program + output := runTool(t, "-C", dirs.tmpDir, "test-task") + + // Check that bootstrap output appears + if !strings.Contains(output, "Bootstrap from frontmatter") { + t.Errorf("bootstrap output from frontmatter not found in stdout") + } + + // Check that rule content is present + if !strings.Contains(output, "# Setup") { + t.Errorf("rule content not found in stdout") + } + + // Check that task content is present + if !strings.Contains(output, "# Test Task") { + t.Errorf("task content not found in stdout") + } +} + +func TestBootstrapFrontmatterPreferredOverFile(t *testing.T) { + dirs := setupTestDirs(t) + + // Create a rule file with bootstrap in frontmatter + ruleFile := filepath.Join(dirs.rulesDir, "setup.md") + ruleContent := `--- +bootstrap: | + echo "Using frontmatter bootstrap" +--- +# Priority Test + +Testing that frontmatter bootstrap is preferred. +` + if err := os.WriteFile(ruleFile, []byte(ruleContent), 0o644); err != nil { + t.Fatalf("failed to write rule file: %v", err) + } + + // Also create a file-based bootstrap (should be ignored) + bootstrapFile := filepath.Join(dirs.rulesDir, "setup-bootstrap") + bootstrapContent := `#!/bin/bash +echo "Using file bootstrap" +` + if err := os.WriteFile(bootstrapFile, []byte(bootstrapContent), 0o755); err != nil { + t.Fatalf("failed to write bootstrap file: %v", err) + } + + createStandardTask(t, dirs.tasksDir, "test-task") + + // Run the program + output := runTool(t, "-C", dirs.tmpDir, "test-task") + + // Check that frontmatter bootstrap is used + if !strings.Contains(output, "Using frontmatter bootstrap") { + t.Errorf("frontmatter bootstrap output not found in stdout") + } + + // Check that file bootstrap is NOT used + if strings.Contains(output, "Using file bootstrap") { + t.Errorf("file bootstrap should not be used when frontmatter bootstrap is present") + } + + // Check that rule content is present + if !strings.Contains(output, "# Priority Test") { + t.Errorf("rule content not found in stdout") + } +} + func TestMultipleBootstrapFiles(t *testing.T) { dirs := setupTestDirs(t) diff --git a/pkg/codingcontext/context.go b/pkg/codingcontext/context.go index 9b81b28..dd2306d 100644 --- a/pkg/codingcontext/context.go +++ b/pkg/codingcontext/context.go @@ -565,7 +565,7 @@ func (cc *Context) findExecuteRuleFiles(ctx context.Context, homeDir string) err _, reason := cc.includes.MatchesIncludes(*baseFm) cc.logger.Info("Including rule file", "path", path, "reason", reason, "tokens", tokens) - if err := cc.runBootstrapScript(ctx, path); err != nil { + if err := cc.runBootstrapScript(ctx, path, frontmatter.Bootstrap); err != nil { return fmt.Errorf("failed to run bootstrap script: %w", err) } @@ -578,7 +578,19 @@ func (cc *Context) findExecuteRuleFiles(ctx context.Context, homeDir string) err return nil } -func (cc *Context) runBootstrapScript(ctx context.Context, path string) error { +func (cc *Context) runBootstrapScript(ctx context.Context, path string, frontmatterBootstrap string) error { + // Prefer frontmatter bootstrap if present + if frontmatterBootstrap != "" { + cc.logger.Info("Running bootstrap from frontmatter", "path", path) + + cmd := exec.CommandContext(ctx, "sh", "-c", frontmatterBootstrap) + cmd.Stdout = os.Stderr + cmd.Stderr = os.Stderr + + return cc.cmdRunner(cmd) + } + + // Fall back to file-based bootstrap // Check for a bootstrap file named -bootstrap // For example, setup.md -> setup-bootstrap, setup.mdc -> setup-bootstrap baseNameWithoutExt := strings.TrimSuffix(path, filepath.Ext(path)) diff --git a/pkg/codingcontext/context_test.go b/pkg/codingcontext/context_test.go index 56eb765..117e00e 100644 --- a/pkg/codingcontext/context_test.go +++ b/pkg/codingcontext/context_test.go @@ -588,6 +588,59 @@ func TestContext_Run_Rules(t *testing.T) { } }, }, + { + name: "bootstrap from frontmatter is preferred", + setup: func(t *testing.T, dir string) { + createTask(t, dir, "frontmatter-bootstrap", "", "Task") + // Create rule with bootstrap in frontmatter + createRule(t, dir, ".agents/rules/rule-with-frontmatter.md", + "bootstrap: |\n echo 'frontmatter bootstrap'\n", + "Rule content") + }, + taskName: "frontmatter-bootstrap", + wantErr: false, + check: func(t *testing.T, result *Result) { + if len(result.Rules) != 1 { + t.Errorf("expected 1 rule, got %d", len(result.Rules)) + } + }, + }, + { + name: "bootstrap from frontmatter preferred over file", + setup: func(t *testing.T, dir string) { + createTask(t, dir, "frontmatter-priority", "", "Task") + // Create rule with BOTH frontmatter and file bootstrap + createRule(t, dir, ".agents/rules/priority-rule.md", + "bootstrap: |\n echo 'using frontmatter'\n", + "Rule content") + // Also create a file-based bootstrap (should be ignored) + createBootstrapScript(t, dir, ".agents/rules/priority-rule.md", "#!/bin/sh\necho 'using file'") + }, + taskName: "frontmatter-priority", + wantErr: false, + check: func(t *testing.T, result *Result) { + if len(result.Rules) != 1 { + t.Errorf("expected 1 rule, got %d", len(result.Rules)) + } + }, + }, + { + name: "bootstrap from file when frontmatter empty", + setup: func(t *testing.T, dir string) { + createTask(t, dir, "file-fallback", "", "Task") + // Create rule WITHOUT frontmatter bootstrap + createRule(t, dir, ".agents/rules/fallback-rule.md", "", "Rule content") + // Create file-based bootstrap (should be used) + createBootstrapScript(t, dir, ".agents/rules/fallback-rule.md", "#!/bin/sh\necho 'using file fallback'") + }, + taskName: "file-fallback", + wantErr: false, + check: func(t *testing.T, result *Result) { + if len(result.Rules) != 1 { + t.Errorf("expected 1 rule, got %d", len(result.Rules)) + } + }, + }, { name: "agent option collects all rules", setup: func(t *testing.T, dir string) { diff --git a/pkg/codingcontext/markdown/frontmatter.go b/pkg/codingcontext/markdown/frontmatter.go index 259253d..c30417a 100644 --- a/pkg/codingcontext/markdown/frontmatter.go +++ b/pkg/codingcontext/markdown/frontmatter.go @@ -167,6 +167,10 @@ type RuleFrontMatter struct { // ExpandParams controls whether parameter expansion should occur // Defaults to true if not specified ExpandParams *bool `yaml:"expand,omitempty" json:"expand,omitempty"` + + // Bootstrap contains a shell script to execute before including the rule + // This is preferred over file-based bootstrap scripts + Bootstrap string `yaml:"bootstrap,omitempty" json:"bootstrap,omitempty"` } // UnmarshalJSON custom unmarshaler that populates both typed fields and Content map From 64d4d8b1cbe8f173a209106e5b12e82eaf1f56ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:52:48 +0000 Subject: [PATCH 3/6] Update documentation for frontmatter bootstrap support Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- docs/how-to/create-rules.md | 49 +++++++++++++--- docs/reference/file-formats.md | 104 +++++++++++++++++++++++++++++++-- 2 files changed, 140 insertions(+), 13 deletions(-) diff --git a/docs/how-to/create-rules.md b/docs/how-to/create-rules.md index ae0567c..c185248 100644 --- a/docs/how-to/create-rules.md +++ b/docs/how-to/create-rules.md @@ -143,7 +143,46 @@ coding-context -s stage=implementation implement-feature ## Rules with Bootstrap Scripts -Create rules that fetch dynamic context: +Bootstrap scripts run before a rule is included, allowing you to fetch or generate dynamic context. + +### Frontmatter Bootstrap (Preferred) + +The preferred way is to define the bootstrap script directly in frontmatter: + +**Rule file (`.agents/rules/jira-context.md`):** +```markdown +--- +source: jira +bootstrap: | + if [ -z "$JIRA_ISSUE_KEY" ]; then + exit 0 + fi + + echo "Fetching JIRA issue: $JIRA_ISSUE_KEY" >&2 + + # Fetch and process JIRA data + curl -s -H "Authorization: Bearer $JIRA_API_TOKEN" \ + "https://your-domain.atlassian.net/rest/api/3/issue/${JIRA_ISSUE_KEY}" \ + | jq -r '.fields | {summary, description}' \ + > /tmp/jira-context.json +--- + +# JIRA Context + +Issue details are fetched by the bootstrap script. +``` + +Use with: +```bash +export JIRA_ISSUE_KEY="PROJ-123" +export JIRA_API_TOKEN="your-token" + +coding-context -s source=jira fix-bug +``` + +### File-Based Bootstrap (Legacy) + +Alternatively, create a separate executable file named `-bootstrap`: **Rule file (`.agents/rules/jira-context.md`):** ```markdown @@ -174,13 +213,7 @@ curl -s -H "Authorization: Bearer $JIRA_API_TOKEN" \ > /tmp/jira-context.json ``` -Use with: -```bash -export JIRA_ISSUE_KEY="PROJ-123" -export JIRA_API_TOKEN="your-token" - -coding-context -s source=jira fix-bug -``` +**Note:** If both frontmatter and file-based bootstrap exist, frontmatter takes precedence. ## Best Practices diff --git a/docs/reference/file-formats.md b/docs/reference/file-formats.md index 69137d9..8a9d8bd 100644 --- a/docs/reference/file-formats.md +++ b/docs/reference/file-formats.md @@ -824,6 +824,65 @@ coding-context -p version=1.2.3 my-task This is useful when rules contain template syntax that should be preserved for the AI agent to process. +#### `bootstrap` (optional) + +**Type:** String (multiline) +**Purpose:** Shell script to execute before the rule is included. This is the **preferred method** for defining bootstrap scripts. + +The bootstrap script runs via `sh -c`, with output sent to stderr (not included in the AI context). This allows rules to fetch dynamic data, set up environment, or prepare context before the rule content is processed. + +**Example:** +```yaml +--- +languages: + - go +bootstrap: | + echo "Fetching project dependencies..." >&2 + go list -m all > /tmp/go-deps.txt + echo "Dependencies cached" >&2 +--- + +# Go Dependency Context + +Dependencies are listed in /tmp/go-deps.txt +``` + +**Multiple commands:** +```yaml +--- +bootstrap: | + #!/bin/bash + set -e + + echo "Setting up environment..." >&2 + + if [ -z "$API_KEY" ]; then + echo "Warning: API_KEY not set" >&2 + exit 0 + fi + + echo "Fetching data..." >&2 + curl -s -H "Authorization: Bearer $API_KEY" \ + "https://api.example.com/data" > /tmp/data.json + + echo "Setup complete" >&2 +--- +``` + +**Usage:** +```bash +export API_KEY="your-api-key" +coding-context implement-feature +``` + +**Notes:** +- Output goes to stderr, not the assembled context +- If both frontmatter `bootstrap:` and file-based bootstrap exist, frontmatter takes precedence +- Environment variables from the parent process are available +- Exit code 0 is required for successful execution + +**See also:** [Bootstrap Scripts](#bootstrap-scripts) for file-based alternative + **Other common fields:** ```yaml --- @@ -833,6 +892,8 @@ stage: implementation priority: high team: backend agent: cursor +bootstrap: | + echo "Running setup..." >&2 --- ``` @@ -849,9 +910,38 @@ Rules are discovered in many locations. See [Search Paths Reference](./search-pa ## Bootstrap Scripts -Bootstrap scripts are executable files that run before their associated rule file is processed. +Bootstrap scripts run before their associated rule file is processed. There are two ways to define bootstrap scripts: + +### Frontmatter Bootstrap (Preferred) + +Define the bootstrap script directly in the rule's frontmatter using the `bootstrap:` field. This is the **preferred method** because: +- The script is co-located with the rule content +- No separate file to manage or chmod +- Easier to version control and review + +**Example:** +```markdown +--- +bootstrap: | + echo "Fetching JIRA data..." >&2 + curl -s -H "Authorization: Bearer $JIRA_API_TOKEN" \ + "https://api.example.com/issue/$JIRA_ISSUE_KEY" \ + | jq -r '.fields' > /tmp/jira-data.json + echo "Data fetched" >&2 +--- + +# JIRA Context + +Issue data is available in /tmp/jira-data.json +``` + +**See:** [Rule Frontmatter - bootstrap field](#bootstrap-optional) for more details. + +### File-Based Bootstrap (Legacy) + +Alternatively, create a separate executable file for backward compatibility. -### Naming Convention +#### Naming Convention For a rule file named `my-rule.md`, the bootstrap script must be named `my-rule-bootstrap` (no extension). @@ -859,13 +949,13 @@ For a rule file named `my-rule.md`, the bootstrap script must be named `my-rule- - Rule: `.agents/rules/jira-context.md` - Bootstrap: `.agents/rules/jira-context-bootstrap` -### Requirements +#### Requirements 1. **Executable permission:** `chmod +x script-name` 2. **Same directory:** Must be in same directory as the rule file 3. **Naming:** Must match rule filename plus `-bootstrap` suffix -### Output Handling +#### Output Handling - Bootstrap script output goes to **stderr**, not the main context - The script's stdout is not captured @@ -881,7 +971,7 @@ curl -s "https://api.example.com/data" > /tmp/data.json echo "Data fetched successfully" >&2 ``` -### Environment Access +#### Environment Access Bootstrap scripts can access all environment variables from the parent process. @@ -904,6 +994,10 @@ curl -s -H "Authorization: Bearer $API_KEY" \ | jq -r '.fields' > /tmp/issue-data.json ``` +### Priority + +If a rule has **both** frontmatter `bootstrap:` and a file-based bootstrap script, the **frontmatter bootstrap is used** (file is ignored). + ## YAML Frontmatter Specification ### Valid Frontmatter From 0c706e9e1841507e4649ca2083568264b87b9283 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:14:48 +0000 Subject: [PATCH 4/6] Address PR feedback: save bootstrap to temp file, improve error messages and tests Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- docs/how-to/create-rules.md | 3 +++ docs/reference/file-formats.md | 4 +++- integration_test.go | 12 +++++++----- pkg/codingcontext/context.go | 32 ++++++++++++++++++++++++++++--- pkg/codingcontext/context_test.go | 18 ++++++++++++----- 5 files changed, 55 insertions(+), 14 deletions(-) diff --git a/docs/how-to/create-rules.md b/docs/how-to/create-rules.md index c185248..91015da 100644 --- a/docs/how-to/create-rules.md +++ b/docs/how-to/create-rules.md @@ -154,6 +154,7 @@ The preferred way is to define the bootstrap script directly in frontmatter: --- source: jira bootstrap: | + #!/bin/sh if [ -z "$JIRA_ISSUE_KEY" ]; then exit 0 fi @@ -180,6 +181,8 @@ export JIRA_API_TOKEN="your-token" coding-context -s source=jira fix-bug ``` +**Note:** The bootstrap script is saved to a temporary file and executed. Include a shebang line (`#!/bin/sh` or `#!/bin/bash`) to specify the interpreter. + ### File-Based Bootstrap (Legacy) Alternatively, create a separate executable file named `-bootstrap`: diff --git a/docs/reference/file-formats.md b/docs/reference/file-formats.md index 8a9d8bd..b5eceee 100644 --- a/docs/reference/file-formats.md +++ b/docs/reference/file-formats.md @@ -851,7 +851,7 @@ Dependencies are listed in /tmp/go-deps.txt ```yaml --- bootstrap: | - #!/bin/bash + #!/bin/sh set -e echo "Setting up environment..." >&2 @@ -876,6 +876,8 @@ coding-context implement-feature ``` **Notes:** +- The bootstrap script is saved to a temporary executable file and run directly +- Use `#!/bin/sh` (POSIX shell) or `#!/bin/bash` (bash) as the first line to select the interpreter - Output goes to stderr, not the assembled context - If both frontmatter `bootstrap:` and file-based bootstrap exist, frontmatter takes precedence - Environment variables from the parent process are available diff --git a/integration_test.go b/integration_test.go index e87ce34..21fdddd 100644 --- a/integration_test.go +++ b/integration_test.go @@ -162,6 +162,7 @@ func TestBootstrapFromFrontmatter(t *testing.T) { ruleFile := filepath.Join(dirs.rulesDir, "setup.md") ruleContent := `--- bootstrap: | + #!/bin/sh echo "Bootstrap from frontmatter" --- # Setup @@ -179,17 +180,17 @@ This is a setup guide with frontmatter bootstrap. // Check that bootstrap output appears if !strings.Contains(output, "Bootstrap from frontmatter") { - t.Errorf("bootstrap output from frontmatter not found in stdout") + t.Errorf("bootstrap output from frontmatter not found in output") } // Check that rule content is present if !strings.Contains(output, "# Setup") { - t.Errorf("rule content not found in stdout") + t.Errorf("rule content not found in output") } // Check that task content is present if !strings.Contains(output, "# Test Task") { - t.Errorf("task content not found in stdout") + t.Errorf("task content not found in output") } } @@ -200,6 +201,7 @@ func TestBootstrapFrontmatterPreferredOverFile(t *testing.T) { ruleFile := filepath.Join(dirs.rulesDir, "setup.md") ruleContent := `--- bootstrap: | + #!/bin/sh echo "Using frontmatter bootstrap" --- # Priority Test @@ -226,7 +228,7 @@ echo "Using file bootstrap" // Check that frontmatter bootstrap is used if !strings.Contains(output, "Using frontmatter bootstrap") { - t.Errorf("frontmatter bootstrap output not found in stdout") + t.Errorf("frontmatter bootstrap output not found in output") } // Check that file bootstrap is NOT used @@ -236,7 +238,7 @@ echo "Using file bootstrap" // Check that rule content is present if !strings.Contains(output, "# Priority Test") { - t.Errorf("rule content not found in stdout") + t.Errorf("rule content not found in output") } } diff --git a/pkg/codingcontext/context.go b/pkg/codingcontext/context.go index dd2306d..62045aa 100644 --- a/pkg/codingcontext/context.go +++ b/pkg/codingcontext/context.go @@ -583,11 +583,34 @@ func (cc *Context) runBootstrapScript(ctx context.Context, path string, frontmat if frontmatterBootstrap != "" { cc.logger.Info("Running bootstrap from frontmatter", "path", path) - cmd := exec.CommandContext(ctx, "sh", "-c", frontmatterBootstrap) + // Create a temporary file for the bootstrap script + tmpFile, err := os.CreateTemp("", "bootstrap-*.sh") + if err != nil { + return fmt.Errorf("failed to create temp file for bootstrap script from %s: %w", path, err) + } + tmpFilePath := tmpFile.Name() + defer os.Remove(tmpFilePath) + + // Write the bootstrap script to the temp file + if _, err := tmpFile.WriteString(frontmatterBootstrap); err != nil { + tmpFile.Close() + return fmt.Errorf("failed to write bootstrap script from %s: %w", path, err) + } + tmpFile.Close() + + // Make it executable + if err := os.Chmod(tmpFilePath, 0o755); err != nil { + return fmt.Errorf("failed to chmod bootstrap script from %s: %w", path, err) + } + + cmd := exec.CommandContext(ctx, tmpFilePath) cmd.Stdout = os.Stderr cmd.Stderr = os.Stderr - return cc.cmdRunner(cmd) + if err := cc.cmdRunner(cmd); err != nil { + return fmt.Errorf("frontmatter bootstrap script failed for %s: %w", path, err) + } + return nil } // Fall back to file-based bootstrap @@ -614,7 +637,10 @@ func (cc *Context) runBootstrapScript(ctx context.Context, path string, frontmat cmd.Stdout = os.Stderr cmd.Stderr = os.Stderr - return cc.cmdRunner(cmd) + if err := cc.cmdRunner(cmd); err != nil { + return fmt.Errorf("file-based bootstrap script failed for %s: %w", path, err) + } + return nil } // discoverSkills searches for skill directories and loads only their metadata (name and description) diff --git a/pkg/codingcontext/context_test.go b/pkg/codingcontext/context_test.go index 117e00e..5b9b1e7 100644 --- a/pkg/codingcontext/context_test.go +++ b/pkg/codingcontext/context_test.go @@ -592,9 +592,9 @@ func TestContext_Run_Rules(t *testing.T) { name: "bootstrap from frontmatter is preferred", setup: func(t *testing.T, dir string) { createTask(t, dir, "frontmatter-bootstrap", "", "Task") - // Create rule with bootstrap in frontmatter + // Create rule with bootstrap in frontmatter that writes a marker file createRule(t, dir, ".agents/rules/rule-with-frontmatter.md", - "bootstrap: |\n echo 'frontmatter bootstrap'\n", + "bootstrap: |\n #!/bin/sh\n echo 'frontmatter' > "+filepath.Join(dir, "bootstrap-ran.txt")+"\n", "Rule content") }, taskName: "frontmatter-bootstrap", @@ -603,6 +603,7 @@ func TestContext_Run_Rules(t *testing.T) { if len(result.Rules) != 1 { t.Errorf("expected 1 rule, got %d", len(result.Rules)) } + // The integration tests verify frontmatter bootstrap actually ran }, }, { @@ -610,11 +611,14 @@ func TestContext_Run_Rules(t *testing.T) { setup: func(t *testing.T, dir string) { createTask(t, dir, "frontmatter-priority", "", "Task") // Create rule with BOTH frontmatter and file bootstrap + // Frontmatter writes "frontmatter", file writes "file" + markerPath := filepath.Join(dir, "bootstrap-marker.txt") createRule(t, dir, ".agents/rules/priority-rule.md", - "bootstrap: |\n echo 'using frontmatter'\n", + "bootstrap: |\n #!/bin/sh\n echo 'frontmatter' > "+markerPath+"\n", "Rule content") // Also create a file-based bootstrap (should be ignored) - createBootstrapScript(t, dir, ".agents/rules/priority-rule.md", "#!/bin/sh\necho 'using file'") + createBootstrapScript(t, dir, ".agents/rules/priority-rule.md", + "#!/bin/sh\necho 'file' > "+markerPath) }, taskName: "frontmatter-priority", wantErr: false, @@ -622,6 +626,7 @@ func TestContext_Run_Rules(t *testing.T) { if len(result.Rules) != 1 { t.Errorf("expected 1 rule, got %d", len(result.Rules)) } + // The integration tests verify which bootstrap actually ran }, }, { @@ -629,9 +634,11 @@ func TestContext_Run_Rules(t *testing.T) { setup: func(t *testing.T, dir string) { createTask(t, dir, "file-fallback", "", "Task") // Create rule WITHOUT frontmatter bootstrap + markerPath := filepath.Join(dir, "bootstrap-marker.txt") createRule(t, dir, ".agents/rules/fallback-rule.md", "", "Rule content") // Create file-based bootstrap (should be used) - createBootstrapScript(t, dir, ".agents/rules/fallback-rule.md", "#!/bin/sh\necho 'using file fallback'") + createBootstrapScript(t, dir, ".agents/rules/fallback-rule.md", + "#!/bin/sh\necho 'file' > "+markerPath) }, taskName: "file-fallback", wantErr: false, @@ -639,6 +646,7 @@ func TestContext_Run_Rules(t *testing.T) { if len(result.Rules) != 1 { t.Errorf("expected 1 rule, got %d", len(result.Rules)) } + // The integration tests verify the file-based bootstrap ran }, }, { From 058f599e464b147ea258abe93eec8413f90f7796 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:48:55 +0000 Subject: [PATCH 5/6] Update bootstrap examples to use shebang with space and set -eux Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- docs/how-to/create-rules.md | 3 ++- docs/reference/file-formats.md | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/how-to/create-rules.md b/docs/how-to/create-rules.md index 91015da..6838707 100644 --- a/docs/how-to/create-rules.md +++ b/docs/how-to/create-rules.md @@ -154,7 +154,8 @@ The preferred way is to define the bootstrap script directly in frontmatter: --- source: jira bootstrap: | - #!/bin/sh + #! /bin/sh + set -eux if [ -z "$JIRA_ISSUE_KEY" ]; then exit 0 fi diff --git a/docs/reference/file-formats.md b/docs/reference/file-formats.md index b5eceee..351c1c6 100644 --- a/docs/reference/file-formats.md +++ b/docs/reference/file-formats.md @@ -837,6 +837,8 @@ The bootstrap script runs via `sh -c`, with output sent to stderr (not included languages: - go bootstrap: | + #! /bin/sh + set -eux echo "Fetching project dependencies..." >&2 go list -m all > /tmp/go-deps.txt echo "Dependencies cached" >&2 @@ -851,8 +853,8 @@ Dependencies are listed in /tmp/go-deps.txt ```yaml --- bootstrap: | - #!/bin/sh - set -e + #! /bin/sh + set -eux echo "Setting up environment..." >&2 From b23a5697b41a081133eb8734c4c21e5e788e4336 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:51:51 +0000 Subject: [PATCH 6/6] Add shebang and set -eux to remaining bootstrap examples Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- docs/reference/file-formats.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/reference/file-formats.md b/docs/reference/file-formats.md index 351c1c6..54d223a 100644 --- a/docs/reference/file-formats.md +++ b/docs/reference/file-formats.md @@ -897,6 +897,8 @@ priority: high team: backend agent: cursor bootstrap: | + #! /bin/sh + set -eux echo "Running setup..." >&2 --- ``` @@ -927,6 +929,8 @@ Define the bootstrap script directly in the rule's frontmatter using the `bootst ```markdown --- bootstrap: | + #! /bin/sh + set -eux echo "Fetching JIRA data..." >&2 curl -s -H "Authorization: Bearer $JIRA_API_TOKEN" \ "https://api.example.com/issue/$JIRA_ISSUE_KEY" \