Skip to content

Commit 8617256

Browse files
refactor!(api,cli): split resume selector from bootstrap skipping (#203)
BREAKING CHANGE: The -r flag now only sets the resume=true selector and no longer skips rule discovery and bootstrap scripts. To achieve the previous behavior of skipping bootstrap, users must now use both -r and --skip-bootstrap flags together. The change separates concerns: - -r: Sets resume selector for task filtering - --skip-bootstrap: Controls whether to discover rules, skills, and run bootstrap scripts This allows users to independently control task filtering and bootstrap behavior, providing more flexibility in workflow configurations. API changes: - Added WithBootstrap() option in options.go to control bootstrap behavior programmatically - WithResume() now only sets the resume selector and no longer affects bootstrap discovery - Context.doBootstrap field controls rule/skill discovery and bootstrap script execution independently from resume selector
1 parent f1bb4a0 commit 8617256

10 files changed

Lines changed: 203 additions & 83 deletions

File tree

docs/how-to/use-selectors.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,21 @@ coding-context -s source=github code-review
113113

114114
## Resume Mode
115115

116-
The `-r` flag is shorthand for `-s resume=true` plus skipping all rules:
116+
The `-r` flag sets the resume selector to "true", which can be used to filter tasks by their frontmatter `resume` field:
117117

118118
```bash
119-
# These are equivalent:
119+
# Set resume selector
120120
coding-context -r fix-bug
121-
coding-context -s resume=true fix-bug # but also skips rules
121+
122+
# Equivalent to:
123+
coding-context -s resume=true fix-bug
124+
```
125+
126+
**Note:** The `-r` flag only sets the selector. To skip rule discovery and bootstrap scripts, use the `--skip-bootstrap` flag:
127+
128+
```bash
129+
# Skip rules and bootstrap (common in resume scenarios)
130+
coding-context -r --skip-bootstrap fix-bug
122131
```
123132

124133
Use resume mode when continuing work in a new session to save tokens.

docs/how-to/use-with-ai-agents.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ Use context in iterative workflows:
7373
coding-context -s resume=false fix-bug > context-initial.txt
7474
cat context-initial.txt | ai-agent > analysis.txt
7575

76-
# Step 2: Implementation (skip rules with -r)
77-
coding-context -r fix-bug > context-resume.txt
76+
# Step 2: Implementation (skip rules with --skip-bootstrap)
77+
coding-context -r --skip-bootstrap fix-bug > context-resume.txt
7878
cat context-resume.txt analysis.txt | ai-agent > implementation.txt
7979
```
8080

@@ -239,7 +239,7 @@ If your context exceeds token limits:
239239
coding-context -s priority=high fix-bug
240240
```
241241

242-
2. **Use resume mode to skip rules:**
242+
2. **Use bootstrap disabled to skip rules:**
243243
```bash
244244
coding-context -r fix-bug
245245
```

docs/reference/cli.md

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -221,21 +221,37 @@ coding-context -a copilot -w implement-feature
221221
**Type:** Boolean flag
222222
**Default:** False
223223

224-
Enable resume mode. This does two things:
225-
1. Skips outputting all rule files (saves tokens)
226-
2. Automatically adds `-s resume=true` selector
224+
Set the resume selector to "true". This automatically adds `-s resume=true` selector, which can be used to filter tasks by their frontmatter `resume` field.
227225

228-
Use this when continuing work in a new session where context has already been established.
226+
**Note:** This flag only sets the selector. To skip rule discovery and bootstrap scripts, use the `--skip-bootstrap` flag instead.
229227

230228
**Example:**
231229
```bash
232-
# Initial session
233-
coding-context -s resume=false fix-bug | ai-agent
234-
235-
# Resume session
230+
# Set resume selector to select resume-specific tasks
236231
coding-context -r fix-bug | ai-agent
232+
233+
# Equivalent to:
234+
coding-context -s resume=true fix-bug | ai-agent
235+
```
236+
237+
### `--skip-bootstrap`
238+
239+
**Type:** Boolean flag
240+
**Default:** False (bootstrap enabled by default)
241+
242+
Skip bootstrap: skip discovering rules, skills, and running bootstrap scripts. When present, rule discovery, skill discovery, and bootstrap script execution are skipped.
243+
244+
**Example:**
245+
```bash
246+
# Skip rule discovery and bootstrap (saves tokens and time)
247+
coding-context --skip-bootstrap fix-bug | ai-agent
248+
249+
# Enable bootstrap (default behavior, omit --skip-bootstrap flag)
250+
coding-context fix-bug | ai-agent
237251
```
238252

253+
**Note:** This flag is independent of the `-r` flag. Use `-r` to set the resume selector, and `--skip-bootstrap` to skip bootstrap operations.
254+
239255
### `-s <key>=<value>`
240256

241257
**Type:** Key-value pair
@@ -296,12 +312,12 @@ coding-context -w fix-bug
296312
# Combine with other options
297313
coding-context -a copilot -w -s languages=go -p issue=123 fix-bug
298314

299-
# Resume mode with write rules: rules are skipped, only task output to stdout
300-
coding-context -a copilot -w -r fix-bug
315+
# Resume selector with bootstrap disabled: rules are skipped, only task output to stdout
316+
coding-context -a copilot -w -r --skip-bootstrap fix-bug
301317
```
302318

303-
**Note on Resume Mode:**
304-
When using `-w` with `-r` (resume mode), no rules file is written since rules are not collected in resume mode. Only the task prompt is output to stdout.
319+
**Note on Bootstrap:**
320+
When using `-w` with `--skip-bootstrap` (bootstrap disabled), no rules file is written since rules are not collected. Only the task prompt is output to stdout.
305321

306322
**Use case:**
307323
This mode is particularly useful when working with AI coding agents that read rules from specific configuration files. Instead of including all rules in the prompt (consuming tokens), you can write them to the agent's config file once and only send the task prompt.
@@ -435,8 +451,8 @@ coding-context -d https://cdn.company.com/rules.tar.gz code-review
435451
coding-context -s resume=false implement-feature > context.txt
436452
cat context.txt | ai-agent > plan.txt
437453

438-
# Continue work (skips rules)
439-
coding-context -r implement-feature | ai-agent
454+
# Continue work (skip rules and bootstrap)
455+
coding-context -r --skip-bootstrap implement-feature | ai-agent
440456
```
441457

442458
### Piping to AI Agents

integration_test.go

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -830,65 +830,65 @@ This is the resume task prompt for continuing the bug fix.
830830
t.Errorf("normal mode: resume task content should not be in stdout")
831831
}
832832

