bmad-method 6.8.1-next.2 → 6.8.1-next.20

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 (96) hide show
  1. package/.claude-plugin/marketplace.json +9 -3
  2. package/package.json +10 -4
  3. package/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md +2 -2
  4. package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +8 -8
  5. package/src/bmm-skills/2-plan-workflows/bmad-prd/SKILL.md +7 -7
  6. package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/headless-schemas.md +2 -2
  7. package/src/bmm-skills/2-plan-workflows/bmad-prd/customize.toml +1 -1
  8. package/src/bmm-skills/2-plan-workflows/bmad-prd/references/headless.md +1 -1
  9. package/src/bmm-skills/2-plan-workflows/bmad-prd/references/validate.md +1 -1
  10. package/src/bmm-skills/2-plan-workflows/bmad-ux/SKILL.md +8 -8
  11. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/design-directions.md +1 -1
  12. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/headless-schemas.md +2 -2
  13. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/key-screens.md +4 -4
  14. package/src/bmm-skills/2-plan-workflows/bmad-ux/customize.toml +1 -1
  15. package/src/bmm-skills/2-plan-workflows/bmad-ux/references/creative-tools.md +1 -1
  16. package/src/bmm-skills/2-plan-workflows/bmad-ux/references/headless.md +1 -1
  17. package/src/bmm-skills/2-plan-workflows/bmad-ux/references/validate.md +2 -2
  18. package/src/bmm-skills/3-solutioning/bmad-agent-architect/customize.toml +2 -2
  19. package/src/bmm-skills/3-solutioning/bmad-architecture/SKILL.md +85 -0
  20. package/src/bmm-skills/3-solutioning/bmad-architecture/assets/spine-template.md +79 -0
  21. package/src/bmm-skills/3-solutioning/bmad-architecture/customize.toml +100 -0
  22. package/src/bmm-skills/3-solutioning/bmad-architecture/references/headless.md +26 -0
  23. package/src/bmm-skills/3-solutioning/bmad-architecture/references/reviewer-gate.md +13 -0
  24. package/src/bmm-skills/3-solutioning/bmad-architecture/scripts/lint_spine.py +257 -0
  25. package/src/bmm-skills/3-solutioning/bmad-architecture/scripts/tests/test_lint_spine.py +270 -0
  26. package/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md +16 -60
  27. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/steps/step-01-validate-prerequisites.md +12 -4
  28. package/src/bmm-skills/4-implementation/bmad-code-review/steps/step-02-review.md +1 -1
  29. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-04-review.md +1 -1
  30. package/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md +29 -14
  31. package/src/bmm-skills/4-implementation/bmad-retrospective/customize.toml +1 -1
  32. package/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md +20 -1
  33. package/src/bmm-skills/4-implementation/bmad-sprint-planning/checklist.md +2 -1
  34. package/src/bmm-skills/4-implementation/bmad-sprint-planning/sprint-status-template.yaml +13 -0
  35. package/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md +13 -0
  36. package/src/bmm-skills/module-help.csv +2 -2
  37. package/src/core-skills/bmad-brainstorming/SKILL.md +8 -10
  38. package/src/core-skills/bmad-brainstorming/references/converge.md +1 -1
  39. package/src/core-skills/bmad-brainstorming/references/finalize.md +1 -1
  40. package/src/core-skills/bmad-brainstorming/references/headless.md +4 -4
  41. package/src/core-skills/bmad-brainstorming/references/in-chat-techniques.md +1 -1
  42. package/src/core-skills/bmad-brainstorming/references/mode-autonomous.md +1 -1
  43. package/src/core-skills/bmad-brainstorming/scripts/tests/test_brain.py +2 -2
  44. package/src/core-skills/bmad-customize/scripts/tests/test_list_customizable_skills.py +1 -1
  45. package/src/core-skills/bmad-forge-idea/SKILL.md +79 -0
  46. package/src/core-skills/bmad-forge-idea/customize.toml +42 -0
  47. package/src/core-skills/bmad-forge-idea/scripts/resolve_personas.py +270 -0
  48. package/src/core-skills/bmad-forge-idea/scripts/tests/test_resolve_personas.py +138 -0
  49. package/src/core-skills/bmad-party-mode/SKILL.md +39 -56
  50. package/src/core-skills/bmad-party-mode/customize.toml +175 -0
  51. package/src/core-skills/bmad-party-mode/references/create-party.md +70 -0
  52. package/src/core-skills/bmad-party-mode/references/mode-agent-team.md +11 -0
  53. package/src/core-skills/bmad-party-mode/references/mode-auto.md +13 -0
  54. package/src/core-skills/bmad-party-mode/references/mode-subagent.md +19 -0
  55. package/src/core-skills/bmad-party-mode/references/party-memory.md +51 -0
  56. package/src/core-skills/bmad-party-mode/scripts/resolve_party.py +272 -0
  57. package/src/core-skills/bmad-party-mode/scripts/tests/test-resolve_party.py +146 -0
  58. package/src/core-skills/bmad-spec/SKILL.md +25 -9
  59. package/src/core-skills/bmad-spec/assets/headless-schemas.md +3 -3
  60. package/src/core-skills/bmad-spec/assets/spec-template.md +4 -4
  61. package/src/core-skills/module-help.csv +1 -0
  62. package/src/{core-skills/bmad-brainstorming/scripts → scripts}/memlog.py +56 -34
  63. package/src/scripts/resolve_config.py +8 -6
  64. package/src/scripts/resolve_customization.py +8 -6
  65. package/src/{core-skills/bmad-brainstorming/scripts → scripts}/tests/test_memlog.py +68 -27
  66. package/tools/installer/commands/install.js +3 -0
  67. package/tools/installer/core/installer.js +35 -1
  68. package/tools/installer/core/uv-check.js +97 -0
  69. package/tools/installer/core/wsl-node-check.js +109 -0
  70. package/tools/installer/ide/platform-codes.yaml +14 -0
  71. package/tools/installer/install-messages.yaml +4 -0
  72. package/tools/installer/ui.js +11 -0
  73. package/evals/bmm-skills/bmad-product-brief/evals.json +0 -237
  74. package/evals/bmm-skills/bmad-product-brief/files/branfield-memo.md +0 -46
  75. package/evals/bmm-skills/bmad-product-brief/files/forkbird-brief/addendum.md +0 -40
  76. package/evals/bmm-skills/bmad-product-brief/files/forkbird-brief/brief.md +0 -56
  77. package/evals/bmm-skills/bmad-product-brief/files/forkbird-brief/decision-log.md +0 -27
  78. package/evals/bmm-skills/bmad-product-brief/files/meridian-mobility-report.md +0 -116
  79. package/evals/bmm-skills/bmad-product-brief/files/mossridge-brief/addendum.md +0 -41
  80. package/evals/bmm-skills/bmad-product-brief/files/mossridge-brief/brief.md +0 -57
  81. package/evals/bmm-skills/bmad-product-brief/files/mossridge-brief/decision-log.md +0 -29
  82. package/evals/bmm-skills/bmad-product-brief/files/pantry-bridge-interviews.md +0 -90
  83. package/evals/bmm-skills/bmad-product-brief/files/q2-brainstorm.md +0 -101
  84. package/evals/bmm-skills/bmad-product-brief/triggers.json +0 -18
  85. package/src/bmm-skills/3-solutioning/bmad-create-architecture/architecture-decision-template.md +0 -12
  86. package/src/bmm-skills/3-solutioning/bmad-create-architecture/data/domain-complexity.csv +0 -13
  87. package/src/bmm-skills/3-solutioning/bmad-create-architecture/data/project-types.csv +0 -7
  88. package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-01-init.md +0 -153
  89. package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-01b-continue.md +0 -173
  90. package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-02-context.md +0 -224
  91. package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-03-starter.md +0 -329
  92. package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-04-decisions.md +0 -318
  93. package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-05-patterns.md +0 -359
  94. package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-06-structure.md +0 -379
  95. package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-07-validation.md +0 -361
  96. package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-08-complete.md +0 -82
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.11"
4
+ # ///
5
+ """Unit tests for resolve_party.py — merge, alias, override, group resolution."""
6
+
7
+ import sys
8
+ import unittest
9
+ from pathlib import Path
10
+
11
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
12
+ import resolve_party as rp # noqa: E402
13
+
14
+ AGENTS = {
15
+ "bmad-agent-analyst": {"name": "Mary", "icon": "📊", "title": "Analyst"},
16
+ "bmad-agent-pm": {"name": "John", "icon": "📋", "title": "PM"},
17
+ }
18
+
19
+
20
+ class TestAlias(unittest.TestCase):
21
+ def test_strips_known_prefixes(self):
22
+ self.assertEqual(rp._alias("bmad-agent-analyst"), "analyst")
23
+ self.assertEqual(rp._alias("bmad-foo"), "foo")
24
+
25
+ def test_passes_through_unprefixed(self):
26
+ self.assertEqual(rp._alias("morpheus"), "morpheus")
27
+
28
+
29
+ class TestBuildCollective(unittest.TestCase):
30
+ def test_installed_agents_indexed_by_code_alias_and_name(self):
31
+ col, idx, _ = rp.build_collective(AGENTS, [])
32
+ self.assertEqual(set(col), {"bmad-agent-analyst", "bmad-agent-pm"})
33
+ self.assertEqual(idx["analyst"], "bmad-agent-analyst") # alias
34
+ self.assertEqual(idx["mary"], "bmad-agent-analyst") # name (ci)
35
+ self.assertEqual(idx["bmad-agent-pm"], "bmad-agent-pm") # full code
36
+ self.assertEqual(col["bmad-agent-analyst"]["source"], "installed")
37
+
38
+ def test_custom_member_appends(self):
39
+ col, _, _ = rp.build_collective(AGENTS, [{"code": "morpheus", "name": "Morpheus", "persona": "riddles"}])
40
+ self.assertIn("morpheus", col)
41
+ self.assertEqual(col["morpheus"]["source"], "custom")
42
+ self.assertEqual(col["morpheus"]["persona"], "riddles")
43
+
44
+ def test_custom_overrides_installed_by_alias(self):
45
+ col, _, _ = rp.build_collective(AGENTS, [{"code": "analyst", "name": "Mary-Custom", "persona": "p"}])
46
+ # Override lands on the canonical installed code, not a new "analyst" entry.
47
+ self.assertNotIn("analyst", col)
48
+ self.assertEqual(col["bmad-agent-analyst"]["source"], "custom")
49
+ self.assertEqual(col["bmad-agent-analyst"]["name"], "Mary-Custom")
50
+
51
+ def test_member_without_code_skipped(self):
52
+ col, _, _ = rp.build_collective(AGENTS, [{"name": "Nameless"}])
53
+ self.assertEqual(set(col), {"bmad-agent-analyst", "bmad-agent-pm"})
54
+
55
+
56
+ class TestResolveMembers(unittest.TestCase):
57
+ def setUp(self):
58
+ self.col, self.idx, _ = rp.build_collective(AGENTS, [{"code": "morpheus", "name": "Morpheus"}])
59
+
60
+ def test_resolves_in_listed_order_and_flags_unknowns(self):
61
+ resolved, unresolved = rp.resolve_members(["morpheus", "analyst", "ghost"], self.col, self.idx)
62
+ self.assertEqual([m["code"] for m in resolved], ["morpheus", "bmad-agent-analyst"])
63
+ self.assertEqual(unresolved, ["ghost"])
64
+
65
+ def test_empty(self):
66
+ self.assertEqual(rp.resolve_members([], self.col, self.idx), ([], []))
67
+
68
+
69
+ class TestGroups(unittest.TestCase):
70
+ GROUPS = [
71
+ {"id": "wr", "name": "Writers", "members": ["analyst", "morpheus"]},
72
+ {"id": "bad"}, # no name -> falls back to id; no members -> count 0
73
+ {"name": "no-id"}, # dropped from menu
74
+ ]
75
+
76
+ def test_menu_is_names_only_with_counts_and_open_cast_flag(self):
77
+ menu = rp.group_menu(self.GROUPS)
78
+ self.assertEqual(menu, [
79
+ {"id": "wr", "name": "Writers", "member_count": 2},
80
+ {"id": "bad", "name": "bad", "member_count": 0, "open_cast": True},
81
+ ])
82
+
83
+ def test_find_group(self):
84
+ self.assertEqual(rp.find_group(self.GROUPS, "wr")["name"], "Writers")
85
+ self.assertIsNone(rp.find_group(self.GROUPS, "missing"))
86
+
87
+
88
+ class TestGroupDetail(unittest.TestCase):
89
+ def setUp(self):
90
+ self.col, self.idx, _ = rp.build_collective(AGENTS, [{"code": "morpheus", "name": "Morpheus"}])
91
+
92
+ def test_scene_passes_through_when_present(self):
93
+ g = {"id": "tos-10-forward", "name": "Ten Forward", "members": ["morpheus"],
94
+ "scene": "Late evening, a few rounds in."}
95
+ d = rp.group_detail(g, self.col, self.idx)
96
+ self.assertEqual(d["scene"], "Late evening, a few rounds in.")
97
+ self.assertEqual([m["code"] for m in d["members"]], ["morpheus"])
98
+
99
+ def test_scene_omitted_when_absent_or_empty(self):
100
+ for g in ({"id": "g", "members": ["morpheus"]},
101
+ {"id": "g", "members": ["morpheus"], "scene": ""}):
102
+ self.assertNotIn("scene", rp.group_detail(g, self.col, self.idx))
103
+
104
+ def test_anchored_group_is_not_open_cast(self):
105
+ g = {"id": "g", "members": ["morpheus"]}
106
+ self.assertNotIn("open_cast", rp.group_detail(g, self.col, self.idx))
107
+
108
+ def test_open_cast_group_flagged_with_empty_members(self):
109
+ g = {"id": "rebels", "name": "Star Wars Rebels",
110
+ "scene": "Figures from the Rebels universe drop in as the topic calls for them."}
111
+ d = rp.group_detail(g, self.col, self.idx)
112
+ self.assertTrue(d["open_cast"])
113
+ self.assertEqual(d["members"], [])
114
+ self.assertEqual(d["scene"][:7], "Figures")
115
+
116
+ def test_memory_enabled_follows_group_flag_and_defaults_off(self):
117
+ on = rp.group_detail({"id": "g", "members": ["morpheus"], "memory": True}, self.col, self.idx)
118
+ self.assertTrue(on["memory_enabled"])
119
+ off = rp.group_detail({"id": "g", "members": ["morpheus"], "memory": False}, self.col, self.idx)
120
+ self.assertFalse(off["memory_enabled"])
121
+ absent = rp.group_detail({"id": "g", "members": ["morpheus"]}, self.col, self.idx)
122
+ self.assertFalse(absent["memory_enabled"]) # opt-in per named group
123
+
124
+
125
+ class TestInstalledCodesIsDefaultRoom(unittest.TestCase):
126
+ """The default room is installed agents only; pure customs stay in the pool."""
127
+
128
+ def test_pure_custom_excluded_override_kept_in_default_room(self):
129
+ col, _, installed = rp.build_collective(AGENTS, [
130
+ {"code": "morpheus", "name": "Morpheus"}, # pure custom
131
+ {"code": "analyst", "name": "Mary-Custom", "persona": "p"}, # override
132
+ {"code": "sec-hawk", "name": "Vex"}, # shipped crew member
133
+ ])
134
+ # Pure customs are in the pool...
135
+ self.assertIn("morpheus", col)
136
+ self.assertIn("sec-hawk", col)
137
+ # ...but NOT in the default room.
138
+ self.assertEqual(installed, ["bmad-agent-analyst", "bmad-agent-pm"])
139
+ default_room = [col[c]["code"] for c in installed]
140
+ self.assertEqual(default_room, ["bmad-agent-analyst", "bmad-agent-pm"])
141
+ # An override keeps its installed slot (and its custom content).
142
+ self.assertEqual(col["bmad-agent-analyst"]["name"], "Mary-Custom")
143
+
144
+
145
+ if __name__ == "__main__":
146
+ unittest.main()
@@ -18,7 +18,7 @@ Multiple skills may call to update the same spec over time.
18
18
 
