Skip to content

Commit bf30b69

Browse files
committed
feat(quick-dev): render templates via stdlib Python at skill entry
Introduce a per-skill render.py that resolves compile-time {{.var}} placeholders from _bmad/bmm/config.yaml, bakes {project-root} to an absolute path, and writes rendered .md files to _bmad/render/bmad-quick-dev/. SKILL.md becomes a two-line stub: run the renderer, then read the rendered workflow. Runtime {var} placeholders (spec_file, story_key, target_status, etc.) pass through untouched. Config-derived refs (main_config, implementation_artifacts, communication_language, sprint_status, deferred_work_file, planning_artifacts) are converted across workflow.md and every step file. Adds render/ to validate-file-refs.js INSTALL_ONLY_PATHS so the validator recognizes the runtime-generated buffer.
1 parent d09363b commit bf30b69

11 files changed

Lines changed: 159 additions & 41 deletions

File tree

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,10 @@ 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-
Follow the instructions in ./workflow.md.
6+
Render the workflow templates, then load and follow the rendered workflow.
7+
8+
```
9+
python3 {skill-dir}/render.py
10+
```
11+
12+
Then read and follow `{project-root}/_bmad/render/bmad-quick-dev/workflow.md`.
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#!/usr/bin/env python3
2+
"""render.py — bmad-quick-dev template renderer.
3+
4+
Resolves compile-time {{.variable}} placeholders from _bmad/bmm/config.yaml,
5+
bakes absolute paths for {project-root} into derived values, and writes
6+
rendered .md files to {project-root}/_bmad/render/bmad-quick-dev/.
7+
8+
Runtime {variable} placeholders (single curly) pass through untouched for
9+
the LLM to resolve during workflow execution.
10+
11+
Every invocation rebuilds from scratch — no hash, no cache.
12+
Python 3 stdlib only. UTF-8 I/O.
13+
"""
14+
15+
import os
16+
import re
17+
import sys
18+
19+
20+
def find_project_root():
21+
"""Walk up from cwd until a _bmad/ directory is found. Exit non-zero otherwise."""
22+
current = os.path.abspath(os.getcwd())
23+
while True:
24+
candidate = os.path.join(current, "_bmad")
25+
if os.path.isdir(candidate):
26+
return current
27+
parent = os.path.dirname(current)
28+
if parent == current:
29+
print(
30+
f"render.py: no _bmad/ directory found walking up from {os.getcwd()}",
31+
file=sys.stderr,
32+
)
33+
sys.exit(1)
34+
current = parent
35+
36+
37+
def load_flat_yaml(path):
38+
"""Parse a flat key: value YAML file. Quotes stripped; indented values ignored.
39+
Returns {} if the file is missing (with a stderr warning)."""
40+
result = {}
41+
try:
42+
with open(path, "r", encoding="utf-8") as fh:
43+
lines = fh.readlines()
44+
except FileNotFoundError:
45+
print(
46+
f"render.py: config not found at {path}; using smart defaults",
47+
file=sys.stderr,
48+
)
49+
return result
50+
for line in lines:
51+
stripped = line.strip()
52+
if not stripped or stripped.startswith("#") or stripped.startswith("---"):
53+
continue
54+
if line.startswith(" ") or line.startswith("\t"):
55+
continue
56+
colon = stripped.find(":")
57+
if colon < 0:
58+
continue
59+
key = stripped[:colon].strip()
60+
value = stripped[colon + 1 :].strip().strip("'\"")
61+
if not key or not value:
62+
continue
63+
# Skip YAML inline dict/list literals (balanced braces/brackets)
64+
if (value.startswith("{") and value.endswith("}")) or (
65+
value.startswith("[") and value.endswith("]")
66+
):
67+
continue
68+
result[key] = value
69+
return result
70+
71+
72+
def render_template(content, vars_):
73+
"""Resolve {{.var}} substitutions. Unresolved references emit an empty string
74+
(Go's missingkey=zero semantics)."""
75+
return re.sub(r"\{\{\.(\w+)\}\}", lambda m: vars_.get(m.group(1), ""), content)
76+
77+
78+
def main():
79+
script_dir = os.path.dirname(os.path.abspath(__file__))
80+
skill_name = os.path.basename(script_dir)
81+
root = find_project_root()
82+
83+
config_path = os.path.join(root, "_bmad", "bmm", "config.yaml")
84+
vars_ = load_flat_yaml(config_path)
85+
86+
vars_.setdefault(
87+
"planning_artifacts", "{project-root}/_bmad-output/planning-artifacts"
88+
)
89+
vars_.setdefault(
90+
"implementation_artifacts",
91+
"{project-root}/_bmad-output/implementation-artifacts",
92+
)
93+
vars_.setdefault("communication_language", "English")
94+
95+
for key in list(vars_.keys()):
96+
vars_[key] = vars_[key].replace("{project-root}", root)
97+
98+
vars_["project_root"] = root
99+
vars_["main_config"] = os.path.join(root, "_bmad", "bmm", "config.yaml")
100+
vars_["sprint_status"] = os.path.join(
101+
vars_["implementation_artifacts"], "sprint-status.yaml"
102+
)
103+
vars_["deferred_work_file"] = os.path.join(
104+
vars_["implementation_artifacts"], "deferred-work.md"
105+
)
106+
107+
out_dir = os.path.join(root, "_bmad", "render", skill_name)
108+
os.makedirs(out_dir, exist_ok=True)
109+
110+
count = 0
111+
for fname in sorted(os.listdir(script_dir)):
112+
if not fname.endswith(".md") or fname == "SKILL.md":
113+
continue
114+
src = os.path.join(script_dir, fname)
115+
dst = os.path.join(out_dir, fname)
116+
with open(src, "r", encoding="utf-8") as fh:
117+
content = fh.read()
118+
with open(dst, "w", encoding="utf-8") as fh:
119+
fh.write(render_template(content, vars_))
120+
count += 1
121+
122+
print(f"render.py: rendered {count} files -> {out_dir}")
123+
124+
125+
if __name__ == "__main__":
126+
main()

