ma-agents 3.17.1 → 3.18.0-beta.2

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 (91) hide show
  1. package/README.md +214 -1
  2. package/bin/cli.js +4409 -279
  3. package/docs/architecture.md +11 -0
  4. package/docs/technical-notes/enforcement-hooks-research.md +10 -1
  5. package/lib/agents.js +81 -3
  6. package/lib/bmad-extension/skills/add-sprint/SKILL.md +112 -0
  7. package/lib/bmad-extension/skills/add-to-sprint/SKILL.md +112 -0
  8. package/lib/bmad-extension/skills/bmad-dev-epic/SKILL.md +112 -0
  9. package/lib/bmad-extension/skills/bmad-dev-story/workflow.md +112 -0
  10. package/lib/bmad-extension/skills/bmad-knowledge/SKILL.md +112 -0
  11. package/lib/bmad-extension/skills/bmad-sprint-planning/workflow.md +161 -0
  12. package/lib/bmad-extension/skills/bmad-sprint-status/workflow.md +112 -0
  13. package/lib/bmad-extension/skills/cleanup-done/SKILL.md +112 -0
  14. package/lib/bmad-extension/skills/close-sprint/SKILL.md +112 -0
  15. package/lib/bmad-extension/skills/generate-backlog/SKILL.md +163 -1
  16. package/lib/bmad-extension/skills/ma-agent-cyber/SKILL.md +2 -2
  17. package/lib/bmad-extension/skills/ma-agent-devops/SKILL.md +2 -2
  18. package/lib/bmad-extension/skills/ma-agent-sqa/SKILL.md +2 -2
  19. package/lib/bmad-extension/skills/ma-agent-sre/SKILL.md +2 -2
  20. package/lib/bmad-extension/skills/mil498-ocd/prompts/01-discover-project-artifacts.md +2 -2
  21. package/lib/bmad-extension/skills/mil498-sdd/prompts/01-discover-project-artifacts.md +2 -2
  22. package/lib/bmad-extension/skills/mil498-sdp/prompts/01-discover-project-artifacts.md +2 -2
  23. package/lib/bmad-extension/skills/mil498-srs/prompts/01-discover-project-artifacts.md +2 -2
  24. package/lib/bmad-extension/skills/mil498-ssdd/prompts/01-discover-project-artifacts.md +2 -2
  25. package/lib/bmad-extension/skills/mil498-sss/prompts/01-discover-project-artifacts.md +2 -2
  26. package/lib/bmad-extension/skills/mil498-std/prompts/01-discover-project-artifacts.md +2 -2
  27. package/lib/bmad-extension/skills/modify-sprint/SKILL.md +112 -0
  28. package/lib/bmad-extension/skills/prioritize-backlog/SKILL.md +163 -1
  29. package/lib/bmad-extension/skills/remove-from-sprint/SKILL.md +112 -0
  30. package/lib/bmad-extension/skills/sprint-status-view/SKILL.md +112 -0
  31. package/lib/bmad-extension/skills/sqa-audit/SKILL.md +6 -2
  32. package/lib/bmad-extension/skills/sqa-ieee12207/SKILL.md +2 -2
  33. package/lib/bmad-extension/skills/sqa-requirements-quality/SKILL.md +1 -1
  34. package/lib/bmad-extension/workflows/add-sprint/workflow.md +112 -0
  35. package/lib/bmad-extension/workflows/add-to-sprint/workflow.md +112 -0
  36. package/lib/bmad-extension/workflows/modify-sprint/workflow.md +112 -0
  37. package/lib/bmad-extension/workflows/remove-from-sprint/workflow.md +112 -0
  38. package/lib/bmad-extension/workflows/sprint-status-view/workflow.md +112 -0
  39. package/lib/bmad-extension-plugin/.claude-plugin/marketplace.json +1 -1
  40. package/lib/bmad-extension-plugin/skills/add-sprint/SKILL.md +112 -0
  41. package/lib/bmad-extension-plugin/skills/add-to-sprint/SKILL.md +112 -0
  42. package/lib/bmad-extension-plugin/skills/bmad-dev-epic/SKILL.md +112 -0
  43. package/lib/bmad-extension-plugin/skills/bmad-dev-story/workflow.md +112 -0
  44. package/lib/bmad-extension-plugin/skills/bmad-knowledge/SKILL.md +112 -0
  45. package/lib/bmad-extension-plugin/skills/bmad-sprint-planning/workflow.md +161 -0
  46. package/lib/bmad-extension-plugin/skills/bmad-sprint-status/workflow.md +112 -0
  47. package/lib/bmad-extension-plugin/skills/cleanup-done/SKILL.md +112 -0
  48. package/lib/bmad-extension-plugin/skills/close-sprint/SKILL.md +112 -0
  49. package/lib/bmad-extension-plugin/skills/generate-backlog/SKILL.md +163 -1
  50. package/lib/bmad-extension-plugin/skills/ma-agent-cyber/SKILL.md +2 -2
  51. package/lib/bmad-extension-plugin/skills/ma-agent-devops/SKILL.md +2 -2
  52. package/lib/bmad-extension-plugin/skills/ma-agent-sqa/SKILL.md +2 -2
  53. package/lib/bmad-extension-plugin/skills/ma-agent-sre/SKILL.md +2 -2
  54. package/lib/bmad-extension-plugin/skills/mil498-ocd/prompts/01-discover-project-artifacts.md +2 -2
  55. package/lib/bmad-extension-plugin/skills/mil498-sdd/prompts/01-discover-project-artifacts.md +2 -2
  56. package/lib/bmad-extension-plugin/skills/mil498-sdp/prompts/01-discover-project-artifacts.md +2 -2
  57. package/lib/bmad-extension-plugin/skills/mil498-srs/prompts/01-discover-project-artifacts.md +2 -2
  58. package/lib/bmad-extension-plugin/skills/mil498-ssdd/prompts/01-discover-project-artifacts.md +2 -2
  59. package/lib/bmad-extension-plugin/skills/mil498-sss/prompts/01-discover-project-artifacts.md +2 -2
  60. package/lib/bmad-extension-plugin/skills/mil498-std/prompts/01-discover-project-artifacts.md +2 -2
  61. package/lib/bmad-extension-plugin/skills/modify-sprint/SKILL.md +112 -0
  62. package/lib/bmad-extension-plugin/skills/prioritize-backlog/SKILL.md +163 -1
  63. package/lib/bmad-extension-plugin/skills/remove-from-sprint/SKILL.md +112 -0
  64. package/lib/bmad-extension-plugin/skills/sprint-status-view/SKILL.md +112 -0
  65. package/lib/bmad-extension-plugin/skills/sqa-audit/SKILL.md +6 -2
  66. package/lib/bmad-extension-plugin/skills/sqa-ieee12207/SKILL.md +2 -2
  67. package/lib/bmad-extension-plugin/skills/sqa-requirements-quality/SKILL.md +1 -1
  68. package/lib/bmad.js +283 -15
  69. package/lib/bound-projects.js +265 -0
  70. package/lib/confluence-page-tree.js +925 -0
  71. package/lib/confluence-publish-hook.js +1781 -0
  72. package/lib/custom-marketplace.js +28 -18
  73. package/lib/installer.js +489 -20
  74. package/lib/store-backends.js +64 -0
  75. package/lib/templates/instruction-block-git.template.md +25 -25
  76. package/lib/templates/instruction-block-onprem.template.md +86 -86
  77. package/lib/templates/instruction-block-universal.template.md +29 -29
  78. package/package.json +2 -2
  79. package/skills/add-sprint/SKILL.md +112 -0
  80. package/skills/add-to-sprint/SKILL.md +112 -0
  81. package/skills/bmad-knowledge/SKILL.md +112 -0
  82. package/skills/bmad-sprint-planning/SKILL.md +176 -3
  83. package/skills/bmad-sprint-status/SKILL.md +112 -0
  84. package/skills/cleanup-done/SKILL.md +112 -0
  85. package/skills/close-sprint/SKILL.md +112 -0
  86. package/skills/generate-backlog/SKILL.md +164 -2
  87. package/skills/modify-sprint/SKILL.md +112 -0
  88. package/skills/prioritize-backlog/SKILL.md +163 -1
  89. package/skills/remove-from-sprint/SKILL.md +112 -0
  90. package/skills/sprint-status-view/SKILL.md +112 -0
  91. package/skills/story-status-lookup/SKILL.md +112 -0