19
19
  ## On Activation
20
20
 
21
- 1. Resolve customization: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly.
21
+ 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly.
22
22
  2. Run `{workflow.activation_steps_prepend}`. Treat `{workflow.persistent_facts}` as foundational context (`file:` entries are loaded).
23
23
  3. Load `{project-root}/_bmad/core/config.yaml` (and `config.user.yaml` if present), root level and `bmm` section. Resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{planning_artifacts}`, `{project_name}`, `{date}`.
24
24
  4. Detect mode. **Headless** when any of: no TTY, programmatic caller (another skill or non-interactive runner), or the first message pre-supplies all inputs and asks for an artifact path back. **Interactive** otherwise. In interactive mode, greet by `{user_name}` in `{communication_language}`, stay in that language, and mention that `bmad-party-mode` and `bmad-advanced-elicitation` are available for deeper exploration on any field.
@@ -43,22 +43,38 @@ Inside the spec folder:
43
43
 
44
44
  ```
45
45
  <spec-folder>/
46
- SPEC.md ← uppercase, the kernel
47
- <companion-1>.md ← optional, content-typed (e.g. glossary.md)
46
+ SPEC.md ← uppercase, the kernel — DERIVED from .memlog.md, never hand-edited
47
+ <companion-1>.md ← optional, content-typed (e.g. glossary.md); spec-authored ones are derived too
48
48
  <companion-2>.md