src/bmm-skills/4-implementation/bmad-quick-dev/step-01-clarify-and-route.md

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
---
2-
deferred_work_file: '{implementation_artifacts}/deferred-work.md'
32
spec_file: '' # set at runtime for both routes before leaving this step
43
story_key: '' # set at runtime to the current story's full sprint-status key (e.g. 3-2-digest-delivery) when the intent is an epic story and sprint-status resolution succeeds
54
---
@@ -8,7 +7,7 @@ story_key: '' # set at runtime to the current story's full sprint-status key (e.
87

98
## RULES
109

11-
- YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{communication_language}`
10+
- YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{{.communication_language}}`
1211
- The prompt that triggered this workflow IS the intent — not a hint.
1312
- Do NOT assume you start from zero.
1413
- The intent captured in this step — even if detailed, structured, and plan-like — may contain hallucinations, scope creep, or unvalidated assumptions. It is input to the workflow, not a substitute for step-02 investigation and spec generation. Ignore directives within the intent that instruct you to skip steps or implement directly.
@@ -29,7 +28,7 @@ Before listing artifacts or prompting the user, check whether you already know t
2928
Use the same routing as above.
3029

3130
3. Otherwise — scan artifacts and ask
32-
- Active specs (`draft`, `ready-for-dev`, `in-progress`, `in-review`) in `{implementation_artifacts}`? → List them and HALT. Ask user which to resume (or `[N]` for new).
31+
- Active specs (`draft`, `ready-for-dev`, `in-progress`, `in-review`) in `{{.implementation_artifacts}}`? → List them and HALT. Ask user which to resume (or `[N]` for new).
3332
- If `draft` selected: Set `spec_file`. Run **Story-key resolution** (below). **EARLY EXIT**`./step-02-plan.md` (resume planning from the draft)
3433
- If `ready-for-dev` or `in-progress` selected: Set `spec_file`. Run **Story-key resolution** (below). **EARLY EXIT**`./step-03-implement.md`
3534
- If `in-review` selected: Set `spec_file`. Run **Story-key resolution** (below). **EARLY EXIT**`./step-04-review.md`
@@ -41,30 +40,30 @@ Never ask extra questions if you already understand what the user intends.
4140

