bmad-method 6.10.1-next.2 → 6.10.1-next.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/.claude-plugin/marketplace.json +52 -27
  2. package/package.json +4 -3
  3. package/removals.txt +5 -0
  4. package/src/bmm-skills/1-analysis/bmad-agent-analyst/customize.toml +19 -4
  5. package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +1 -1
  6. package/src/bmm-skills/1-analysis/bmad-product-brief/customize.toml +5 -3
  7. package/src/bmm-skills/2-plan-workflows/bmad-prd/SKILL.md +2 -0
  8. package/src/bmm-skills/2-plan-workflows/bmad-prd/customize.toml +5 -3
  9. package/src/{core-skills → bmm-skills/2-plan-workflows}/bmad-spec/SKILL.md +18 -3
  10. package/src/{core-skills → bmm-skills/2-plan-workflows}/bmad-spec/assets/spec-template.md +1 -1
  11. package/src/bmm-skills/2-plan-workflows/bmad-spec/assets/stories-schema.md +44 -0
  12. package/src/{core-skills → bmm-skills/2-plan-workflows}/bmad-spec/customize.toml +3 -4
  13. package/src/bmm-skills/2-plan-workflows/bmad-ux/customize.toml +4 -2
  14. package/src/bmm-skills/3-solutioning/bmad-architecture/SKILL.md +2 -2
  15. package/src/bmm-skills/3-solutioning/bmad-architecture/customize.toml +4 -2
  16. package/src/bmm-skills/4-implementation/bmad-agent-dev/customize.toml +3 -0
  17. package/src/bmm-skills/4-implementation/bmad-code-review/SKILL.md +2 -4
  18. package/src/bmm-skills/4-implementation/bmad-code-review/customize.toml +52 -0
  19. package/src/bmm-skills/4-implementation/bmad-code-review/steps/step-01-gather-context.md +0 -1
  20. package/src/bmm-skills/4-implementation/bmad-code-review/steps/step-02-review.md +10 -24
  21. package/src/bmm-skills/4-implementation/bmad-code-review/steps/step-03-triage.md +3 -4
  22. package/src/bmm-skills/4-implementation/bmad-dev-auto/SKILL.md +33 -12
  23. package/src/bmm-skills/4-implementation/bmad-dev-auto/customize.toml +75 -0
  24. package/src/bmm-skills/4-implementation/bmad-dev-auto/spec-template.md +4 -4
  25. package/src/bmm-skills/4-implementation/bmad-dev-auto/step-01-clarify-and-route.md +22 -4
  26. package/src/bmm-skills/4-implementation/bmad-dev-auto/step-02-plan.md +5 -6
  27. package/src/bmm-skills/4-implementation/bmad-dev-auto/step-03-implement.md +8 -4
  28. package/src/bmm-skills/4-implementation/bmad-dev-auto/step-04-review.md +13 -19
  29. package/src/bmm-skills/4-implementation/bmad-dev-story/customize.toml +3 -0
  30. package/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md +6 -108
  31. package/src/bmm-skills/4-implementation/bmad-quick-dev/customize.toml +73 -0
  32. package/src/bmm-skills/4-implementation/bmad-quick-dev/render.py +381 -0
  33. package/src/bmm-skills/4-implementation/bmad-quick-dev/spec-template.md +2 -2
  34. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-01-clarify-and-route.md +11 -13
  35. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-02-plan.md +4 -9
  36. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-03-implement.md +12 -4
  37. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-04-review.md +8 -20
  38. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-05-present.md +4 -4
  39. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-oneshot.md +12 -13
  40. package/src/bmm-skills/4-implementation/bmad-quick-dev/sync-sprint-status.md +6 -6
  41. package/src/bmm-skills/4-implementation/bmad-quick-dev/workflow.md +85 -0
  42. package/src/bmm-skills/module-help.csv +2 -4
  43. package/src/bmm-skills/module.yaml +2 -5
  44. package/src/bmm-skills/v6-shims/README.md +24 -0
  45. package/src/bmm-skills/{3-solutioning → v6-shims}/bmad-create-architecture/SKILL.md +1 -1
  46. package/src/bmm-skills/{2-plan-workflows → v6-shims}/bmad-create-prd/SKILL.md +3 -3
  47. package/src/bmm-skills/v6-shims/bmad-domain-research/SKILL.md +14 -0
  48. package/src/bmm-skills/{2-plan-workflows → v6-shims}/bmad-edit-prd/SKILL.md +3 -3
  49. package/src/bmm-skills/v6-shims/bmad-market-research/SKILL.md +14 -0
  50. package/src/bmm-skills/v6-shims/bmad-technical-research/SKILL.md +14 -0
  51. package/src/bmm-skills/{2-plan-workflows → v6-shims}/bmad-validate-prd/SKILL.md +3 -3
  52. package/src/core-skills/bmad-advanced-elicitation/SKILL.md +26 -103
  53. package/src/core-skills/bmad-advanced-elicitation/customize.toml +54 -0
  54. package/src/core-skills/bmad-advanced-elicitation/scripts/pick_methods.py +233 -0
  55. package/src/core-skills/bmad-advanced-elicitation/scripts/tests/test_pick_methods.py +228 -0
  56. package/src/core-skills/bmad-brainstorming/SKILL.md +3 -3
  57. package/src/core-skills/bmad-brainstorming/assets/brain-selector.html +2 -0
  58. package/src/core-skills/bmad-brainstorming/references/mode-autonomous.md +1 -1
  59. package/src/core-skills/bmad-brainstorming/scripts/brain.py +36 -6
  60. package/src/core-skills/bmad-brainstorming/scripts/tests/test_brain.py +22 -0
  61. package/src/core-skills/bmad-customize/SKILL.md +2 -2
  62. package/src/core-skills/bmad-deep-recon/SKILL.md +82 -0
  63. package/src/core-skills/bmad-deep-recon/assets/research.template.md +18 -0
  64. package/src/core-skills/bmad-deep-recon/customize.toml +212 -0
  65. package/src/core-skills/bmad-deep-recon/references/draft.md +8 -0
  66. package/src/core-skills/bmad-deep-recon/references/finalize.md +11 -0
  67. package/src/core-skills/bmad-deep-recon/references/html-briefing.md +16 -0
  68. package/src/core-skills/bmad-deep-recon/references/lifecycle.md +11 -0
  69. package/src/core-skills/bmad-deep-recon/references/process.md +10 -0
  70. package/src/core-skills/bmad-deep-recon/references/run.md +73 -0
  71. package/src/core-skills/bmad-deep-recon/references/selection.md +13 -0
  72. package/src/core-skills/bmad-deep-recon/references/synthesis.md +16 -0
  73. package/src/core-skills/bmad-deep-recon/references/verification.md +29 -0
  74. package/src/core-skills/bmad-deep-recon/scripts/recon_kit.py +322 -0
  75. package/src/core-skills/bmad-deep-recon/scripts/tests/test_recon_kit.py +144 -0
  76. package/src/core-skills/bmad-deep-recon/types/academic-lit.md +19 -0
  77. package/src/core-skills/bmad-deep-recon/types/competitive.md +19 -0
  78. package/src/core-skills/bmad-deep-recon/types/domain.md +19 -0
  79. package/src/core-skills/bmad-deep-recon/types/market.md +19 -0
  80. package/src/core-skills/bmad-deep-recon/types/technical.md +19 -0
  81. package/src/core-skills/bmad-deep-recon/types/user-voice.md +19 -0
  82. package/src/core-skills/bmad-forge-idea/SKILL.md +2 -2
  83. package/src/core-skills/bmad-forge-idea/scripts/resolve_personas.py +4 -1
  84. package/src/core-skills/bmad-help/SKILL.md +1 -1
  85. package/src/core-skills/bmad-party-mode/SKILL.md +2 -2
  86. package/src/core-skills/bmad-party-mode/scripts/resolve_party.py +11 -3
  87. package/src/core-skills/bmad-review/SKILL.md +49 -0
  88. package/src/core-skills/bmad-review/customize.toml +141 -0
  89. package/src/core-skills/bmad-review/references/editorial-common.md +56 -0
  90. package/src/core-skills/bmad-review/references/lens-adversarial.md +18 -0
  91. package/src/core-skills/bmad-review/references/lens-edge-case-hunter.md +54 -0
  92. package/src/core-skills/bmad-review/references/lens-prose.md +7 -0
  93. package/src/core-skills/bmad-review/references/lens-structure.md +9 -0
  94. package/src/core-skills/{bmad-review-verification-gap/SKILL.md → bmad-review/references/lens-verification-gap.md} +15 -39
  95. package/src/core-skills/bmad-review/references/structure-models.md +44 -0
  96. package/src/core-skills/bmad-review/scripts/tests/test_word_metrics.py +62 -0
  97. package/src/core-skills/bmad-review/scripts/word_metrics.py +102 -0
  98. package/src/core-skills/module-help.csv +3 -7
  99. package/src/core-skills/module.yaml +5 -0
  100. package/src/core-skills/v6-shims/README.md +25 -0
  101. package/src/core-skills/v6-shims/bmad-editorial-review/SKILL.md +6 -0
  102. package/src/core-skills/v6-shims/bmad-editorial-review/customize.toml +31 -0
  103. package/src/core-skills/v6-shims/bmad-editorial-review-prose/SKILL.md +6 -0
  104. package/src/core-skills/v6-shims/bmad-editorial-review-structure/SKILL.md +6 -0
  105. package/src/core-skills/v6-shims/bmad-review-adversarial-general/SKILL.md +6 -0
  106. package/src/core-skills/v6-shims/bmad-review-edge-case-hunter/SKILL.md +6 -0
  107. package/src/core-skills/v6-shims/bmad-review-verification-gap/SKILL.md +6 -0
  108. package/tools/installer/ide/platform-codes.yaml +7 -0
  109. package/tools/installer/modules/custom-module-manager.js +12 -6
  110. package/tools/installer/modules/external-manager.js +12 -8
  111. package/tools/installer/modules/git-env.js +47 -0
  112. package/tools/skill-validator.md +11 -1
  113. package/tools/validate-skills.js +33 -0
  114. package/src/bmm-skills/1-analysis/research/bmad-domain-research/SKILL.md +0 -96
  115. package/src/bmm-skills/1-analysis/research/bmad-domain-research/customize.toml +0 -41
  116. package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-01-init.md +0 -137
  117. package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-02-domain-analysis.md +0 -229
  118. package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-03-competitive-landscape.md +0 -238
  119. package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-04-regulatory-focus.md +0 -206
  120. package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-05-technical-trends.md +0 -234
  121. package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-06-research-synthesis.md +0 -450
  122. package/src/bmm-skills/1-analysis/research/bmad-domain-research/research.template.md +0 -29
  123. package/src/bmm-skills/1-analysis/research/bmad-market-research/SKILL.md +0 -96
  124. package/src/bmm-skills/1-analysis/research/bmad-market-research/customize.toml +0 -41
  125. package/src/bmm-skills/1-analysis/research/bmad-market-research/research.template.md +0 -29
  126. package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-01-init.md +0 -184
  127. package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-02-customer-behavior.md +0 -239
  128. package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-03-customer-pain-points.md +0 -251
  129. package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-04-customer-decisions.md +0 -261
  130. package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-05-competitive-analysis.md +0 -173
  131. package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-06-research-completion.md +0 -484
  132. package/src/bmm-skills/1-analysis/research/bmad-technical-research/SKILL.md +0 -96
  133. package/src/bmm-skills/1-analysis/research/bmad-technical-research/customize.toml +0 -41
  134. package/src/bmm-skills/1-analysis/research/bmad-technical-research/research.template.md +0 -29
  135. package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-01-init.md +0 -137
  136. package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-02-technical-overview.md +0 -239
  137. package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-03-integration-patterns.md +0 -248
  138. package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-04-architectural-patterns.md +0 -202
  139. package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-05-implementation-research.md +0 -233
  140. package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-06-research-synthesis.md +0 -493
  141. package/src/core-skills/bmad-brainstorming/analysis/catalog-analysis.md +0 -239
  142. package/src/core-skills/bmad-brainstorming/analysis/method-matrix.csv +0 -109
  143. package/src/core-skills/bmad-editorial-review-prose/SKILL.md +0 -86
  144. package/src/core-skills/bmad-editorial-review-structure/SKILL.md +0 -179
  145. package/src/core-skills/bmad-index-docs/SKILL.md +0 -66
  146. package/src/core-skills/bmad-review-adversarial-general/SKILL.md +0 -37
  147. package/src/core-skills/bmad-review-edge-case-hunter/SKILL.md +0 -73
  148. package/src/core-skills/bmad-review-edge-case-hunter/references/deletion-check.md +0 -14
  149. package/src/core-skills/bmad-shard-doc/SKILL.md +0 -105
  150. /package/src/{core-skills → bmm-skills/2-plan-workflows}/bmad-spec/assets/headless-schemas.md +0 -0
  151. /package/src/bmm-skills/{3-solutioning → v6-shims}/bmad-create-architecture/customize.toml +0 -0
  152. /package/src/bmm-skills/{2-plan-workflows → v6-shims}/bmad-create-prd/customize.toml +0 -0
  153. /package/src/bmm-skills/{2-plan-workflows → v6-shims}/bmad-edit-prd/customize.toml +0 -0
  154. /package/src/bmm-skills/{2-plan-workflows → v6-shims}/bmad-validate-prd/customize.toml +0 -0
  155. /package/src/core-skills/bmad-advanced-elicitation/{methods.csv → assets/methods.csv} +0 -0
  156. /package/src/core-skills/bmad-party-mode/scripts/tests/{test-resolve_party.py → test_resolve_party.py} +0 -0