49
- .decision-log.md ← canonical memory for this spec
49
+ .memlog.md ← canonical, append-only memory; what SPEC.md is distilled from
50
50
  ```
51
51
 
52
+ ## Memory and derivation
53
+
54
+ `.memlog.md` is canonical — an append-only, chronological record of every decision, constraint, capability (with its stable `CAP-N`), assumption, open question, and bit of user direction, one line each in the order it happened, never edited or reordered. `SPEC.md` and every spec-authored companion are **derived on each run** from the memlog (the decision-of-record) plus the sources it cites for raw content — never hand-patched.
55
+
56
+ Deriving the contract from a living log instead of editing the contract in place is what lets the steps around the spec (PRD, UX, architecture, epics) run in any order and feed the same spec without merge drift: the log only accumulates, the artifact is re-rendered. So the spec is updated *only* by re-deriving it here — bmad-spec is its single writer; a hand-edit to `SPEC.md` from outside is unsupported and is overwritten on the next derive.
57
+
58
+ Writes go through the shared script — `{project-root}/_bmad/scripts/memlog.py`, the same location as `resolve_customization.py` (atomic; never read it back except to resume):
59
+
60
+ - `uv run {project-root}/_bmad/scripts/memlog.py init --workspace {spec-folder} --field topic="<what is being specced>"` — once, at create.
61
+ - `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {spec-folder} --type <decision|constraint|capability|assumption|question|direction|note|event> --text "<one-line gist, reason included>"` — as each lands.
62
+ - Terminal moments (a validation verdict, "spec finalized") are `--type event` entries; the memlog carries no status field.
63
+
52
64
  ## The Operation
53
65
 
54
- Read the input and its ancillary linked materials. If there is no input, follow the no-input branch in **Workspace** (ask or block). If a prior `SPEC.md` exists at the target folder, read it too — the operation becomes an update. Preserve capability IDs; new capabilities get the next unused `CAP-N`; never reuse retired IDs. Otherwise this is a create.
66
+ Read the input and its ancillary linked materials. If there is no input, follow the no-input branch in **Workspace** (ask or block). If a prior `.memlog.md` exists at the target folder, read it — the operation becomes an update, and the memlog (not the rendered `SPEC.md`) is the authority on what was decided and on capability IDs. Preserve those IDs; new capabilities get the next unused `CAP-N`; never reuse retired IDs. Otherwise this is a create, and the first move is `memlog.py init`.
55
67
 
56
68
  When the input is structured and pre-sorted (a PRD with an addendum, a GDD, a brief produced by an upstream BMad skill), trust the authored separation: lift kernel-fitting content into SPEC.md, lift overflow into appropriately-named companions. When the input is mixed (a brain dump, a transcript, an RFC, a customer email), do the sorting yourself: walk each claim, apply the three-lens load-bearing test (Spec Law rule 7), and route to the kernel field or a companion.
57
69
 
58
70
  Distill the input into the five-field kernel using `{workflow.spec_template}` as the skeleton. When input is rich, extract directly — no elicitation. When input is sparse, choose: **express** (best-effort distill, every gap becomes an `open_questions[]` entry) or **guided** (walk the five fields with the user one at a time). Headless defaults to express and logs the choice. Interactive asks.
59
71
 
72
+ A recognized domain implication the input leaves unaddressed *is* such a gap — name it as an `open_questions[]` entry (healthcare input silent on PHI/HIPAA, payments silent on PCI, control systems silent on fail-safe) and move on. Flag it; never invent the answer or coach toward it. If these dominate, the input is too thin — suggest `bmad-prd`.
73
+
60
74
  Write lean from the first pass: every sentence must earn its place. Decoration costs tokens and dilutes downstream readers.
61
75
 
76
+ Log each decision, capability, constraint, and accepted change to `.memlog.md` as it is made — that running record is what the render reads. Because the log is append-only, a later entry supersedes an earlier one on the same point while the history stays intact. When two currently-live sources or companions disagree on the same field, or an either/or never got resolved, surface it to the user rather than silently choosing — the resolution is itself a new memlog entry.
77
+
62
78
  If the input is genuinely too thin to distill (e.g. "an app for hikers" with no surrounding context), stop and suggest `bmad-prd` (or sibling ceremony skill). This skill distills; it does not coach.
63
79
 
64
80
  ## Load-bearing
@@ -94,7 +110,7 @@ Every spec must satisfy these eight rules. The operation aims for them; the self
94
110
  5. **Success signal is concrete enough to test or demonstrate against.** "Users love it" doesn't qualify.
95
111
  6. **Capability IDs are stable and unique.** Never reused, never renumbered.
96
112
  7. **Preservation.** Every load-bearing source claim lands in SPEC.md or a companion. Wrapper ceremony does not.
97
- 8. **Lean prose.** Every sentence carries load-bearing content. Cut decoration, hedges, backstory, throat-clearing. Applies to SPEC.md, companions, and `.decision-log.md`.
113
+ 8. **Lean prose.** Every sentence carries load-bearing content. Cut decoration, hedges, backstory, throat-clearing. Applies to SPEC.md, companions, and `.memlog.md`.
98
114
 
99
115
  ## Self-Validate
100
116
 
@@ -104,7 +120,7 @@ After every create or update, sweep the resulting artifact in **two passes** bef
104
120
 
105
121
  **Pass 2 — Preservation.** Walk the source claim by claim. Confirm each load-bearing claim landed in SPEC.md or a companion. Wrapper-ceremony drops are logged under "Wrapper-only content" so the drop is on the record, not silent.
106
122
 
107
- Append a one-paragraph verdict to `.decision-log.md` covering both passes. In interactive mode, review the verdict with the user. In headless mode, `.decision-log.md` is one of the files returned, so the caller (or its downstream LLM) reads the verdict there.
123
+ Record the verdict for each pass to `.memlog.md` (`append --type event`). In interactive mode, review it with the user. In headless mode, `.memlog.md` is one of the files returned, so the caller (or its downstream LLM) reads the verdict there.
108
124
 
109
125
  ## Spec with no change signal
110
126
 
@@ -120,10 +136,10 @@ Run `{workflow.on_complete}` if set.
120
136
 
121
137
  ## After Spec is Output
122
138
 
123
- Any update to spec regarding assumptions, open questions, or other changes should be appended to that source's decision log also and offer to update the source.
139
+ Any update to the spec resolved assumptions, answered open questions, other changes is appended to `.memlog.md` as it happens. When a change overrides something that came from a source input, offer to update that source too, so upstream and the spec don't silently diverge.
124
140
 
125
141
  ## Frontmatter conventions
126
142
 
127
143
  - `companions:` array of `.md` files downstream MUST read alongside SPEC.md to have the full contract. Paths may point inside the spec folder (spec-authored companions like `glossary.md`) or outside it (adopted companions like `../planning-artifacts/ux-designs/ux-foo-bar-2026-05-23/DESIGN.md`). The split between spec-authored and adopted is implicit by path; downstream treats both the same.
128
144
  - `sources:` array of paths to files that were **fully absorbed** into the SPEC, with no remaining downstream value (e.g., a PRD whose every load-bearing claim is now in the kernel). Listed for audit and for bmad-spec to re-read on update. Downstream does NOT read these. Files that downstream still needs to read belong in `companions:`, not here.
129
- - **Do not list** decision logs, README files, organizational artifacts, or any operational record of how upstream skills produced their artifacts. Those are not source content; they are process metadata that downstream consumers don't need.
145
+ - **Do not list** the memlog, README files, organizational artifacts, or any operational record of how upstream skills produced their artifacts. Those are not source content; they are process metadata that downstream consumers don't need.
@@ -1,6 +1,6 @@
1
1
  # Headless JSON Response
2
2
 
3
- The default invocation is headless: input goes in, JSON comes out. The contract is intentionally tiny — return the outcome and the files touched. Anything else a caller needs is inside those files (SPEC.md, companions, `.decision-log.md`).
3
+ The default invocation is headless: input goes in, JSON comes out. The contract is intentionally tiny — return the outcome and the files touched. Anything else a caller needs is inside those files (SPEC.md, companions, `.memlog.md`).
4
4
 
5
5
  ## Success
6
6
 
@@ -10,12 +10,12 @@ The default invocation is headless: input goes in, JSON comes out. The contract
10
10
  "files": [
11
11
  "_bmad-output/specs/spec-quarter-drop/SPEC.md",
12
12
  "_bmad-output/specs/spec-quarter-drop/glossary.md",
13
- "_bmad-output/specs/spec-quarter-drop/.decision-log.md"
13
+ "_bmad-output/specs/spec-quarter-drop/.memlog.md"
14
14
  ]
15
15
  }
16
16
  ```
