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
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ Options:
Include rules with matching frontmatter. Can be specified multiple times as key=value.
Note: Only matches top-level YAML fields in frontmatter.
-t Print task frontmatter at the beginning of output.
-a value
Target agent to use (excludes rules from other agents). Supported agents: cursor, opencode, copilot, claude, gemini, augment, windsurf, codex.
```

### Examples
Expand Down Expand Up @@ -457,6 +459,52 @@ Note the capitalization - for example, use `Go` not `go`, `JavaScript` not `java

If you need to filter on nested data, flatten your frontmatter structure to use top-level fields only.

### Targeting a Specific Agent

When working with a specific AI coding agent, the agent itself will read its own configuration files. The `-a` flag lets you specify which agent you're using, automatically excluding that agent's specific rule paths while including rules from other agents and generic rules.

**Supported agents:**
- `cursor` - Excludes `.cursor/rules`, `.cursorrules`; includes other agents and generic rules
- `opencode` - Excludes `.opencode/agent`, `.opencode/command`; includes other agents and generic rules
- `copilot` - Excludes `.github/copilot-instructions.md`, `.github/agents`; includes other agents and generic rules
- `claude` - Excludes `.claude/`, `CLAUDE.md`, `CLAUDE.local.md`; includes other agents and generic rules
- `gemini` - Excludes `.gemini/`, `GEMINI.md`; includes other agents and generic rules
- `augment` - Excludes `.augment/`; includes other agents and generic rules
- `windsurf` - Excludes `.windsurf/`, `.windsurfrules`; includes other agents and generic rules
- `codex` - Excludes `.codex/`, `AGENTS.md`; includes other agents and generic rules

**Example: Using Cursor:**

```bash
# When using Cursor, exclude .cursor/ and .cursorrules (Cursor reads those itself)
# But include rules from other agents and generic rules
coding-context -a cursor fix-bug
```

**How it works:**
- The `-a` flag sets the target agent
- The target agent's own paths are excluded (e.g., `.cursor/` for cursor)
- Rules from other agents are included (e.g., `.opencode/`, `.github/copilot-instructions.md`)
- Generic rules (from `.agents/rules`) are always included
- The agent name is automatically added as a selector, so generic rules can filter themselves with `agent: cursor` in frontmatter

**Example generic rule with agent filtering:**

```markdown
---
agent: cursor
---
# This rule only applies when using Cursor
Use Cursor-specific features...
```

**Use cases:**
- **Avoid duplication**: The agent reads its own config, so exclude it from the context
- **Cross-agent rules**: Include rules from other agents that might be relevant
- **Generic rules**: Always include generic rules, with optional agent-specific filtering

The exclusion happens before rule processing, so excluded paths are never loaded or counted toward token estimates.

### Bootstrap Scripts