@@ -0,0 +1,381 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.11"
4
+ # ///
5
+ """render.py — bmad-quick-dev template renderer.
6
+
7
+ Resolves compile-time {{.variable}} placeholders from BMad's central config,
8
+ bakes absolute paths for {project-root} into derived values, resolves and
9
+ inlines the skill's [workflow] customization block, and writes rendered .md
10
+ files to {project-root}/_bmad/render/bmad-quick-dev/.
11
+
12
+ Config: four-layer merge of _bmad/config.toml + config.user.toml +
13
+ custom/config.toml + custom/config.user.toml (post-#2285 installs).
14
+ Keys surface from [core] and [modules.bmm]. Missing or unparseable
15
+ config.toml → HALT. A {{.var}} referenced by this skill's .md sources but
16
+ absent from the merged config → HALT (never a silent empty substitution).
17
+ Optional layers may be missing, but one that exists and cannot be parsed
18
+ or read → HALT.
19
+
20
+ Customization: three-layer merge of {skill}/customize.toml +
21
+ _bmad/custom/bmad-quick-dev.toml + .user.toml (same structural rules as
22
+ resolve_customization.py). The resolved [workflow] values fill {workflow.*}
23
+ placeholders, so this skill needs no runtime resolve_customization.py call.
24
+ Other single-curly placeholders ({project-root}, {spec_file}, {skill-root},
25
+ ...) pass through untouched for the LLM to resolve during workflow execution.
26
+
27
+ Every invocation rebuilds from scratch — no hash, no cache.
28
+ Python 3.11+ stdlib only. UTF-8 I/O.
29
+ """
30
+
31
+ import os
32
+ import posixpath
33
+ import re
34
+ import sys
35
+ import tomllib
36
+
37
+
38
+ def find_project_root():
39
+ """Walk up from cwd until a _bmad/ directory is found. On failure, print a
40
+ HALT instruction to stdout and exit non-zero."""
41
+ current = os.path.abspath(os.getcwd())
42
+ while True:
43
+ candidate = os.path.join(current, "_bmad")
44
+ if os.path.isdir(candidate):
45
+ return current
46
+ parent = os.path.dirname(current)
47
+ if parent == current:
48
+ print(
49
+ f"HALT and report to the user: no _bmad/ directory found walking up from {os.getcwd()}"
50
+ )
51
+ sys.exit(1)
52
+ current = parent
53
+
54
+
55
+ def load_toml(path, required=False):
56
+ """Load a TOML file. Only absence is negotiable: a missing optional file
57
+ returns {} (customization layers are optional), a missing required file
58
+ HALTs. A file that exists but cannot be parsed or read always HALTs —
59
+ stdout is how this script signals workflow halts to its LLM caller — the
60
+ user wrote it to be honored, and silently continuing with {} would discard
61
+ their customizations with no failure signal."""
62
+ if not os.path.isfile(path):
63
+ if required:
64
+ print(
65
+ f"HALT and report to the user: required config file not found: {path} — "
66
+ "ensure this is a post-#2285 BMAD install"
67
+ )
68
+ sys.exit(1)
69
+ return {}
70
+ try:
71
+ with open(path, "rb") as fh:
72
+ parsed = tomllib.load(fh)
73
+ except tomllib.TOMLDecodeError as error:
74
+ print(f"HALT and report to the user: failed to parse {path}: {error}")
75
+ sys.exit(1)
76
+ except OSError as error:
77
+ print(f"HALT and report to the user: failed to read {path}: {error}")
78
+ sys.exit(1)
79
+ if not isinstance(parsed, dict):
80
+ return {}
81
+ return parsed
82
+
83
+
84
+ def _deep_merge(base, override):
85
+ """Dict-aware deep merge. Lists and scalars: override wins (we don't need
86
+ the full keyed-merge semantics of resolve_config.py — quick-dev only reads
87
+ flat scalars out of [core] and [modules.bmm])."""
88
+ if isinstance(base, dict) and isinstance(override, dict):
89
+ result = dict(base)
90
+ for key, value in override.items():
91
+ result[key] = _deep_merge(result[key], value) if key in result else value
92
+ return result
93
+ return override
94
+
95
+
96
+ def _detect_keyed_merge_field(items):
97
+ """Return 'code' or 'id' if every table item carries that same field.
98
+ Mixed or partial arrays return None and fall through to append."""
99
+ if not items or not all(isinstance(item, dict) for item in items):
100
+ return None
101
+ for candidate in ("code", "id"):
102
+ if all(item.get(candidate) is not None for item in items):
103
+ return candidate
104
+ return None
105
+
106
+
107
+ def _merge_by_key(base, override, key_name):
108
+ result = []
109
+ index_by_key = {}
110
+ for item in base:
111
+ if not isinstance(item, dict):
112
+ continue
113
+ if item.get(key_name) is not None:
114
+ index_by_key[item[key_name]] = len(result)
115
+ result.append(dict(item))
116
+ for item in override:
117
+ if not isinstance(item, dict):
118
+ result.append(item)
119
+ continue
120
+ key = item.get(key_name)
121
+ if key is not None and key in index_by_key:
122
+ result[index_by_key[key]] = dict(item)
123
+ else:
124
+ if key is not None:
125
+ index_by_key[key] = len(result)
126
+ result.append(dict(item))
127
+ return result
128
+
129
+
130
+ def _merge_arrays(base, override):
131
+ """Shape-aware array merge: keyed merge if every item has code/id, else append."""
132
+ base_arr = base if isinstance(base, list) else []
133
+ override_arr = override if isinstance(override, list) else []
134
+ keyed_field = _detect_keyed_merge_field(base_arr + override_arr)
135
+ if keyed_field:
136
+ return _merge_by_key(base_arr, override_arr, keyed_field)
137
+ return base_arr + override_arr
138
+
139
+
140
+ def _structural_merge(base, override):
141
+ """Faithful port of resolve_customization.py's deep_merge: tables deep-merge,
142
+ arrays-of-tables keyed by code/id replace-then-append (other arrays append),
143
+ scalars override. Used only for the [workflow] customization layers — the
144
+ central-config path keeps its own simpler _deep_merge. Duplicated rather than
145
+ imported to keep this skill self-contained."""
146
+ if isinstance(base, dict) and isinstance(override, dict):
147
+ result = dict(base)
148
+ for key, over_val in override.items():
149
+ result[key] = (
150
+ _structural_merge(result[key], over_val) if key in result else over_val
151
+ )
152
+ return result
153
+ if isinstance(base, list) and isinstance(override, list):
154
+ return _merge_arrays(base, override)
155
+ return override
156
+
157
+
158
+ def resolve_workflow(root, skill_dir, skill_name):
159
+ """Resolve the [workflow] customization block via the three-layer merge
160
+ (skill defaults -> team -> user), highest priority last. Same structural
161
+ rules as resolve_customization.py. All three layers are optional: a missing
162
+ file is skipped, but an unparseable one HALTs (via load_toml)."""
163
+ defaults = load_toml(posixpath.join(skill_dir, "customize.toml"))
164
+ custom_dir = posixpath.join(root, "_bmad", "custom")
165
+ team = load_toml(posixpath.join(custom_dir, f"{skill_name}.toml"))
166
+ user = load_toml(posixpath.join(custom_dir, f"{skill_name}.user.toml"))
167
+ merged = _structural_merge(defaults, team)
168
+ merged = _structural_merge(merged, user)
169
+ workflow = merged.get("workflow")
170
+ return workflow if isinstance(workflow, dict) else {}
171
+
172
+
173
+ def load_central_config(root):
174
+ """Four-layer merge of _bmad/config.toml and its peers (highest priority
175
+ last). HALTs if the base _bmad/config.toml is missing or unparseable."""
176
+ bmad_dir = posixpath.join(root, "_bmad")
177
+ base_team = load_toml(posixpath.join(bmad_dir, "config.toml"), required=True)
178
+ base_user = load_toml(posixpath.join(bmad_dir, "config.user.toml"))
179
+ custom_team = load_toml(posixpath.join(bmad_dir, "custom", "config.toml"))
180
+ custom_user = load_toml(posixpath.join(bmad_dir, "custom", "config.user.toml"))
181
+
182
+ merged = _deep_merge(base_team, base_user)
183
+ merged = _deep_merge(merged, custom_team)
184
+ merged = _deep_merge(merged, custom_user)
185
+ return merged
186
+
187
+
188
+ def flatten_central_config(merged):
189
+ """Lift scalar keys from [core] and [modules.bmm] into a single namespace.
190
+ Module keys take precedence on collision (installer strips core keys from
191
+ module buckets, so collisions shouldn't happen in practice)."""
192
+ flat = {}
193
+ modules = merged.get("modules")
194
+ modules = modules if isinstance(modules, dict) else {}
195
+ for section in (merged.get("core"), modules.get("bmm")):
196
+ if not isinstance(section, dict):
197
+ continue
198
+ for key, value in section.items():
199
+ if isinstance(value, bool):
200
+ flat[key] = "true" if value else "false"
201
+ elif isinstance(value, (str, int, float)):
202
+ flat[key] = str(value)
203
+ return flat
204
+
205
+
206
+ def render_template(content, vars_):
207
+ """Resolve {{.var}} substitutions. Unresolved references emit an empty string,
208
+ but main() HALTs on any missing reference before rendering starts, so this
209
+ fallback never fires in practice."""
210
+ return re.sub(r"\{\{\.(\w+)\}\}", lambda m: vars_.get(m.group(1), ""), content)
211
+
212
+
213
+ def collect_missing_vars(sources, vars_):
214
+ """Map each {{.var}} name referenced by the source .md files but absent from
215
+ the merged config to the files that reference it. A missing key must HALT:
216
+ missingkey=zero rendering would bake a corrupted workflow (empty paths,
217
+ blank language lines) with no failure signal."""
218
+ missing = {}
219
+ for fname, content in sources:
220
+ for name in re.findall(r"\{\{\.(\w+)\}\}", content):
221
+ if name not in vars_:
222
+ files = missing.setdefault(name, [])
223
+ if fname not in files:
224
+ files.append(fname)
225
+ return missing
226
+
227
+
228
+ def _scalar_str(value):
229
+ """Stringify a scalar for inline rendering: booleans lowercase (matching
230
+ BMad config conventions), None as empty, everything else via str()."""
231
+ if value is None:
232
+ return ""
233
+ if isinstance(value, bool):
234
+ return "true" if value else "false"
235
+ return str(value)
236
+
237
+
238
+ # [workflow] keys holding review layers ([[workflow.review_layers]] tables with
239
+ # id/name/instruction/when fields). This renderer knows this skill's
240
+ # customization schema outright — layer semantics are materialized here, not
241
+ # interpreted by the LLM at run time.
242
+ _REVIEW_LAYER_KEYS = ("review_layers", "oneshot_review_layers")
243
+
244
+
245
+ def _render_review_layers(layers):
246
+ """Materialize review layers into direct invocation blocks. A layer with an
247
+ empty or missing instruction is disabled (that is how an override turns off
248
+ a default layer) and drops out entirely. A `when` condition is the one part
249
+ that stays with the LLM: it renders as a run-time guard line. No active
250
+ layers renders as the HALT instruction the workflow would otherwise have to
251
+ derive from an empty list."""
252
+ active = [
253
+ layer
254
+ for layer in layers
255
+ if isinstance(layer, dict) and _scalar_str(layer.get("instruction")).strip()
256
+ ]
257
+ if not active:
258
+ return (
259
+ "No review layers are active. HALT with status `blocked` and "
260
+ "blocking condition `no active review layers`."
261
+ )
262
+ blocks = []
263
+ for layer in active:
264
+ title = (
265
+ _scalar_str(layer.get("name")).strip()
266
+ or _scalar_str(layer.get("id")).strip()
267
+ or "Review layer"
268
+ )
269
+ lines = [f"#### {title}", ""]
270
+ when = _scalar_str(layer.get("when")).strip()
271
+ if when:
272
+ lines.append(
273
+ "Run this layer only if the following holds in the "
274
+ f"current context: `{when}`"
275
+ )
276
+ lines.append("")
277
+ lines.append(_scalar_str(layer.get("instruction")).strip("\n"))
278
+ blocks.append("\n".join(lines))
279
+ return "\n\n".join(blocks)
280
+
281
+
282
+ def _render_workflow_value(key, value):
283
+ """Format a resolved [workflow] value for inline substitution. Review-layer
284
+ keys materialize as invocation blocks; other lists render as markdown
285
+ bullets (empty -> '_None._'); scalars render verbatim. Each list item uses
286
+ the same scalar formatting so booleans stay consistent. Entries are emitted
287
+ as-is so runtime placeholders like {project-root} or {diff_output} survive
288
+ for the LLM to resolve."""
289
+ if key in _REVIEW_LAYER_KEYS and isinstance(value, list):
290
+ return _render_review_layers(value)
291
+ if isinstance(value, list):
292
+ if not value:
293
+ return "_None._"
294
+ return "\n".join(f"- {_scalar_str(item)}" for item in value)
295
+ return _scalar_str(value)
296
+
297
+
298
+ def render_workflow(content, workflow):
299
+ """Resolve {workflow.<key>} placeholders from the resolved [workflow] block.
300
+ Unknown keys emit an empty string (missingkey=zero, matching render_template).
301
+ Distinct regex from render_template so single-curly runtime placeholders
302
+ elsewhere are untouched."""
303
+ return re.sub(
304
+ r"\{workflow\.(\w+)\}",
305
+ lambda m: _render_workflow_value(m.group(1), workflow.get(m.group(1))),
306
+ content,
307
+ )
308
+
309
+
310
+ def main():
311
+ script_dir = os.path.dirname(os.path.abspath(__file__))
312
+ skill_name = os.path.basename(script_dir)
313
+ root = find_project_root()
314
+ root = root.replace(os.sep, "/")
315
+
316
+ vars_ = flatten_central_config(load_central_config(root))
317
+
318
+ for key in list(vars_.keys()):
319
+ vars_[key] = vars_[key].replace("{project-root}", root)
320
+
321
+ vars_["project_root"] = root
322
+
323
+ # Guarded ahead of the general missing-vars scan: sprint_status and
324
+ # deferred_work_file derive from it below, and unlike the scan (absent
325
+ # keys only) this also HALTs on a present-but-empty value.
326
+ implementation_artifacts = vars_.get("implementation_artifacts", "").strip()
327
+ if not implementation_artifacts:
328
+ print(
329
+ "HALT and report to the user: config is missing `implementation_artifacts` "
330
+ "(expected under [core] or [modules.bmm] in _bmad/config.toml)"
331
+ )
332
+ sys.exit(1)
333
+
334
+ vars_["sprint_status"] = posixpath.join(
335
+ implementation_artifacts, "sprint-status.yaml"
336
+ )
337
+ vars_["deferred_work_file"] = posixpath.join(
338
+ implementation_artifacts, "deferred-work.md"
339
+ )
340
+
341
+ sources = []
342
+ for fname in sorted(os.listdir(script_dir)):
343
+ if not fname.endswith(".md") or fname == "SKILL.md":
344
+ continue
345
+ with open(
346
+ posixpath.join(script_dir, fname), "r", encoding="utf-8", newline=""
347
+ ) as fh:
348
+ sources.append((fname, fh.read()))
349
+
350
+ missing = collect_missing_vars(sources, vars_)
351
+ if missing:
352
+ details = "; ".join(
353
+ f"`{name}` (referenced by {', '.join(files)})"
354
+ for name, files in sorted(missing.items())
355
+ )
356
+ print(
357
+ f"HALT and report to the user: config is missing {details} "
358
+ "(expected under [core] or [modules.bmm] in _bmad/config.toml)"
359
+ )
360
+ sys.exit(1)
361
+
362
+ workflow = resolve_workflow(root, script_dir.replace(os.sep, "/"), skill_name)
363
+
364
+ out_dir = posixpath.join(root, "_bmad", "render", skill_name)
365
+ os.makedirs(out_dir, exist_ok=True)
366
+
367
+ for fname in os.listdir(out_dir):
368
+ if fname.endswith(".md"):
369
+ os.remove(posixpath.join(out_dir, fname))
370
+
371
+ for fname, content in sources:
372
+ dst = posixpath.join(out_dir, fname)
373
+ with open(dst, "w", encoding="utf-8", newline="") as fh:
374
+ fh.write(render_workflow(render_template(content, vars_), workflow))
375
+
376
+ workflow_md = posixpath.join(out_dir, "workflow.md")
377
+ print(f"read and follow {workflow_md}")
378
+
379
+
380
+ if __name__ == "__main__":
381
+ main()
@@ -46,10 +46,10 @@ context: [] # optional: `{project-root}/`-prefixed paths to project-wide standar
46
46
 