17
17
 
18
- `files` lists every file written or modified in this run, in any order. The spec folder, kernel filename, decision log location, capabilities, companions, and verdict are all readable from those files; no need to re-encode them in the response.
18
+ `files` lists every file written or modified in this run, in any order. The spec folder, kernel filename, memlog location, capabilities, companions, and verdict are all readable from those files; no need to re-encode them in the response.
19
19
 
20
20
  ## Blocked
21
21
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  id: SPEC-{slug}
3
3
  companions: [] # files downstream MUST read alongside SPEC.md. Paths may point inside the spec folder (spec-authored) or outside it (adopted from an upstream skill).
4
- sources: [] # files fully absorbed into the SPEC (audit only; downstream does NOT read these). Never decision logs.
4
+ sources: [] # files fully absorbed into the SPEC (audit only; downstream does NOT read these). Never the memlog.
5
5
  ---
6
6
 
7
7
  > **Canonical contract.** This SPEC and the files in `companions:` are the complete, preservation-validated contract for what to build, test, and validate. Source documents listed in frontmatter are for traceability only — consult them only if you need narrative rationale or prose color this contract intentionally omits.
@@ -20,9 +20,9 @@ Name which (or which combination) applies, who is affected, and the backdrop tha
20
20
 
21
21
  ## Capabilities