833-
// Test 2: Run in resume mode (with -s resume=true selector)
833+
// Test 2: Run with resume selector and bootstrap disabled (with -s resume=true and --skip-bootstrap)
834834
// Capture stdout and stderr separately to verify bootstrap scripts don't run
835-
cmd = exec.Command("go", "run", wd, "-C", dirs.tmpDir, "-s", "resume=true", "fix-bug-resume")
835+
cmd = exec.Command("go", "run", wd, "-C", dirs.tmpDir, "-s", "resume=true", "--skip-bootstrap", "fix-bug-resume")
836836
stdout.Reset()
837837
stderr.Reset()
838838
cmd.Stdout = &stdout
839839
cmd.Stderr = &stderr
840840
if err = cmd.Run(); err != nil {
841-
t.Fatalf("failed to run binary in resume mode: %v\nstdout: %s\nstderr: %s", err, stdout.String(), stderr.String())
841+
t.Fatalf("failed to run binary with resume selector and bootstrap disabled: %v\nstdout: %s\nstderr: %s", err, stdout.String(), stderr.String())
842842
}
843843
output = stdout.String()
844844
stderrOutput = stderr.String()
845845

846-
// In resume mode, rules should NOT be included
846+
// With bootstrap disabled, rules should NOT be included
847847
if strings.Contains(output, "# Coding Standards") {
848-
t.Errorf("resume mode: rule content should not be in stdout")
848+
t.Errorf("bootstrap disabled: rule content should not be in stdout")
849849
}
850850

851-
// In resume mode, bootstrap scripts should NOT run
851+
// With bootstrap disabled, bootstrap scripts should NOT run
852852
if strings.Contains(stderrOutput, "RULE_BOOTSTRAP_RAN") {
853-
t.Errorf("resume mode: rule bootstrap script should not run (found in stderr: %s)", stderrOutput)
853+
t.Errorf("bootstrap disabled: rule bootstrap script should not run (found in stderr: %s)", stderrOutput)
854854
}
855855

856-
// In resume mode, should use the resume task
856+
// With resume selector, should use the resume task
857857
if !strings.Contains(output, "# Fix Bug (Resume)") {
858-
t.Errorf("resume mode: resume task content not found in stdout")
858+
t.Errorf("resume selector: resume task content not found in stdout")
859859
}
860860
if strings.Contains(output, "# Fix Bug (Initial)") {
861-
t.Errorf("resume mode: normal task content should not be in stdout")
861+
t.Errorf("resume selector: normal task content should not be in stdout")
862862
}
863863

