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
15 changes: 12 additions & 3 deletions docs/how-to/use-selectors.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,21 @@ coding-context -s source=github code-review

## Resume Mode

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

```bash
# These are equivalent:
# Set resume selector
coding-context -r fix-bug
coding-context -s resume=true fix-bug # but also skips rules

# Equivalent to:
coding-context -s resume=true fix-bug
```

**Note:** The `-r` flag only sets the selector. To skip rule discovery and bootstrap scripts, use the `--skip-bootstrap` flag:

```bash
# Skip rules and bootstrap (common in resume scenarios)
coding-context -r --skip-bootstrap fix-bug
```

Use resume mode when continuing work in a new session to save tokens.
Expand Down
6 changes: 3 additions & 3 deletions docs/how-to/use-with-ai-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ Use context in iterative workflows:
coding-context -s resume=false fix-bug > context-initial.txt
cat context-initial.txt | ai-agent > analysis.txt

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

Expand Down Expand Up @@ -239,7 +239,7 @@ If your context exceeds token limits:
coding-context -s priority=high fix-bug
```

2. **Use resume mode to skip rules:**
2. **Use bootstrap disabled to skip rules:**
```bash
coding-context -r fix-bug
```
Expand Down
44 changes: 30 additions & 14 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,21 +221,37 @@ coding-context -a copilot -w implement-feature
**Type:** Boolean flag
**Default:** False

Enable resume mode. This does two things:
1. Skips outputting all rule files (saves tokens)
2. Automatically adds `-s resume=true` selector
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.

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

**Example:**
```bash
# Initial session
coding-context -s resume=false fix-bug | ai-agent

# Resume session
# Set resume selector to select resume-specific tasks
coding-context -r fix-bug | ai-agent

# Equivalent to:
coding-context -s resume=true fix-bug | ai-agent
```

### `--skip-bootstrap`

**Type:** Boolean flag
**Default:** False (bootstrap enabled by default)

Skip bootstrap: skip discovering rules, skills, and running bootstrap scripts. When present, rule discovery, skill discovery, and bootstrap script execution are skipped.

**Example:**
```bash
# Skip rule discovery and bootstrap (saves tokens and time)
coding-context --skip-bootstrap fix-bug | ai-agent

# Enable bootstrap (default behavior, omit --skip-bootstrap flag)
coding-context fix-bug | ai-agent
```

**Note:** This flag is independent of the `-r` flag. Use `-r` to set the resume selector, and `--skip-bootstrap` to skip bootstrap operations.

### `-s <key>=<value>`

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

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

**Note on Resume Mode:**
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.
**Note on Bootstrap:**
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.

**Use case:**
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.
Expand Down Expand Up @@ -435,8 +451,8 @@ coding-context -d https://cdn.company.com/rules.tar.gz code-review
coding-context -s resume=false implement-feature > context.txt
cat context.txt | ai-agent > plan.txt

# Continue work (skips rules)
coding-context -r implement-feature | ai-agent
# Continue work (skip rules and bootstrap)
coding-context -r --skip-bootstrap implement-feature | ai-agent
```

### Piping to AI Agents
Expand Down
58 changes: 29 additions & 29 deletions integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -830,65 +830,65 @@ This is the resume task prompt for continuing the bug fix.
t.Errorf("normal mode: resume task content should not be in stdout")
}

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

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

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

// In resume mode, should use the resume task
// With resume selector, should use the resume task
if !strings.Contains(output, "# Fix Bug (Resume)") {
t.Errorf("resume mode: resume task content not found in stdout")
t.Errorf("resume selector: resume task content not found in stdout")
}
if strings.Contains(output, "# Fix Bug (Initial)") {
t.Errorf("resume mode: normal task content should not be in stdout")
t.Errorf("resume selector: normal task content should not be in stdout")
}

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

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

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

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