22
22
 
23
- - id: CAP-1
24
- intent: {One sentence. "User or system can do X to achieve Y." WHAT, not HOW.}
25
- success: {Testable or demonstrable criterion. Something a test or a real demonstration can decide.}
23
+ - **CAP-1**
24
+ - **intent:** {One sentence. "User or system can do X to achieve Y." WHAT, not HOW.}
25
+ - **success:** {Testable or demonstrable criterion. Something a test or a real demonstration can decide.}
26
26
 
27
27
  ## Constraints
28
28
 
@@ -11,3 +11,4 @@ Core,bmad-review-adversarial-general,Adversarial Review,AR,"Use for quality assu
11
11
  Core,bmad-review-edge-case-hunter,Edge Case Hunter Review,ECH,Use alongside adversarial review for orthogonal coverage — method-driven not attitude-driven.,,[path],anytime,,,false,,
12
12
  Core,bmad-spec,Spec,SP,"Use to distill any intent input (brief, PRD, transcript, brain dump, design folder, mixed multi-source) into a succinct, no-fluff SPEC.md contract + companions that downstream work derives from. Locks the WHAT before the HOW. Works for software, game design, research, editorial, policy, business, anything intent-bearing. Validation mode also available.",,[path],anytime,,,false,{output_folder}/specs/spec-{slug},SPEC.md + companion files