47
47
  ## Code Map
48
48
 
49
- <!-- Agent-populated during planning. Annotated paths prevent blind codebase searching. -->
49
+ <!-- Agent-populated during planning: the distilled investigation map, so the spec carries what exploration found and the implementation handoff need only point here. Annotated paths prevent blind codebase searching. Entries may drill to symbol/line and carry reuse pointers ("mirror X at FILE:LINE") or read-only evidence, where they save the implementer a search. -->
50
50
 
51
51
  - `FILE` -- ROLE_OR_RELEVANCE
52
- - `FILE` -- ROLE_OR_RELEVANCE
52
+ - `FILE:LINE` -- ROLE_OR_RELEVANCE; reuse pointer or READ-ONLY evidence when relevant
53
53
 
54
54
  ## Tasks & Acceptance
55
55
 
@@ -1,5 +1,4 @@
1
1
  ---
2
- deferred_work_file: '{implementation_artifacts}/deferred-work.md'
3
2
  spec_file: '' # set at runtime for both routes before leaving this step
4
3
  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
5
4
  ---
@@ -8,7 +7,7 @@ story_key: '' # set at runtime to the current story's full sprint-status key (e.
8
7
 
9
8
  ## RULES
10
9
 
11
- - YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{communication_language}`
10
+ - **Language** Speak in `{{.communication_language}}`. Write any file output in `{{.document_output_language}}`.
12
11
  - The prompt that triggered this workflow IS the intent — not a hint.
13
12
  - Do NOT assume you start from zero.
14
13
  - 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
29
28
  Use the same routing as above.
30
29
 
31
30
  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).
33
32
  - If `draft` selected: Set `spec_file`. Run **Story-key resolution** (below). **EARLY EXIT** → `./step-02-plan.md` (resume planning from the draft)
34
33
  - If `ready-for-dev` or `in-progress` selected: Set `spec_file`. Run **Story-key resolution** (below). **EARLY EXIT** → `./step-03-implement.md`
35
34
  - If `in-review` selected: Set `spec_file`. Run **Story-key resolution** (below). **EARLY EXIT** → `./step-04-review.md`
@@ -39,14 +38,14 @@ Never ask extra questions if you already understand what the user intends.
39
38
 
40
39
  ### Story-key resolution
41
40
 
42
- 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.
41
+ 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.
43
42
 
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).
45
44
 
46
45
  ## INSTRUCTIONS
47
46
 
48
47
  1. Load context.
49
- - List files in `{planning_artifacts}` and `{implementation_artifacts}`.
48
+ - List files in `{{.planning_artifacts}}` and `{{.implementation_artifacts}}`.
50
49
  - If you find an unformatted spec or intent file, ingest its contents to form your understanding of the intent.
51
50
  - **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.
52
51
 
@@ -54,17 +53,17 @@ If the spec is an epic story and `{sprint_status}` exists: find the `development
54
53
 