864-
// Test 3: Run in resume mode (with -r flag)
865-
cmd = exec.Command("go", "run", wd, "-C", dirs.tmpDir, "-r", "fix-bug-resume")
864+
// Test 3: Run with -r flag (sets resume selector) and --skip-bootstrap (disables bootstrap)
865+
cmd = exec.Command("go", "run", wd, "-C", dirs.tmpDir, "-r", "--skip-bootstrap", "fix-bug-resume")
866866
stdout.Reset()
867867
stderr.Reset()
868868
cmd.Stdout = &stdout
869869
cmd.Stderr = &stderr
870870
if err = cmd.Run(); err != nil {
871-
t.Fatalf("failed to run binary in resume mode with -r flag: %v\nstdout: %s\nstderr: %s", err, stdout.String(), stderr.String())
871+
t.Fatalf("failed to run binary with -r flag and --skip-bootstrap: %v\nstdout: %s\nstderr: %s", err, stdout.String(), stderr.String())
872872
}
873873
output = stdout.String()
874874
stderrOutput = stderr.String()
875875

876-
// In resume mode with -r flag, rules should NOT be included
876+
// With bootstrap disabled, rules should NOT be included
877877
if strings.Contains(output, "# Coding Standards") {
878-
t.Errorf("resume mode (-r flag): rule content should not be in stdout")
878+
t.Errorf("bootstrap disabled (--skip-bootstrap): rule content should not be in stdout")
879879
}
880880

881-
// In resume mode with -r flag, bootstrap scripts should NOT run
881+
// With bootstrap disabled, bootstrap scripts should NOT run
882882
if strings.Contains(stderrOutput, "RULE_BOOTSTRAP_RAN") {
883-
t.Errorf("resume mode (-r flag): rule bootstrap script should not run (found in stderr: %s)", stderrOutput)
883+
t.Errorf("bootstrap disabled (--skip-bootstrap): rule bootstrap script should not run (found in stderr: %s)", stderrOutput)
884884
}
885885

886-
// In resume mode with -r flag, should use the resume task
886+
// With -r flag, should use the resume task
887887
if !strings.Contains(output, "# Fix Bug (Resume)") {
888-
t.Errorf("resume mode (-r flag): resume task content not found in stdout")
888+
t.Errorf("resume selector (-r flag): resume task content not found in stdout")
889889
}
890890
if strings.Contains(output, "# Fix Bug (Initial)") {
891-
t.Errorf("resume mode (-r flag): normal task content should not be in stdout")
891+
t.Errorf("resume selector (-r flag): normal task content should not be in stdout")
892892
}
893893
}
894894