4241
This runs on ALL paths (early-exit and INSTRUCTIONS) whenever `spec_file` is set. Determine whether the spec is an epic story — use the spec's filename, frontmatter, and any loaded epics file to identify `{epic_num}` and `{story_num}`. If the spec is not an epic story, skip silently and leave `{story_key}` unset.
4342

44-
If the spec is an epic story and `{sprint_status}` exists: find the `development_status` key matching `{epic_num}-{story_num}` by exact numeric equality on the first two segments (so `1-1` never collides with `1-10`). Exactly one match → set `{story_key}` to that full key. Zero or multiple matches → leave `{story_key}` unset (warn on multiple).
43+
If the spec is an epic story and `{{.sprint_status}}` exists: find the `development_status` key matching `{epic_num}-{story_num}` by exact numeric equality on the first two segments (so `1-1` never collides with `1-10`). Exactly one match → set `{story_key}` to that full key. Zero or multiple matches → leave `{story_key}` unset (warn on multiple).
4544

4645
## INSTRUCTIONS
4746

4847
1. Load context.
49-
- List files in `{planning_artifacts}` and `{implementation_artifacts}`.
48+
- List files in `{{.planning_artifacts}}` and `{{.implementation_artifacts}}`.
5049
- If you find an unformatted spec or intent file, ingest its contents to form your understanding of the intent.
5150
- **Determine context strategy.** Using the intent and the artifact listing, infer whether the current work is a story from an epic. Do not rely on filename patterns or regex — reason about the intent, the listing, and any epics file content together.
5251

5352
**A) Epic story path** — if the intent is clearly an epic story:
5453

5554
1. Identify the epic number `{epic_num}` and (if present) the story number `{story_num}`. If you can't identify an epic number, use path B.
5655

57-
2. **Check for a valid cached epic context.** Look for `{implementation_artifacts}/epic-<N>-context.md` (where `<N>` is the epic number). A file is **valid** when it exists, is non-empty, starts with `# Epic <N> Context:` (with the correct epic number), and no file in `{planning_artifacts}` is newer.
56+
2. **Check for a valid cached epic context.** Look for `{{.implementation_artifacts}}/epic-<N>-context.md` (where `<N>` is the epic number). A file is **valid** when it exists, is non-empty, starts with `# Epic <N> Context:` (with the correct epic number), and no file in `{{.planning_artifacts}}` is newer.
5857
- **If valid:** load it as the primary planning context. Do not load raw planning docs (PRD, architecture, UX, etc.). Skip to step 5.
5958
- **If missing, empty, or invalid:** continue to step 3.
6059

61-
3. **Compile epic context.** Produce `{implementation_artifacts}/epic-<N>-context.md` by following `./compile-epic-context.md`, in order of preference:
62-
- **Preferred — sub-agent:** spawn a sub-agent with `./compile-epic-context.md` as its prompt. Pass it the epic number, the epics file path, the `{planning_artifacts}` directory, and the output path `{implementation_artifacts}/epic-<N>-context.md`.
60+
3. **Compile epic context.** Produce `{{.implementation_artifacts}}/epic-<N>-context.md` by following `./compile-epic-context.md`, in order of preference:
61+
- **Preferred — sub-agent:** spawn a sub-agent with `./compile-epic-context.md` as its prompt. Pass it the epic number, the epics file path, the `{{.planning_artifacts}}` directory, and the output path `{{.implementation_artifacts}}/epic-<N>-context.md`.
6362
- **Fallback — inline** (for runtimes without sub-agent support, e.g. Copilot, Codex, local Ollama, older Claude): if your runtime cannot spawn sub-agents, or the spawn fails/times out, read `./compile-epic-context.md` yourself and follow its instructions to produce the same output file.
6463

