Skip to content

Commit b0d7076

Browse files
committed
feat(quick-dev): stdout-dispatch SKILL.md shim + TPL-01 validator rule
SKILL.md becomes a two-line shim: run render.py, follow the instruction it prints to stdout. render.py emits the read-and-follow line on success, HALT-and-report on failure. Progress chatter moves to stderr. This removes the skill-dir and project-root tokens from SKILL.md — per Anthropic skills spec, script output is the defined channel for agent communication, and no public skill in the reference set uses template tokens for path resolution. Adds TPL-01 to the deterministic validator: files whose name contains template must not contain compile-time double-curly substitutions. Template files seed durable, version-controlled artifacts (spec files) that execute on other machines; baking a value at render time would freeze a machine-local path into every downstream artifact.
1 parent bf30b69 commit b0d7076

4 files changed

Lines changed: 52 additions & 9 deletions

File tree

src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@ name: bmad-quick-dev
33
description: 'Implements any user intent, requirement, story, bug fix or change request by producing clean working code artifacts that follow the project''s existing architecture, patterns and conventions. Use when the user wants to build, fix, tweak, refactor, add or modify any code, component or feature.'
44
---
55

6-
Render the workflow templates, then load and follow the rendered workflow.
7-
86
```
9-
python3 {skill-dir}/render.py
7+
python render.py
108
```
119

12-
Then read and follow `{project-root}/_bmad/render/bmad-quick-dev/workflow.md`.
10+
Then follow the instruction it prints to stdout.

src/bmm-skills/4-implementation/bmad-quick-dev/render.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818

1919

2020
def find_project_root():
21-
"""Walk up from cwd until a _bmad/ directory is found. Exit non-zero otherwise."""
21+
"""Walk up from cwd until a _bmad/ directory is found. On failure, print a
22+
HALT instruction to stdout and exit non-zero."""
2223
current = os.path.abspath(os.getcwd())
2324
while True:
2425
candidate = os.path.join(current, "_bmad")
@@ -27,8 +28,7 @@ def find_project_root():
2728
parent = os.path.dirname(current)
2829
if parent == current:
2930
print(
30-
f"render.py: no _bmad/ directory found walking up from {os.getcwd()}",
31-
file=sys.stderr,
31+
f"HALT and report to the user: no _bmad/ directory found walking up from {os.getcwd()}"
3232
)
3333
sys.exit(1)
3434
current = parent
@@ -119,7 +119,9 @@ def main():
119119
fh.write(render_template(content, vars_))
120120
count += 1
121121

122-
print(f"render.py: rendered {count} files -> {out_dir}")
122+
print(f"render.py: rendered {count} files -> {out_dir}", file=sys.stderr)
123+
workflow_md = os.path.join(out_dir, "workflow.md")
124+
print(f"read and follow {workflow_md}")
123125

124126

125127
if __name__ == "__main__":

