bmad-method 6.8.1-next.9 → 6.9.1-next.0
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.
- package/.claude-plugin/marketplace.json +9 -3
- package/package.json +10 -4
- package/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md +2 -2
- package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +8 -8
- package/src/bmm-skills/2-plan-workflows/bmad-prd/SKILL.md +7 -7
- package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/headless-schemas.md +2 -2
- package/src/bmm-skills/2-plan-workflows/bmad-prd/customize.toml +1 -1
- package/src/bmm-skills/2-plan-workflows/bmad-prd/references/headless.md +1 -1
- package/src/bmm-skills/2-plan-workflows/bmad-prd/references/validate.md +1 -1
- package/src/bmm-skills/2-plan-workflows/bmad-ux/SKILL.md +8 -8
- package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/design-directions.md +1 -1
- package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/headless-schemas.md +2 -2
- package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/key-screens.md +4 -4
- package/src/bmm-skills/2-plan-workflows/bmad-ux/customize.toml +1 -1
- package/src/bmm-skills/2-plan-workflows/bmad-ux/references/creative-tools.md +1 -1
- package/src/bmm-skills/2-plan-workflows/bmad-ux/references/headless.md +1 -1
- package/src/bmm-skills/2-plan-workflows/bmad-ux/references/validate.md +2 -2
- package/src/bmm-skills/3-solutioning/bmad-agent-architect/customize.toml +2 -2
- package/src/bmm-skills/3-solutioning/bmad-architecture/SKILL.md +85 -0
- package/src/bmm-skills/3-solutioning/bmad-architecture/assets/spine-template.md +79 -0
- package/src/bmm-skills/3-solutioning/bmad-architecture/customize.toml +100 -0
- package/src/bmm-skills/3-solutioning/bmad-architecture/references/headless.md +26 -0
- package/src/bmm-skills/3-solutioning/bmad-architecture/references/reviewer-gate.md +13 -0
- package/src/bmm-skills/3-solutioning/bmad-architecture/scripts/lint_spine.py +257 -0
- package/src/bmm-skills/3-solutioning/bmad-architecture/scripts/tests/test_lint_spine.py +270 -0
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md +16 -60
- package/src/bmm-skills/4-implementation/bmad-dev-auto/SKILL.md +101 -0
- package/src/bmm-skills/4-implementation/bmad-dev-auto/compile-epic-context.md +62 -0
- package/src/bmm-skills/4-implementation/bmad-dev-auto/customize.toml +33 -0
- package/src/bmm-skills/4-implementation/bmad-dev-auto/spec-template.md +90 -0
- package/src/bmm-skills/4-implementation/bmad-dev-auto/step-01-clarify-and-route.md +66 -0
- package/src/bmm-skills/4-implementation/bmad-dev-auto/step-02-plan.md +32 -0
- package/src/bmm-skills/4-implementation/bmad-dev-auto/step-03-implement.md +38 -0
- package/src/bmm-skills/4-implementation/bmad-dev-auto/step-04-review.md +61 -0
- package/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md +15 -0
- package/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md +20 -1
- package/src/bmm-skills/4-implementation/bmad-sprint-planning/checklist.md +2 -1
- package/src/bmm-skills/4-implementation/bmad-sprint-planning/sprint-status-template.yaml +13 -0
- package/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md +13 -0
- package/src/bmm-skills/module-help.csv +2 -2
- package/src/core-skills/bmad-brainstorming/SKILL.md +8 -10
- package/src/core-skills/bmad-brainstorming/references/converge.md +1 -1
- package/src/core-skills/bmad-brainstorming/references/finalize.md +1 -1
- package/src/core-skills/bmad-brainstorming/references/headless.md +4 -4
- package/src/core-skills/bmad-brainstorming/references/in-chat-techniques.md +1 -1
- package/src/core-skills/bmad-brainstorming/references/mode-autonomous.md +1 -1
- package/src/core-skills/bmad-brainstorming/scripts/tests/test_brain.py +2 -2
- package/src/core-skills/bmad-customize/scripts/tests/test_list_customizable_skills.py +1 -1
- package/src/core-skills/bmad-forge-idea/SKILL.md +79 -0
- package/src/core-skills/bmad-forge-idea/customize.toml +42 -0
- package/src/core-skills/bmad-forge-idea/scripts/resolve_personas.py +270 -0
- package/src/core-skills/bmad-forge-idea/scripts/tests/test_resolve_personas.py +138 -0
- package/src/core-skills/bmad-party-mode/SKILL.md +39 -56
- package/src/core-skills/bmad-party-mode/customize.toml +175 -0
- package/src/core-skills/bmad-party-mode/references/create-party.md +70 -0
- package/src/core-skills/bmad-party-mode/references/mode-agent-team.md +11 -0
- package/src/core-skills/bmad-party-mode/references/mode-auto.md +13 -0
- package/src/core-skills/bmad-party-mode/references/mode-subagent.md +19 -0
- package/src/core-skills/bmad-party-mode/references/party-memory.md +51 -0
- package/src/core-skills/bmad-party-mode/scripts/resolve_party.py +272 -0
- package/src/core-skills/bmad-party-mode/scripts/tests/test-resolve_party.py +146 -0
- package/src/core-skills/bmad-spec/SKILL.md +5 -3
- package/src/core-skills/bmad-spec/assets/spec-template.md +3 -3
- package/src/core-skills/module-help.csv +1 -0
- package/src/scripts/resolve_config.py +8 -6
- package/src/scripts/resolve_customization.py +8 -6
- package/tools/installer/commands/install.js +3 -0
- package/tools/installer/core/installer.js +3 -0
- package/tools/installer/core/uv-check.js +97 -0
- package/tools/installer/core/wsl-node-check.js +109 -0
- package/tools/installer/ide/platform-codes.yaml +7 -0
- package/tools/installer/install-messages.yaml +4 -0
- package/tools/installer/ui.js +10 -9
- package/evals/bmm-skills/bmad-product-brief/evals.json +0 -237
- package/evals/bmm-skills/bmad-product-brief/files/branfield-memo.md +0 -46
- package/evals/bmm-skills/bmad-product-brief/files/forkbird-brief/addendum.md +0 -40
- package/evals/bmm-skills/bmad-product-brief/files/forkbird-brief/brief.md +0 -56
- package/evals/bmm-skills/bmad-product-brief/files/forkbird-brief/decision-log.md +0 -27
- package/evals/bmm-skills/bmad-product-brief/files/meridian-mobility-report.md +0 -116
- package/evals/bmm-skills/bmad-product-brief/files/mossridge-brief/addendum.md +0 -41
- package/evals/bmm-skills/bmad-product-brief/files/mossridge-brief/brief.md +0 -57
- package/evals/bmm-skills/bmad-product-brief/files/mossridge-brief/decision-log.md +0 -29
- package/evals/bmm-skills/bmad-product-brief/files/pantry-bridge-interviews.md +0 -90
- package/evals/bmm-skills/bmad-product-brief/files/q2-brainstorm.md +0 -101
- package/evals/bmm-skills/bmad-product-brief/triggers.json +0 -18
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/architecture-decision-template.md +0 -12
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/data/domain-complexity.csv +0 -13
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/data/project-types.csv +0 -7
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-01-init.md +0 -153
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-01b-continue.md +0 -173
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-02-context.md +0 -224
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-03-starter.md +0 -329
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-04-decisions.md +0 -318
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-05-patterns.md +0 -359
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-06-structure.md +0 -379
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-07-validation.md +0 -361
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-08-complete.md +0 -82
- package/src/core-skills/bmad-brainstorming/scripts/memlog.py +0 -202
- package/src/core-skills/bmad-brainstorming/scripts/tests/test_memlog.py +0 -265
- package/tools/installer/core/python-check.js +0 -199
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# /// script
|
|
2
|
+
# requires-python = ">=3.10"
|
|
3
|
+
# dependencies = ["pytest>=8.0"]
|
|
4
|
+
# ///
|
|
5
|
+
"""Tests for lint_spine.py. Run: uv run --with pytest pytest scripts/tests/test_lint_spine.py
|
|
6
|
+
|
|
7
|
+
The spine under test: a clean spine lints empty; the linter catches exactly the
|
|
8
|
+
mechanical defects a prompt is unreliable at — literal placeholders, AD-n id breakage,
|
|
9
|
+
AD-n blocks missing required fields, and unpinned Stack versions.
|
|
10
|
+
"""
|
|
11
|
+
import importlib.util
|
|
12
|
+
import json
|
|
13
|
+
import re
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
_SPEC = importlib.util.spec_from_file_location(
|
|
20
|
+
"lint_spine", Path(__file__).resolve().parent.parent / "lint_spine.py"
|
|
21
|
+
)
|
|
22
|
+
lint_spine = importlib.util.module_from_spec(_SPEC)
|
|
23
|
+
sys.modules["lint_spine"] = lint_spine
|
|
24
|
+
_SPEC.loader.exec_module(lint_spine)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
CLEAN = """---
|
|
28
|
+
name: 'Demo'
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Invariants & Rules
|
|
32
|
+
|
|
33
|
+
### AD-1 — single write path
|
|
34
|
+
|
|
35
|
+
- **Binds:** all
|
|
36
|
+
- **Prevents:** divergent mutation
|
|
37
|
+
- **Rule:** state changes only through the command bus
|
|
38
|
+
|
|
39
|
+
### AD-2 — layered deps `[ADOPTED]`
|
|
40
|
+
|
|
41
|
+
- **Binds:** all
|
|
42
|
+
- **Prevents:** import cycles
|
|
43
|
+
- **Rule:** ui -> app -> domain, never backward
|
|
44
|
+
|
|
45
|
+
```mermaid
|
|
46
|
+
flowchart LR
|
|
47
|
+
A --> B{decision}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Stack
|
|
51
|
+
|
|
52
|
+
| Name | Version |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| fastapi | 0.115 |
|
|
55
|
+
| pydantic | 2.9 |
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def cats(result):
|
|
60
|
+
return sorted(f["category"] for f in result["findings"])
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_clean_spine_passes():
|
|
64
|
+
result = lint_spine.lint(CLEAN)
|
|
65
|
+
assert result["ok"] is True
|
|
66
|
+
assert result["total_findings"] == 0
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_mermaid_braces_not_flagged():
|
|
70
|
+
# the {decision} node lives in a fenced block and must not read as a template token
|
|
71
|
+
result = lint_spine.lint(CLEAN)
|
|
72
|
+
assert "placeholder" not in cats(result)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_placeholder_markers_caught():
|
|
76
|
+
text = CLEAN.replace("the command bus", "TBD")
|
|
77
|
+
result = lint_spine.lint(text)
|
|
78
|
+
assert "placeholder" in cats(result)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_similar_to_caught():
|
|
82
|
+
text = CLEAN.replace("import cycles", "similar to AD-1")
|
|
83
|
+
result = lint_spine.lint(text)
|
|
84
|
+
assert any("cross-reference" in f["detail"] for f in result["findings"])
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_unfilled_template_token_caught():
|
|
88
|
+
text = CLEAN.replace("single write path", "{decision}")
|
|
89
|
+
result = lint_spine.lint(text)
|
|
90
|
+
assert any(f["category"] == "placeholder" for f in result["findings"])
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_duplicate_ad_id_caught():
|
|
94
|
+
text = CLEAN.replace("### AD-2 — layered deps `[ADOPTED]`", "### AD-1 — layered deps")
|
|
95
|
+
result = lint_spine.lint(text)
|
|
96
|
+
assert "ad_id" in cats(result)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_non_monotonic_ad_id_caught():
|
|
100
|
+
text = CLEAN.replace("### AD-2 — layered deps `[ADOPTED]`", "### AD-5 — layered deps").replace(
|
|
101
|
+
"### AD-1 — single write path", "### AD-9 — single write path"
|
|
102
|
+
)
|
|
103
|
+
result = lint_spine.lint(text)
|
|
104
|
+
assert any("non-monotonic" in f["detail"] for f in result["findings"])
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_missing_field_caught():
|
|
108
|
+
text = CLEAN.replace("- **Rule:** state changes only through the command bus\n", "")
|
|
109
|
+
result = lint_spine.lint(text)
|
|
110
|
+
assert any(f["category"] == "ad_fields" and "rule" in f["detail"] for f in result["findings"])
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_unpinned_dep_caught():
|
|
114
|
+
text = CLEAN.replace("| fastapi | 0.115 |", "| fastapi | |")
|
|
115
|
+
result = lint_spine.lint(text)
|
|
116
|
+
assert "version_pin" in cats(result)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_placeholder_version_caught():
|
|
120
|
+
text = CLEAN.replace("| fastapi | 0.115 |", "| fastapi | {pin} |")
|
|
121
|
+
result = lint_spine.lint(text)
|
|
122
|
+
assert any(f["category"] == "version_pin" and "fastapi" in f["detail"] for f in result["findings"])
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_no_stack_section_ok():
|
|
126
|
+
text = CLEAN.split("## Stack")[0]
|
|
127
|
+
result = lint_spine.lint(text)
|
|
128
|
+
assert "version_pin" not in cats(result)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_stack_skeleton_row_not_version_pinned():
|
|
132
|
+
# a leftover {token} name is the placeholder pass's job, not a double-reported version_pin
|
|
133
|
+
text = CLEAN.replace("| fastapi | 0.115 |", "| {language / framework} | {pinned version} |")
|
|
134
|
+
result = lint_spine.lint(text)
|
|
135
|
+
assert "version_pin" not in cats(result)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_stack_html_comment_not_parsed_as_row():
|
|
139
|
+
text = CLEAN.replace("## Stack\n", "## Stack\n\n<!-- SEED — verified current 2026-06 -->\n")
|
|
140
|
+
result = lint_spine.lint(text)
|
|
141
|
+
assert "version_pin" not in cats(result)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def test_template_token_is_low_severity():
|
|
145
|
+
# a bare {token} can be legitimate brace prose; it is flagged, but low (not high) so the
|
|
146
|
+
# mechanical pass stays near-zero false-positive
|
|
147
|
+
text = CLEAN.replace("single write path", "{decision}")
|
|
148
|
+
result = lint_spine.lint(text)
|
|
149
|
+
toks = [f for f in result["findings"] if f["category"] == "placeholder" and "template token" in f["detail"]]
|
|
150
|
+
assert toks and all(f["severity"] == "low" for f in toks)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_no_frontmatter_body_still_scanned():
|
|
154
|
+
text = "## Invariants\n\n### AD-1 — x\n\n- **Binds:** all\n- **Prevents:** drift\n- **Rule:** TBD\n"
|
|
155
|
+
result = lint_spine.lint(text)
|
|
156
|
+
assert "placeholder" in cats(result) # TBD caught even with no frontmatter
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def test_frontmatter_value_with_dashes_not_truncated():
|
|
160
|
+
# a value containing '---' must not be read as the closing fence (line-exact close)
|
|
161
|
+
text = ("---\nname: 'x'\nscope: 'phase 1 --- phase 2'\n---\n\n"
|
|
162
|
+
"## Stack\n\n| Name | Version |\n| --- | --- |\n| fastapi | |\n")
|
|
163
|
+
result = lint_spine.lint(text)
|
|
164
|
+
assert any(f["category"] == "version_pin" for f in result["findings"]) # read past the inline ---
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_ad_heading_in_fence_not_counted():
|
|
168
|
+
text = (
|
|
169
|
+
"---\nname: 'x'\n---\n\n"
|
|
170
|
+
"### AD-1 — real\n\n- **Binds:** all\n- **Prevents:** drift\n- **Rule:** do x\n\n"
|
|
171
|
+
"## Docs\n\n```text\n### AD-2 — illustrative only, no fields\n```\n"
|
|
172
|
+
)
|
|
173
|
+
result = lint_spine.lint(text)
|
|
174
|
+
assert result["ok"] is True # the fenced AD-2 is not a live AD → no ad_fields/ad_id finding
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_stack_table_flags_only_the_unpinned_row():
|
|
178
|
+
text = ("---\nname: 'x'\n---\n\n## Stack\n\n| Name | Version |\n| --- | --- |\n"
|
|
179
|
+
"| fastapi | 0.115 |\n| redis | |\n")
|
|
180
|
+
result = lint_spine.lint(text)
|
|
181
|
+
pins = [f for f in result["findings"] if f["category"] == "version_pin"]
|
|
182
|
+
assert len(pins) == 1 and "redis" in pins[0]["detail"]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_stack_table_all_pinned_ok():
|
|
186
|
+
text = ("---\nname: 'x'\n---\n\n## Stack\n\n| Name | Version |\n| --- | --- |\n"
|
|
187
|
+
"| fastapi | 0.115 |\n")
|
|
188
|
+
result = lint_spine.lint(text)
|
|
189
|
+
assert "version_pin" not in cats(result)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_fenced_stack_rows_not_parsed():
|
|
193
|
+
# an illustrative fenced table under ## Stack must not be read as live rows (fences are
|
|
194
|
+
# blanked first, like every other pass) — a blank-version row inside a fence is not a finding
|
|
195
|
+
text = ("---\nname: 'x'\n---\n\n## Stack\n\n| Name | Version |\n| --- | --- |\n"
|
|
196
|
+
"| fastapi | 0.115 |\n\n```text\n| example | |\n```\n")
|
|
197
|
+
result = lint_spine.lint(text)
|
|
198
|
+
assert "version_pin" not in cats(result)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def test_fenced_stack_heading_not_live():
|
|
202
|
+
# a `## Stack` heading shown inside a code fence is not the live Stack section
|
|
203
|
+
text = ("---\nname: 'x'\n---\n\n## Docs\n\n```md\n## Stack\n\n| foo | |\n```\n")
|
|
204
|
+
result = lint_spine.lint(text)
|
|
205
|
+
assert "version_pin" not in cats(result)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_renamed_stack_heading_still_scanned():
|
|
209
|
+
# the heading match is word-boundary, so a varied `## Stack` heading still counts
|
|
210
|
+
text = ("---\nname: 'x'\n---\n\n## Stack & Versions\n\n| Name | Version |\n| --- | --- |\n"
|
|
211
|
+
"| redis | |\n")
|
|
212
|
+
result = lint_spine.lint(text)
|
|
213
|
+
pins = [f for f in result["findings"] if f["category"] == "version_pin"]
|
|
214
|
+
assert len(pins) == 1 and "redis" in pins[0]["detail"]
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def test_reordered_columns_pair_name_to_version():
|
|
218
|
+
# Version-then-Name header: the unpinned row must still be flagged by its real name
|
|
219
|
+
text = ("---\nname: 'x'\n---\n\n## Stack\n\n| Version | Name |\n| --- | --- |\n"
|
|
220
|
+
"| 0.115 | fastapi |\n| | redis |\n")
|
|
221
|
+
result = lint_spine.lint(text)
|
|
222
|
+
pins = [f for f in result["findings"] if f["category"] == "version_pin"]
|
|
223
|
+
assert len(pins) == 1 and "redis" in pins[0]["detail"]
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def test_placeholder_line_number_is_absolute():
|
|
227
|
+
# a TBD after a multi-line fence reports its real file line (fence blanked, not collapsed)
|
|
228
|
+
text = (
|
|
229
|
+
"---\nname: 'x'\n---\n\n"
|
|
230
|
+
"## A\n\n"
|
|
231
|
+
"```text\nf1\nf2\nf3\n```\n\n"
|
|
232
|
+
"TBD here\n"
|
|
233
|
+
)
|
|
234
|
+
result = lint_spine.lint(text)
|
|
235
|
+
ph = next(f for f in result["findings"] if "TBD" in f["detail"])
|
|
236
|
+
n = int(re.search(r"line (\d+)", ph["location"]).group(1))
|
|
237
|
+
assert n == 13
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def test_missing_spine_file_reports_error(tmp_path, capsys):
|
|
241
|
+
rc = lint_spine.main(["--workspace", str(tmp_path)])
|
|
242
|
+
out = json.loads(capsys.readouterr().out)
|
|
243
|
+
assert rc == 0 and out["ok"] is False and "not found" in out["error"]
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def test_frontmatter_unfilled_token_caught():
|
|
247
|
+
# an unfilled {scope}/{paradigm}/{date} in frontmatter is part of the contract and must lint
|
|
248
|
+
text = "---\nname: 'x'\nscope: '{what this spine governs}'\n---\n\n## Invariants\n"
|
|
249
|
+
result = lint_spine.lint(text)
|
|
250
|
+
fm = [f for f in result["findings"] if f["category"] == "placeholder" and "frontmatter" in f["detail"]]
|
|
251
|
+
assert fm and any("template token" in f["detail"] for f in fm)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def test_frontmatter_tbd_caught():
|
|
255
|
+
text = "---\nname: 'x'\nstatus: TBD\n---\n\n## Invariants\n"
|
|
256
|
+
result = lint_spine.lint(text)
|
|
257
|
+
assert any(f["category"] == "placeholder" and "frontmatter" in f["detail"] and "TBD" in f["detail"]
|
|
258
|
+
for f in result["findings"])
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def test_unreadable_spine_returns_error_not_crash(tmp_path, capsys):
|
|
262
|
+
# a spine that exists but can't be UTF-8 decoded must yield error JSON + exit 0, not a traceback
|
|
263
|
+
(tmp_path / lint_spine.SPINE).write_bytes(b"\xff\xfe bad bytes not utf-8")
|
|
264
|
+
rc = lint_spine.main(["--workspace", str(tmp_path)])
|
|
265
|
+
out = json.loads(capsys.readouterr().out)
|
|
266
|
+
assert rc == 0 and out["ok"] is False and "could not read" in out["error"]
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
if __name__ == "__main__":
|
|
270
|
+
sys.exit(pytest.main([__file__, "-q"]))
|
|
@@ -1,74 +1,30 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bmad-create-architecture
|
|
3
|
-
description: '
|
|
3
|
+
description: 'DEPRECATED — consolidated into bmad-architecture create intent - this skill will be removed in v7 in favor of `bmad-architecture`.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# DEPRECATED — forwards to bmad-architecture (create intent)
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
**Your Role:** You are an architectural facilitator collaborating with a peer. This is a partnership, not a client-vendor relationship. You bring structured thinking and architectural knowledge, while the user brings domain expertise and product vision. Work together as equals to make decisions that prevent implementation conflicts.
|
|
11
|
-
|
|
12
|
-
## Conventions
|
|
13
|
-
|
|
14
|
-
- Bare paths (e.g. `steps/step-01-init.md`) resolve from the skill root.
|
|
15
|
-
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
|
|
16
|
-
- `{project-root}`-prefixed paths resolve from the project working directory.
|
|
17
|
-
- `{skill-name}` resolves to the skill directory's basename.
|
|
18
|
-
|
|
19
|
-
## WORKFLOW ARCHITECTURE
|
|
20
|
-
|
|
21
|
-
This uses **micro-file architecture** for disciplined execution:
|
|
22
|
-
|
|
23
|
-
- Each step is a self-contained file with embedded rules
|
|
24
|
-
- Sequential progression with user control at each step
|
|
25
|
-
- Document state tracked in frontmatter
|
|
26
|
-
- Append-only document building through conversation
|
|
27
|
-
- You NEVER proceed to a step file if the current step file indicates the user must approve and indicate continuation.
|
|
8
|
+
This skill was consolidated into `bmad-architecture`. It is retained as a thin compatibility shim so existing invocations by name and `_bmad/custom/bmad-create-architecture.toml` override files keep working. New work should invoke `bmad-architecture` directly — it detects create / update / validate intent from the conversation.
|
|
28
9
|
|
|
29
10
|
## On Activation
|
|
30
11
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
|
|
34
|
-
|
|
35
|
-
**If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
|
|
36
|
-
|
|
37
|
-
1. `{skill-root}/customize.toml` — defaults
|
|
38
|
-
2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides
|
|
39
|
-
3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides
|
|
40
|
-
|
|
41
|
-
Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append.
|
|
42
|
-
|
|
43
|
-
### Step 2: Execute Prepend Steps
|
|
44
|
-
|
|
45
|
-
Execute each entry in `{workflow.activation_steps_prepend}` in order before proceeding.
|
|
46
|
-
|
|
47
|
-
### Step 3: Load Persistent Facts
|
|
48
|
-
|
|
49
|
-
Treat every entry in `{workflow.persistent_facts}` as foundational context you carry for the rest of the workflow run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim.
|
|
50
|
-
|
|
51
|
-
### Step 4: Load Config
|
|
52
|
-
|
|
53
|
-
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
|
|
54
|
-
- Use `{user_name}` for greeting
|
|
55
|
-
- Use `{communication_language}` for all communications
|
|
56
|
-
- Use `{document_output_language}` for output documents
|
|
57
|
-
- Use `{planning_artifacts}` for output location and artifact scanning
|
|
58
|
-
- Use `{project_knowledge}` for additional context scanning
|
|
59
|
-
|
|
60
|
-
### Step 5: Greet the User
|
|
61
|
-
|
|
62
|
-
Greet `{user_name}`, speaking in `{communication_language}`.
|
|
12
|
+
1. Resolve customization: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. This picks up any `{project-root}/_bmad/custom/bmad-create-architecture.toml` and `bmad-create-architecture.user.toml` overrides for the legacy fields (`activation_steps_prepend`, `activation_steps_append`, `persistent_facts`, `on_complete`).
|
|
63
13
|
|
|
64
|
-
|
|
14
|
+
2. Load `{project-root}/_bmad/bmm/config.yaml` (and `config.user.yaml` if present) to resolve `{user_name}` and `{communication_language}`.
|
|
65
15
|
|
|
66
|
-
|
|
16
|
+
3. Emit a deprecation notice to the user in `{communication_language}`:
|
|
67
17
|
|
|
68
|
-
|
|
18
|
+
> Notice: `bmad-create-architecture` is deprecated and will be removed in a future release. It now forwards to `bmad-architecture` with create intent. To silence this notice and access the full new customization surface (`spine_template`, `spine_output_path`, `run_folder_pattern`, `doc_standards`, `external_sources`, `external_handoffs`, `finalize_reviewers`), migrate `_bmad/custom/bmad-create-architecture.toml` to `_bmad/custom/bmad-architecture.toml` and invoke `bmad-architecture` directly next time. Customization fields that were in this version still remain in the new version and will be respected if present in `_bmad/custom/bmad-architecture.toml`, but the new version also supports additional fields that you can take advantage of by migrating.
|
|
69
19
|
|
|
70
|
-
|
|
20
|
+
4. Invoke `bmad-architecture` with the following context. Pass these as the activating context so `bmad-architecture` honors them instead of resolving its own customization from scratch:
|
|
71
21
|
|
|
72
|
-
|
|
22
|
+
- **Intent:** `create` — skip `bmad-architecture`'s usual intent detection step.
|
|
23
|
+
- **Pre-resolved legacy customization** — use these in place of resolving from `bmad-architecture`'s own `customize.toml` for the four legacy fields. For everything else (`spine_template`, `spine_output_path`, `run_folder_pattern`, `doc_standards`, `external_sources`, `external_handoffs`, `finalize_reviewers`), use `bmad-architecture`'s own defaults and overrides as normal:
|
|
24
|
+
- `activation_steps_prepend` = the resolved value from step 1
|
|
25
|
+
- `activation_steps_append` = the resolved value from step 1
|
|
26
|
+
- `persistent_facts` = the resolved value from step 1
|
|
27
|
+
- `on_complete` = the resolved value from step 1
|
|
28
|
+
- **Original user input:** forward whatever the user said when invoking this skill verbatim.
|
|
73
29
|
|
|
74
|
-
|
|
30
|
+
`bmad-architecture` takes the workflow from here. Do not execute any further steps in this shim.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bmad-dev-auto
|
|
3
|
+
description: 'One iteration of an unattended development loop. Use when invoked by name.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Dev Auto Workflow
|
|
7
|
+
|
|
8
|
+
**Goal:** Turn intent into a hardened, reviewable artifact, without human interaction.
|
|
9
|
+
|
|
10
|
+
**CRITICAL:** If a step says "read fully and follow step-XX", you read and follow step-XX. No exceptions.
|
|
11
|
+
|
|
12
|
+
## HALT
|
|
13
|
+
|
|
14
|
+
To HALT with a final status and optional blocking condition:
|
|
15
|
+
|
|
16
|
+
1. If `{spec_file}` is known and exists, update `status` in frontmatter and append missing result details under `## Auto Run Result`.
|
|
17
|
+
2. If `{spec_file}` is unknown or missing, create `{implementation_artifacts}/bmad-dev-auto-result-<slug-or-timestamp>.md` with:
|
|
18
|
+
```markdown
|
|
19
|
+
---
|
|
20
|
+
status: <final status>
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# BMad Dev Auto Result
|
|
24
|
+
|
|
25
|
+
Status: <final status>
|
|
26
|
+
Blocking condition: <blocking condition, if any>
|
|
27
|
+
```
|
|
28
|
+
3. Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete`
|
|
29
|
+
4. If the resolved `workflow.on_complete` is non-empty, follow it as the final instruction before exiting.
|
|
30
|
+
5. Stop the workflow.
|
|
31
|
+
|
|
32
|
+
## Subagents
|
|
33
|
+
|
|
34
|
+
Using subagents when instructed is mandatory. If you cannot, HALT with status `blocked` and blocking condition `no subagents`.
|
|
35
|
+
|
|
36
|
+
## READY FOR DEVELOPMENT STANDARD
|
|
37
|
+
|
|
38
|
+
A specification is "Ready for Development" when:
|
|
39
|
+
|
|
40
|
+
- **Actionable**: Every task has a file path and specific action.
|
|
41
|
+
- **Logical**: Tasks ordered by dependency.
|
|
42
|
+
- **Testable**: All ACs use Given/When/Then.
|
|
43
|
+
- **Complete**: No placeholders or TBDs.
|
|
44
|
+
- **Sufficient**: No known requirement, acceptance, dependency, or implementation gaps remain unresolved.
|
|
45
|
+
- **Coherent**: No unresolved ambiguities or internal contradictions.
|
|
46
|
+
|
|
47
|
+
## Conventions
|
|
48
|
+
|
|
49
|
+
- Bare paths (e.g. `step-01-clarify-and-route.md`) resolve from the skill root.
|
|
50
|
+
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
|
|
51
|
+
- `{project-root}`-prefixed paths resolve from the project working directory.
|
|
52
|
+
- `{skill-name}` resolves to the skill directory's basename.
|
|
53
|
+
|
|
54
|
+
## On Activation
|
|
55
|
+
|
|
56
|
+
### Step 1: Resolve the Workflow Block
|
|
57
|
+
|
|
58
|
+
Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
|
|
59
|
+
|
|
60
|
+
**If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
|
|
61
|
+
|
|
62
|
+
1. `{skill-root}/customize.toml` — defaults
|
|
63
|
+
2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides
|
|
64
|
+
3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides
|
|
65
|
+
|
|
66
|
+
Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append.
|
|
67
|
+
|
|
68
|
+
### Step 2: Execute Prepend Steps
|
|
69
|
+
|
|
70
|
+
Execute each entry in `{workflow.activation_steps_prepend}` in order before proceeding.
|
|
71
|
+
|
|
72
|
+
### Step 3: Load Persistent Facts
|
|
73
|
+
|
|
74
|
+
Treat every entry in `{workflow.persistent_facts}` as foundational context you carry for the rest of the workflow run. Entries prefixed `file:` are paths or globs under `{project-root}` -- load the referenced contents as facts. All other entries are facts verbatim.
|
|
75
|
+
|
|
76
|
+
### Step 4: Load Config
|
|
77
|
+
|
|
78
|
+
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
|
|
79
|
+
|
|
80
|
+
- `project_name`, `planning_artifacts`, `implementation_artifacts`, `user_name`
|
|
81
|
+
- `communication_language`, `document_output_language`, `user_skill_level`
|
|
82
|
+
- `date` as system-generated current datetime
|
|
83
|
+
- `project_context` = `**/project-context.md` (load if exists)
|
|
84
|
+
- CLAUDE.md / memory files (load if exist)
|
|
85
|
+
- YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{communication_language}`
|
|
86
|
+
- Language MUST be tailored to `{user_skill_level}`
|
|
87
|
+
- Generate all documents in `{document_output_language}`
|
|
88
|
+
|
|
89
|
+
### Step 5: Execute Append Steps
|
|
90
|
+
|
|
91
|
+
Execute each entry in `{workflow.activation_steps_append}` in order.
|
|
92
|
+
|
|
93
|
+
Activation is complete after all activation steps have run.
|
|
94
|
+
|
|
95
|
+
## Workflow Execution
|
|
96
|
+
|
|
97
|
+
Follow the step files in order. Read one step fully, execute it, then load the next step only when directed. Do not skip, reorder, or pre-load steps.
|
|
98
|
+
|
|
99
|
+
## First workflow step
|
|
100
|
+
|
|
101
|
+
Read fully and follow: `./step-01-clarify-and-route.md` to begin the workflow.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Compile Epic Context
|
|
2
|
+
|
|
3
|
+
**Task**
|
|
4
|
+
Given an epic number, the epics file, the planning artifacts directory, and a desired output path, compile a clean, focused, developer-ready context file (`epic-<N>-context.md`).
|
|
5
|
+
|
|
6
|
+
**Steps**
|
|
7
|
+
|
|
8
|
+
1. Read the epics file and extract the target epic's title, goal, and list of stories.
|
|
9
|
+
2. Scan the planning artifacts directory for the standard files (PRD, architecture, UX/design, product brief).
|
|
10
|
+
3. Pull only the information relevant to this epic.
|
|
11
|
+
4. Write the compiled context to the exact output path using the format below.
|
|
12
|
+
|
|
13
|
+
## Exact Output Format
|
|
14
|
+
|
|
15
|
+
Use these headings:
|
|
16
|
+
|
|
17
|
+
```markdown
|
|
18
|
+
# Epic {N} Context: {Epic Title}
|
|
19
|
+
|
|
20
|
+
<!-- Generated from planning artifacts. Regenerate with compile-epic-context if planning docs change. -->
|
|
21
|
+
|
|
22
|
+
## Goal
|
|
23
|
+
|
|
24
|
+
{One clear paragraph: what this epic achieves and why it matters.}
|
|
25
|
+
|
|
26
|
+
## Stories
|
|
27
|
+
|
|
28
|
+
- Story X.Y: Brief title only
|
|
29
|
+
- ...
|
|
30
|
+
|
|
31
|
+
## Requirements & Constraints
|
|
32
|
+
|
|
33
|
+
{Relevant functional/non-functional requirements and success criteria for this epic (describe by purpose, not source).}
|
|
34
|
+
|
|
35
|
+
## Technical Decisions
|
|
36
|
+
|
|
37
|
+
{Key architecture decisions, constraints, patterns, data models, and conventions relevant to this epic.}
|
|
38
|
+
|
|
39
|
+
## UX & Interaction Patterns
|
|
40
|
+
|
|
41
|
+
{Relevant UX flows, interaction patterns, and design constraints (omit section entirely if nothing relevant).}
|
|
42
|
+
|
|
43
|
+
## Cross-Story Dependencies
|
|
44
|
+
|
|
45
|
+
{Dependencies between stories in this epic or with other epics/systems (omit if none).}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Rules
|
|
49
|
+
|
|
50
|
+
- **Scope aggressively.** Include only what a developer working on any story in this epic actually needs. When in doubt, leave it out — the developer can always read the full planning doc.
|
|
51
|
+
- **Describe by purpose, not by source.** Write "API responses must include pagination metadata" not "Per PRD section 3.2.1, pagination is required." Planning doc internals will change; the constraint won't.
|
|
52
|
+
- **No full copies.** Never quote source documents, section numbers, or paste large blocks verbatim. Always distill.
|
|
53
|
+
- **No story-level details.** The story list is for orientation only. Individual story specs handle the details.
|
|
54
|
+
- **Nothing derivable from the codebase.** Don't document what a developer can learn by reading the code.
|
|
55
|
+
- **Be concise and actionable.** Target 800–1500 tokens total. This file loads into bmad-dev-auto's context alongside other material.
|
|
56
|
+
- **Never hallucinate content.** If source material doesn't say something, don't invent it.
|
|
57
|
+
- **Omit empty sections entirely**, except Goal and Stories, which are always required.
|
|
58
|
+
|
|
59
|
+
## Error handling
|
|
60
|
+
|
|
61
|
+
- **If the epics file is missing or the target epic is not found:** write nothing and report the problem to the calling agent. Goal and Stories cannot be populated without a usable epics file.
|
|
62
|
+
- **If planning artifacts are missing or empty:** still produce the file with Goal and Stories populated from the epics file. Under Requirements & Constraints, write: "Planning artifacts were unavailable; only epics-file context was used." Never hallucinate content to fill missing sections.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# DO NOT EDIT -- overwritten on every update.
|
|
2
|
+
#
|
|
3
|
+
# Default customization values for bmad-dev-auto.
|
|
4
|
+
# Override in _bmad/custom/bmad-dev-auto.toml or
|
|
5
|
+
# _bmad/custom/bmad-dev-auto.user.toml.
|
|
6
|
+
#
|
|
7
|
+
# Merge rules:
|
|
8
|
+
# - Strings replace the default.
|
|
9
|
+
# - Lists append to the default list.
|
|
10
|
+
# - Tables merge key by key.
|
|
11
|
+
|
|
12
|
+
[workflow]
|
|
13
|
+
|
|
14
|
+
# Extra instructions to run before config is loaded.
|
|
15
|
+
|
|
16
|
+
activation_steps_prepend = []
|
|
17
|
+
|
|
18
|
+
# Extra instructions to run after config is loaded and before step 01.
|
|
19
|
+
|
|
20
|
+
activation_steps_append = []
|
|
21
|
+
|
|
22
|
+
# Facts kept in context for the whole run.
|
|
23
|
+
# Entries are literal text or file references prefixed with "file:".
|
|
24
|
+
# File entries may use globs and are loaded during activation.
|
|
25
|
+
|
|
26
|
+
persistent_facts = [
|
|
27
|
+
"file:{project-root}/**/project-context.md",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
# Instruction run by HALT after writing the terminal result.
|
|
31
|
+
# Empty means no extra terminal behavior.
|
|
32
|
+
|
|
33
|
+
on_complete = ""
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '{title}'
|
|
3
|
+
type: 'feature' # feature | bugfix | refactor | chore
|
|
4
|
+
created: '{date}'
|
|
5
|
+
status: 'draft' # draft | ready-for-dev | in-progress | in-review | done | blocked
|
|
6
|
+
review_loop_iteration: 0 # incremented by step-04 before each review loopback
|
|
7
|
+
context: [] # optional: `{project-root}/`-prefixed paths to project-wide standards/docs the implementation agent should load. Keep short — only what isn't already distilled into the spec body.
|
|
8
|
+
warnings: [] # optional: machine-readable warnings for orchestration, e.g. oversized, multiple-goals
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<!-- Aim for 900–1600 tokens. If larger, add `oversized` to frontmatter `warnings` and continue.
|
|
12
|
+
Never over-specify "how" — use boundaries + examples instead.
|
|
13
|
+
Cohesive cross-layer stories (DB+BE+UI) stay in ONE file.
|
|
14
|
+
IMPORTANT: Remove all HTML comments when filling this template. -->
|
|
15
|
+
|
|
16
|
+
<intent-contract>
|
|
17
|
+
|
|
18
|
+
## Intent
|
|
19
|
+
|
|
20
|
+
<!-- What is broken or missing, and why it matters. Then the high-level approach — the "what", not the "how". -->
|
|
21
|
+
|
|
22
|
+
**Problem:** ONE_TO_TWO_SENTENCES
|
|
23
|
+
|
|
24
|
+
**Approach:** ONE_TO_TWO_SENTENCES
|
|
25
|
+
|
|
26
|
+
## Boundaries & Constraints
|
|
27
|
+
|
|
28
|
+
<!-- Three tiers: Always = invariant rules. Block If = decisions that cannot be made unattended. Never = out of scope + forbidden approaches. -->
|
|
29
|
+
|
|
30
|
+
**Always:** INVARIANT_RULES
|
|
31
|
+
|
|
32
|
+
**Block If:** DECISIONS_REQUIRING_HUMAN_INPUT
|
|
33
|
+
<!-- Agent: if any of these trigger during execution, HALT with status blocked and the blocking condition. -->
|
|
34
|
+
|
|
35
|
+
**Never:** NON_GOALS_AND_FORBIDDEN_APPROACHES
|
|
36
|
+
|
|
37
|
+
## I/O & Edge-Case Matrix
|
|
38
|
+
|
|
39
|
+
<!-- If no meaningful I/O scenarios exist, DELETE THIS ENTIRE SECTION. Do not write "N/A" or "None". -->
|
|
40
|
+
|
|
41
|
+
| Scenario | Input / State | Expected Output / Behavior | Error Handling |
|
|
42
|
+
|----------|--------------|---------------------------|----------------|
|
|
43
|
+
| HAPPY_PATH | INPUT | OUTCOME | No error expected |
|
|
44
|
+
| ERROR_CASE | INPUT | OUTCOME | ERROR_HANDLING |
|
|
45
|
+
|
|
46
|
+
</intent-contract>
|
|
47
|
+
|
|
48
|
+
## Code Map
|
|
49
|
+
|
|
50
|
+
<!-- Agent-populated during planning. Annotated paths prevent blind codebase searching. -->
|
|
51
|
+
|
|
52
|
+
- `FILE` -- ROLE_OR_RELEVANCE
|
|
53
|
+
- `FILE` -- ROLE_OR_RELEVANCE
|
|
54
|
+
|
|
55
|
+
## Tasks & Acceptance
|
|
56
|
+
|
|
57
|
+
<!-- Tasks: backtick-quoted file path -- action -- rationale. Prefer one task per file; group tightly-coupled changes when splitting would be artificial. -->
|
|
58
|
+
<!-- If an I/O Matrix is present, include a task to unit-test its edge cases. -->
|
|
59
|
+
<!-- AC covers system-level behaviors not captured by the I/O Matrix. Do not duplicate I/O scenarios here. -->
|
|
60
|
+
|
|
61
|
+
**Execution:**
|
|
62
|
+
- [ ] `FILE` -- ACTION -- RATIONALE
|
|
63
|
+
|
|
64
|
+
**Acceptance Criteria:**
|
|
65
|
+
- Given PRECONDITION, when ACTION, then EXPECTED_RESULT
|
|
66
|
+
|
|
67
|
+
## Spec Change Log
|
|
68
|
+
|
|
69
|
+
<!-- Append-only. Populated by step-04 during review loops. Do not modify or delete existing entries.
|
|
70
|
+
Each entry records: what finding triggered the change, what was amended, what known-bad state
|
|
71
|
+
the amendment avoids, and any KEEP instructions (what worked well and must survive re-derivation).
|
|
72
|
+
Empty until the first bad_spec loopback. -->
|
|
73
|
+
|
|
74
|
+
## Design Notes
|
|
75
|
+
|
|
76
|
+
<!-- If the approach is straightforward, DELETE THIS ENTIRE SECTION. Do not write "N/A" or "None". -->
|
|
77
|
+
<!-- Design rationale and golden examples only when non-obvious. Keep examples to 5–10 lines. -->
|
|
78
|
+
|
|
79
|
+
DESIGN_RATIONALE_AND_EXAMPLES
|
|
80
|
+
|
|
81
|
+
## Verification
|
|
82
|
+
|
|
83
|
+
<!-- If no build, test, or lint commands apply, DELETE THIS ENTIRE SECTION. Do not write "N/A" or "None". -->
|
|
84
|
+
<!-- How the agent confirms its own work. Prefer CLI commands. When no CLI check applies, state what to inspect manually. -->
|
|
85
|
+
|
|
86
|
+
**Commands:**
|
|
87
|
+
- `COMMAND` -- expected: SUCCESS_CRITERIA
|
|
88
|
+
|
|
89
|
+
**Manual checks (if no CLI):**
|
|
90
|
+
- WHAT_TO_INSPECT_AND_EXPECTED_STATE
|