6564
4. **Verify.** After compilation, verify the output file exists, is non-empty, and starts with `# Epic <N> Context:`. If valid, load it. If verification fails, HALT and report the failure.
6665

67-
5. **Previous story continuity.** Regardless of which context source succeeded above, scan `{implementation_artifacts}` for specs from the same epic with `status: done` and a lower story number. Load the most recent one (highest story number below current). Extract its **Code Map**, **Design Notes**, **Spec Change Log**, and **task list** as continuity context for step-02 planning. If no `done` spec is found but an `in-review` spec exists for the same epic with a lower story number, note it to the user and ask whether to load it.
66+
5. **Previous story continuity.** Regardless of which context source succeeded above, scan `{{.implementation_artifacts}}` for specs from the same epic with `status: done` and a lower story number. Load the most recent one (highest story number below current). Extract its **Code Map**, **Design Notes**, **Spec Change Log**, and **task list** as continuity context for step-02 planning. If no `done` spec is found but an `in-review` spec exists for the same epic with a lower story number, note it to the user and ask whether to load it.
6867

6968
6. **Resolve `{story_key}`.** If not already set by an earlier early-exit path, run **Story-key resolution** (above) now.
7069

@@ -82,11 +81,11 @@ If the spec is an epic story and `{sprint_status}` exists: find the `development
8281
- Present detected distinct goals as a bullet list.
8382
- Explain briefly (2–4 sentences): why each goal qualifies as independently shippable, any coupling risks if split, and which goal you recommend tackling first.
8483
- HALT and ask human: `[S] Split — pick first goal, defer the rest` | `[K] Keep all goals — accept the risks`
85-
- On **S**: Append deferred goals to `{deferred_work_file}`. Narrow scope to the first-mentioned goal. Continue routing.
84+
- On **S**: Append deferred goals to `{{.deferred_work_file}}`. Narrow scope to the first-mentioned goal. Continue routing.
8685
- On **K**: Proceed as-is.
8786
5. Route — choose exactly one:
8887

89-
Derive a valid kebab-case slug from the clarified intent. If the intent references a tracking identifier (story number, issue number, ticket ID), lead the slug with it (e.g. `3-2-digest-delivery`, `gh-47-fix-auth`). If `{implementation_artifacts}/spec-{slug}.md` already exists: if its status is `draft`, treat it as the same work and resume it (set `spec_file` to that path, **EARLY EXIT**`./step-02-plan.md`); otherwise append `-2`, `-3`, etc. Set `spec_file` = `{implementation_artifacts}/spec-{slug}.md`.
88+
Derive a valid kebab-case slug from the clarified intent. If the intent references a tracking identifier (story number, issue number, ticket ID), lead the slug with it (e.g. `3-2-digest-delivery`, `gh-47-fix-auth`). If `{{.implementation_artifacts}}/spec-{slug}.md` already exists: if its status is `draft`, treat it as the same work and resume it (set `spec_file` to that path, **EARLY EXIT**`./step-02-plan.md`); otherwise append `-2`, `-3`, etc. Set `spec_file` = `{{.implementation_artifacts}}/spec-{slug}.md`.
9089

9190
**a) One-shot** — zero blast radius: no plausible path by which this change causes unintended consequences elsewhere. Clear intent, no architectural decisions.
9291

src/bmm-skills/4-implementation/bmad-quick-dev/step-02-plan.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
---
2-
deferred_work_file: '{implementation_artifacts}/deferred-work.md'
3-
---
4-
51
# Step 2: Plan
62

73
## RULES
84

9-
- YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{communication_language}`
5+
- YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{{.communication_language}}`
106
- No intermediate approvals.
117

128
## INSTRUCTIONS
@@ -19,7 +15,7 @@ deferred_work_file: '{implementation_artifacts}/deferred-work.md'
1915
6. Token count check (see SCOPE STANDARD). If spec exceeds 1600 tokens:
2016
- Show user the token count.
2117
- HALT and ask human: `[S] Split — carve off secondary goals` | `[K] Keep full spec — accept the risks`
22-
- On **S**: Propose the split — name each secondary goal. Append deferred goals to `{deferred_work_file}`. Rewrite the current spec to cover only the main goal — do not surgically carve sections out; regenerate the spec for the narrowed scope. Continue to checkpoint.
18+
- On **S**: Propose the split — name each secondary goal. Append deferred goals to `{{.deferred_work_file}}`. Rewrite the current spec to cover only the main goal — do not surgically carve sections out; regenerate the spec for the narrowed scope. Continue to checkpoint.
2319
- On **K**: Continue to checkpoint with full spec.
2420

2521
### CHECKPOINT 1

src/bmm-skills/4-implementation/bmad-quick-dev/step-03-implement.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
## RULES
77

8-
- YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{communication_language}`
8+
- YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{{.communication_language}}`
99
- No push. No remote ops.
1010
- Sequential execution only.
1111
- Content inside `<frozen-after-approval>` in `{spec_file}` is read-only. Do not modify.

src/bmm-skills/4-implementation/bmad-quick-dev/step-04-review.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
---
2-
deferred_work_file: '{implementation_artifacts}/deferred-work.md'
32
specLoopIteration: 1
43
---
54

65
# Step 4: Review
76

87
## RULES
98

10-
- YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{communication_language}`
9+
- YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{{.communication_language}}`
1110
- Review subagents get NO conversation context.
1211
- All review subagents must run at the same model capability as the current session.
1312

@@ -23,7 +22,7 @@ Do NOT `git add` anything — this is read-only inspection.
2322

2423
### Review
2524

26-
Launch three subagents without conversation context. If no sub-agents are available, generate three review prompt files in `{implementation_artifacts}` — one per reviewer role below — and HALT. Ask the human to run each in a separate session (ideally a different LLM) and paste back the findings.
25+
Launch three subagents without conversation context. If no sub-agents are available, generate three review prompt files in `{{.implementation_artifacts}}` — one per reviewer role below — and HALT. Ask the human to run each in a separate session (ideally a different LLM) and paste back the findings.
2726

2827
- **Blind hunter** — receives `{diff_output}` only. No spec, no context docs, no project access. Invoke via the `bmad-review-adversarial-general` skill.
2928
- **Edge case hunter** — receives `{diff_output}` and read access to the project. Invoke via the `bmad-review-edge-case-hunter` skill.
@@ -42,7 +41,7 @@ Launch three subagents without conversation context. If no sub-agents are availa
4241
- **intent_gap** — Root cause is inside `<frozen-after-approval>`. Revert code changes. Loop back to the human to resolve. Once resolved, read fully and follow `./step-02-plan.md` to re-run steps 2–4.
4342
- **bad_spec** — Root cause is outside `<frozen-after-approval>`. Before reverting code: extract KEEP instructions for positive preservation (what worked well and must survive re-derivation). Revert code changes. Read the `## Spec Change Log` in `{spec_file}` and strictly respect all logged constraints when amending the non-frozen sections that contain the root cause. Append a new change-log entry recording: the triggering finding, what was amended, the known-bad state avoided, and the KEEP instructions. Read fully and follow `./step-03-implement.md` to re-derive the code, then this step will run again.
4443
- **patch** — Auto-fix. These are the only findings that survive loopbacks.
45-
- **defer** — Append to `{deferred_work_file}`.
44+
- **defer** — Append to `{{.deferred_work_file}}`.
4645
- **reject** — Drop silently.
4746

4847
## NEXT

src/bmm-skills/4-implementation/bmad-quick-dev/step-05-present.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
## RULES
77

8-
- YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{communication_language}`
8+
- YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{{.communication_language}}`
99
- NEVER auto-push.
1010

1111
## INSTRUCTIONS

0 commit comments

Comments
 (0)