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
59 changes: 31 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ The tool assembles context into a structured prompt with the following component
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 1. Task Frontmatter (YAML) │ │
│ │ • Task metadata (selectors, agent, etc.) │ │
│ │ • Always included when task has frontmatter │ │
│ │ 1. Rules Content (Markdown) │ │
│ │ • Coding standards and guidelines │ │
│ │ • Team conventions and best practices │ │
│ │ • Filtered by selectors (-s flag) │ │
│ │ • Skipped in resume mode (-r flag) │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
Expand All @@ -31,15 +33,7 @@ The tool assembles context into a structured prompt with the following component
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 3. Rules Content (Markdown) │ │
│ │ • Coding standards and guidelines │ │
│ │ • Team conventions and best practices │ │
│ │ • Filtered by selectors (-s flag) │ │
│ │ • Skipped in resume mode (-r flag) │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 4. Task Content (Markdown) │ │
│ │ 3. Task Content (Markdown) │ │
│ │ • Task-specific instructions │ │
│ │ • Parameter substitutions (${param}) │ │
│ │ • Command expansions (!`command`) │ │
Expand All @@ -50,10 +44,10 @@ The tool assembles context into a structured prompt with the following component
```

**Key Points:**
- **Task Frontmatter**: Provides metadata for agent decision-making and workflow automation
- **Skills**: Enable progressive disclosure of specialized capabilities
- **Rules**: Reusable context that applies across multiple tasks
- **Skills**: Enable progressive disclosure of specialized capabilities
- **Task Content**: Specific instructions for the current task with dynamic content expansion
- **Note**: Task frontmatter is used for filtering and metadata but is not included in the output

## Features

Expand Down Expand Up @@ -767,40 +761,49 @@ The AI agent can then read the full skill content from the provided location whe

### Task Frontmatter

Task frontmatter is **always** automatically included at the beginning of the output when a task file has frontmatter. This allows the AI agent or downstream tool to access metadata about the task being executed. There is no flag needed to enable this - it happens automatically.
Task frontmatter contains metadata used for filtering, agent selection, and workflow automation. While the frontmatter itself is not included in the generated output, it serves important purposes:

**What frontmatter is used for:**
- **Task selection**: Filtering between multiple task files using selectors
- **Agent specification**: The `agent` field can override the `-a` command-line flag
- **Rule filtering**: The `selectors` field automatically filters rules without requiring explicit `-s` flags
- **Workflow control**: Fields like `resume` control task behavior in different modes

**Example usage:**
```bash
coding-context -p issue_number=123 fix-bug
```

**Output format:**
```yaml
**Example task file:**
```markdown
---
resume: false
selectors:
languages: go
stage: implementation
---
# Fix Bug Task

Fix the bug in issue #${issue_number}...
```

**Output format:**
The task frontmatter is NOT included in the output. Only the task content (below the frontmatter delimiters) is included:
```markdown
# Fix Bug Task

Fix the bug in issue #123...
```

This can be useful for:
- **Agent decision making**: The AI can see metadata like priority, environment, or stage
- **Workflow automation**: Downstream tools can parse the frontmatter to make decisions
- **Debugging**: You can verify which task variant was selected and what selectors were applied
This design keeps the output focused on actionable content while still allowing frontmatter to control behavior and filtering.

**Example with selectors in frontmatter:**
```bash
coding-context implement-feature
```

