Skip to content

Commit baba551

Browse files
Copilotalexec
andauthored
Add expand_params frontmatter field to disable parameter expansion (#148)
* Initial plan * Add expand_params frontmatter field to control parameter expansion - Added expand_params field to TaskFrontMatter, CommandFrontMatter, and RuleFrontMatter - Updated context.go to check expand_params before expanding parameters - Added shouldExpandParams helper function (defaults to true when not specified) - Added comprehensive tests for all scenarios (tasks, commands, rules with various combinations) - All tests passing, code linted successfully Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> * Add documentation and examples for expand_params feature - Added expand_params documentation to file-formats.md for tasks, commands, and rules - Created example files demonstrating expand_params: false usage - Added Command Files section to documentation - All examples show how to disable parameter expansion when needed Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> * Address code review feedback - Added UnmarshalJSON method to CommandFrontMatter for consistency with TaskFrontMatter and RuleFrontMatter - Updated findCommand function comment to reflect conditional parameter expansion behavior - All tests passing, code linted successfully Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> * Improve code comments for clarity - Updated shouldExpandParams comment to use lowercase 'expandParams' for consistency - Clarified findCommand comment to explicitly state default expansion behavior - All tests passing, code linted successfully Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> * Add historical context to comments - Added comment explaining CommandFrontMatter evolution from empty placeholder - Enhanced findCommand comment to explain command frontmatter support - All tests passing, code linted successfully Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> * Clarify parameter substitution comment for better accuracy - Enhanced findCommand comment to explicitly state default behavior - Clarified that substitution happens by default (nil or true) - Emphasized that substitution is skipped only when explicitly false - All tests passing, code linted successfully Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: alexec <1142830+alexec@users.noreply.github.com>
1 parent 2a3c178 commit baba551

9 files changed

Lines changed: 596 additions & 13 deletions

File tree

docs/reference/file-formats.md

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,52 @@ selectors:
313313

314314
This would only include the rules from `security-standards.md` and `go-best-practices.md`.
315315

316+
#### `expand_params` (optional)
317+
318+
**Type:** Boolean
319+
**Purpose:** Controls whether parameter expansion should occur in the task content. Defaults to `true` if not specified.
320+
321+
When set to `false`, parameter placeholders like `${variable}` are preserved as-is in the output, rather than being replaced with values from `-p` flags.
322+
323+
**Example (with parameter expansion disabled):**
324+
```yaml
325+
---
326+
task_name: preserve-template
327+
expand_params: false
328+
---
329+
330+
Issue: ${issue_number}
331+
Title: ${issue_title}
332+
```
333+
334+
**Usage:**
335+
```bash
336+
# Even with -p flags, parameters won't be expanded
337+
coding-context -p issue_number=123 -p issue_title="Bug" preserve-template
338+
# Output will contain: ${issue_number} and ${issue_title}
339+
```
340+
341+
**Use cases:**
342+
- Passing templates to AI agents that handle their own parameter substitution
343+
- Preserving template syntax that conflicts with the parameter expansion format
344+
- Keeping templates intact for later processing
345+
346+
**Default behavior (expand_params: true or omitted):**
347+
```yaml
348+
---
349+
task_name: normal-task
350+
# expand_params defaults to true
351+
---
352+
353+
Issue: ${issue_number}
354+
Title: ${issue_title}
355+
```
356+
357+
```bash
358+
coding-context -p issue_number=123 -p issue_title="Bug" normal-task
359+
# Output will contain: Issue: 123 and Title: Bug
360+
```
361+
316362
### Parameter Substitution
317363

318364
Use `${parameter_name}` syntax for dynamic values.
@@ -348,6 +394,92 @@ Task files must be in one of these directories:
348394

349395
Tasks are matched by filename (without `.md` extension). The `task_name` field in frontmatter is optional and used only for metadata. For example, a file named `fix-bug.md` is matched by the command `/fix-bug`, regardless of whether it has `task_name` in its frontmatter.
350396

397+
## Command Files
398+
399+
Command files are reusable content blocks that can be referenced from task files using slash command syntax (e.g., `/command-name`). They are Markdown files with optional YAML frontmatter.
400+
401+
### Format
402+
403+
```markdown
404+
---
405+
<optional-frontmatter-fields>
406+
---
407+
408+
# Command content in Markdown
409+
410+
This content will be substituted when the command is referenced.
411+
```
412+
413+
### Frontmatter Fields (optional)
414+
415+
#### `expand_params` (optional)
416+
417+
**Type:** Boolean
418+
**Purpose:** Controls whether parameter expansion should occur in the command content. Defaults to `true` if not specified.
419+
420+
When set to `false`, parameter placeholders like `${variable}` are preserved as-is in the output.
421+
422+
**Example:**
423+
```yaml
424+
---
425+
expand_params: false
426+
---
427+
428+
Deploy to ${environment} with version ${version}
429+
```
430+
431+
**Usage in a task:**
432+
```yaml
433+
---
434+
task_name: my-task
435+
---
436+
437+
/deploy-steps
438+
```
439+
440+
**Command line:**
441+
```bash
442+
coding-context -p environment=prod -p version=1.0 my-task
443+
# Command output will contain: ${environment} and ${version} (not expanded)
444+
```
445+
446+
This is useful when commands contain template syntax that should be preserved.
447+
448+
### Slash Command Syntax
449+
450+
Commands are referenced from tasks using slash command syntax:
451+
452+
```markdown
453+
---
454+
task_name: deploy-app
455+
---
456+
457+
# Deployment Steps
458+
459+
/pre-deploy
460+
461+
/deploy
462+
463+
/post-deploy
464+
```
465+
466+
Commands can also receive inline parameters:
467+
468+
```markdown
469+
/greet name="Alice"
470+
/deploy env="production" version="1.2.3"
471+
```
472+
473+
### File Locations
474+
475+
Command files must be in one of these directories:
476+
- `./.agents/commands/`
477+
- `./.cursor/commands/`
478+
- `./.opencode/command/`
479+
- `~/.agents/commands/`
480+
481+
Commands are matched by filename (without `.md` extension). For example, a file named `deploy.md` is matched by the slash command `/deploy`.
482+
351483
## Rule Files
352484

353485
Rule files provide reusable context snippets. They are Markdown or `.mdc` files with optional YAML frontmatter.
@@ -466,6 +598,32 @@ mcp_servers:
466598

467599
**Note:** This field is informational and does not affect rule selection.
468600

601+
#### `expand_params` (optional)
602+
603+
**Type:** Boolean
604+
**Purpose:** Controls whether parameter expansion should occur in the rule content. Defaults to `true` if not specified.
605+
606+
When set to `false`, parameter placeholders like `${variable}` are preserved as-is in the output.
607+
608+
**Example:**
609+
```yaml
610+
---
611+
languages:
612+
- go
613+
expand_params: false
614+
---
615+
616+
Use version ${version} when building the project.
617+
```
618+
619+
**Usage:**
620+
```bash
621+
coding-context -p version=1.2.3 my-task
622+
# Rule output will contain: ${version} (not expanded)
623+
```
624+
625+
This is useful when rules contain template syntax that should be preserved for the AI agent to process.
626+
469627
**Other common fields:**
470628
```yaml
471629
---
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
expand_params: false
3+
---
4+
5+
# Example Command Without Parameter Expansion
6+
7+
This command content will not have parameters expanded.
8+
9+
Placeholders like ${project_name}, ${version}, and ${environment} will be preserved as-is.
10+
11+
Use this when you want the command's output to contain template variables for later processing.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
expand_params: false
3+
---
4+
5+
# Example Rule Without Parameter Expansion
6+
7+
This rule demonstrates disabling parameter expansion at the rule level.
8+
9+
Template variables: ${variable1}, ${variable2}, ${variable3}
10+
11+
This is useful when rules contain template syntax that should be preserved for the AI agent to process.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
task_name: example-no-expansion
3+
expand_params: false
4+
---
5+
6+
# Example Task Without Parameter Expansion
7+
8+
This task demonstrates how to disable parameter expansion using the `expand_params: false` frontmatter field.
9+
10+
## Usage
11+
12+
When `expand_params` is set to `false`, parameter placeholders are preserved as-is:
13+
14+
- Issue Number: ${issue_number}
15+
- Issue Title: ${issue_title}
16+
- Repository: ${repo}
17+
- Branch: ${branch}
18+
19+
This is useful when:
20+
1. You want to pass parameters directly to an AI agent that will handle its own substitution
21+
2. You're using template syntax that conflicts with the parameter expansion syntax
22+
3. You want to preserve the template for later processing
23+
24+
## Example
25+
26+
```bash
27+
coding-context -p issue_number=123 -p issue_title="Bug Fix" example-no-expansion
28+
```
29+
30+
Even though parameters are passed on the command line, they won't be expanded in the output.
31+
The placeholders `${issue_number}` and `${issue_title}` will remain unchanged.
Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,38 @@
11
package codingcontext
22

3-
// CommandFrontMatter is an empty struct used as a placeholder for commands.
4-
// Commands don't have frontmatter, but we use this type to maintain consistency
5-
// in the getMarkdown API instead of passing nil.
6-
type CommandFrontMatter struct{}
3+
import (
4+
"encoding/json"
5+
)
6+
7+
// CommandFrontMatter represents the frontmatter fields for command files.
8+
// Previously this was an empty placeholder struct, but now supports the expand_params field
9+
// to control parameter expansion behavior in command content.
10+
type CommandFrontMatter struct {
11+
BaseFrontMatter `yaml:",inline"`
12+
13+
// ExpandParams controls whether parameter expansion should occur
14+
// Defaults to true if not specified
15+
ExpandParams *bool `yaml:"expand_params,omitempty" json:"expand_params,omitempty"`
16+
}
17+
18+
// UnmarshalJSON custom unmarshaler that populates both typed fields and Content map
19+
func (c *CommandFrontMatter) UnmarshalJSON(data []byte) error {
20+
// First unmarshal into a temporary type to avoid infinite recursion
21+
type Alias CommandFrontMatter
22+
aux := &struct {
23+
*Alias
24+
}{
25+
Alias: (*Alias)(c),
26+
}
27+
28+
if err := json.Unmarshal(data, aux); err != nil {
29+
return err
30+
}
31+
32+
// Also unmarshal into Content map
33+
if err := json.Unmarshal(data, &c.BaseFrontMatter.Content); err != nil {
34+
return err
35+
}
36+
37+
return nil
38+
}

pkg/codingcontext/context.go

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,13 @@ func (cc *Context) findTask(taskName string) error {
193193
cc.agent = agent
194194
}
195195

196-
expandedContent := cc.expandParams(md.Content, nil)
196+
// Expand parameters only if expand_params is not explicitly set to false
197+
contentToProcess := md.Content
198+
if shouldExpandParams(frontMatter.ExpandParams) {
199+
contentToProcess = cc.expandParams(md.Content, nil)
200+
}
197201

198-
task, err := ParseTask(expandedContent)
202+
task, err := ParseTask(contentToProcess)
199203
if err != nil {
200204
return err
201205
}
@@ -233,8 +237,10 @@ func (cc *Context) findTask(taskName string) error {
233237
return nil
234238
}
235239

236-
// findCommand searches for a command markdown file and returns it with parameters substituted.
237-
// Commands don't have frontmatter, so only the content is returned.
240+
// findCommand searches for a command markdown file and returns its content.
241+
// Commands now support optional frontmatter with the expand_params field.
242+
// Parameters are substituted by default (when expand_params is nil or true).
243+
// Substitution is skipped only when expand_params is explicitly set to false.
238244
func (cc *Context) findCommand(commandName string, params map[string]string) (string, error) {
239245
var content *string
240246
err := cc.visitMarkdownFiles(commandSearchPaths, func(path string) error {
@@ -244,8 +250,14 @@ func (cc *Context) findCommand(commandName string, params map[string]string) (st
244250
return err
245251
}
246252

247-
expanded := cc.expandParams(md.Content, params)
248-
content = &expanded
253+
// Expand parameters only if expand_params is not explicitly set to false
254+
var processedContent string
255+
if shouldExpandParams(frontMatter.ExpandParams) {
256+
processedContent = cc.expandParams(md.Content, params)
257+
} else {
258+
processedContent = md.Content
259+
}
260+
content = &processedContent
249261

250262
return nil
251263
})
@@ -273,6 +285,15 @@ func (cc *Context) expandParams(content string, params map[string]string) string
273285
})
274286
}
275287

288+
// shouldExpandParams returns true if parameter expansion should occur based on the expandParams field.
289+
// If expandParams is nil (not specified), it defaults to true.
290+
func shouldExpandParams(expandParams *bool) bool {
291+
if expandParams == nil {
292+
return true
293+
}
294+
return *expandParams
295+
}
296+
276297
// Run executes the context assembly for the given taskName and returns the assembled result.
277298
// The taskName is looked up in task search paths and its content is parsed into blocks.
278299
// If the taskName cannot be found as a task file, an error is returned.
@@ -406,12 +427,18 @@ func (cc *Context) findExecuteRuleFiles(ctx context.Context, homeDir string) err
406427
return fmt.Errorf("failed to parse markdown file: %w", err)
407428
}
408429

409-
expandedContent := cc.expandParams(md.Content, nil)
410-
tokens := estimateTokens(expandedContent)
430+
// Expand parameters only if expand_params is not explicitly set to false
431+
var processedContent string
432+
if shouldExpandParams(frontmatter.ExpandParams) {
433+
processedContent = cc.expandParams(md.Content, nil)
434+
} else {
435+
processedContent = md.Content
436+
}
437+
tokens := estimateTokens(processedContent)
411438

412439
cc.rules = append(cc.rules, Markdown[RuleFrontMatter]{
413440
FrontMatter: frontmatter,
414-
Content: expandedContent,
441+
Content: processedContent,
415442
Tokens: tokens,
416443
})
417444

0 commit comments

Comments
 (0)