Expand Down Expand Up @@ -1221,7 +1221,7 @@ func TestSingleExpansion(t *testing.T) {
taskContent := `Task with parameter: ${param1}

And a value that looks like expansion syntax but should not be expanded: ${"nested"}`
if err := os.WriteFile(taskFile, []byte(taskContent), 0644); err != nil {
if err := os.WriteFile(taskFile, []byte(taskContent), 0o644); err != nil {
t.Fatalf("failed to create task file: %v", err)
}

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

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

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

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

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

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

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

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

Expand Down
9 changes: 6 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func main() {

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

flag.StringVar(&workDir, "C", ".", "Change to directory before doing anything.")
flag.BoolVar(&resume, "r", false, "Resume mode: skip outputting rules and select task with 'resume: true' in frontmatter.")
flag.BoolVar(&resume, "r", false, "Resume mode: set 'resume=true' selector to filter tasks by their frontmatter resume field.")
flag.BoolVar(&skipBootstrap, "skip-bootstrap", false, "Skip bootstrap: skip discovering rules, skills, and running bootstrap scripts.")
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).")
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.")
flag.Var(&params, "p", "Parameter to substitute in the prompt. Can be specified multiple times as key=value.")
Expand Down Expand Up @@ -87,6 +89,7 @@ func main() {
codingcontext.WithSearchPaths(searchPaths...),
codingcontext.WithLogger(logger),
codingcontext.WithResume(resume),
codingcontext.WithBootstrap(!skipBootstrap), // Invert: skipBootstrap=false means bootstrap enabled
codingcontext.WithAgent(agent),
codingcontext.WithManifestURL(manifestURL),
codingcontext.WithUserPrompt(userPrompt),
Expand All @@ -107,8 +110,8 @@ func main() {
os.Exit(1)
}

// Skip writing rules file in resume mode since no rules are collected
if !resume {
// Skip writing rules file if bootstrap is disabled since no rules are collected
if !skipBootstrap {
relativePath := result.Agent.UserRulePath()
if relativePath == "" {
logger.Error("Error", "error", fmt.Errorf("no user rule path available for agent"))
Expand Down
4 changes: 3 additions & 1 deletion pkg/codingcontext/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func main() {
codingcontext.WithSelectors(sel),
codingcontext.WithAgent(codingcontext.AgentCursor),
codingcontext.WithResume(false),
codingcontext.WithBootstrap(true),
codingcontext.WithUserPrompt("Additional context or instructions"),
codingcontext.WithManifestURL("https://example.com/manifest.txt"),
codingcontext.WithLogger(slog.New(slog.NewTextHandler(os.Stderr, nil))),
Expand Down Expand Up @@ -293,7 +294,8 @@ Creates a new Context with the given options.
- `WithParams(params taskparams.Params)` - Set parameters for substitution (import `taskparams` package)
- `WithSelectors(selectors selectors.Selectors)` - Set selectors for filtering rules (import `selectors` package)
- `WithAgent(agent Agent)` - Set target agent (excludes that agent's own rules)
- `WithResume(resume bool)` - Enable resume mode (skips rules)
- `WithResume(resume bool)` - Set resume selector to "true" (for filtering tasks by frontmatter resume field)
- `WithBootstrap(doBootstrap bool)` - Control whether to discover rules, skills, and run bootstrap scripts (default: true)
- `WithUserPrompt(userPrompt string)` - Set user prompt to append to task
- `WithManifestURL(manifestURL string)` - Set manifest URL for additional search paths
- `WithLogger(logger *slog.Logger)` - Set logger
Expand Down
22 changes: 11 additions & 11 deletions pkg/codingcontext/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,20 @@ type Context struct {
logger *slog.Logger
cmdRunner func(cmd *exec.Cmd) error
resume bool
doBootstrap bool // Controls whether to discover rules, skills, and run bootstrap scripts
agent Agent
userPrompt string // User-provided prompt to append to task
}

// New creates a new Context with the given options
func New(opts ...Option) *Context {
c := &Context{
params: make(taskparser.Params),
includes: make(selectors.Selectors),
rules: make([]markdown.Markdown[markdown.RuleFrontMatter], 0),
skills: skills.AvailableSkills{Skills: make([]skills.Skill, 0)},
logger: slog.New(slog.NewTextHandler(os.Stderr, nil)),
params: make(taskparser.Params),
includes: make(selectors.Selectors),
rules: make([]markdown.Markdown[markdown.RuleFrontMatter], 0),
skills: skills.AvailableSkills{Skills: make([]skills.Skill, 0)},
logger: slog.New(slog.NewTextHandler(os.Stderr, nil)),
doBootstrap: true, // Default to true for backward compatibility
cmdRunner: func(cmd *exec.Cmd) error {
return cmd.Run()
},
Expand Down Expand Up @@ -522,9 +524,8 @@ func (cc *Context) cleanupDownloadedDirectories() {
}

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

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

Expand Down
Loading