@@ -213,6 +213,17 @@ The registry has stayed **YAML** throughout — only its path has moved. (`tools
213
213
 
214
214
  > **Maintenance note:** because the registry path has now moved twice (YAML→YAML→YAML at three locations), the cache builder fails loud — if the resolved `type: bmad-org` set is smaller than the known bundled set, or no module source is found, it exits non-zero rather than producing a thin/empty cache, so a future BMAD registry move surfaces at build time, not as a broken air-gapped install.
215
215
 
216
+ #### 3.6.1 Air-gap posture with a Confluence-backed knowledge store (NFR80)
217
+
218
+ F26 lets each of the two knowledge stores choose a `backend` of `file-system` or `confluence`, which changes what "offline" means for a project — so the posture is stated exactly here rather than left to be inferred from the paragraph above:
219
+
220
+ - **Installation and skill loading require no network at all**, unchanged. Neither reads a knowledge store: the installer writes the binding and generates `_bmad/bmm/config.yaml` from it, and both are local files.
221
+ - **Every `file-system`-backed workflow requires no network at all**, unchanged. The backend gate embedded in the skills that resolve knowledge-store artifacts reads `{system,software}_requirements_backend` out of the generated `config.yaml` — a local read — and on `file-system` proceeds exactly as it did before, consulting nothing.
222
+ - **A `confluence`-backed store additionally requires its Confluence host to be reachable from the machine running the agent.** That is not the same as requiring internet egress: an intra-perimeter Confluence Server or Data Center on an air-gapped network satisfies it. It does require a Confluence-capable MCP server configured in the agent; with none, the affected skill halts loudly and does not degrade to local files (FR262). There is no local mirror, no cache and therefore no sync (P5-9.6).
223
+ - **A project that must operate with no network whatsoever keeps both stores `file-system`-backed.** That is the default, and it is what `--yes` always selects.
224
+
225
+ Proven rather than asserted: `test/project-layout-confluence-routing.test.js` runs the whole binding-and-generation flow in a child process whose `dns`, `net`, `http`, `https`, `tls` and `fetch` primitives are poisoned, verifies that the block actually armed, and requires the flow to complete having attempted no egress at all.
226
+
216
227
  ## 4. Data Flow
217
228
 
218
229
  ### 4.1 Install Flow
@@ -294,11 +294,20 @@ All 11 BMAD agents have enforcement via `critical_actions` in `.customize.yaml`
294
294
  **Mechanism:**
295
295
  ```yaml
296
296
  critical_actions:
297
- 1: "Read the skills MANIFEST at {project-root}/_bmad/skills/MANIFEST.yaml"
297
+ 1: "Read the skills MANIFEST at {project-root}/_bmad/skills/<agent-slug>/MANIFEST.yaml if it exists"
298
298
  2: "For each skill marked always_load: true, read the skill file completely"
299
299
  3: "Follow all skill directives during this session"
300
300
  ```
301
301
 
302
+ > **Note (2026-09-12):** this snippet previously showed a bare
303
+ > `{project-root}/_bmad/skills/MANIFEST.yaml`. That path is not what Story 8.3
304
+ > specified — the manifest is per-agent-host, at
305
+ > `{project-root}/_bmad/skills/<agent-slug>/MANIFEST.yaml`, where `<agent-slug>`
306
+ > matches the `skillsDir` in `lib/agents.js` (`sre`, `devops`, `cyber`, `sqa`).
307
+ > The bare form leaked from here into `ma-agent-sqa`'s Critical Actions block and
308
+ > survived there until it was corrected. Keep the slug segment in any copy of this
309
+ > snippet.
310
+
302
311
  **Deployment:** Extension module at `_bmad/extensions/ma-agents-skills/` deployed during BMAD customization pipeline (Stage 3 in `bmad.js`).
303
312
 
304
313
  **Coverage:**
package/lib/agents.js CHANGED
@@ -242,8 +242,27 @@ const agents = [
242
242
  version: '1.0.0',
243
243
  category: 'ide',
244
244
  description: 'Google Deepmind Antigravity Agent',
245
- skillsDir: '.antigravity/skills',
246
- getProjectPath: () => path.join(process.cwd(), '.antigravity', 'skills'),
245
+ // Bug `antigravity-path-divergence`: this used to read `.antigravity/skills`,
246
+ // a path ma-agents invented from its own per-tool naming convention. Upstream
247
+ // bmad-method maps the SAME tool to `.agent/skills`
248
+ // (node_modules/bmad-method/tools/installer/ide/platform-codes.yaml, platform
249
+ // `antigravity`, in a file whose header records the paths as "verified against
250
+ // each tool's primary docs"; its global counterpart `~/.gemini/antigravity/skills`
251
+ // shows the entry was researched rather than guessed). Because ma-agents
252
+ // delegates part of the install to that installer, a single run produced TWO
253
+ // trees — and `.agent/skills`, the one Antigravity actually reads, was named by
254
+ // neither this registry nor lib/bmad.js, so every ma-agents post-deploy patch
255
+ // (the on-prem phase prefix among them) silently skipped it. Measured in a real
256
+ // on-prem project: .claude/.agents/.cline each carried the prefix on 6 personas,
257
+ // .agent/skills on 0.
258
+ // Upstream is the authority on which directory a tool reads, so we align —
259
+ // exactly as copilot/roo-code/kilocode were aligned to `.agents/skills` when
260
+ // bmad-method 6.5.0 moved them (see the copilot entry above). This is a
261
+ // per-tool decision on per-tool evidence: the other three divergences in the
262
+ // table are recorded in PLATFORM_PATH_DIVERGENCES below, deliberately unchanged.
263
+ // Existing installs migrate via OBSOLETE_TOOL_SKILL_DIRS in lib/installer.js.
264
+ skillsDir: '.agent/skills',
265
+ getProjectPath: () => path.join(process.cwd(), '.agent', 'skills'),
247
266
  getGlobalPath: () => {
248
267
  const platform = os.platform();
249
268
  if (platform === 'win32') {
@@ -356,6 +375,64 @@ const agents = [
356
375
  // Add them here in a dedicated future story if/when demand appears.
357
376
  ];
358
377
 
378
+ /**
379
+ * Declared, deliberate divergences between this registry's project `skillsDir`
380
+ * and bmad-method's `installer.target_dir` for the SAME tool
381
+ * (`node_modules/bmad-method/tools/installer/ide/platform-codes.yaml`).
382
+ * Keyed by bmad platform code (see `getBmadPlatformCode`).
383
+ *
384
+ * Why this exists (bug `antigravity-path-divergence`): the two tables had drifted
385
+ * apart for four tools and nothing compared them outside the five flagships, so a
386
+ * whole second skill tree appeared in real projects with no owner and no patcher.
387
+ * `test/routing-parity.test.js` (section 5) now enumerates EVERY tool in BOTH
388
+ * tables and fails on any divergence that is not declared here — and on any
389
+ * declaration whose recorded paths have gone stale, so an entry cannot rot into
390
+ * cover for a different divergence.
391
+ *
392
+ * Scope note — PROJECT paths only. `getGlobalPath()` follows a per-OS
393
+ * application-data convention that differs from upstream's `global_target_dir`
394
+ * for EVERY tool, claude-code included (`%APPDATA%/Claude/skills` vs
395
+ * `~/.claude/skills`). That is a whole-table convention difference, not a
396
+ * per-tool divergence, and is deliberately out of scope here.
397
+ *
398
+ * To add an entry you must record BOTH live paths and a real reason. To remove
399
+ * one, align the paths — the test fails on a declaration whose paths now agree.
400
+ */
401
+ const PLATFORM_PATH_DIVERGENCES = Object.freeze({
402
+ gemini: Object.freeze({
403
+ maSkillsDir: '.gemini/skills',
404
+ bmadTargetDir: '.agents/skills',
405
+ reason:
406
+ "Upstream's `gemini` platform is the Gemini CLI, which reads the shared " +
407
+ '`.agents/skills` tree; this registry entry targets Google Gemini Code Assist ' +
408
+ '(the IDE extension — note its instruction file `.gemini/gemini.md`), a ' +
409
+ 'different product sharing the same code. Contained: upstream writes ' +
410
+ '`.agents/skills`, which is already an IDE skillsDir in this registry, already ' +
411
+ 'a seed in lib/bmad.js TOOL_SKILL_DIRS, and already reconciled by bug 27.16 — ' +
412
+ 'so no tree goes unpatched. Realigning is a compatibility call for its own story.'
413
+ }),
414
+ cursor: Object.freeze({
415
+ maSkillsDir: '.cursor/skills',
416
+ bmadTargetDir: '.agents/skills',
417
+ reason:
418
+ 'Cursor reads its own `.cursor/` workspace directory (this registry also stamps ' +
419
+ '`.cursor/cursor.md` there); upstream routes it to the shared `.agents/skills` ' +
420
+ 'cross-tool tree instead. Contained for the same reason as `gemini`: upstream ' +
421
+ "'s target is already a known, patched and reconciled tree. Kept deliberately " +
422
+ 'so this bug fix changes the write path of exactly one tool, on that tool’s evidence.'
423
+ }),
424
+ opencode: Object.freeze({
425
+ maSkillsDir: '.opencode/skills',
426
+ bmadTargetDir: '.agents/skills',
427
+ reason:
428
+ 'OpenCode is wired here through `opencode.json::instructions[]` plus AGENTS.md ' +
429
+ '(Story 21.4) against its own `.opencode/` tree; upstream routes it to the shared ' +
430
+ '`.agents/skills`. Contained for the same reason as `gemini` and `cursor`: ' +
431
+ "upstream's target is already a known, patched and reconciled tree. Realignment " +
432
+ 'would also have to move the instruction wiring, so it belongs in its own story.'
433
+ })
434
+ });
435
+
359
436
  function getAgent(agentId) {
360
437
  return agents.find(a => a.id === agentId);
361
438
  }
@@ -383,5 +460,6 @@ module.exports = {
383
460
  getAgent,
384
461
  getAllAgents,
385
462
  getAgentsByCategory,
386
- getBmadPlatformCode
463
+ getBmadPlatformCode,
464
+ PLATFORM_PATH_DIVERGENCES
387
465
  };
@@ -11,6 +11,118 @@ triggers:
11
11
 
12
12
  Guided workflow to create a new sprint entry in the `sprints` section of `sprint-status.yaml` with capacity limits, optional ISO dates, and auto-incremented sprint ID.
13
13
 
14
+ ## Knowledge Store Binding Preflight
15
+
16
+ **Gateway check — run this before anything else in this skill.** Before any read, any write, any user prompt, and before any other routing or configuration step below. No other step of this skill executes until this preflight returns PROCEED.
17
+
18
+ `_bmad/bmm/config.yaml` is a GENERATED file. `ma-agents` derives it from the committed binding in `_bmad-output/project-layout.yaml` on install and on `ma-agents bind`, and records which binding it was generated from in its `project_layout_stamp:` field. If the binding has moved since — the usual cause is a `git pull` that relocated a knowledge store — every path this skill would resolve from `config.yaml` points at where the stores used to be. This preflight refuses to let that happen silently.
19
+
20
+ **This preflight is read-only.** Whatever it finds, do NOT regenerate, repair, edit or create `config.yaml`, `_bmad-output/project-layout.yaml`, or any other file, and do not offer to. Regenerating the config belongs to the installer and to `ma-agents bind`, never to a skill.
21
+
22
+ **Compare content, never timestamps.** Never decide staleness from file modification times: mtime ordering is not preserved by `git pull`, `git checkout` or a fresh clone, so it both misses real drift and reports drift on a clean checkout.
23
+
24
+ Work through the steps below IN ORDER and stop at the first branch that matches. The order is load-bearing — `_bmad-output/project-layout.yaml` is the fact, and the stamp only ever corroborates it.
25
+
26
+ ### Step P1 — Read `_bmad-output/project-layout.yaml`
27
+
28
+ - **Missing, or present but blank / comments-only** → go to **Step P2**.
29
+ - **Present but not cleanly readable** — it is not valid YAML, it carries unresolved merge-conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`), it yields no readable fields, its `schema_version:` is greater than `2`, its `schema_version:` is present but is not a number, it names a `backend:` or a `mode:` this build does not recognise, or any store record in it cannot be read → **HALT** and report:
30
+
31
+ > **ma-agents — stopping: the committed knowledge binding cannot be read.**
32
+ > `_bmad-output/project-layout.yaml` exists but this build cannot use it: {say exactly what is wrong}.
33
+ > Every knowledge-store path in `_bmad/bmm/config.yaml` is derived from that file, so nothing this skill would resolve can be trusted. Fix the binding by hand — if this is a merge conflict, resolve it; the file is committed, so conflicts are ordinary — then run `ma-agents bind` to regenerate `_bmad/bmm/config.yaml` from it.
34
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
35
+
36
+ That list of faults is illustrative, not exhaustive. **Default-deny:** the only two ways out of this step without halting are "absent, or blank / comments-only" (→ Step P2) and "resolves cleanly" (→ Step P3). Anything else takes this HALT, including a fault not named above. Do not fall back to defaults and do not treat this as "no binding". A binding that cannot be read is a different fact from a project that has none.
37
+ - **Present and resolves cleanly** → go to **Step P3**.
38
+
39
+ ### Step P2 — There is no committed binding
40
+
41
+ No `_bmad-output/project-layout.yaml`. That is the pre-F26 shape and by itself it is NOT drift — but corroborate it instead of trusting it, because "a binding was never written here" and "the binding was deleted, ignored, or not pulled" look identical from the missing file alone. Read `_bmad/bmm/config.yaml`:
42
+
43
+ - **`config.yaml` does not exist either** → **HALT** and report:
44
+
45
+ > **ma-agents — stopping: this project is not configured.**
46
+ > Neither `_bmad-output/project-layout.yaml` nor `_bmad/bmm/config.yaml` exists, so nothing states where this project's knowledge stores live. Run `npx ma-agents install` to configure the project, or `ma-agents bind` if ma-agents is already installed and only the binding is missing.
47
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
48
+
49
+ - **`config.yaml` exists and carries, at column 0, any of `project_layout_stamp:`, `system_requirements_path:` or `software_requirements_path:`** (a commented-out or indented one does not count — it is not the field) → **HALT** and report:
50
+
51
+ > **ma-agents — stopping: the committed knowledge binding is missing.**
52
+ > `_bmad/bmm/config.yaml` carries {name the fields you found}, which only an F26 generation writes — so this project HAS been bound — but `_bmad-output/project-layout.yaml` is not here. The binding was deleted, is untracked or ignored, or has not been pulled. This is not a pre-F26 project, so proceeding would mean trusting generated paths against a binding that no longer exists.
53
+ > Restore the file (for example `git checkout -- _bmad-output/project-layout.yaml`), or run `ma-agents bind` to write a fresh one.
54
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
55
+
56
+ Those three fields are the discriminator and they are reliable: a genuine pre-F26 `config.yaml` carries none of them. Do not skip this check. An absent stamp on its own is never evidence that a project predates the stamp.
57
+
58
+ - **`config.yaml` exists and carries none of those three fields at column 0** → this is genuinely a pre-F26 project. **PROCEED** with the rest of this skill exactly as it behaved before F26, and say nothing at all about the binding — no warning, no prompt, no offer to bind. Nothing has moved, so there is nothing to re-bind. The existence of `config.yaml` is NOT on its own a reason to take this branch: all three fields must be absent.
59
+
60
+ ### Step P3 — The binding is readable: check what `config.yaml` was generated from
61
+
62
+ Read `_bmad/bmm/config.yaml`.
63
+
64
+ - **`config.yaml` does not exist** → **HALT** and report:
65
+
66
+ > **ma-agents — stopping: the generated config is missing.**
67
+ > `_bmad-output/project-layout.yaml` commits this project's knowledge binding, but `_bmad/bmm/config.yaml` — the file this skill resolves its paths from — has never been generated from it. Run `npx ma-agents install` to install into this project, or `ma-agents bind` if ma-agents is already installed, to generate `_bmad/bmm/config.yaml` from the committed binding.
68
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
69
+
70
+ - **No `project_layout_stamp:` line at column 0 of `config.yaml`** (a commented-out or indented one does not count — it is not the field) → **HALT** and report:
71
+
72
+ > **ma-agents — stopping: `_bmad/bmm/config.yaml` does not record which binding it came from.**
73
+ > `_bmad-output/project-layout.yaml` exists, but `config.yaml` carries no `project_layout_stamp:`, so there is no evidence its paths were generated from the committed binding. Run `ma-agents bind` to regenerate it.
74
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
75
+
76
+ - **`project_layout_stamp:` is present but its value is not `sha256:` followed by exactly 64 lowercase hexadecimal characters** — truncated, uppercased, a different algorithm, unbalanced quotes, or trailing content after the digest → **HALT** and report:
77
+
78
+ > **ma-agents — stopping: `_bmad/bmm/config.yaml` claims to have been generated and the claim cannot be read.**
79
+ > Its `project_layout_stamp:` is {quote the value you found}, which is not a stamp ma-agents wrote (expected `sha256:` followed by 64 lowercase hex characters) — merge damage, a hand-edit, or a truncated write. Treat this config as unverified; it is NOT a config that predates the stamp. Run `ma-agents bind` to regenerate it from `_bmad-output/project-layout.yaml`.
80
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
81
+
82
+ - **`project_layout_stamp:` is well-formed** → go to **Step P4**.
83
+
84
+ ### Step P4 — Compare the committed binding against the generated fields
85
+
86
+ Derive, from `_bmad-output/project-layout.yaml`, what each generated field in `config.yaml` WOULD be if it were regenerated right now, and compare it to what `config.yaml` actually carries. This is a content comparison of the two files — never a timestamp comparison.
87
+
88
+ A schema-2 binding holds three records: `system_requirements_store:`, `software_requirements_store:` and `sprint_management:`. A legacy schema-1 binding holds a single `knowledgebase:` record, which stands for BOTH requirement stores.
89
+
90
+ The table below IS the whole comparison: every field named in its right-hand column is compared, and no field outside that column is. Read each field only from a line at column 0, and compare VALUES rather than bytes — the generator quotes its scalars (`sprint_backend: "jira"`, `knowledgebase_path: "."`), so strip one balanced pair of surrounding quotes from each side first.
91
+
92
+ | When | Binding record it is derived from | Field(s) it generates in `config.yaml` |
93
+ | --- | --- | --- |
94
+ | always | `software_requirements_store` → its `path:` (a store whose `backend:` is not `file-system` has no `path:` at all, and so generates `.`); the artifacts field is that same path, except that a path of `.` gives `{project-root}/_bmad-output/planning-artifacts`; its resolved `backend:` (an absent or empty `backend:` resolves to `file-system`), and, when that backend is `confluence`, its `space_key:` — the generated space-key field is empty for every other backend, so a store re-bound away from Confluence does not keep the old space | `software_requirements_path:`, `knowledgebase_path:`, `planning_artifacts:`, `software_requirements_backend:`, `software_requirements_space_key:` |
95
+ | always | `system_requirements_store` → its `path:` (a store whose `backend:` is not `file-system` has no `path:` at all, and so generates `.`); its resolved `backend:` (an absent or empty `backend:` resolves to `file-system`), and, when that backend is `confluence`, its `space_key:` — the generated space-key field is empty for every other backend, so a store re-bound away from Confluence does not keep the old space | `system_requirements_path:`, `system_requirements_backend:`, `system_requirements_space_key:` |
96
+ | `sprint_management` has `mode: jira` | `sprint_management` → its `jira_url:` and `jira_project_key:`; the backend field is the literal `jira`; the artifacts field is always `{project-root}/_bmad-output/implementation-artifacts`, because a Jira sprint store has no path | `sprint_backend:`, `jira_url:`, `jira_project_key:`, `implementation_artifacts:` |
97
+ | `sprint_management` has any other mode | `sprint_management` → its `path:`; the backend field is the literal `file-system`; the artifacts field is that same path, except that a path of `.` gives `{project-root}/_bmad-output/implementation-artifacts` | `sprint_backend:`, `sprint_management_path:`, `implementation_artifacts:` |
98
+
99
+ **When the binding says `mode: jira`, `sprint_management_path:` is not compared at all.** The generator writes no `sprint_management_path:` in that mode — and it never DELETES a field, it only updates or appends one. So a project that was bound to the file system and later re-bound to Jira still carries its old `sprint_management_path:` line in a freshly generated `config.yaml`. That leftover line is not drift, and comparing it would halt every Jira project that was ever file-system-bound.
100
+
101
+ Not every field in that table is a path. Where a store's `backend:` or `space_key:` is what disagrees, the comparison is the same and so is the halt — read the message template's `{path in …}` placeholders as "the value that disagrees", and name the two values you actually compared.
102
+
103
+ **Any disagreement is drift** → **HALT** and report, naming every store that moved and both of its paths:
104
+
105
+ > **ma-agents — stopping: `_bmad/bmm/config.yaml` is stale.**
106
+ > The committed binding in `_bmad-output/project-layout.yaml` no longer matches the state `_bmad/bmm/config.yaml` was generated from:
107
+ > - {store name}: was `{path in config.yaml}`, is now `{path in project-layout.yaml}`
108
+ > (one line per store that moved)
109
+ > Running this skill now would read and write artifacts where those stores used to be. Run `ma-agents bind` to regenerate `_bmad/bmm/config.yaml` from the committed binding, then run this skill again.
110
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
111
+
112
+ **If everything in the table agrees** → go to **Step P5**.
113
+
114
+ The stamp covers binding state that `config.yaml` does not carry — a Confluence `root_page:`, a `gitUrl:` — so the table alone cannot see every kind of move. If, and only if, a command shell is available to you and `ma-agents` resolves as a local dependency of this project, you may confirm the comparison exactly, without parsing the stamp yourself:
115
+
116
+ ```bash
117
+ node -e "const c=require('ma-agents/bin/cli.js'),f=require('fs'),p=process.cwd();const t=c.resolveProjectTopology(p);if(!t.ok){console.log('binding:'+t.reason)}else{const s=c.readProjectLayoutStamp(f.readFileSync('_bmad/bmm/config.yaml','utf8'));console.log(s.ok?(s.value===c.computeProjectLayoutStamp(t.topology,p)?'MATCH':'DRIFT'):'stamp:'+s.reason)}"
118
+ ```
119
+
120
+ `DRIFT` is authoritative: HALT with the stale-config message above even when the table found nothing, and say that the binding changed in a way `config.yaml` does not record. Never treat a failure to run this command as a result — if it does not run, the table is the check, and you say nothing about the stamp.
121
+
122
+ ### Step P5 — PROCEED
123
+
124
+ The generated config matches the committed binding. Say NOTHING about the binding, the stamp or this preflight — a matched project gets no message — and continue with the rest of this skill.
125
+
14
126
  ## Backend Routing