13
13
  Core,bmad-customize,BMad Customize,BC,"Use when you want to change how an agent or workflow behaves — add persistent facts, swap templates, insert activation hooks, or customize menus. Scans what's customizable, picks the right scope (agent vs workflow), writes the override to _bmad/custom/, and verifies the merge. No TOML hand-authoring required.",,,anytime,,,false,{project-root}/_bmad/custom,TOML override files
14
+ Core,bmad-forge-idea,Forge Idea,FI,"Use to pressure-test and harden an idea — software, business, creative, research, or life — until it proves out, hardens into something buildable, or dies cheaply. Persona-driven interrogation; optional handoff to bmad-spec or bmad-quick-dev.",,,anytime,,,false,{output_folder}/forge,refined-idea brief (optional)
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  # /// script
3
- # requires-python = ">=3.10"
3
+ # requires-python = ">=3.8"
4
4
  # ///
5
5
  """memlog — an append-only memory log: LLM-optimal working memory for a skill.
6
6
 
@@ -15,21 +15,31 @@ It is a FLAT log: there are no sections or grouping. Every entry is one line, re
15
15
  at the END in the order it happened. The chronology itself is the structure — an event
16
16
  like "started technique X" is just another entry, same as an idea or an insight.
17
17
 
18
- Two invariants make it trustworthy:
18
+ Three invariants make it trustworthy:
19
19
 
20
20
  1. Append-only, chronological. Entries land at the end, in the order they happen.
21
- Nothing is ever inserted backward, reordered, or grouped.
21
+ Nothing is ever inserted backward, reordered, edited, or removed. There is no
22
+ edit or delete subcommand by design; history is never rewritten.
22
23
  2. Write-only / blind. Every command is an atomic, context-free write and echoes the
23
- new state as JSON, so the caller never re-reads the file mid-session. The one time
24
- the file is read is on resume — and the caller reads it itself, not via this script.
24
+ new state as one line of JSON, so the caller never re-reads the file mid-session.
25
+ The one time the file is read is on resume — and the caller reads it itself, not
26
+ via this script.
27
+ 3. No lifecycle status. A memory log has no "complete" flag. Whether the work is done,
28
+ blocked, or paused is itself a fact that happened, so it is recorded as an entry
29
+ (e.g. `append --type event --text "session complete"`), never as frontmatter the
30
+ log would have to mutate. The chronology stays the single source of truth, and a
31
+ resume learns the state by reading the last entries — the same way it learns
32
+ everything else.
33
+
34
+ Atomicity: every write goes to a temp file, is flushed and fsync'd, then atomically
35
+ renamed over the target, so a crash never leaves a half-written entry.
25
36
 
26
37
  The file shape (.memlog.md):
27
38
 
28
39
  ---
29
40
  topic: Onboarding flow for a budgeting app
30
41
  goal: lift week-1 retention
31
- status: active
32
- updated: 2026-05-30T14:22
42
+ updated: 2026-06-07T14:22
33
43
  ---
34
44
 
35
45
  - (note) user picked techniques: SCAMPER, then Six Thinking Hats
@@ -37,25 +47,27 @@ The file shape (.memlog.md):
37
47
  - (idea) skip the signup wall: let people try with sample data first
38
48
  - (idea) auto-import one bank account so the first screen shows real numbers
39
49
  - (question) is open-banking consent too heavy for step one?
40
- - (technique) started Six Thinking Hats
41
- - (idea) black-hat: imported transactions look scary before they're categorized
42
50
  - (insight) the "scary numbers" risk and the "real numbers" idea are one lever: show real data, pre-categorized
43
- - (direction) user wants to optimize for the anxious first-timer, not the power user
51
+ - (direction) optimize for the anxious first-timer, not the power user
44
52
  - (decision) lead with one pre-categorized account; defer multi-account import
53
+ - (event) session complete
45
54
 
46
55
  Each entry may carry an optional `--type` — what KIND it is (idea, insight, question,
47
- decision, technique, …) — and an optional `--by` naming who it came from (e.g. `user`,
48
- `coach`), for sessions where authorship matters. Both render into one short inline tag:
49
- `(idea)`, `(idea by user)`, `(by coach)`. Omit them for a plain note. The host skill
50
- names the vocabulary; the script does not.
56
+ decision, direction, assumption, gap, note, event, …) — and an optional `--by` naming
57
+ who it came from (e.g. `user`, `coach`), for sessions where authorship matters. Both
58
+ render into one short inline tag: `(idea)`, `(idea by user)`, `(by coach)`. Omit them
59
+ for a plain note. The host skill names the vocabulary; the script does not enforce one.
51
60
 
52
61
  Commands:
53
- init --workspace DIR [--field k=v ...] create the memlog (errors if it exists)
54
- append --workspace DIR --text STR [--type T] [--by W] append one entry at the end
55
- set --workspace DIR --key K --value V set/replace a frontmatter field
62
+ init (--workspace DIR | --path FILE) [--field k=v ...] create the memlog (errors if it exists)
63
+ append (--workspace DIR | --path FILE) --text STR [--type T] [--by W] append one entry at the end
64
+ set (--workspace DIR | --path FILE) --key K --value V set/replace a descriptive frontmatter field
56
65
 
57
- The workspace is the run folder; the memlog is always {workspace}/.memlog.md.
66
+ Addressing: `--workspace` is the run folder, and the memlog is always {workspace}/.memlog.md.
67
+ `--path` points straight at the memlog file instead, for callers that already hold the path.
58
68
  """
