diff --git a/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md b/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md index 87cb82036..9bcc3f176 100644 --- a/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md @@ -3,10 +3,8 @@ name: bmad-quick-dev 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.' --- -Render the workflow templates, then load and follow the rendered workflow. - ``` -python3 {skill-dir}/render.py +python render.py ``` -Then read and follow `{project-root}/_bmad/render/bmad-quick-dev/workflow.md`. +Then follow the instruction it prints to stdout. diff --git a/src/bmm-skills/4-implementation/bmad-quick-dev/render.py b/src/bmm-skills/4-implementation/bmad-quick-dev/render.py index 1eb2f309e..cc201bef6 100644 --- a/src/bmm-skills/4-implementation/bmad-quick-dev/render.py +++ b/src/bmm-skills/4-implementation/bmad-quick-dev/render.py @@ -18,7 +18,8 @@ import sys def find_project_root(): - """Walk up from cwd until a _bmad/ directory is found. Exit non-zero otherwise.""" + """Walk up from cwd until a _bmad/ directory is found. On failure, print a + HALT instruction to stdout and exit non-zero.""" current = os.path.abspath(os.getcwd()) while True: candidate = os.path.join(current, "_bmad") @@ -27,8 +28,7 @@ def find_project_root(): parent = os.path.dirname(current) if parent == current: print( - f"render.py: no _bmad/ directory found walking up from {os.getcwd()}", - file=sys.stderr, + f"HALT and report to the user: no _bmad/ directory found walking up from {os.getcwd()}" ) sys.exit(1) current = parent @@ -119,7 +119,9 @@ def main(): fh.write(render_template(content, vars_)) count += 1 - print(f"render.py: rendered {count} files -> {out_dir}") + print(f"render.py: rendered {count} files -> {out_dir}", file=sys.stderr) + workflow_md = os.path.join(out_dir, "workflow.md") + print(f"read and follow {workflow_md}") if __name__ == "__main__": diff --git a/tools/skill-validator.md b/tools/skill-validator.md index 9566e1132..66bce3ab7 100644 --- a/tools/skill-validator.md +++ b/tools/skill-validator.md @@ -10,7 +10,7 @@ Before running inference-based validation, run the deterministic validator: node tools/validate-skills.js --json path/to/skill-dir ``` -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. +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. 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). @@ -271,6 +271,16 @@ If no findings are generated (from either pass), the skill passes validation. --- +### TPL-01 — Template Files Must Not Contain Compile-Time Substitutions + +- **Severity:** HIGH +- **Applies to:** `.md` files whose name contains `template` (case-insensitive) +- **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. +- **Detection:** Regex `\{\{\.\w+\}\}` match anywhere in a file whose basename matches `/template/i`. +- **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. + +--- + ### REF-01 — Variable References Must Be Defined - **Severity:** HIGH diff --git a/tools/validate-skills.js b/tools/validate-skills.js index 997f8449a..bd29cde54 100644 --- a/tools/validate-skills.js +++ b/tools/validate-skills.js @@ -19,6 +19,7 @@ * - STEP-06: step frontmatter has no name/description * - STEP-07: step count 2-10 * - SEQ-02: no time estimates + * - TPL-01: template files must not contain compile-time {{.var}} substitutions * * Usage: * node tools/validate-skills.js # All skills, human-readable @@ -45,6 +46,8 @@ const positionalArgs = args.filter((a) => !a.startsWith('--')); const NAME_REGEX = /^bmad-[a-z0-9]+(-[a-z0-9]+)*$/; const STEP_FILENAME_REGEX = /^step-\d{2}[a-z]?-[a-z0-9-]+\.md$/; const TIME_ESTIMATE_PATTERNS = [/takes?\s+\d+\s*min/i, /~\s*\d+\s*min/i, /estimated\s+time/i, /\bETA\b/]; +const TEMPLATE_FILENAME_REGEX = /template/i; +const COMPILE_TIME_SUB_REGEX = /\{\{\.\w+\}\}/; const SEVERITY_ORDER = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 }; @@ -569,6 +572,36 @@ function validateSkill(skillDir) { } } + // --- TPL-01: template files must not contain compile-time {{.var}} substitutions --- + // Template files seed durable, version-controlled artifacts (spec files) that + // execute on other machines. Baking a {{.var}} at render time would freeze a + // machine-local value into every downstream artifact. + for (const filePath of allFiles) { + if (path.extname(filePath) !== '.md') continue; + const base = path.basename(filePath); + if (!TEMPLATE_FILENAME_REGEX.test(base)) continue; + + const relFile = path.relative(skillDir, filePath); + const content = safeReadFile(filePath, findings, relFile); + if (content === null) continue; + + const lines = content.split('\n'); + for (const [i, line] of lines.entries()) { + const match = line.match(COMPILE_TIME_SUB_REGEX); + if (match) { + findings.push({ + rule: 'TPL-01', + title: 'Template files must not contain compile-time substitutions', + severity: 'HIGH', + file: relFile, + line: i + 1, + 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.`, + 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.', + }); + } + } + } + return findings; }