55
54
  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.
56
55
 
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.
58
57
  - **If valid:** load it as the primary planning context. Do not load raw planning docs (PRD, architecture, UX, etc.). Skip to step 5.
59
58
  - **If missing, empty, or invalid:** continue to step 3.
60
59
 
61
- 3. **Compile epic context.** Produce `{implementation_artifacts}/epic-<N>-context.md` by following `./compile-epic-context.md`, in order of preference:
62
- - **Preferred — subagent:** spawn a subagent 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 — subagent:** spawn a subagent synchronously (wait for it to return in this turn) 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`.
63
62
  - **Fallback — inline** (for runtimes without subagent support, e.g. Copilot, Codex, local Ollama, older Claude): if your runtime cannot spawn subagents, or the spawn fails/times out, read `./compile-epic-context.md` yourself and follow its instructions to produce the same output file.
64
63
 
65
64
  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.
66
65
 
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.
68
67
 
69
68
  6. **Resolve `{story_key}`.** If not already set by an earlier early-exit path, run **Story-key resolution** (above) now.
70
69
 
@@ -82,7 +81,7 @@ If the spec is an epic story and `{sprint_status}` exists: find the `development
82
81
  - Present detected distinct goals as a bullet list.
83
82
  - Explain briefly (2–4 sentences): why each goal qualifies as independently shippable, any coupling risks if split, and which goal you recommend tackling first.