@@ -1221,7 +1221,7 @@ func TestSingleExpansion(t *testing.T) {
12211221
taskContent := `Task with parameter: ${param1}
12221222
12231223
And a value that looks like expansion syntax but should not be expanded: ${"nested"}`
1224-
if err := os.WriteFile(taskFile, []byte(taskContent), 0644); err != nil {
1224+
if err := os.WriteFile(taskFile, []byte(taskContent), 0o644); err != nil {
12251225
t.Fatalf("failed to create task file: %v", err)
12261226
}
12271227

@@ -1252,14 +1252,14 @@ func TestCommandExpansionOnce(t *testing.T) {
12521252
// Create a command file with a parameter
12531253
commandFile := filepath.Join(commandsDir, "test-cmd.md")
12541254
commandContent := `Command param: ${cmd_param}`
1255-
if err := os.WriteFile(commandFile, []byte(commandContent), 0644); err != nil {
1255+
if err := os.WriteFile(commandFile, []byte(commandContent), 0o644); err != nil {
12561256
t.Fatalf("failed to create command file: %v", err)
12571257
}
12581258

12591259
// Create a task that calls the command with a param containing expansion syntax
12601260
taskFile := filepath.Join(dirs.tasksDir, "test-cmd-task.md")
12611261
taskContent := `/test-cmd cmd_param="!` + "`echo injected`" + `"`
1262-
if err := os.WriteFile(taskFile, []byte(taskContent), 0644); err != nil {
1262+
if err := os.WriteFile(taskFile, []byte(taskContent), 0o644); err != nil {
12631263
t.Fatalf("failed to create task file: %v", err)
12641264
}
12651265

@@ -1420,12 +1420,12 @@ This is the task prompt for resume mode.
14201420
// Create a temporary home directory for this test
14211421
tmpHome := t.TempDir()
14221422

1423-
// Run with -w flag, -r flag (resume mode), and -a copilot
1423+
// Run with -w flag, -r flag (sets resume selector), --skip-bootstrap (disables bootstrap), and -a copilot
14241424
wd, err := os.Getwd()
14251425
if err != nil {
14261426
t.Fatalf("failed to get working directory: %v", err)
14271427
}
1428-
cmd := exec.Command("go", "run", wd, "-C", dirs.tmpDir, "-a", "copilot", "-w", "-r", "test-task-resume")
1428+
cmd := exec.Command("go", "run", wd, "-C", dirs.tmpDir, "-a", "copilot", "-w", "-r", "--skip-bootstrap", "test-task-resume")
14291429
var stdout, stderr bytes.Buffer
14301430
cmd.Stdout = &stdout
14311431
cmd.Stderr = &stderr
@@ -1448,7 +1448,7 @@ This is the task prompt for resume mode.
14481448

14491449
// Verify that the rules were NOT printed to stdout
14501450
if strings.Contains(output, "# Test Rule") {
1451-
t.Errorf("rules should not be in stdout when using -w flag with resume mode")
1451+
t.Errorf("rules should not be in stdout when using -w flag with bootstrap disabled")
14521452
}
14531453

14541454
// Verify that the task IS printed to stdout
@@ -1459,17 +1459,17 @@ This is the task prompt for resume mode.
14591459
t.Errorf("task description not found in stdout")
14601460
}
14611461

1462-
// Verify that NO rules file was created in resume mode
1462+
// Verify that NO rules file was created when bootstrap is disabled
14631463
expectedRulesPath := filepath.Join(tmpHome, ".github", "agents", "AGENTS.md")
14641464
if _, err := os.Stat(expectedRulesPath); err == nil {
1465-
t.Errorf("rules file should NOT be created in resume mode with -w flag, but found at %s", expectedRulesPath)
1465+
t.Errorf("rules file should NOT be created when bootstrap is disabled with -w flag, but found at %s", expectedRulesPath)
14661466
} else if !os.IsNotExist(err) {
14671467
t.Fatalf("unexpected error checking for rules file: %v", err)
14681468
}
14691469

14701470
// Verify that the logger did NOT report writing rules
14711471
if strings.Contains(stderrOutput, "Rules written") {
1472-
t.Errorf("stderr should NOT contain 'Rules written' message in resume mode")
1472+
t.Errorf("stderr should NOT contain 'Rules written' message when bootstrap is disabled")
14731473
}
14741474
}
14751475

main.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ func main() {
2424

2525
var workDir string
2626
var resume bool
27+
var skipBootstrap bool // When true, skips bootstrap (default false means bootstrap enabled)
2728
var writeRules bool
2829
var agent codingcontext.Agent
2930
params := make(taskparser.Params)
@@ -32,7 +33,8 @@ func main() {
3233
var manifestURL string
3334

3435
flag.StringVar(&workDir, "C", ".", "Change to directory before doing anything.")
35-
flag.BoolVar(&resume, "r", false, "Resume mode: skip outputting rules and select task with 'resume: true' in frontmatter.")
36+
flag.BoolVar(&resume, "r", false, "Resume mode: set 'resume=true' selector to filter tasks by their frontmatter resume field.")
37+
flag.BoolVar(&skipBootstrap, "skip-bootstrap", false, "Skip bootstrap: skip discovering rules, skills, and running bootstrap scripts.")
3638
flag.BoolVar(&writeRules, "w", false, "Write rules to the agent's user rules path and only print the prompt to stdout. Requires agent (via task 'agent' field or -a flag).")
3739
flag.Var(&agent, "a", "Target agent to use. Required when using -w to write rules to the agent's user rules path. Supported agents: cursor, opencode, copilot, claude, gemini, augment, windsurf, codex.")
3840
flag.Var(&params, "p", "Parameter to substitute in the prompt. Can be specified multiple times as key=value.")
@@ -87,6 +89,7 @@ func main() {
8789
codingcontext.WithSearchPaths(searchPaths...),
8890
codingcontext.WithLogger(logger),
8991
codingcontext.WithResume(resume),
92+
codingcontext.WithBootstrap(!skipBootstrap), // Invert: skipBootstrap=false means bootstrap enabled
9093
codingcontext.WithAgent(agent),
9194
codingcontext.WithManifestURL(manifestURL),
9295
codingcontext.WithUserPrompt(userPrompt),
@@ -107,8 +110,8 @@ func main() {
107110
os.Exit(1)
108111
}
109112

110-
// Skip writing rules file in resume mode since no rules are collected
111-
if !resume {
113+
// Skip writing rules file if bootstrap is disabled since no rules are collected
114+
if !skipBootstrap {
112115
relativePath := result.Agent.UserRulePath()
113116
if relativePath == "" {
114117
logger.Error("Error", "error", fmt.Errorf("no user rule path available for agent"))

pkg/codingcontext/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ func main() {
8686
codingcontext.WithSelectors(sel),
8787
codingcontext.WithAgent(codingcontext.AgentCursor),
8888
codingcontext.WithResume(false),
89+
codingcontext.WithBootstrap(true),
8990
codingcontext.WithUserPrompt("Additional context or instructions"),
9091
codingcontext.WithManifestURL("https://example.com/manifest.txt"),
9192
codingcontext.WithLogger(slog.New(slog.NewTextHandler(os.Stderr, nil))),
@@ -293,7 +294,8 @@ Creates a new Context with the given options.
293294
- `WithParams(params taskparams.Params)` - Set parameters for substitution (import `taskparams` package)
294295
- `WithSelectors(selectors selectors.Selectors)` - Set selectors for filtering rules (import `selectors` package)
295296
- `WithAgent(agent Agent)` - Set target agent (excludes that agent's own rules)
296-
- `WithResume(resume bool)` - Enable resume mode (skips rules)
297+
- `WithResume(resume bool)` - Set resume selector to "true" (for filtering tasks by frontmatter resume field)
298+
- `WithBootstrap(doBootstrap bool)` - Control whether to discover rules, skills, and run bootstrap scripts (default: true)
297299
- `WithUserPrompt(userPrompt string)` - Set user prompt to append to task
298300
- `WithManifestURL(manifestURL string)` - Set manifest URL for additional search paths
299301
- `WithLogger(logger *slog.Logger)` - Set logger

pkg/codingcontext/context.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,20 @@ type Context struct {
3434
logger *slog.Logger
3535
cmdRunner func(cmd *exec.Cmd) error
3636
resume bool
37+
doBootstrap bool // Controls whether to discover rules, skills, and run bootstrap scripts
3738
agent Agent
3839
userPrompt string // User-provided prompt to append to task
3940
}
4041

4142
// New creates a new Context with the given options
4243
func New(opts ...Option) *Context {
4344
c := &Context{
44-
params: make(taskparser.Params),
45-
includes: make(selectors.Selectors),
46-
rules: make([]markdown.Markdown[markdown.RuleFrontMatter], 0),
47-
skills: skills.AvailableSkills{Skills: make([]skills.Skill, 0)},
48-
logger: slog.New(slog.NewTextHandler(os.Stderr, nil)),
45+
params: make(taskparser.Params),
46+
includes: make(selectors.Selectors),
47+
rules: make([]markdown.Markdown[markdown.RuleFrontMatter], 0),
48+
skills: skills.AvailableSkills{Skills: make([]skills.Skill, 0)},
49+
logger: slog.New(slog.NewTextHandler(os.Stderr, nil)),
50+
doBootstrap: true, // Default to true for backward compatibility
4951
cmdRunner: func(cmd *exec.Cmd) error {
5052
return cmd.Run()
5153
},
@@ -522,9 +524,8 @@ func (cc *Context) cleanupDownloadedDirectories() {
522524
}
523525

524526
func (cc *Context) findExecuteRuleFiles(ctx context.Context, homeDir string) error {
525-
// Skip rule file discovery if resume mode is enabled
526-
// Check cc.resume directly first, then fall back to selector check for backward compatibility
527-
if cc.resume || (cc.includes != nil && cc.includes.GetValue("resume", "true")) {
527+
// Skip rule file discovery if bootstrap is disabled
528+
if !cc.doBootstrap {
528529
return nil
529530
}
530531

@@ -607,9 +608,8 @@ func (cc *Context) runBootstrapScript(ctx context.Context, path string) error {
607608
// discoverSkills searches for skill directories and loads only their metadata (name and description)
608609
// for progressive disclosure. Skills are folders containing a SKILL.md file.
609610
func (cc *Context) discoverSkills() error {
610-
// Skip skill discovery if resume mode is enabled
611-
// Check cc.resume directly first, then fall back to selector check for backward compatibility
612-
if cc.resume || (cc.includes != nil && cc.includes.GetValue("resume", "true")) {
611+
// Skip skill discovery if bootstrap is disabled
612+
if !cc.doBootstrap {
613613
return nil
614614
}
615615

0 commit comments

Comments
 (0)