bmad-method 6.10.1-next.38 → 6.10.1-next.39
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/package.json +4 -5
- package/src/bmm-skills/ship/bmad-build/SKILL.md +4 -4
- package/src/bmm-skills/ship/bmad-build/step-01-clarify-and-route.md +12 -12
- package/src/bmm-skills/ship/bmad-build/step-02-plan.md +3 -3
- package/src/bmm-skills/ship/bmad-build/step-03-implement.md +2 -2
- package/src/bmm-skills/ship/bmad-build/step-04-review.md +4 -4
- package/src/bmm-skills/ship/bmad-build/step-05-present.md +1 -1
- package/src/bmm-skills/ship/bmad-build/step-oneshot.md +5 -5
- package/src/bmm-skills/ship/bmad-build/sync-sprint-status.md +2 -2
- package/src/bmm-skills/ship/bmad-build/workflow.md +3 -5
- package/src/bmm-skills/ship/bmad-build-auto/SKILL.md +1 -1
- package/src/bmm-skills/ship/bmad-retrospective/scripts/__pycache__/sprint_status.cpython-311.pyc +0 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/__pycache__/test_git_evidence.cpython-311-pytest-9.1.1.pyc +0 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/__pycache__/test_sprint_status.cpython-311-pytest-9.1.1.pyc +0 -0
- package/src/scripts/__pycache__/config_utils.cpython-311.pyc +0 -0
- package/src/scripts/render_skill.py +2 -1
- package/src/scripts/tests/__pycache__/test_config_utils.cpython-311.pyc +0 -0
- package/src/scripts/tests/__pycache__/test_resolve_config.cpython-311.pyc +0 -0
- package/src/scripts/tests/__pycache__/test_resolve_customization.cpython-311.pyc +0 -0
- package/src/bmm-skills/ship/bmad-build/render.py +0 -420
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "bmad-method",
|
|
4
|
-
"version": "6.10.1-next.
|
|
4
|
+
"version": "6.10.1-next.39",
|
|
5
5
|
"description": "Breakthrough Method of Agile AI-driven Development",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agile",
|
|
@@ -40,14 +40,13 @@
|
|
|
40
40
|
"lint:fix": "eslint . --ext .js,.cjs,.mjs,.yaml --fix",
|
|
41
41
|
"lint:md": "markdownlint-cli2 \"**/*.md\"",
|
|
42
42
|
"prepare": "command -v husky >/dev/null 2>&1 && husky || exit 0",
|
|
43
|
-
"quality": "npm run format:check && npm run lint && npm run lint:md && npm run docs:build && npm run test:site-url && npm run test:install && npm run test:urls && npm run test:renderer && npm run test:retrospective && npm run
|
|
43
|
+
"quality": "npm run format:check && npm run lint && npm run lint:md && npm run docs:build && npm run test:site-url && npm run test:install && npm run test:urls && npm run test:renderer && npm run test:retrospective && npm run validate:refs && npm run validate:skills && npm run docs:validate-sidebar",
|
|
44
44
|
"rebundle": "node tools/installer/bundlers/bundle-web.js rebundle",
|
|
45
|
-
"test": "npm run test:refs && npm run test:install && npm run test:urls && npm run test:site-url && npm run test:channels && npm run test:renderer && npm run test:retrospective && npm run test:
|
|
46
|
-
"test:build-auto-contract": "node test/test-build-auto-contract.js",
|
|
45
|
+
"test": "npm run test:refs && npm run test:install && npm run test:urls && npm run test:site-url && npm run test:channels && npm run test:renderer && npm run test:retrospective && npm run test:skills && npm run lint && npm run lint:md && npm run format:check",
|
|
47
46
|
"test:channels": "node test/test-installer-channels.js",
|
|
48
47
|
"test:install": "node test/test-installation-components.js",
|
|
49
48
|
"test:refs": "node test/test-file-refs-csv.js",
|
|
50
|
-
"test:renderer": "uv run --python 3.11 python -m unittest src/scripts/tests/test_config_utils.py src/scripts/tests/test_resolve_config.py src/scripts/tests/test_resolve_customization.py && node test/test-build-
|
|
49
|
+
"test:renderer": "uv run --python 3.11 python -m unittest src/scripts/tests/test_config_utils.py src/scripts/tests/test_resolve_config.py src/scripts/tests/test_resolve_customization.py && node test/test-build-auto-renderer.js",
|
|
51
50
|
"test:retrospective": "uv run --python 3.11 src/bmm-skills/ship/bmad-retrospective/scripts/tests/test_git_evidence.py && uv run --python 3.11 src/bmm-skills/ship/bmad-retrospective/scripts/tests/test_sprint_status.py",
|
|
52
51
|
"test:site-url": "node test/test-site-url.mjs",
|
|
53
52
|
"test:skills": "node test/test-validate-skills.js",
|
|
@@ -3,11 +3,11 @@ name: bmad-build
|
|
|
3
3
|
description: 'Implements any user intent, requirement, story, bug fix or change request by producing clean working code artifacts that follow the project''s existing architecture, patterns and conventions. Use when the user wants to build, fix, tweak, refactor, add or modify any code, component or feature.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Run
|
|
6
|
+
Run the following command exactly once without changing the current working directory. Replace `{project-root}` with the absolute path to the project root and `{skill-root}` with the absolute path to this skill's directory:
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
|
-
uv run --no-cache {
|
|
9
|
+
uv run --no-cache "{project-root}/_bmad/scripts/render_skill.py" --project-root "{project-root}" --skill "{skill-root}"
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
12
|
+
- On success, read and follow the one absolute `workflow.md` instruction printed to stdout.
|
|
13
|
+
- On failure (including `uv` being unavailable), report the command output and HALT. Do not run any workflow source directly.
|
|
@@ -20,7 +20,7 @@ Before listing artifacts or prompting the user, check whether you already know t
|
|
|
20
20
|
|
|
21
21
|
1. Explicit argument
|
|
22
22
|
Did the user pass a specific file path, spec name, or clear instruction this message?
|
|
23
|
-
- If it points to a file that matches the spec template (has `status` frontmatter with a recognized value: draft, ready-for-dev, in-progress, in-review, or done) → set `spec_file`. Before exiting, run **Story-key resolution** (below). Then **EARLY EXIT** to the appropriate step
|
|
23
|
+
- If it points to a file that matches the spec template (has `status` frontmatter with a recognized value: draft, ready-for-dev, in-progress, in-review, or done) → set `spec_file`. Before exiting, run **Story-key resolution** (below). Then **EARLY EXIT** to the appropriate step: `draft` → `[[bmad-snapshot:step-02-plan.md]]`, `ready-for-dev`/`in-progress` → `[[bmad-snapshot:step-03-implement.md]]`, `in-review` → `[[bmad-snapshot:step-04-review.md]]`. For `done`, ingest as context and proceed to INSTRUCTIONS — do not resume.
|
|
24
24
|
- Anything else (intent files, external docs, plans, descriptions) → ingest it as starting intent and proceed to INSTRUCTIONS. Do not attempt to infer a workflow state from it.
|
|
25
25
|
|
|
26
26
|
2. Recent conversation
|
|
@@ -29,9 +29,9 @@ Before listing artifacts or prompting the user, check whether you already know t
|
|
|
29
29
|
|
|
30
30
|
3. Otherwise — scan artifacts and ask
|
|
31
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).
|
|
32
|
-
- If `draft` selected: Set `spec_file`. Run **Story-key resolution** (below). **EARLY EXIT** →
|
|
33
|
-
- If `ready-for-dev` or `in-progress` selected: Set `spec_file`. Run **Story-key resolution** (below). **EARLY EXIT** →
|
|
34
|
-
- If `in-review` selected: Set `spec_file`. Run **Story-key resolution** (below). **EARLY EXIT** →
|
|
32
|
+
- If `draft` selected: Set `spec_file`. Run **Story-key resolution** (below). **EARLY EXIT** → `[[bmad-snapshot:step-02-plan.md]]` (resume planning from the draft)
|
|
33
|
+
- If `ready-for-dev` or `in-progress` selected: Set `spec_file`. Run **Story-key resolution** (below). **EARLY EXIT** → `[[bmad-snapshot:step-03-implement.md]]`
|
|
34
|
+
- If `in-review` selected: Set `spec_file`. Run **Story-key resolution** (below). **EARLY EXIT** → `[[bmad-snapshot:step-04-review.md]]`
|
|
35
35
|
- Unformatted spec or intent file lacking `status` frontmatter? → Suggest treating its contents as the starting intent. Do NOT attempt to infer a state and resume it.
|
|
36
36
|
|
|
37
37
|
Never ask extra questions if you already understand what the user intends.
|
|
@@ -40,7 +40,7 @@ Never ask extra questions if you already understand what the user intends.
|
|
|
40
40
|
|
|
41
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.
|
|
42
42
|
|
|
43
|
-
If the spec is an epic story and `{{.
|
|
43
|
+
If the spec is an epic story and `{{.implementation_artifacts}}/sprint-status.yaml` 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).
|
|
44
44
|
|
|
45
45
|
## INSTRUCTIONS
|
|
46
46
|
|
|
@@ -57,9 +57,9 @@ If the spec is an epic story and `{{.sprint_status}}` exists: find the `developm
|
|
|
57
57
|
- **If valid:** load it as the primary planning context. Do not load raw planning docs (PRD, architecture, UX, etc.). Skip to step 5.
|
|
58
58
|
- **If missing, empty, or invalid:** continue to step 3.
|
|
59
59
|
|
|
60
|
-
3. **Compile epic context.** Produce `{{.implementation_artifacts}}/epic-<N>-context.md` by following
|
|
61
|
-
- **Preferred — subagent:** spawn a subagent synchronously (wait for it to return in this turn) with
|
|
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
|
|
60
|
+
3. **Compile epic context.** Produce `{{.implementation_artifacts}}/epic-<N>-context.md` by following `[[bmad-snapshot:compile-epic-context.md]]`, in order of preference:
|
|
61
|
+
- **Preferred — subagent:** spawn a subagent synchronously (wait for it to return in this turn) with `[[bmad-snapshot: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`.
|
|
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 `[[bmad-snapshot:compile-epic-context.md]]` yourself and follow its instructions to produce the same output file.
|
|
63
63
|
|
|
64
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.
|
|
65
65
|
|
|
@@ -81,7 +81,7 @@ If the spec is an epic story and `{{.sprint_status}}` exists: find the `developm
|
|
|
81
81
|
- Present detected distinct goals as a bullet list.
|
|
82
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.
|
|
83
83
|
- HALT and ask human: `[S] Split — pick first goal, defer the rest` | `[K] Keep all goals — accept the risks`
|
|
84
|
-
- On **S**: For each deferred goal, append one new entry to `{{.
|
|
84
|
+
- On **S**: For each deferred goal, append one new entry to `{{.implementation_artifacts}}/deferred-work.md` using this format. Do not modify existing entries or look for duplicates. Narrow scope to the first-mentioned goal. Continue routing.
|
|
85
85
|
```markdown
|
|
86
86
|
- source_spec: none
|
|
87
87
|
summary: <one sentence naming the deferred goal>
|
|
@@ -90,14 +90,14 @@ If the spec is an epic story and `{{.sprint_status}}` exists: find the `developm
|
|
|
90
90
|
- On **K**: Proceed as-is.
|
|
91
91
|
5. Route — choose exactly one:
|
|
92
92
|
|
|
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** →
|
|
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** → `[[bmad-snapshot:step-02-plan.md]]`); otherwise append `-2`, `-3`, etc. Set `spec_file` = `{{.implementation_artifacts}}/spec-{slug}.md`.
|
|
94
94
|
|
|
95
95
|
**a) One-shot** — zero blast radius: no plausible path by which this change causes unintended consequences elsewhere. Clear intent, no architectural decisions.
|
|
96
96
|
|
|
97
|
-
**EARLY EXIT** →
|
|
97
|
+
**EARLY EXIT** → `[[bmad-snapshot:step-oneshot.md]]`
|
|
98
98
|
|
|
99
99
|
**b) Plan-code-review** — everything else. When uncertain whether blast radius is truly zero, choose this path.
|
|
100
100
|
|
|
101
101
|
## NEXT
|
|
102
102
|
|
|
103
|
-
Read fully and follow
|
|
103
|
+
Read fully and follow `[[bmad-snapshot:step-02-plan.md]]`
|
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
|
|
10
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.
|
|
11
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
|
|
12
|
+
3. Read `[[bmad-snapshot: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}`.
|
|
13
13
|
4. Self-review against READY FOR DEVELOPMENT standard.
|
|
14
14
|
5. If intent gaps exist, do not fantasize, do not leave open questions, HALT and ask the human.
|
|
15
15
|
6. Token count check (see SCOPE STANDARD). If spec exceeds 1600 tokens:
|
|
16
16
|
- Show user the token count.
|
|
17
17
|
- HALT and ask human: `[S] Split — carve off secondary goals` | `[K] Keep full spec — accept the risks`
|
|
18
|
-
- On **S**: Propose the split — name each secondary goal. For each deferred goal, append one new entry to `{{.
|
|
18
|
+
- On **S**: Propose the split — name each secondary goal. For each deferred goal, append one new entry to `{{.implementation_artifacts}}/deferred-work.md` 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.
|
|
19
19
|
```markdown
|
|
20
20
|
- source_spec: `{spec_file}`
|
|
21
21
|
summary: <one sentence naming the deferred goal>
|
|
@@ -44,4 +44,4 @@ HALT and ask human: `[A] Approve` | `[E] Edit`
|
|
|
44
44
|
|
|
45
45
|
## NEXT
|
|
46
46
|
|
|
47
|
-
Read fully and follow
|
|
47
|
+
Read fully and follow `[[bmad-snapshot:step-03-implement.md]]`
|
|
@@ -24,7 +24,7 @@ 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
|
|
27
|
+
Follow `[[bmad-snapshot:sync-sprint-status.md]]` with `target_status` = `in-progress`.
|
|
28
28
|
|
|
29
29
|
Execute the implementation handoff below: substitute the runtime placeholders (e.g. `{spec_file}`) into it, then follow it verbatim.
|
|
30
30
|
|
|
@@ -46,4 +46,4 @@ If `{spec_file}`'s `<frozen-after-approval>` block contains an I/O & Edge-Case M
|
|
|
46
46
|
|
|
47
47
|
## NEXT
|
|
48
48
|
|
|
49
|
-
Read fully and follow
|
|
49
|
+
Read fully and follow `[[bmad-snapshot:step-04-review.md]]`
|
|
@@ -39,10 +39,10 @@ If a layer's instruction requires subagents and none are available, for each suc
|
|
|
39
39
|
- **defer** — pre-existing issue not caused by this story, surfaced incidentally by the review. Collect for later focused attention.
|
|
40
40
|
- **reject** — noise. Drop silently. When unsure between defer and reject, prefer reject — only defer findings you are confident are real.
|
|
41
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.
|
|
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
|
|
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
|
|
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 `[[bmad-snapshot:step-02-plan.md]]` to re-run steps 2–4.
|
|
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 `[[bmad-snapshot:step-03-implement.md]]` to re-derive the code, then this step will run again.
|
|
44
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 `{{.
|
|
45
|
+
- **defer** — Append one new entry to `{{.implementation_artifacts}}/deferred-work.md` using this format. Do not modify existing entries or look for duplicates.
|
|
46
46
|
```markdown
|
|
47
47
|
- source_spec: `{spec_file}`
|
|
48
48
|
summary: <one sentence>
|
|
@@ -52,4 +52,4 @@ If a layer's instruction requires subagents and none are available, for each suc
|
|
|
52
52
|
|
|
53
53
|
## NEXT
|
|
54
54
|
|
|
55
|
-
Read fully and follow
|
|
55
|
+
Read fully and follow `[[bmad-snapshot:step-05-present.md]]`
|
|
@@ -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
|
|
55
|
+
Follow `[[bmad-snapshot:sync-sprint-status.md]]` with `target_status` = `review`.
|
|
56
56
|
|
|
57
57
|
### Commit and Complete
|
|
58
58
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
### Implement
|
|
13
13
|
|
|
14
|
-
Follow
|
|
14
|
+
Follow `[[bmad-snapshot:sync-sprint-status.md]]` with `target_status` = `in-progress`.
|
|
15
15
|
|
|
16
16
|
Implement the clarified intent directly.
|
|
17
17
|
|
|
@@ -28,7 +28,7 @@ If a layer's instruction requires subagents and none are available, for each suc
|
|
|
28
28
|
Deduplicate all review findings. Three categories only:
|
|
29
29
|
|
|
30
30
|
- **patch** — trivially fixable. Auto-fix immediately.
|
|
31
|
-
- **defer** — pre-existing issue not caused by this change. Append one new entry to `{{.
|
|
31
|
+
- **defer** — pre-existing issue not caused by this change. Append one new entry to `{{.implementation_artifacts}}/deferred-work.md` using this format. Do not modify existing entries or look for duplicates.
|
|
32
32
|
```markdown
|
|
33
33
|
- source_spec: `{spec_file}`
|
|
34
34
|
summary: <one sentence>
|
|
@@ -42,13 +42,13 @@ If a finding is caused by this change but too significant for a trivial patch, H
|
|
|
42
42
|
|
|
43
43
|
Set `title` = a concise title derived from the clarified intent.
|
|
44
44
|
|
|
45
|
-
Write `{spec_file}` using
|
|
45
|
+
Write `{spec_file}` using `[[bmad-snapshot:spec-template.md]]`. Fill only these sections — delete all others:
|
|
46
46
|
|
|
47
47
|
1. **Frontmatter** — set `title: '{title}'`, `type`, `created`, `status: 'done'`. Add `route: 'one-shot'`.
|
|
48
48
|
2. **Title and Intent** — `# {title}` heading and `## Intent` with **Problem** and **Approach** lines. Reuse the summary you already generated for the terminal.
|
|
49
|
-
3. **Suggested Review Order** — append after Intent. Build using the same convention as
|
|
49
|
+
3. **Suggested Review Order** — append after Intent. Build using the same convention as `[[bmad-snapshot:step-05-present.md]]` § "Generate Suggested Review Order" (spec-file-relative links, concern-based ordering, ultra-concise framing).
|
|
50
50
|
|
|
51
|
-
Follow
|
|
51
|
+
Follow `[[bmad-snapshot:sync-sprint-status.md]]` with `target_status` = `review`.
|
|
52
52
|
|
|
53
53
|
### Commit
|
|
54
54
|
|
|
@@ -6,11 +6,11 @@ Shared sub-step for updating `sprint-status.yaml` during build. Called from any
|
|
|
6
6
|
|
|
7
7
|
Skip this entire file (return to caller) if ANY of:
|
|
8
8
|
- `story_key` is unset
|
|
9
|
-
- `{{.
|
|
9
|
+
- `{{.implementation_artifacts}}/sprint-status.yaml` does not exist on disk
|
|
10
10
|
|
|
11
11
|
## Instructions
|
|
12
12
|
|
|
13
|
-
1. Load the FULL `{{.
|
|
13
|
+
1. Load the FULL `{{.implementation_artifacts}}/sprint-status.yaml` file.
|
|
14
14
|
2. Find the `development_status` entry matching `{story_key}`. If not found, warn the user once (`"{story_key} not found in sprint-status; skipping sprint sync"`) and return to caller.
|
|
15
15
|
3. **Idempotency check.** If `development_status[{story_key}]` is already at `target_status` or a later state (`review` is later than `in-progress`; `done` is later than both), return to caller — no write needed. Never regress a story's status.
|
|
16
16
|
4. Set `development_status[{story_key}]` to `{target_status}`.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Goal:** Turn user intent into a hardened, reviewable artifact.
|
|
4
4
|
|
|
5
|
-
**CRITICAL:** If a step
|
|
5
|
+
**CRITICAL:** If a step directs you to another snapshot file, read it fully and follow it. No exceptions.
|
|
6
6
|
|
|
7
7
|
Subagents, when the capability is available, are an important part of this workflow. Use them as directed by the workflow steps.
|
|
8
8
|
If you need an explicit user instruction to run them, ask once now for the whole workflow run.
|
|
@@ -30,10 +30,8 @@ A specification should target a **single user-facing goal** within **900–1600
|
|
|
30
30
|
|
|
31
31
|
## Conventions
|
|
32
32
|
|
|
33
|
-
-
|
|
34
|
-
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
|
|
33
|
+
- Every operational cross-file reference in this workflow is an absolute snapshot path. Open it directly; do not resolve it relative to a skill directory.
|
|
35
34
|
- `{project-root}`-prefixed paths resolve from the project working directory.
|
|
36
|
-
- `{skill-name}` resolves to the skill directory's basename.
|
|
37
35
|
- Whenever this workflow captures or records a version-control revision, obtain the full canonical identifier directly from version control and preserve it verbatim.
|
|
38
36
|
|
|
39
37
|
## On Activation
|
|
@@ -83,4 +81,4 @@ This uses **step-file architecture** for disciplined execution:
|
|
|
83
81
|
|
|
84
82
|
## FIRST STEP
|
|
85
83
|
|
|
86
|
-
Read fully and follow:
|
|
84
|
+
Read fully and follow: `[[bmad-snapshot:step-01-clarify-and-route.md]]` to begin the workflow.
|
|
@@ -3,7 +3,7 @@ name: bmad-build-auto
|
|
|
3
3
|
description: 'One iteration of an unattended development loop. Use when invoked by name.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Run
|
|
6
|
+
Run the following command exactly once without changing the current working directory. Replace `{project-root}` with the absolute path to the project root and `{skill-root}` with the absolute path to this skill's directory:
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
9
|
uv run --no-cache "{project-root}/_bmad/scripts/render_skill.py" --project-root "{project-root}" --skill "{skill-root}"
|
package/src/bmm-skills/ship/bmad-retrospective/scripts/__pycache__/sprint_status.cpython-311.pyc
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -177,7 +177,8 @@ def _format_review_layers(layers: list[dict[str, str]]) -> str:
|
|
|
177
177
|
|
|
178
178
|
def _resolve_customization_value(value: Any, default: Any, label: str) -> tuple[Any, str]:
|
|
179
179
|
if isinstance(default, str):
|
|
180
|
-
|
|
180
|
+
allow_empty = not default.strip() or label == "customization.workflow.open_spec"
|
|
181
|
+
resolved = _require_string(value, label, allow_empty=allow_empty)
|
|
181
182
|
return resolved, resolved
|
|
182
183
|
if isinstance(default, list):
|
|
183
184
|
if default and all(isinstance(item, dict) for item in default):
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,420 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# /// script
|
|
3
|
-
# requires-python = ">=3.11"
|
|
4
|
-
# ///
|
|
5
|
-
"""render.py — bmad-build 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-build/.
|
|
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-build.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}, ...) pass
|
|
25
|
-
through untouched for the LLM to resolve during workflow execution. The sole
|
|
26
|
-
exception is {skill-root} in resolved review-layer instructions, which must be
|
|
27
|
-
an absolute prompt-file path before those instructions reach the parent LLM.
|
|
28
|
-
|
|
29
|
-
Every invocation rebuilds from scratch — no hash, no cache.
|
|
30
|
-
Python 3.11+ stdlib only. UTF-8 I/O.
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
import os
|
|
34
|
-
import posixpath
|
|
35
|
-
import re
|
|
36
|
-
import sys
|
|
37
|
-
import tomllib
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def find_project_root():
|
|
41
|
-
"""Walk up from cwd until a _bmad/ directory is found. On failure, print a
|
|
42
|
-
HALT instruction to stdout and exit non-zero."""
|
|
43
|
-
current = os.path.abspath(os.getcwd())
|
|
44
|
-
while True:
|
|
45
|
-
candidate = os.path.join(current, "_bmad")
|
|
46
|
-
if os.path.isdir(candidate):
|
|
47
|
-
return current
|
|
48
|
-
parent = os.path.dirname(current)
|
|
49
|
-
if parent == current:
|
|
50
|
-
print(
|
|
51
|
-
f"HALT and report to the user: no _bmad/ directory found walking up from {os.getcwd()}"
|
|
52
|
-
)
|
|
53
|
-
sys.exit(1)
|
|
54
|
-
current = parent
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def load_toml(path, required=False):
|
|
58
|
-
"""Load a TOML file. Only absence is negotiable: a missing optional file
|
|
59
|
-
returns {} (customization layers are optional), a missing required file
|
|
60
|
-
HALTs. A file that exists but cannot be parsed or read always HALTs —
|
|
61
|
-
stdout is how this script signals workflow halts to its LLM caller — the
|
|
62
|
-
user wrote it to be honored, and silently continuing with {} would discard
|
|
63
|
-
their customizations with no failure signal."""
|
|
64
|
-
if not os.path.isfile(path):
|
|
65
|
-
if required:
|
|
66
|
-
print(
|
|
67
|
-
f"HALT and report to the user: required config file not found: {path} — "
|
|
68
|
-
"ensure this is a post-#2285 BMAD install"
|
|
69
|
-
)
|
|
70
|
-
sys.exit(1)
|
|
71
|
-
return {}
|
|
72
|
-
try:
|
|
73
|
-
with open(path, "rb") as fh:
|
|
74
|
-
parsed = tomllib.load(fh)
|
|
75
|
-
except tomllib.TOMLDecodeError as error:
|
|
76
|
-
print(f"HALT and report to the user: failed to parse {path}: {error}")
|
|
77
|
-
sys.exit(1)
|
|
78
|
-
except OSError as error:
|
|
79
|
-
print(f"HALT and report to the user: failed to read {path}: {error}")
|
|
80
|
-
sys.exit(1)
|
|
81
|
-
if not isinstance(parsed, dict):
|
|
82
|
-
return {}
|
|
83
|
-
return parsed
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def _deep_merge(base, override):
|
|
87
|
-
"""Dict-aware deep merge. Lists and scalars: override wins (we don't need
|
|
88
|
-
the full keyed-merge semantics of resolve_config.py — build only reads
|
|
89
|
-
flat scalars out of [core] and [modules.bmm])."""
|
|
90
|
-
if isinstance(base, dict) and isinstance(override, dict):
|
|
91
|
-
result = dict(base)
|
|
92
|
-
for key, value in override.items():
|
|
93
|
-
result[key] = _deep_merge(result[key], value) if key in result else value
|
|
94
|
-
return result
|
|
95
|
-
return override
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def _detect_keyed_merge_field(items):
|
|
99
|
-
"""Return 'code' or 'id' if every table item carries that same field.
|
|
100
|
-
Mixed or partial arrays return None and fall through to append."""
|
|
101
|
-
if not items or not all(isinstance(item, dict) for item in items):
|
|
102
|
-
return None
|
|
103
|
-
for candidate in ("code", "id"):
|
|
104
|
-
if all(item.get(candidate) is not None for item in items):
|
|
105
|
-
return candidate
|
|
106
|
-
return None
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def _merge_by_key(base, override, key_name):
|
|
110
|
-
result = []
|
|
111
|
-
index_by_key = {}
|
|
112
|
-
for item in base:
|
|
113
|
-
if not isinstance(item, dict):
|
|
114
|
-
continue
|
|
115
|
-
if item.get(key_name) is not None:
|
|
116
|
-
index_by_key[item[key_name]] = len(result)
|
|
117
|
-
result.append(dict(item))
|
|
118
|
-
for item in override:
|
|
119
|
-
if not isinstance(item, dict):
|
|
120
|
-
result.append(item)
|
|
121
|
-
continue
|
|
122
|
-
key = item.get(key_name)
|
|
123
|
-
if key is not None and key in index_by_key:
|
|
124
|
-
result[index_by_key[key]] = dict(item)
|
|
125
|
-
else:
|
|
126
|
-
if key is not None:
|
|
127
|
-
index_by_key[key] = len(result)
|
|
128
|
-
result.append(dict(item))
|
|
129
|
-
return result
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def _merge_arrays(base, override):
|
|
133
|
-
"""Shape-aware array merge: keyed merge if every item has code/id, else append."""
|
|
134
|
-
base_arr = base if isinstance(base, list) else []
|
|
135
|
-
override_arr = override if isinstance(override, list) else []
|
|
136
|
-
keyed_field = _detect_keyed_merge_field(base_arr + override_arr)
|
|
137
|
-
if keyed_field:
|
|
138
|
-
return _merge_by_key(base_arr, override_arr, keyed_field)
|
|
139
|
-
return base_arr + override_arr
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
def _structural_merge(base, override):
|
|
143
|
-
"""Faithful port of resolve_customization.py's deep_merge: tables deep-merge,
|
|
144
|
-
arrays-of-tables keyed by code/id replace-then-append (other arrays append),
|
|
145
|
-
scalars override. Used only for the [workflow] customization layers — the
|
|
146
|
-
central-config path keeps its own simpler _deep_merge. Duplicated rather than
|
|
147
|
-
imported to keep this skill self-contained."""
|
|
148
|
-
if isinstance(base, dict) and isinstance(override, dict):
|
|
149
|
-
result = dict(base)
|
|
150
|
-
for key, over_val in override.items():
|
|
151
|
-
result[key] = (
|
|
152
|
-
_structural_merge(result[key], over_val) if key in result else over_val
|
|
153
|
-
)
|
|
154
|
-
return result
|
|
155
|
-
if isinstance(base, list) and isinstance(override, list):
|
|
156
|
-
return _merge_arrays(base, override)
|
|
157
|
-
return override
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def resolve_workflow(root, skill_dir, skill_name):
|
|
161
|
-
"""Resolve the [workflow] customization block via the three-layer merge
|
|
162
|
-
(skill defaults -> team -> user), highest priority last. Same structural
|
|
163
|
-
rules as resolve_customization.py. All three layers are optional: a missing
|
|
164
|
-
file is skipped, but an unparseable one HALTs (via load_toml)."""
|
|
165
|
-
defaults = load_toml(posixpath.join(skill_dir, "customize.toml"))
|
|
166
|
-
custom_dir = posixpath.join(root, "_bmad", "custom")
|
|
167
|
-
team = load_toml(posixpath.join(custom_dir, f"{skill_name}.toml"))
|
|
168
|
-
user = load_toml(posixpath.join(custom_dir, f"{skill_name}.user.toml"))
|
|
169
|
-
merged = _structural_merge(defaults, team)
|
|
170
|
-
merged = _structural_merge(merged, user)
|
|
171
|
-
workflow = merged.get("workflow")
|
|
172
|
-
return workflow if isinstance(workflow, dict) else {}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def load_central_config(root):
|
|
176
|
-
"""Four-layer merge of _bmad/config.toml and its peers (highest priority
|
|
177
|
-
last). HALTs if the base _bmad/config.toml is missing or unparseable."""
|
|
178
|
-
bmad_dir = posixpath.join(root, "_bmad")
|
|
179
|
-
base_team = load_toml(posixpath.join(bmad_dir, "config.toml"), required=True)
|
|
180
|
-
base_user = load_toml(posixpath.join(bmad_dir, "config.user.toml"))
|
|
181
|
-
custom_team = load_toml(posixpath.join(bmad_dir, "custom", "config.toml"))
|
|
182
|
-
custom_user = load_toml(posixpath.join(bmad_dir, "custom", "config.user.toml"))
|
|
183
|
-
|
|
184
|
-
merged = _deep_merge(base_team, base_user)
|
|
185
|
-
merged = _deep_merge(merged, custom_team)
|
|
186
|
-
merged = _deep_merge(merged, custom_user)
|
|
187
|
-
return merged
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
def flatten_central_config(merged):
|
|
191
|
-
"""Lift scalar keys from [core] and [modules.bmm] into a single namespace.
|
|
192
|
-
Module keys take precedence on collision (installer strips core keys from
|
|
193
|
-
module buckets, so collisions shouldn't happen in practice)."""
|
|
194
|
-
flat = {}
|
|
195
|
-
modules = merged.get("modules")
|
|
196
|
-
modules = modules if isinstance(modules, dict) else {}
|
|
197
|
-
for section in (merged.get("core"), modules.get("bmm")):
|
|
198
|
-
if not isinstance(section, dict):
|
|
199
|
-
continue
|
|
200
|
-
for key, value in section.items():
|
|
201
|
-
if isinstance(value, bool):
|
|
202
|
-
flat[key] = "true" if value else "false"
|
|
203
|
-
elif isinstance(value, (str, int, float)):
|
|
204
|
-
flat[key] = str(value)
|
|
205
|
-
return flat
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
def render_template(content, vars_):
|
|
209
|
-
"""Resolve {{.var}} substitutions. Unresolved references emit an empty string,
|
|
210
|
-
but main() HALTs on any missing reference before rendering starts, so this
|
|
211
|
-
fallback never fires in practice."""
|
|
212
|
-
return re.sub(r"\{\{\.(\w+)\}\}", lambda m: vars_.get(m.group(1), ""), content)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
def collect_missing_vars(sources, vars_):
|
|
216
|
-
"""Map each {{.var}} name referenced by the source .md files but absent from
|
|
217
|
-
the merged config to the files that reference it. A missing key must HALT:
|
|
218
|
-
missingkey=zero rendering would bake a corrupted workflow (empty paths,
|
|
219
|
-
blank language lines) with no failure signal."""
|
|
220
|
-
missing = {}
|
|
221
|
-
for fname, content in sources:
|
|
222
|
-
for name in re.findall(r"\{\{\.(\w+)\}\}", content):
|
|
223
|
-
if name not in vars_:
|
|
224
|
-
files = missing.setdefault(name, [])
|
|
225
|
-
if fname not in files:
|
|
226
|
-
files.append(fname)
|
|
227
|
-
return missing
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
def _scalar_str(value):
|
|
231
|
-
"""Stringify a scalar for inline rendering: booleans lowercase (matching
|
|
232
|
-
BMad config conventions), None as empty, everything else via str()."""
|
|
233
|
-
if value is None:
|
|
234
|
-
return ""
|
|
235
|
-
if isinstance(value, bool):
|
|
236
|
-
return "true" if value else "false"
|
|
237
|
-
return str(value)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
# [workflow] keys holding review layers ([[workflow.review_layers]] tables with
|
|
241
|
-
# id/name/instruction/when fields). This renderer knows this skill's
|
|
242
|
-
# customization schema outright — layer semantics are materialized here, not
|
|
243
|
-
# interpreted by the LLM at run time.
|
|
244
|
-
_REVIEW_LAYER_KEYS = ("review_layers", "oneshot_review_layers")
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def expand_review_layer_skill_roots(workflow, skill_root):
|
|
248
|
-
"""Expand only {skill-root} inside resolved review-layer instructions.
|
|
249
|
-
|
|
250
|
-
Review layers originate in customization after regular template rendering,
|
|
251
|
-
so their prompt paths need this explicit pass. Runtime placeholders remain
|
|
252
|
-
untouched for the parent to supply to each child at dispatch time.
|
|
253
|
-
"""
|
|
254
|
-
expanded_workflow = dict(workflow)
|
|
255
|
-
for key in _REVIEW_LAYER_KEYS:
|
|
256
|
-
layers = workflow.get(key)
|
|
257
|
-
if not isinstance(layers, list):
|
|
258
|
-
continue
|
|
259
|
-
expanded_layers = []
|
|
260
|
-
for layer in layers:
|
|
261
|
-
if not isinstance(layer, dict):
|
|
262
|
-
expanded_layers.append(layer)
|
|
263
|
-
continue
|
|
264
|
-
expanded_layer = dict(layer)
|
|
265
|
-
instruction = expanded_layer.get("instruction")
|
|
266
|
-
if isinstance(instruction, str):
|
|
267
|
-
expanded_layer["instruction"] = instruction.replace(
|
|
268
|
-
"{skill-root}", skill_root
|
|
269
|
-
)
|
|
270
|
-
expanded_layers.append(expanded_layer)
|
|
271
|
-
expanded_workflow[key] = expanded_layers
|
|
272
|
-
return expanded_workflow
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
def _render_review_layers(layers):
|
|
276
|
-
"""Materialize review layers into direct invocation blocks. A layer with an
|
|
277
|
-
empty or missing instruction is disabled (that is how an override turns off
|
|
278
|
-
a default layer) and drops out entirely. A `when` condition is the one part
|
|
279
|
-
that stays with the LLM: it renders as a run-time guard line. No active
|
|
280
|
-
layers renders as the HALT instruction the workflow would otherwise have to
|
|
281
|
-
derive from an empty list."""
|
|
282
|
-
active = [
|
|
283
|
-
layer
|
|
284
|
-
for layer in layers
|
|
285
|
-
if isinstance(layer, dict) and _scalar_str(layer.get("instruction")).strip()
|
|
286
|
-
]
|
|
287
|
-
if not active:
|
|
288
|
-
return (
|
|
289
|
-
"No review layers are active. HALT with status `blocked` and "
|
|
290
|
-
"blocking condition `no active review layers`."
|
|
291
|
-
)
|
|
292
|
-
blocks = []
|
|
293
|
-
for layer in active:
|
|
294
|
-
title = (
|
|
295
|
-
_scalar_str(layer.get("name")).strip()
|
|
296
|
-
or _scalar_str(layer.get("id")).strip()
|
|
297
|
-
or "Review layer"
|
|
298
|
-
)
|
|
299
|
-
lines = [f"#### {title}", ""]
|
|
300
|
-
when = _scalar_str(layer.get("when")).strip()
|
|
301
|
-
if when:
|
|
302
|
-
lines.append(
|
|
303
|
-
"Run this layer only if the following holds in the "
|
|
304
|
-
f"current context: `{when}`"
|
|
305
|
-
)
|
|
306
|
-
lines.append("")
|
|
307
|
-
lines.append(_scalar_str(layer.get("instruction")).strip("\n"))
|
|
308
|
-
blocks.append("\n".join(lines))
|
|
309
|
-
return "\n\n".join(blocks)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
def _render_workflow_value(key, value):
|
|
313
|
-
"""Format a resolved [workflow] value for inline substitution. Review-layer
|
|
314
|
-
keys materialize as invocation blocks; other lists render as markdown
|
|
315
|
-
bullets (empty -> '_None._'); scalars render verbatim. Each list item uses
|
|
316
|
-
the same scalar formatting so booleans stay consistent. Entries are emitted
|
|
317
|
-
as-is so runtime placeholders like {project-root} or {diff_output} survive
|
|
318
|
-
for the LLM to resolve."""
|
|
319
|
-
if key in _REVIEW_LAYER_KEYS and isinstance(value, list):
|
|
320
|
-
return _render_review_layers(value)
|
|
321
|
-
if isinstance(value, list):
|
|
322
|
-
if not value:
|
|
323
|
-
return "_None._"
|
|
324
|
-
return "\n".join(f"- {_scalar_str(item)}" for item in value)
|
|
325
|
-
return _scalar_str(value)
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
def render_workflow(content, workflow):
|
|
329
|
-
"""Resolve {workflow.<key>} placeholders from the resolved [workflow] block.
|
|
330
|
-
Unknown keys emit an empty string (missingkey=zero, matching render_template).
|
|
331
|
-
Distinct regex from render_template so single-curly runtime placeholders
|
|
332
|
-
elsewhere are untouched."""
|
|
333
|
-
return re.sub(
|
|
334
|
-
r"\{workflow\.(\w+)\}",
|
|
335
|
-
lambda m: _render_workflow_value(m.group(1), workflow.get(m.group(1))),
|
|
336
|
-
content,
|
|
337
|
-
)
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
def main():
|
|
341
|
-
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
342
|
-
skill_name = os.path.basename(script_dir)
|
|
343
|
-
root = find_project_root()
|
|
344
|
-
root = root.replace(os.sep, "/")
|
|
345
|
-
|
|
346
|
-
vars_ = flatten_central_config(load_central_config(root))
|
|
347
|
-
|
|
348
|
-
for key in list(vars_.keys()):
|
|
349
|
-
vars_[key] = vars_[key].replace("{project-root}", root)
|
|
350
|
-
|
|
351
|
-
vars_["project_root"] = root
|
|
352
|
-
|
|
353
|
-
# Guarded ahead of the general missing-vars scan: sprint_status and
|
|
354
|
-
# deferred_work_file derive from it below, and unlike the scan (absent
|
|
355
|
-
# keys only) this also HALTs on a present-but-empty value.
|
|
356
|
-
implementation_artifacts = vars_.get("implementation_artifacts", "").strip()
|
|
357
|
-
if not implementation_artifacts:
|
|
358
|
-
print(
|
|
359
|
-
"HALT and report to the user: config is missing `implementation_artifacts` "
|
|
360
|
-
"(expected under [core] or [modules.bmm] in _bmad/config.toml)"
|
|
361
|
-
)
|
|
362
|
-
sys.exit(1)
|
|
363
|
-
|
|
364
|
-
vars_["sprint_status"] = posixpath.join(
|
|
365
|
-
implementation_artifacts, "sprint-status.yaml"
|
|
366
|
-
)
|
|
367
|
-
vars_["deferred_work_file"] = posixpath.join(
|
|
368
|
-
implementation_artifacts, "deferred-work.md"
|
|
369
|
-
)
|
|
370
|
-
|
|
371
|
-
sources = []
|
|
372
|
-
for fname in sorted(os.listdir(script_dir)):
|
|
373
|
-
if not fname.endswith(".md") or fname == "SKILL.md":
|
|
374
|
-
continue
|
|
375
|
-
with open(
|
|
376
|
-
posixpath.join(script_dir, fname), "r", encoding="utf-8", newline=""
|
|
377
|
-
) as fh:
|
|
378
|
-
sources.append((fname, fh.read()))
|
|
379
|
-
|
|
380
|
-
missing = collect_missing_vars(sources, vars_)
|
|
381
|
-
if missing:
|
|
382
|
-
details = "; ".join(
|
|
383
|
-
f"`{name}` (referenced by {', '.join(files)})"
|
|
384
|
-
for name, files in sorted(missing.items())
|
|
385
|
-
)
|
|
386
|
-
print(
|
|
387
|
-
f"HALT and report to the user: config is missing {details} "
|
|
388
|
-
"(expected under [core] or [modules.bmm] in _bmad/config.toml)"
|
|
389
|
-
)
|
|
390
|
-
sys.exit(1)
|
|
391
|
-
|
|
392
|
-
skill_dir = script_dir.replace(os.sep, "/")
|
|
393
|
-
workflow = resolve_workflow(root, skill_dir, skill_name)
|
|
394
|
-
open_spec = workflow.get("open_spec")
|
|
395
|
-
if not isinstance(open_spec, str):
|
|
396
|
-
print(
|
|
397
|
-
"HALT and report to the user: customization `workflow.open_spec` "
|
|
398
|
-
"must be a string"
|
|
399
|
-
)
|
|
400
|
-
sys.exit(1)
|
|
401
|
-
workflow = expand_review_layer_skill_roots(workflow, skill_dir)
|
|
402
|
-
|
|
403
|
-
out_dir = posixpath.join(root, "_bmad", "render", skill_name)
|
|
404
|
-
os.makedirs(out_dir, exist_ok=True)
|
|
405
|
-
|
|
406
|
-
for fname in os.listdir(out_dir):
|
|
407
|
-
if fname.endswith(".md"):
|
|
408
|
-
os.remove(posixpath.join(out_dir, fname))
|
|
409
|
-
|
|
410
|
-
for fname, content in sources:
|
|
411
|
-
dst = posixpath.join(out_dir, fname)
|
|
412
|
-
with open(dst, "w", encoding="utf-8", newline="") as fh:
|
|
413
|
-
fh.write(render_workflow(render_template(content, vars_), workflow))
|
|
414
|
-
|
|
415
|
-
workflow_md = posixpath.join(out_dir, "workflow.md")
|
|
416
|
-
print(f"read and follow {workflow_md}")
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
if __name__ == "__main__":
|
|
420
|
-
main()
|