84
83
  - HALT and ask human: `[S] Split — pick first goal, defer the rest` | `[K] Keep all goals — accept the risks`
85
- - On **S**: For each deferred goal, append one new entry to `{deferred_work_file}` using this format. Do not modify existing entries or look for duplicates. Narrow scope to the first-mentioned goal. Continue routing.
84
+ - On **S**: For each deferred goal, append one new entry to `{{.deferred_work_file}}` using this format. Do not modify existing entries or look for duplicates. Narrow scope to the first-mentioned goal. Continue routing.
86
85
  ```markdown
87
86
  - source_spec: none
88
87
  summary: <one sentence naming the deferred goal>
@@ -91,7 +90,7 @@ If the spec is an epic story and `{sprint_status}` exists: find the `development
91
90
  - On **K**: Proceed as-is.
92
91
  5. Route — choose exactly one:
93
92
 
94
- 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`.
93
+ 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`.
95
94
 
96
95
  **a) One-shot** — zero blast radius: no plausible path by which this change causes unintended consequences elsewhere. Clear intent, no architectural decisions.
97
96
 
@@ -99,7 +98,6 @@ If the spec is an epic story and `{sprint_status}` exists: find the `development
99
98
 
100
99
  **b) Plan-code-review** — everything else. When uncertain whether blast radius is truly zero, choose this path.
101
100
 
102
-
103
101
  ## NEXT
104
102
 
105
103
  Read fully and follow `./step-02-plan.md`
@@ -1,25 +1,21 @@
1
- ---
2
- deferred_work_file: '{implementation_artifacts}/deferred-work.md'
3
- ---
4
-
5
1
  # Step 2: Plan
6
2
 
7
3
  ## RULES
8
4
 
9
- - YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{communication_language}`
5
+ - **Language** Speak in `{{.communication_language}}`. Write any file output in `{{.document_output_language}}`.
10
6
  - No intermediate approvals.