69
+ from __future__ import annotations # keep type-hint syntax lazy so the script runs on 3.8+
70
+
59
71
  import argparse
60
72
  import json
61
73
  import os
@@ -70,8 +82,9 @@ def now() -> str:
70
82
  return datetime.now().strftime("%Y-%m-%dT%H:%M")
71
83
 
72
84
 
73
- def memlog_path(workspace: str) -> Path:
74
- return Path(workspace) / MEMLOG
85
+ def resolve(args) -> Path:
86
+ """The memlog file, from either addressing mode: {workspace}/.memlog.md or an explicit --path."""
87
+ return Path(args.path) if args.path else Path(args.workspace) / MEMLOG
75
88
 
76
89
 
77
90
  def split(text: str) -> tuple[dict, str]:
@@ -107,8 +120,12 @@ def touch(meta: dict) -> None:
107
120
 
108
121
 
109
122
  def write_atomic(path: Path, text: str) -> None:
123
+ """Temp + flush + fsync + atomic rename, so a crash never half-writes an entry."""
110
124
  tmp = path.with_suffix(path.suffix + ".tmp")
111
- tmp.write_text(text, encoding="utf-8")
125
+ with open(tmp, "w", encoding="utf-8") as f:
126
+ f.write(text)
127
+ f.flush()
128
+ os.fsync(f.fileno())
112
129
  os.replace(tmp, path)
113
130
 
114
131
 
@@ -116,18 +133,17 @@ def entry_count(body: str) -> int:
116
133
  return sum(1 for ln in body.splitlines() if ln.startswith("- "))
117
134
 
118
135
 
119
- def ack(path: Path, meta: dict, body: str) -> None:
136
+ def ack(path: Path, body: str) -> None:
120
137
  """Echo new state so the caller never re-reads the file to know where it stands."""
121
138
  print(json.dumps({
122
139
  "ok": True,
123
140
  "memlog": str(path),
124
- "status": meta.get("status", ""),
125
141
  "entries": entry_count(body),
126
142
  }))
127
143
 
128
144
 
129
145
  def cmd_init(args) -> int:
130
- path = memlog_path(args.workspace)
146
+ path = resolve(args)
131
147
  if path.exists():
132
148
  print(f"error: {path} already exists; use append/set to update it", file=sys.stderr)
133
149
  return 2
@@ -139,15 +155,14 @@ def cmd_init(args) -> int:
139
155
  return 2
140
156
  k, v = pair.split("=", 1)
141
157
  meta[k.strip()] = v.strip()
142
- meta.setdefault("status", "active")
143
158
  touch(meta)
144
159
  write_atomic(path, render(meta, ""))
145
- ack(path, meta, "")
160
+ ack(path, "")
146
161
  return 0
147
162
 
148
163
 