A bootstrap script is an executable file that has the same name as a rule or task file but with a `-bootstrap` suffix. These scripts are used to prepare the environment, for example by installing necessary tools. The output of these scripts is sent to `stderr` and is not part of the AI context.
Expand Down
3 changes: 3 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ func main() {
var emitTaskFrontmatter bool
params := make(codingcontext.Params)
includes := make(codingcontext.Selectors)
var targetAgent codingcontext.TargetAgent
var remotePaths []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(&emitTaskFrontmatter, "t", false, "Print task frontmatter at the beginning of output.")
flag.Var(&params, "p", "Parameter to substitute in the prompt. Can be specified multiple times as key=value.")
flag.Var(&includes, "s", "Include rules with matching frontmatter. Can be specified multiple times as key=value.")
flag.Var(&targetAgent, "a", "Target agent to use (excludes rules from other agents). Supported agents: cursor, opencode, copilot, claude, gemini, augment, windsurf, codex.")
flag.Func("d", "Remote directory containing rules and tasks. Can be specified multiple times. Supports various protocols via go-getter (http://, https://, git::, s3::, etc.).", func(s string) error {
remotePaths = append(remotePaths, s)
return nil
Expand All @@ -58,6 +60,7 @@ func main() {
codingcontext.WithResume(resume),
codingcontext.WithParams(params),
codingcontext.WithSelectors(includes),
codingcontext.WithAgent(targetAgent),
codingcontext.WithRemotePaths(remotePaths),
codingcontext.WithEmitTaskFrontmatter(emitTaskFrontmatter),
codingcontext.WithLogger(logger),
Expand Down
144 changes: 144 additions & 0 deletions pkg/codingcontext/agent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package codingcontext

import (
"fmt"
"path/filepath"
"strings"
)

// Agent represents an AI coding agent
type Agent string

// Supported agents
const (
AgentCursor Agent = "cursor"
AgentOpenCode Agent = "opencode"
AgentCopilot Agent = "copilot"
AgentClaude Agent = "claude"
AgentGemini Agent = "gemini"
AgentAugment Agent = "augment"
AgentWindsurf Agent = "windsurf"
AgentCodex Agent = "codex"
)

// AllAgents returns all supported agents
func AllAgents() []Agent {
agents := make([]Agent, 0, len(agentPathPatterns))
for agent := range agentPathPatterns {
agents = append(agents, agent)
}
return agents
}

// ParseAgent parses a string into an Agent type
func ParseAgent(s string) (Agent, error) {
agent := Agent(s)

// Check if agent exists in the path patterns map
if _, exists := agentPathPatterns[agent]; exists {
return agent, nil
}

// Build list of supported agents for error message
supported := make([]string, 0, len(agentPathPatterns))
for a := range agentPathPatterns {
supported = append(supported, a.String())
}
return "", fmt.Errorf("unknown agent: %s (supported: %s)", s, strings.Join(supported, ", "))
}

// String returns the string representation of the agent
func (a Agent) String() string {
return string(a)
}

// PathPatterns returns the path patterns associated with this agent
func (a Agent) PathPatterns() []string {
return agentPathPatterns[a]
}

// MatchesPath returns true if the given path matches any of the agent's patterns
func (a Agent) MatchesPath(path string) bool {
normalizedPath := filepath.ToSlash(path)
patterns := a.PathPatterns()

for _, pattern := range patterns {
if strings.Contains(normalizedPath, pattern) {
return true
}
}

return false
}

// agentPathPatterns maps agents to their associated path patterns
var agentPathPatterns = map[Agent][]string{
AgentCursor: {
".cursor/",
".cursorrules",
},
AgentOpenCode: {
".opencode/",
},
AgentCopilot: {
".github/copilot-instructions.md",
".github/agents/",
},
AgentClaude: {
".claude/",
"CLAUDE.md",
"CLAUDE.local.md",
},
AgentGemini: {
".gemini/",
"GEMINI.md",
},
AgentAugment: {
".augment/",
},
AgentWindsurf: {
".windsurf/",
".windsurfrules",
},
AgentCodex: {
".codex/",
"AGENTS.md",
},
}

// TargetAgent represents the agent being used, which excludes that agent's own rules
// Empty string means no agent specified
type TargetAgent string

// String implements the fmt.Stringer interface for TargetAgent
func (t TargetAgent) String() string {
return string(t)
}

// Set implements the flag.Value interface for TargetAgent
func (t *TargetAgent) Set(value string) error {
agent, err := ParseAgent(value)
if err != nil {
return err
}

*t = TargetAgent(agent)
return nil
}

// ShouldExcludePath returns true if the given path should be excluded based on target agent
func (t TargetAgent) ShouldExcludePath(path string) bool {
if t == "" {
return false
}

// Exclude paths from ONLY the target agent
// The target agent will read its own rules, so we don't need to include them
// But we might want rules from other agents or generic rules
return Agent(t).MatchesPath(path)
}

// IsSet returns true if a target agent has been specified
func (t TargetAgent) IsSet() bool {
return t != ""
}
Loading