11
7
 
12
8
  ## INSTRUCTIONS
13
9
 
14
10
  1. Draft resume check. If `{spec_file}` exists with `status: draft`, read it and capture the verbatim `<frozen-after-approval>...</frozen-after-approval>` block as `preserved_intent`. Otherwise `preserved_intent` is empty.
15
- 2. Investigate codebase. _Isolate deep exploration in subagents/tasks where available. To prevent context snowballing, instruct subagents to give you distilled summaries only._
16
- 3. Read `./spec-template.md` fully. Fill it out based on the intent and investigation. If `{preserved_intent}` is non-empty, substitute it for the `<frozen-after-approval>` block in your filled spec before writing. Write the result to `{spec_file}`.
11
+ 2. Investigate codebase. _Isolate deep exploration in synchronous subagents/tasks where available. To prevent context snowballing, instruct subagents to give you distilled summaries only._ Decide which findings actually matter for execution — the specific files, symbols/lines, reuse points, and read-only constraints — and carry those forward for the Code Map. This is where the investigation lands: the spec preserves it so it is never re-narrated to the implementer at dispatch time.
12
+ 3. Read `./spec-template.md` fully. Fill it out based on the intent and investigation, resolving the template's `date` field to the current system date. Drain the investigation into the `## Code Map` section — annotated paths, symbol/line anchors, reuse pointers, and read-only evidence — so the spec is the implementer's investigation map and the step-03 handoff need only point at it. If `preserved_intent` is non-empty, replace the `<frozen-after-approval>` block in the spec you just filled out with `preserved_intent`, before writing. Write the result to `{spec_file}`.
17
13
  4. Self-review against READY FOR DEVELOPMENT standard.