149
164
  def cmd_append(args) -> int:
150
- path = memlog_path(args.workspace)
165
+ path = resolve(args)
151
166
  meta, body = split(path.read_text(encoding="utf-8"))
152
167
  text = " ".join(args.text.split()) # collapse newlines/runs → one-line entry, no prose bloat
153
168
  label = args.type or ""
@@ -158,38 +173,45 @@ def cmd_append(args) -> int:
158
173
  body = (body.rstrip("\n") + "\n" + entry) if body.strip() else entry # always at the end
159
174
  touch(meta)
160
175
  write_atomic(path, render(meta, body))
161
- ack(path, meta, body)
176
+ ack(path, body)
162
177
  return 0
163
178
 
164
179
 
165
180
  def cmd_set(args) -> int:
166
- path = memlog_path(args.workspace)
181
+ path = resolve(args)
167
182
  meta, body = split(path.read_text(encoding="utf-8"))
168
183
  meta[args.key] = args.value
169
184
  touch(meta)
170
185
  write_atomic(path, render(meta, body))
171
- ack(path, meta, body)
186
+ ack(path, body)
172
187
  return 0
173
188
 
174
189
 
190
+ def add_target(sp) -> None:
191
+ """Every command addresses the memlog the same way: a run folder or an explicit path."""
192
+ g = sp.add_mutually_exclusive_group(required=True)
193
+ g.add_argument("--workspace", help="run folder; the memlog is {workspace}/.memlog.md")
194
+ g.add_argument("--path", help="explicit memlog file path (alternative to --workspace)")
195
+
196
+
175
197
  def main(argv: list[str] | None = None) -> int:
176
198
  p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
177
199
  sub = p.add_subparsers(dest="cmd", required=True)
178
200
 
179
201
  pi = sub.add_parser("init", help="create the memlog")
180
- pi.add_argument("--workspace", required=True)
202
+ add_target(pi)
181
203
  pi.add_argument("--field", action="append", metavar="KEY=VALUE", help="frontmatter field (repeatable)")
182
204
  pi.set_defaults(func=cmd_init)
183
205
 
184
206
  pa = sub.add_parser("append", help="append one entry at the end")
185
- pa.add_argument("--workspace", required=True)
207
+ add_target(pa)
186
208
  pa.add_argument("--text", required=True)
187
209
  pa.add_argument("--type", help="entry kind, rendered as an inline tag")
188
210
  pa.add_argument("--by", help="who the entry came from (e.g. user, coach); rendered into the tag")
189
211
  pa.set_defaults(func=cmd_append)
190
212
 
191
- pset = sub.add_parser("set", help="set a frontmatter field")
192
- pset.add_argument("--workspace", required=True)
213
+ pset = sub.add_parser("set", help="set a descriptive frontmatter field")
214
+ add_target(pset)
193
215
  pset.add_argument("--key", required=True)
194
216
  pset.add_argument("--value", required=True)
195
217
  pset.set_defaults(func=cmd_set)
@@ -10,12 +10,14 @@ Reads from four layers (highest priority last):
10
10
 
11
11
  Outputs merged JSON to stdout. Errors go to stderr.
12
12
 
13
- Requires Python 3.11+ (uses stdlib `tomllib`). No `uv`, no `pip install`,
14
- no virtualenv plain `python3` is sufficient.
15
-
16
- python3 resolve_config.py --project-root /abs/path/to/project
17
- python3 resolve_config.py --project-root ... --key core
18
- python3 resolve_config.py --project-root ... --key agents
13
+ Uses only the Python stdlib (`tomllib`) no third-party dependencies.
14
+ BMad is standardizing on `uv run` to invoke scripts (uv provisions a suitable
15
+ interpreter for you); a plain `python3` on PATH still works during the
16
+ transition. Either runner needs Python 3.11+ for `tomllib`.
17
+
18
+ uv run resolve_config.py --project-root /abs/path/to/project
19
+ uv run resolve_config.py --project-root ... --key core
20
+ uv run resolve_config.py --project-root ... --key agents
19
21
 
20
22
  Merge rules (same as resolve_customization.py):
21
23
  - Scalars: override wins
@@ -11,12 +11,14 @@ Skill name is derived from the basename of the skill directory.
11
11
 
12
12
  Outputs merged JSON to stdout. Errors go to stderr.
13
13
 
14
- Requires Python 3.11+ (uses stdlib `tomllib`). No `uv`, no `pip install`,
15
- no virtualenv plain `python3` is sufficient.
16
-
17
- python3 resolve_customization.py --skill /abs/path/to/skill-dir
18
- python3 resolve_customization.py --skill ... --key agent
19
- python3 resolve_customization.py --skill ... --key agent.menu
14
+ Uses only the Python stdlib (`tomllib`) no third-party dependencies.
15
+ BMad is standardizing on `uv run` to invoke scripts (uv provisions a suitable
16
+ interpreter for you); a plain `python3` on PATH still works during the
17
+ transition. Either runner needs Python 3.11+ for `tomllib`.
18
+
19
+ uv run resolve_customization.py --skill /abs/path/to/skill-dir
20
+ uv run resolve_customization.py --skill ... --key agent
21
+ uv run resolve_customization.py --skill ... --key agent.menu
20
22
 
21
23
  Merge rules (purely structural — no field-name special-casing):
22
24
  - Scalars (string, int, bool, float): override wins