tools/skill-validator.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Before running inference-based validation, run the deterministic validator:
1010
node tools/validate-skills.js --json path/to/skill-dir
1111
```
1212

13-
This checks 14 rules deterministically: SKILL-01, SKILL-02, SKILL-03, SKILL-04, SKILL-05, SKILL-06, SKILL-07, WF-01, WF-02, PATH-02, STEP-01, STEP-06, STEP-07, SEQ-02.
13+
This checks 15 rules deterministically: SKILL-01, SKILL-02, SKILL-03, SKILL-04, SKILL-05, SKILL-06, SKILL-07, WF-01, WF-02, PATH-02, STEP-01, STEP-06, STEP-07, SEQ-02, TPL-01.
1414

1515
Review its JSON output. For any rule that produced **zero findings** in the first pass, **skip it** during inference-based validation below — it has already been verified. If a rule produced any findings, the inference validator should still review that rule (some rules like SKILL-04 and SKILL-06 have sub-checks that benefit from judgment). Focus your inference effort on the remaining rules that require judgment (PATH-01, PATH-03, PATH-04, PATH-05, WF-03, STEP-02, STEP-03, STEP-04, STEP-05, SEQ-01, REF-01, REF-02, REF-03).
1616

@@ -271,6 +271,16 @@ If no findings are generated (from either pass), the skill passes validation.
271271

272272
---
273273

274+
### TPL-01 — Template Files Must Not Contain Compile-Time Substitutions
275+
276+
- **Severity:** HIGH
277+
- **Applies to:** `.md` files whose name contains `template` (case-insensitive)
278+
- **Rule:** Template files seed durable, version-controlled artifacts (e.g. spec files) that execute on other machines. A `{{.var}}` compile-time substitution would be baked at render time and freeze a machine-local value into every artifact produced from the template.
279+
- **Detection:** Regex `\{\{\.\w+\}\}` match anywhere in a file whose basename matches `/template/i`.
280+
- **Fix:** Remove the `{{.var}}` reference. Use single-curly `{var}` if the value should be resolved at LLM runtime by the consumer of the generated artifact.
281+
282+
---
283+
274284
### REF-01 — Variable References Must Be Defined
275285

276286
- **Severity:** HIGH

tools/validate-skills.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
* - STEP-06: step frontmatter has no name/description
2020
* - STEP-07: step count 2-10
2121
* - SEQ-02: no time estimates
22+
* - TPL-01: template files must not contain compile-time {{.var}} substitutions
2223
*
2324
* Usage:
2425
* node tools/validate-skills.js # All skills, human-readable
@@ -45,6 +46,8 @@ const positionalArgs = args.filter((a) => !a.startsWith('--'));
4546
const NAME_REGEX = /^bmad-[a-z0-9]+(-[a-z0-9]+)*$/;
4647
const STEP_FILENAME_REGEX = /^step-\d{2}[a-z]?-[a-z0-9-]+\.md$/;
4748
const TIME_ESTIMATE_PATTERNS = [/takes?\s+\d+\s*min/i, /~\s*\d+\s*min/i, /estimated\s+time/i, /\bETA\b/];
49+
const TEMPLATE_FILENAME_REGEX = /template/i;
50+
const COMPILE_TIME_SUB_REGEX = /\{\{\.\w+\}\}/;
4851

4952
const SEVERITY_ORDER = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 };
5053

@@ -569,6 +572,36 @@ function validateSkill(skillDir) {
569572
}
570573
}
571574

575+
// --- TPL-01: template files must not contain compile-time {{.var}} substitutions ---
576+
// Template files seed durable, version-controlled artifacts (spec files) that
577+
// execute on other machines. Baking a {{.var}} at render time would freeze a
578+
// machine-local value into every downstream artifact.
579+
for (const filePath of allFiles) {
580+
if (path.extname(filePath) !== '.md') continue;
581+
const base = path.basename(filePath);
582+
if (!TEMPLATE_FILENAME_REGEX.test(base)) continue;
583+
584+
const relFile = path.relative(skillDir, filePath);
585+
const content = safeReadFile(filePath, findings, relFile);
586+
if (content === null) continue;
587+
588+
const lines = content.split('\n');
589+
for (const [i, line] of lines.entries()) {
590+
const match = line.match(COMPILE_TIME_SUB_REGEX);
591+
if (match) {
592+
findings.push({
593+
rule: 'TPL-01',
594+
title: 'Template files must not contain compile-time substitutions',
595+
severity: 'HIGH',
596+
file: relFile,
597+
line: i + 1,
598+
detail: `Template file contains compile-time substitution \`${match[0]}\` — this would be baked at render time and leak a machine-local value into every spec produced from the template.`,
599+
fix: 'Remove the `{{.var}}` reference. Use single-curly `{var}` if the value should be resolved at LLM runtime by the consumer of the generated spec.',
600+
});
601+
}
602+
}
603+
}
604+
572605
return findings;
573606
}
574607

0 commit comments

Comments
 (0)