15
127
 
16
128
  Before executing any file-system operations, determine and route to the correct backend:
@@ -13,6 +13,118 @@ Guided workflow to move backlog items to a sprint in the unified `sprint-status.
13
13
 
14
14
  **Movement semantics:** Each item exists in exactly one location — either the `backlog` array OR a sprint's `items` array, never both. This skill atomically removes items from `backlog` and appends them to the target sprint's `items` array in a single file write.
15
15
 
16
+ ## Knowledge Store Binding Preflight
17
+
18
+ **Gateway check — run this before anything else in this skill.** Before any read, any write, any user prompt, and before any other routing or configuration step below. No other step of this skill executes until this preflight returns PROCEED.
19
+
20
+ `_bmad/bmm/config.yaml` is a GENERATED file. `ma-agents` derives it from the committed binding in `_bmad-output/project-layout.yaml` on install and on `ma-agents bind`, and records which binding it was generated from in its `project_layout_stamp:` field. If the binding has moved since — the usual cause is a `git pull` that relocated a knowledge store — every path this skill would resolve from `config.yaml` points at where the stores used to be. This preflight refuses to let that happen silently.
21
+
22
+ **This preflight is read-only.** Whatever it finds, do NOT regenerate, repair, edit or create `config.yaml`, `_bmad-output/project-layout.yaml`, or any other file, and do not offer to. Regenerating the config belongs to the installer and to `ma-agents bind`, never to a skill.
23
+
24
+ **Compare content, never timestamps.** Never decide staleness from file modification times: mtime ordering is not preserved by `git pull`, `git checkout` or a fresh clone, so it both misses real drift and reports drift on a clean checkout.
25
+
26
+ Work through the steps below IN ORDER and stop at the first branch that matches. The order is load-bearing — `_bmad-output/project-layout.yaml` is the fact, and the stamp only ever corroborates it.
27
+
28
+ ### Step P1 — Read `_bmad-output/project-layout.yaml`
29
+
30
+ - **Missing, or present but blank / comments-only** → go to **Step P2**.
31
+ - **Present but not cleanly readable** — it is not valid YAML, it carries unresolved merge-conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`), it yields no readable fields, its `schema_version:` is greater than `2`, its `schema_version:` is present but is not a number, it names a `backend:` or a `mode:` this build does not recognise, or any store record in it cannot be read → **HALT** and report:
32
+
33
+ > **ma-agents — stopping: the committed knowledge binding cannot be read.**
34
+ > `_bmad-output/project-layout.yaml` exists but this build cannot use it: {say exactly what is wrong}.
35
+ > Every knowledge-store path in `_bmad/bmm/config.yaml` is derived from that file, so nothing this skill would resolve can be trusted. Fix the binding by hand — if this is a merge conflict, resolve it; the file is committed, so conflicts are ordinary — then run `ma-agents bind` to regenerate `_bmad/bmm/config.yaml` from it.
36
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
37
+
38
+ That list of faults is illustrative, not exhaustive. **Default-deny:** the only two ways out of this step without halting are "absent, or blank / comments-only" (→ Step P2) and "resolves cleanly" (→ Step P3). Anything else takes this HALT, including a fault not named above. Do not fall back to defaults and do not treat this as "no binding". A binding that cannot be read is a different fact from a project that has none.
39
+ - **Present and resolves cleanly** → go to **Step P3**.
40
+
41
+ ### Step P2 — There is no committed binding
42
+
43
+ No `_bmad-output/project-layout.yaml`. That is the pre-F26 shape and by itself it is NOT drift — but corroborate it instead of trusting it, because "a binding was never written here" and "the binding was deleted, ignored, or not pulled" look identical from the missing file alone. Read `_bmad/bmm/config.yaml`:
44
+
45
+ - **`config.yaml` does not exist either** → **HALT** and report:
46
+
47
+ > **ma-agents — stopping: this project is not configured.**
48
+ > Neither `_bmad-output/project-layout.yaml` nor `_bmad/bmm/config.yaml` exists, so nothing states where this project's knowledge stores live. Run `npx ma-agents install` to configure the project, or `ma-agents bind` if ma-agents is already installed and only the binding is missing.
49
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
50
+
51
+ - **`config.yaml` exists and carries, at column 0, any of `project_layout_stamp:`, `system_requirements_path:` or `software_requirements_path:`** (a commented-out or indented one does not count — it is not the field) → **HALT** and report:
52
+
53
+ > **ma-agents — stopping: the committed knowledge binding is missing.**
54
+ > `_bmad/bmm/config.yaml` carries {name the fields you found}, which only an F26 generation writes — so this project HAS been bound — but `_bmad-output/project-layout.yaml` is not here. The binding was deleted, is untracked or ignored, or has not been pulled. This is not a pre-F26 project, so proceeding would mean trusting generated paths against a binding that no longer exists.
55
+ > Restore the file (for example `git checkout -- _bmad-output/project-layout.yaml`), or run `ma-agents bind` to write a fresh one.
56
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
57
+
58
+ Those three fields are the discriminator and they are reliable: a genuine pre-F26 `config.yaml` carries none of them. Do not skip this check. An absent stamp on its own is never evidence that a project predates the stamp.
59
+
60
+ - **`config.yaml` exists and carries none of those three fields at column 0** → this is genuinely a pre-F26 project. **PROCEED** with the rest of this skill exactly as it behaved before F26, and say nothing at all about the binding — no warning, no prompt, no offer to bind. Nothing has moved, so there is nothing to re-bind. The existence of `config.yaml` is NOT on its own a reason to take this branch: all three fields must be absent.
61
+
62
+ ### Step P3 — The binding is readable: check what `config.yaml` was generated from
63
+
64
+ Read `_bmad/bmm/config.yaml`.
65
+
66
+ - **`config.yaml` does not exist** → **HALT** and report:
67
+
68
+ > **ma-agents — stopping: the generated config is missing.**
69
+ > `_bmad-output/project-layout.yaml` commits this project's knowledge binding, but `_bmad/bmm/config.yaml` — the file this skill resolves its paths from — has never been generated from it. Run `npx ma-agents install` to install into this project, or `ma-agents bind` if ma-agents is already installed, to generate `_bmad/bmm/config.yaml` from the committed binding.
70
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
71
+
72
+ - **No `project_layout_stamp:` line at column 0 of `config.yaml`** (a commented-out or indented one does not count — it is not the field) → **HALT** and report:
73
+
74
+ > **ma-agents — stopping: `_bmad/bmm/config.yaml` does not record which binding it came from.**
75
+ > `_bmad-output/project-layout.yaml` exists, but `config.yaml` carries no `project_layout_stamp:`, so there is no evidence its paths were generated from the committed binding. Run `ma-agents bind` to regenerate it.
76
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
77
+
78
+ - **`project_layout_stamp:` is present but its value is not `sha256:` followed by exactly 64 lowercase hexadecimal characters** — truncated, uppercased, a different algorithm, unbalanced quotes, or trailing content after the digest → **HALT** and report:
79
+
80
+ > **ma-agents — stopping: `_bmad/bmm/config.yaml` claims to have been generated and the claim cannot be read.**
81
+ > Its `project_layout_stamp:` is {quote the value you found}, which is not a stamp ma-agents wrote (expected `sha256:` followed by 64 lowercase hex characters) — merge damage, a hand-edit, or a truncated write. Treat this config as unverified; it is NOT a config that predates the stamp. Run `ma-agents bind` to regenerate it from `_bmad-output/project-layout.yaml`.
82
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
83
+
84
+ - **`project_layout_stamp:` is well-formed** → go to **Step P4**.
85
+
86
+ ### Step P4 — Compare the committed binding against the generated fields
87
+
88
+ Derive, from `_bmad-output/project-layout.yaml`, what each generated field in `config.yaml` WOULD be if it were regenerated right now, and compare it to what `config.yaml` actually carries. This is a content comparison of the two files — never a timestamp comparison.
89
+
90
+ A schema-2 binding holds three records: `system_requirements_store:`, `software_requirements_store:` and `sprint_management:`. A legacy schema-1 binding holds a single `knowledgebase:` record, which stands for BOTH requirement stores.
91
+
92
+ The table below IS the whole comparison: every field named in its right-hand column is compared, and no field outside that column is. Read each field only from a line at column 0, and compare VALUES rather than bytes — the generator quotes its scalars (`sprint_backend: "jira"`, `knowledgebase_path: "."`), so strip one balanced pair of surrounding quotes from each side first.
93
+
94
+ | When | Binding record it is derived from | Field(s) it generates in `config.yaml` |
95
+ | --- | --- | --- |
96
+ | always | `software_requirements_store` → its `path:` (a store whose `backend:` is not `file-system` has no `path:` at all, and so generates `.`); the artifacts field is that same path, except that a path of `.` gives `{project-root}/_bmad-output/planning-artifacts`; its resolved `backend:` (an absent or empty `backend:` resolves to `file-system`), and, when that backend is `confluence`, its `space_key:` — the generated space-key field is empty for every other backend, so a store re-bound away from Confluence does not keep the old space | `software_requirements_path:`, `knowledgebase_path:`, `planning_artifacts:`, `software_requirements_backend:`, `software_requirements_space_key:` |
97
+ | always | `system_requirements_store` → its `path:` (a store whose `backend:` is not `file-system` has no `path:` at all, and so generates `.`); its resolved `backend:` (an absent or empty `backend:` resolves to `file-system`), and, when that backend is `confluence`, its `space_key:` — the generated space-key field is empty for every other backend, so a store re-bound away from Confluence does not keep the old space | `system_requirements_path:`, `system_requirements_backend:`, `system_requirements_space_key:` |
98
+ | `sprint_management` has `mode: jira` | `sprint_management` → its `jira_url:` and `jira_project_key:`; the backend field is the literal `jira`; the artifacts field is always `{project-root}/_bmad-output/implementation-artifacts`, because a Jira sprint store has no path | `sprint_backend:`, `jira_url:`, `jira_project_key:`, `implementation_artifacts:` |
99
+ | `sprint_management` has any other mode | `sprint_management` → its `path:`; the backend field is the literal `file-system`; the artifacts field is that same path, except that a path of `.` gives `{project-root}/_bmad-output/implementation-artifacts` | `sprint_backend:`, `sprint_management_path:`, `implementation_artifacts:` |
100
+
101
+ **When the binding says `mode: jira`, `sprint_management_path:` is not compared at all.** The generator writes no `sprint_management_path:` in that mode — and it never DELETES a field, it only updates or appends one. So a project that was bound to the file system and later re-bound to Jira still carries its old `sprint_management_path:` line in a freshly generated `config.yaml`. That leftover line is not drift, and comparing it would halt every Jira project that was ever file-system-bound.
102
+
103
+ Not every field in that table is a path. Where a store's `backend:` or `space_key:` is what disagrees, the comparison is the same and so is the halt — read the message template's `{path in …}` placeholders as "the value that disagrees", and name the two values you actually compared.
104
+
105
+ **Any disagreement is drift** → **HALT** and report, naming every store that moved and both of its paths:
106
+
107
+ > **ma-agents — stopping: `_bmad/bmm/config.yaml` is stale.**
108
+ > The committed binding in `_bmad-output/project-layout.yaml` no longer matches the state `_bmad/bmm/config.yaml` was generated from:
109
+ > - {store name}: was `{path in config.yaml}`, is now `{path in project-layout.yaml}`
110
+ > (one line per store that moved)
111
+ > Running this skill now would read and write artifacts where those stores used to be. Run `ma-agents bind` to regenerate `_bmad/bmm/config.yaml` from the committed binding, then run this skill again.
112
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
113
+
114
+ **If everything in the table agrees** → go to **Step P5**.
115
+
116
+ The stamp covers binding state that `config.yaml` does not carry — a Confluence `root_page:`, a `gitUrl:` — so the table alone cannot see every kind of move. If, and only if, a command shell is available to you and `ma-agents` resolves as a local dependency of this project, you may confirm the comparison exactly, without parsing the stamp yourself:
117
+
118
+ ```bash
119
+ node -e "const c=require('ma-agents/bin/cli.js'),f=require('fs'),p=process.cwd();const t=c.resolveProjectTopology(p);if(!t.ok){console.log('binding:'+t.reason)}else{const s=c.readProjectLayoutStamp(f.readFileSync('_bmad/bmm/config.yaml','utf8'));console.log(s.ok?(s.value===c.computeProjectLayoutStamp(t.topology,p)?'MATCH':'DRIFT'):'stamp:'+s.reason)}"
120
+ ```
121
+
122
+ `DRIFT` is authoritative: HALT with the stale-config message above even when the table found nothing, and say that the binding changed in a way `config.yaml` does not record. Never treat a failure to run this command as a result — if it does not run, the table is the check, and you say nothing about the stamp.
123
+
124
+ ### Step P5 — PROCEED
125
+
126
+ The generated config matches the committed binding. Say NOTHING about the binding, the stamp or this preflight — a matched project gets no message — and continue with the rest of this skill.
127
+
16
128
  ## Backend Routing
