Support bootstrap scripts in rule frontmatter (preferred over file-based)#207
Support bootstrap scripts in rule frontmatter (preferred over file-based)#207
Conversation
Co-authored-by: alexec <1142830+alexec@users.noreply.github.com>
Co-authored-by: alexec <1142830+alexec@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds support for defining rule bootstrap scripts inline via a new bootstrap: frontmatter field, making it the preferred mechanism while keeping the legacy <rule>-bootstrap executable file behavior as a fallback for backward compatibility.
Changes:
- Extend rule frontmatter schema with a
bootstrapfield. - Update bootstrap execution to prefer frontmatter-provided scripts, falling back to legacy file-based scripts.
- Add unit + integration tests and update docs to describe the new frontmatter-based bootstrap.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/codingcontext/markdown/frontmatter.go | Adds Bootstrap to RuleFrontMatter so YAML frontmatter can carry the script. |
| pkg/codingcontext/context.go | Executes frontmatter bootstrap via sh -c when present; otherwise runs legacy *-bootstrap file. |
| pkg/codingcontext/context_test.go | Adds table-driven cases for frontmatter preference and file fallback. |
| integration_test.go | Adds end-to-end tests exercising frontmatter bootstrap and precedence over file bootstrap. |
| docs/reference/file-formats.md | Documents the new bootstrap field and clarifies precedence vs legacy bootstrap files. |
| docs/how-to/create-rules.md | Updates “Rules with Bootstrap Scripts” guide to recommend frontmatter bootstrap first. |
integration_test.go
Outdated
| 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") |
There was a problem hiding this comment.
runTool uses cmd.CombinedOutput(), so this test is asserting against combined stdout+stderr, not stdout specifically. The error message text should reflect that (or the test should be updated to capture stdout/stderr separately) to avoid confusion about where bootstrap output is expected to go.
| 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") | |
| 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 output") | |
| } | |
| // Check that task content is present | |
| if !strings.Contains(output, "# Test Task") { | |
| t.Errorf("task content not found in output") |
There was a problem hiding this comment.
Fixed in 0c706e9. Updated error messages to refer to "output" instead of "stdout".
| // 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") | ||
| } |
There was a problem hiding this comment.
Same issue here: assertions are made on the combined output from CombinedOutput(), but the failure messages refer to stdout. Consider rewording to “output”/“combined output” or capturing streams separately if you want to verify bootstrap writes to stderr.
There was a problem hiding this comment.
Fixed in 0c706e9. Updated error messages to refer to "output" instead of "stdout".
| check: func(t *testing.T, result *Result) { | ||
| if len(result.Rules) != 1 { | ||
| t.Errorf("expected 1 rule, got %d", len(result.Rules)) | ||
| } | ||
| }, |
There was a problem hiding this comment.
This test case doesn’t actually verify that the frontmatter bootstrap executed (or that the correct script was run); it only checks the number of rules. Since the PR adds new bootstrap behavior, consider asserting which command was invoked by overriding c.cmdRunner to record cmd.Args (or capturing stderr) and verifying it matches the expected frontmatter script.
There was a problem hiding this comment.
Acknowledged. The unit tests verify that rules are included. The integration tests verify which bootstrap actually runs by checking the output. Given the test infrastructure, this provides good coverage of the feature behavior.
| check: func(t *testing.T, result *Result) { | ||
| if len(result.Rules) != 1 { | ||
| t.Errorf("expected 1 rule, got %d", len(result.Rules)) | ||
| } | ||
| }, |
There was a problem hiding this comment.
This check doesn’t validate the precedence rule (frontmatter should win over file-based bootstrap). As written, it would still pass if only the file-based bootstrap ran. Consider overriding c.cmdRunner (or capturing stderr) and asserting that the executed command was sh -c with the frontmatter content, and that the file-based bootstrap path was not executed.
There was a problem hiding this comment.
Acknowledged. The integration tests verify precedence by checking output content. Given the test infrastructure, this provides good coverage of the priority behavior.
| check: func(t *testing.T, result *Result) { | ||
| if len(result.Rules) != 1 { | ||
| t.Errorf("expected 1 rule, got %d", len(result.Rules)) | ||
| } | ||
| }, |
There was a problem hiding this comment.
This check doesn’t confirm the fallback behavior (file-based bootstrap should run when frontmatter bootstrap is absent). Consider asserting the executed command path (e.g., <rule>-bootstrap) by intercepting c.cmdRunner, rather than only checking rule count.
There was a problem hiding this comment.
Acknowledged. The integration tests verify fallback behavior by checking output content. Given the test infrastructure, this provides good coverage.
pkg/codingcontext/context.go
Outdated
| 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) |
There was a problem hiding this comment.
When the frontmatter bootstrap command fails, the returned error won’t include which rule file triggered it (it will just look like a generic sh failure). Consider wrapping the cc.cmdRunner(cmd) error with the rule path (and indicate it was frontmatter bootstrap) to make failures actionable in logs/CI.
There was a problem hiding this comment.
Fixed in 0c706e9. Error messages now include rule path and indicate whether it was frontmatter or file-based bootstrap that failed.
docs/reference/file-formats.md
Outdated
| ```yaml | ||
| --- | ||
| bootstrap: | | ||
| #!/bin/bash |
There was a problem hiding this comment.
This example includes #!/bin/bash, but frontmatter bootstrap is executed via sh -c (the shebang line won’t select bash and may mislead users into writing bash-specific scripts). Suggest removing the shebang line from the example or updating the docs to show invoking bash explicitly (e.g., bash -c ...) if bash is required.
| #!/bin/bash |
There was a problem hiding this comment.
@copilot rather than making this a shell script, we should save the bootstrap script and run it.
There was a problem hiding this comment.
Fixed in 0c706e9. Changed implementation to save the bootstrap script to a temporary file and execute it directly (not via sh -c). Updated documentation to clarify that a shebang line is required and should specify the desired interpreter (#!/bin/sh or #!/bin/bash).
…ges and tests Co-authored-by: alexec <1142830+alexec@users.noreply.github.com>
| languages: | ||
| - go | ||
| bootstrap: | | ||
| echo "Fetching project dependencies..." >&2 |
There was a problem hiding this comment.
| echo "Fetching project dependencies..." >&2 | |
| #! /bin/sh | |
| set -eux | |
| echo "Fetching project dependencies..." >&2 |
There was a problem hiding this comment.
Fixed in 058f599. Updated all frontmatter bootstrap examples in documentation to use #! /bin/sh (with space) and set -eux for better error handling and debugging.
| team: backend | ||
| agent: cursor | ||
| bootstrap: | | ||
| echo "Running setup..." >&2 |
There was a problem hiding this comment.
Fixed in b23a569. Added #! /bin/sh and set -eux to the bootstrap example.
| ```markdown | ||
| --- | ||
| bootstrap: | | ||
| echo "Fetching JIRA data..." >&2 |
There was a problem hiding this comment.
Fixed in b23a569. Added #! /bin/sh and set -eux to the bootstrap example.
Co-authored-by: alexec <1142830+alexec@users.noreply.github.com>
Co-authored-by: alexec <1142830+alexec@users.noreply.github.com>
Without a shebang line, the bootstrap script will fail with an "exec format error" because the OS cannot determine which interpreter to use. The implementation saves the frontmatter content to a temporary file and executes it directly (not via |
sh -c)#!(with space) andset -eux💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.