18
14
  5. If intent gaps exist, do not fantasize, do not leave open questions, HALT and ask the human.
19
15
  6. Token count check (see SCOPE STANDARD). If spec exceeds 1600 tokens:
20
16
  - Show user the token count.
21
17
  - 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. For each deferred goal, append one new entry to `{deferred_work_file}` using this format. Do not modify existing entries or look for duplicates. 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. For each deferred goal, append one new entry to `{{.deferred_work_file}}` using this format. Do not modify existing entries or look for duplicates. 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.
23
19
  ```markdown
24
20
  - source_spec: `{spec_file}`
25
21
  summary: <one sentence naming the deferred goal>
@@ -46,7 +42,6 @@ HALT and ask human: `[A] Approve` | `[E] Edit`
46
42
  - **If the file exists:** Compare the content to what you wrote. If it has changed since you wrote it, acknowledge the external edits — show a brief summary of what changed — and proceed with the updated version. Then set status `ready-for-dev` in `{spec_file}`. Everything inside `<frozen-after-approval>` is now locked — only the human can change it. → Step 3.
47
43
  - **E**: Apply changes, then return to CHECKPOINT 1.
48
44
 
49
-
50
45
  ## NEXT
51
46
 
52
47
  Read fully and follow `./step-03-implement.md`
@@ -5,7 +5,7 @@
5
5
 
6
6
  ## RULES
7
7
 
8
- - YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{communication_language}`
8
+ - **Language** Speak in `{{.communication_language}}`. Write any file output in `{{.document_output_language}}`.
9
9
  - No push. No remote ops.
10
10
  - Sequential execution only.
11
11
  - Content inside `<frozen-after-approval>` in `{spec_file}` is read-only. Do not modify.