If the task has `selectors` in its frontmatter, they will be visible in the output:
```yaml
---
selectors:
languages: go
stage: implementation
---
If the task has `selectors` in its frontmatter, they will automatically filter rules, but the frontmatter itself won't appear in the output:
```markdown
# Implementation Task
...
```
Expand Down
53 changes: 27 additions & 26 deletions SPECIFICATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ selectors:
**Rules:**
- `task_name` field is optional metadata
- Tasks matched by filename (e.g., `fix-bug.md` → task name `fix-bug`)
- Frontmatter is automatically output with task
- Frontmatter is used for filtering and metadata but not included in output
- Can specify `selectors` to auto-filter rules
- Supports parameter expansion: `${param_name}`

Expand Down Expand Up @@ -1096,20 +1096,28 @@ Remote directories support (via go-getter):

### 11.1 Assembly Order

1. **Task frontmatter** (YAML format, automatically included if present)
1. **Rule content** (all included rules, content only)
2. **Skill metadata** (XML format, if skills found)
3. **Rule content** (all included rules, content only)
4. **Task content** (with expansions applied)
5. **User prompt** (if provided, after `---` delimiter)
3. **Task content** (with expansions applied)
4. **User prompt** (if provided, after `---` delimiter)

**Note**: Task frontmatter is used for filtering and metadata purposes but is not included in the output.

### 11.2 Output Format

**To stdout (the context):**
```yaml
---
task_name: fix-bug
resume: false
---
```markdown
# Rule 1 Content

Rule 1 text...

# Rule 2 Content

Rule 2 text...

# Skills

You have access to the following skills. Skills are specialized capabilities that provide domain expertise, workflows, and procedural knowledge. When a task matches a skill's description, you can load the full skill content by reading the SKILL.md file at the location provided.

<available_skills>
<skill>
Expand All @@ -1119,14 +1127,6 @@ resume: false
</skill>
</available_skills>

# Rule 1 Content

Rule 1 text...

# Rule 2 Content

Rule 2 text...

# Task Content

Fix bug #123...
Expand All @@ -1148,8 +1148,7 @@ INFO: Estimated tokens: 2,345
With `-r` flag:
1. All rules are **skipped**
2. Implicit selector: `-s resume=true` added
3. Task frontmatter still included
4. Useful for continuing work with established context
3. Useful for continuing work with established context

### 11.4 Token Estimation

Expand Down Expand Up @@ -1396,12 +1395,14 @@ Bootstrap scripts output to stderr (not context) because:
- Allows logging without polluting context
- Enables verification without affecting AI input

#### 14.2.5 Frontmatter in Task Output
Task frontmatter is automatically included because:
- Enables agent decision-making
- Supports downstream tooling
- Aids debugging and verification
- No overhead for simple cases
#### 14.2.5 Task Frontmatter Usage
Task frontmatter serves metadata purposes:
- Enables task selection and filtering via selectors
- Specifies agent preferences
- Controls workflow behavior (e.g., resume mode)
- Defines rule filtering via `selectors` field

The frontmatter is not included in the output to keep the generated context focused on actionable content.

### 14.3 Limitations

Expand Down
1 change: 1 addition & 0 deletions examples/.agents
71 changes: 18 additions & 53 deletions integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,15 +379,12 @@ Command with param: !`+"`echo '${secret}'`"+`
output := runTool(t, "-C", tmpDir, "-p", "evil=!`echo INJECTED`", "-p", "secret=TOPSECRET", "test-security")

// Split output into lines to separate stderr logs from stdout prompt
// Since task frontmatter is no longer printed, we identify stdout by filtering out stderr log lines
lines := strings.Split(output, "\n")
var promptLines []string
inPrompt := false
for _, line := range lines {
// Look for the start of the frontmatter (which marks beginning of stdout)
if strings.HasPrefix(line, "---") {
inPrompt = true
}
if inPrompt {
// Stderr log lines start with "time=", stdout lines don't
if !strings.HasPrefix(line, "time=") {
promptLines = append(promptLines, line)
}
}
Expand Down Expand Up @@ -974,78 +971,46 @@ This is a test task.
t.Fatalf("failed to write task file: %v", err)
}

// Test that frontmatter is always printed
// Test that task frontmatter is NOT printed
output := runTool(t, "-C", dirs.tmpDir, "test-task")

lines := strings.Split(output, "\n")

// Find the first non-log line (skip lines starting with "time=")
var firstContentLine string
for _, line := range lines {
if !strings.HasPrefix(line, "time=") {
firstContentLine = line
break
}
}

// First content line should be frontmatter delimiter
if firstContentLine != "---" {
t.Errorf("expected first content line to be '---', got %q", firstContentLine)
}

// Should contain task frontmatter fields
if !strings.Contains(output, "task_name: test-task") {
t.Errorf("task frontmatter field 'task_name' not found in output")
// Task frontmatter fields should NOT be in the output
if strings.Contains(output, "task_name: test-task") {
t.Errorf("task frontmatter field 'task_name' should not be in output")
}
if !strings.Contains(output, "author: tester") {
t.Errorf("task frontmatter field 'author' not found in output")
if strings.Contains(output, "author: tester") {
t.Errorf("task frontmatter field 'author' should not be in output")
}
if !strings.Contains(output, "version: 1.0") {
t.Errorf("task frontmatter field 'version' not found in output")
if strings.Contains(output, "version: 1.0") {
t.Errorf("task frontmatter field 'version' should not be in output")
}

// Find the second --- (end of frontmatter)
secondDelimiterIdx := -1
for i := 1; i < len(lines); i++ {
if lines[i] == "---" {
secondDelimiterIdx = i
break
}
}
if secondDelimiterIdx == -1 {
t.Errorf("expected to find closing frontmatter delimiter '---'")
// Rule frontmatter should NOT be printed
if strings.Contains(output, "language: go") {
t.Errorf("rule frontmatter should not be printed in output")
}

// Rule content should appear after frontmatter
// Rule content should be in the output
if !strings.Contains(output, "# Test Rule") {
t.Errorf("rule content not found in output")
}

// Task content should appear after rules
// Task content should be in the output
if !strings.Contains(output, "# Test Task") {
t.Errorf("task content not found in output")
}

// Verify order: frontmatter should come before rules, rules before task content
frontmatterIdx := strings.Index(output, "task_name: test-task")
// Verify order: rules should appear before task content
ruleIdx := strings.Index(output, "# Test Rule")
taskIdx := strings.Index(output, "# Test Task")

if frontmatterIdx == -1 || ruleIdx == -1 || taskIdx == -1 {
if ruleIdx == -1 || taskIdx == -1 {
t.Fatalf("could not find all required sections in output")
}

if frontmatterIdx > ruleIdx {
t.Errorf("frontmatter should appear before rules")
}
if ruleIdx > taskIdx {
t.Errorf("rules should appear before task content")
}

// Rule frontmatter should NOT be printed
if strings.Contains(output, "language: go") {
t.Errorf("rule frontmatter should not be printed in output")
}
}

func TestTaskBootstrapFromFile(t *testing.T) {
Expand Down
35 changes: 3 additions & 32 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"strings"
"syscall"

yaml "github.com/goccy/go-yaml"
"github.com/kitproj/coding-context-cli/pkg/codingcontext"
"github.com/kitproj/coding-context-cli/pkg/codingcontext/selectors"
"github.com/kitproj/coding-context-cli/pkg/codingcontext/taskparser"
Expand Down Expand Up @@ -144,40 +143,12 @@ func main() {
logger.Info("Rules written", "path", rulesFile)
}

// Output only task frontmatter and content
if taskContent := result.Task.FrontMatter.Content; taskContent != nil {
fmt.Println("---")
if err := yaml.NewEncoder(os.Stdout).Encode(taskContent); err != nil {
logger.Error("Failed to encode task frontmatter", "error", err)
os.Exit(1)
}
fmt.Println("---")
}
// Output only task content (task frontmatter is not included)
fmt.Println(result.Task.Content)
} else {
// Normal mode: output everything
// Output task frontmatter (always enabled)
if taskContent := result.Task.FrontMatter.Content; taskContent != nil {
fmt.Println("---")
if err := yaml.NewEncoder(os.Stdout).Encode(taskContent); err != nil {
logger.Error("Failed to encode task frontmatter", "error", err)
os.Exit(1)
}
fmt.Println("---")
}

// Output available skills metadata (progressive disclosure)
if len(result.Skills.Skills) > 0 {
skillsXML, err := result.Skills.AsXML()
if err != nil {
logger.Error("Failed to encode skills as XML", "error", err)
os.Exit(1)
}
fmt.Println(skillsXML)
fmt.Println()
}

// Output the combined prompt (rules + task)
// Output the combined prompt (rules + skills + task)
// Note: Task frontmatter is not included in the output
fmt.Println(result.Prompt)
}
}