17
129
 
18
130
  Before executing any file-system operations, determine and route to the correct backend:
@@ -9,6 +9,118 @@ description: 'Orchestrate a full epic end-to-end with parallel subagents — per
9
9
 
10
10
  **Your Role:** You are the Epic Orchestrator. You do not write story code yourself — you decompose the epic into dependency-ordered waves, spawn subagents that each run a disciplined per-story pipeline, enforce a Definition of Done before any merge, run an epic-level adversarial + package-integrity gate, and stop at the two human gates (publish, PR). You are precise about git isolation, model tiers, and failure handling. No noise, no filler.
11
11
 
12
+ ## Knowledge Store Binding Preflight
13
+
14
+ **Gateway check — run this before anything else in this skill.** Before any read, any write, any user prompt, and before any other routing or configuration step below. No other step of this skill executes until this preflight returns PROCEED.
15
+
16
+ `_bmad/bmm/config.yaml` is a GENERATED file. `ma-agents` derives it from the committed binding in `_bmad-output/project-layout.yaml` on install and on `ma-agents bind`, and records which binding it was generated from in its `project_layout_stamp:` field. If the binding has moved since — the usual cause is a `git pull` that relocated a knowledge store — every path this skill would resolve from `config.yaml` points at where the stores used to be. This preflight refuses to let that happen silently.
17
+
18
+ **This preflight is read-only.** Whatever it finds, do NOT regenerate, repair, edit or create `config.yaml`, `_bmad-output/project-layout.yaml`, or any other file, and do not offer to. Regenerating the config belongs to the installer and to `ma-agents bind`, never to a skill.
19
+
20
+ **Compare content, never timestamps.** Never decide staleness from file modification times: mtime ordering is not preserved by `git pull`, `git checkout` or a fresh clone, so it both misses real drift and reports drift on a clean checkout.
21
+
22
+ Work through the steps below IN ORDER and stop at the first branch that matches. The order is load-bearing — `_bmad-output/project-layout.yaml` is the fact, and the stamp only ever corroborates it.
23
+
24
+ ### Step P1 — Read `_bmad-output/project-layout.yaml`
25
+
26
+ - **Missing, or present but blank / comments-only** → go to **Step P2**.
27
+ - **Present but not cleanly readable** — it is not valid YAML, it carries unresolved merge-conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`), it yields no readable fields, its `schema_version:` is greater than `2`, its `schema_version:` is present but is not a number, it names a `backend:` or a `mode:` this build does not recognise, or any store record in it cannot be read → **HALT** and report:
28
+
29
+ > **ma-agents — stopping: the committed knowledge binding cannot be read.**
30
+ > `_bmad-output/project-layout.yaml` exists but this build cannot use it: {say exactly what is wrong}.
31
+ > Every knowledge-store path in `_bmad/bmm/config.yaml` is derived from that file, so nothing this skill would resolve can be trusted. Fix the binding by hand — if this is a merge conflict, resolve it; the file is committed, so conflicts are ordinary — then run `ma-agents bind` to regenerate `_bmad/bmm/config.yaml` from it.
32
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
33
+
34
+ That list of faults is illustrative, not exhaustive. **Default-deny:** the only two ways out of this step without halting are "absent, or blank / comments-only" (→ Step P2) and "resolves cleanly" (→ Step P3). Anything else takes this HALT, including a fault not named above. Do not fall back to defaults and do not treat this as "no binding". A binding that cannot be read is a different fact from a project that has none.
35
+ - **Present and resolves cleanly** → go to **Step P3**.
36
+
37
+ ### Step P2 — There is no committed binding
38
+
39
+ No `_bmad-output/project-layout.yaml`. That is the pre-F26 shape and by itself it is NOT drift — but corroborate it instead of trusting it, because "a binding was never written here" and "the binding was deleted, ignored, or not pulled" look identical from the missing file alone. Read `_bmad/bmm/config.yaml`:
40
+
41
+ - **`config.yaml` does not exist either** → **HALT** and report:
42
+
43
+ > **ma-agents — stopping: this project is not configured.**
44
+ > Neither `_bmad-output/project-layout.yaml` nor `_bmad/bmm/config.yaml` exists, so nothing states where this project's knowledge stores live. Run `npx ma-agents install` to configure the project, or `ma-agents bind` if ma-agents is already installed and only the binding is missing.
45
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
46
+
47
+ - **`config.yaml` exists and carries, at column 0, any of `project_layout_stamp:`, `system_requirements_path:` or `software_requirements_path:`** (a commented-out or indented one does not count — it is not the field) → **HALT** and report:
48
+
49
+ > **ma-agents — stopping: the committed knowledge binding is missing.**
50
+ > `_bmad/bmm/config.yaml` carries {name the fields you found}, which only an F26 generation writes — so this project HAS been bound — but `_bmad-output/project-layout.yaml` is not here. The binding was deleted, is untracked or ignored, or has not been pulled. This is not a pre-F26 project, so proceeding would mean trusting generated paths against a binding that no longer exists.
51
+ > Restore the file (for example `git checkout -- _bmad-output/project-layout.yaml`), or run `ma-agents bind` to write a fresh one.
52
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
53
+
54
+ Those three fields are the discriminator and they are reliable: a genuine pre-F26 `config.yaml` carries none of them. Do not skip this check. An absent stamp on its own is never evidence that a project predates the stamp.
55
+
56
+ - **`config.yaml` exists and carries none of those three fields at column 0** → this is genuinely a pre-F26 project. **PROCEED** with the rest of this skill exactly as it behaved before F26, and say nothing at all about the binding — no warning, no prompt, no offer to bind. Nothing has moved, so there is nothing to re-bind. The existence of `config.yaml` is NOT on its own a reason to take this branch: all three fields must be absent.
57
+
58
+ ### Step P3 — The binding is readable: check what `config.yaml` was generated from
59
+
60
+ Read `_bmad/bmm/config.yaml`.
61
+
62
+ - **`config.yaml` does not exist** → **HALT** and report:
63
+
64
+ > **ma-agents — stopping: the generated config is missing.**
65
+ > `_bmad-output/project-layout.yaml` commits this project's knowledge binding, but `_bmad/bmm/config.yaml` — the file this skill resolves its paths from — has never been generated from it. Run `npx ma-agents install` to install into this project, or `ma-agents bind` if ma-agents is already installed, to generate `_bmad/bmm/config.yaml` from the committed binding.
66
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
67
+
68
+ - **No `project_layout_stamp:` line at column 0 of `config.yaml`** (a commented-out or indented one does not count — it is not the field) → **HALT** and report:
69
+
70
+ > **ma-agents — stopping: `_bmad/bmm/config.yaml` does not record which binding it came from.**
71
+ > `_bmad-output/project-layout.yaml` exists, but `config.yaml` carries no `project_layout_stamp:`, so there is no evidence its paths were generated from the committed binding. Run `ma-agents bind` to regenerate it.
72
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
73
+
74
+ - **`project_layout_stamp:` is present but its value is not `sha256:` followed by exactly 64 lowercase hexadecimal characters** — truncated, uppercased, a different algorithm, unbalanced quotes, or trailing content after the digest → **HALT** and report:
75
+
76
+ > **ma-agents — stopping: `_bmad/bmm/config.yaml` claims to have been generated and the claim cannot be read.**
77
+ > Its `project_layout_stamp:` is {quote the value you found}, which is not a stamp ma-agents wrote (expected `sha256:` followed by 64 lowercase hex characters) — merge damage, a hand-edit, or a truncated write. Treat this config as unverified; it is NOT a config that predates the stamp. Run `ma-agents bind` to regenerate it from `_bmad-output/project-layout.yaml`.
78
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
79
+
80
+ - **`project_layout_stamp:` is well-formed** → go to **Step P4**.
81
+
82
+ ### Step P4 — Compare the committed binding against the generated fields
83
+
84
+ Derive, from `_bmad-output/project-layout.yaml`, what each generated field in `config.yaml` WOULD be if it were regenerated right now, and compare it to what `config.yaml` actually carries. This is a content comparison of the two files — never a timestamp comparison.
85
+
86
+ A schema-2 binding holds three records: `system_requirements_store:`, `software_requirements_store:` and `sprint_management:`. A legacy schema-1 binding holds a single `knowledgebase:` record, which stands for BOTH requirement stores.
87
+
88
+ The table below IS the whole comparison: every field named in its right-hand column is compared, and no field outside that column is. Read each field only from a line at column 0, and compare VALUES rather than bytes — the generator quotes its scalars (`sprint_backend: "jira"`, `knowledgebase_path: "."`), so strip one balanced pair of surrounding quotes from each side first.
89
+
90
+ | When | Binding record it is derived from | Field(s) it generates in `config.yaml` |
91
+ | --- | --- | --- |
92
+ | always | `software_requirements_store` → its `path:` (a store whose `backend:` is not `file-system` has no `path:` at all, and so generates `.`); the artifacts field is that same path, except that a path of `.` gives `{project-root}/_bmad-output/planning-artifacts`; its resolved `backend:` (an absent or empty `backend:` resolves to `file-system`), and, when that backend is `confluence`, its `space_key:` — the generated space-key field is empty for every other backend, so a store re-bound away from Confluence does not keep the old space | `software_requirements_path:`, `knowledgebase_path:`, `planning_artifacts:`, `software_requirements_backend:`, `software_requirements_space_key:` |
93
+ | always | `system_requirements_store` → its `path:` (a store whose `backend:` is not `file-system` has no `path:` at all, and so generates `.`); its resolved `backend:` (an absent or empty `backend:` resolves to `file-system`), and, when that backend is `confluence`, its `space_key:` — the generated space-key field is empty for every other backend, so a store re-bound away from Confluence does not keep the old space | `system_requirements_path:`, `system_requirements_backend:`, `system_requirements_space_key:` |
94
+ | `sprint_management` has `mode: jira` | `sprint_management` → its `jira_url:` and `jira_project_key:`; the backend field is the literal `jira`; the artifacts field is always `{project-root}/_bmad-output/implementation-artifacts`, because a Jira sprint store has no path | `sprint_backend:`, `jira_url:`, `jira_project_key:`, `implementation_artifacts:` |
95
+ | `sprint_management` has any other mode | `sprint_management` → its `path:`; the backend field is the literal `file-system`; the artifacts field is that same path, except that a path of `.` gives `{project-root}/_bmad-output/implementation-artifacts` | `sprint_backend:`, `sprint_management_path:`, `implementation_artifacts:` |
96
+
97
+ **When the binding says `mode: jira`, `sprint_management_path:` is not compared at all.** The generator writes no `sprint_management_path:` in that mode — and it never DELETES a field, it only updates or appends one. So a project that was bound to the file system and later re-bound to Jira still carries its old `sprint_management_path:` line in a freshly generated `config.yaml`. That leftover line is not drift, and comparing it would halt every Jira project that was ever file-system-bound.
98
+
99
+ Not every field in that table is a path. Where a store's `backend:` or `space_key:` is what disagrees, the comparison is the same and so is the halt — read the message template's `{path in …}` placeholders as "the value that disagrees", and name the two values you actually compared.
100
+
101
+ **Any disagreement is drift** → **HALT** and report, naming every store that moved and both of its paths:
102
+
103
+ > **ma-agents — stopping: `_bmad/bmm/config.yaml` is stale.**
104
+ > The committed binding in `_bmad-output/project-layout.yaml` no longer matches the state `_bmad/bmm/config.yaml` was generated from:
105
+ > - {store name}: was `{path in config.yaml}`, is now `{path in project-layout.yaml}`
106
+ > (one line per store that moved)
107
+ > Running this skill now would read and write artifacts where those stores used to be. Run `ma-agents bind` to regenerate `_bmad/bmm/config.yaml` from the committed binding, then run this skill again.
108
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
109
+
110
+ **If everything in the table agrees** → go to **Step P5**.
111
+
112
+ The stamp covers binding state that `config.yaml` does not carry — a Confluence `root_page:`, a `gitUrl:` — so the table alone cannot see every kind of move. If, and only if, a command shell is available to you and `ma-agents` resolves as a local dependency of this project, you may confirm the comparison exactly, without parsing the stamp yourself:
113
+
114
+ ```bash
115
+ node -e "const c=require('ma-agents/bin/cli.js'),f=require('fs'),p=process.cwd();const t=c.resolveProjectTopology(p);if(!t.ok){console.log('binding:'+t.reason)}else{const s=c.readProjectLayoutStamp(f.readFileSync('_bmad/bmm/config.yaml','utf8'));console.log(s.ok?(s.value===c.computeProjectLayoutStamp(t.topology,p)?'MATCH':'DRIFT'):'stamp:'+s.reason)}"
116
+ ```
117
+
118
+ `DRIFT` is authoritative: HALT with the stale-config message above even when the table found nothing, and say that the binding changed in a way `config.yaml` does not record. Never treat a failure to run this command as a result — if it does not run, the table is the check, and you say nothing about the stamp.
119
+
120
+ ### Step P5 — PROCEED
121
+
122
+ The generated config matches the committed binding. Say NOTHING about the binding, the stamp or this preflight — a matched project gets no message — and continue with the rest of this skill.
123
+
12
124
  ## Conventions
13
125
 
14
126
  - Bare paths (e.g. `checklist.md`) resolve from the skill root.