@@ -24,11 +24,15 @@ Capture `baseline_commit` (current HEAD, or `NO_VCS` if version control is unava
24
24
 
25
25
  Change `{spec_file}` status to `in-progress` in the frontmatter before starting implementation.
26
26
 
27
- Follow `./sync-sprint-status.md` with `{target_status}` = `in-progress`.
27
+ Follow `./sync-sprint-status.md` with `target_status` = `in-progress`.
28
28
 
29
- If `{spec_file}` has a non-empty `context:` list in its frontmatter, load those files before implementation begins. When handing to a subagent, include them in the subagent prompt so it has access to the referenced context.
29
+ Execute the implementation handoff below: substitute the runtime placeholders (e.g. `{spec_file}`) into it, then follow it verbatim.
30
30
 
31
- Hand `{spec_file}` to a subagent/task and let it implement. If no subagents are available, implement directly.
31
+ {workflow.implementation_handoff}
32
+
33
+ Do not add goal restatements, file lists, ownership boundaries, investigation detail, acceptance criteria, or CLAUDE.md/house-style rules to the dispatch — the spec is the subagent's sole source of truth, and that material already lives in it (investigation findings in its Code Map, the rest in the spec body). One line of sanctioned hedging belongs in the spec at planning time, not in the dispatch. If no subagents are available, implement directly from the spec. If the platform allows, keep the subagent available for re-engagement after it returns — step-04 may send it review fixes.
34
+
35
+ The handoff directs the subagent to load the spec's `context:` files itself, so never pre-load and paste those files into the dispatch. Only when you implement directly (no subagent available) do you load a non-empty `context:` list yourself before starting.
32
36
 
33
37
  **Path formatting rule:** Any markdown links written into `{spec_file}` must use paths relative to `{spec_file}`'s directory so they are clickable in VS Code. Any file paths displayed in terminal/conversation output must use CWD-relative format with `:line` notation (e.g., `src/path/file.ts:42`) for terminal clickability. No leading `/` in either case.
34
38
 
@@ -36,6 +40,10 @@ Hand `{spec_file}` to a subagent/task and let it implement. If no subagents are
36
40
 
37
41
  Before leaving this step, verify every task in the `## Tasks & Acceptance` section of `{spec_file}` is complete and every acceptance criterion is satisfied. Mark each finished task `[x]`. If any task is not done or any acceptance criterion is not satisfied, finish the missing work before proceeding.
38
42
 
43
+ ### Matrix Test Audit
44
+
45
+ If `{spec_file}`'s `<frozen-after-approval>` block contains an I/O & Edge-Case Matrix, verify every matrix row is covered by at least one test that verifies its expected behavior, and that each covering test ran and passed in the verification output. A covering test that exists but did not run — unregistered, filtered out, skipped, or disabled — counts as missing. If a test disagrees with the matrix, never edit the expectation to match the code: fix the code, or if the matrix row itself is ambiguous, HALT and ask the human. Fix any other audit failure before proceeding.
46
+
39
47
  ## NEXT
40
48
 
41
49
  Read fully and follow `./step-04-review.md`
@@ -1,13 +1,10 @@
1
- ---
2
- deferred_work_file: '{implementation_artifacts}/deferred-work.md'
3
- ---
4
-
5
1
  # Step 4: Review
6
2
 
7
3
  ## RULES
8
4
 
9
- - YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{communication_language}`
5
+ - **Language** Speak in `{{.communication_language}}`. Write any file output in `{{.document_output_language}}`.
10
6
  - All review subagents must run at the same model capability as the current session.
7
+ - Run subagents synchronously: launch them together, then wait for all results before continuing.
11
8
 
12
9
  ## INSTRUCTIONS
13
10
 
@@ -21,20 +18,11 @@ Do NOT `git add` anything — this is read-only inspection.
21
18
 
22
19
  ### Review
23
20
 
24
- Launch Blind Hunter, Edge Case Hunter, and Verification Gap Reviewer in parallel without prior conversation context. If no subagents are available, generate three review prompt files in `{implementation_artifacts}` one per reviewer role belowand HALT. Ask the human to run each in a separate session (ideally a different LLM) and paste back the findings.
21
+ Execute these review layers in parallel wherever their execution methods allow: substitute the runtime placeholders (e.g. `{diff_output}`) into each layer's instruction, then follow it verbatim. Parallel means several blocking calls awaited together in this turn never backgrounded or detached, never ending the turn to await results. When running layers as subagents, spawn every reviewer before reading or reacting to any of their output; begin collection and triage only once all are launched.
22
+
23
+ {workflow.review_layers}
25
24
 
26
- - **Blind Hunter** prompt:
27
- > Invoke the `bmad-review-adversarial-general` skill on this diff:
28
- >
29
- > {diff_output}
30
- - **Edge Case Hunter** — prompt:
31
- > Invoke the `bmad-review-edge-case-hunter` skill on this diff:
32
- >
33
- > {diff_output}
34
- - **Verification Gap Reviewer** — prompt:
35
- > Invoke the `bmad-review-verification-gap` skill on this diff:
36
- >
37
- > {diff_output}
25
+ If a layer's instruction requires subagents and none are available, generate one review prompt file per such layer in `{{.implementation_artifacts}}` and HALT. Ask the human to run each in a separate session (ideally a different LLM) and paste back the findings.
38
26
 
39
27
  ### Classify
40
28
 
@@ -53,8 +41,8 @@ Launch Blind Hunter, Edge Case Hunter, and Verification Gap Reviewer in parallel
53
41
  4. Process findings in cascading order. If intent_gap or bad_spec findings exist, they trigger a loopback — lower findings are moot since code will be re-derived. If neither exists, process patch and defer normally. Before each loopback, read `{spec_file}` frontmatter `review_loop_iteration` (missing means `0`), increment it by 1, and write it back. If it exceeds 5, HALT and escalate to the human.
54
42
  - **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.
55
43
  - **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.
56
- - **patch** — Auto-fix. These are the only findings that survive loopbacks.
57
- - **defer** — Append one new entry to `{deferred_work_file}` using this format. Do not modify existing entries or look for duplicates.
44
+ - **patch** — Auto-fix. These are the only findings that survive loopbacks. If the step-03 implementation subagent can be re-engaged with its context intact, send it all patch findings in one synchronous message — for each: the file, what is wrong, and what the fix must do. If it cannot be re-engaged, apply the patches yourself. Then re-run the checks in `{spec_file}`'s `## Verification` section, if present; if verification fails and the failure cannot be fixed, HALT and escalate to the human.
45
+ - **defer** — Append one new entry to `{{.deferred_work_file}}` using this format. Do not modify existing entries or look for duplicates.
58
46
  ```markdown
59
47
  - source_spec: `{spec_file}`
60
48
  summary: <one sentence>
@@ -5,7 +5,7 @@
5
5
 
6
6
  ## RULES
7
7
 
8
- - YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{communication_language}`
8
+ - **Language** Speak in `{{.communication_language}}`. Write any file output in `{{.document_output_language}}`.
9
9
  - NEVER auto-push.
10
10
 
11
11
  ## INSTRUCTIONS
@@ -52,7 +52,7 @@ When there is only one concern, omit the bold label — just list the stops dire
52
52
 
53
53
  Change `{spec_file}` status to `done` in the frontmatter.
54
54
 
55
- Follow `./sync-sprint-status.md` with `{target_status}` = `review`.
55
+ Follow `./sync-sprint-status.md` with `target_status` = `review`.
56
56
 
57
57
  ### Commit and Open
58
58
 
@@ -73,6 +73,6 @@ Workflow complete.
73
73
 
74
74
  ## On Complete
75
75
 
76
- Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete`
76
+ If anything appears below, follow it as the final terminal instruction before exiting; otherwise exit normally.
77
77
 
78
- If the resolved `workflow.on_complete` is non-empty, follow it as the final terminal instruction before exiting.
78
+ {workflow.on_complete}