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
@@ -13,6 +13,118 @@
13
13
 
14
14
  ---
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
  ## INITIALIZATION
17
129
 
18
130
  ### Configuration Loading
@@ -12,6 +12,118 @@ triggers:
12
12
 
13
13
  Generate a self-contained, offline-ready Knowledge Atlas HTML site from the project's planning and implementation artifacts. No network access is required at generation or view time.
14
14
 
15
+ ## Knowledge Store Binding Preflight
16
+
17
+ **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.
18
+
19
+ `_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.
20
+
21
+ **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.
22
+
23
+ **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.
24
+
25
+ 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.
26
+
27
+ ### Step P1 — Read `_bmad-output/project-layout.yaml`
28
+
29
+ - **Missing, or present but blank / comments-only** → go to **Step P2**.
30
+ - **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:
31
+
32
+ > **ma-agents — stopping: the committed knowledge binding cannot be read.**
33
+ > `_bmad-output/project-layout.yaml` exists but this build cannot use it: {say exactly what is wrong}.
34
+ > 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.
35
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
36
+
37
+ 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.
38
+ - **Present and resolves cleanly** → go to **Step P3**.
39
+
40
+ ### Step P2 — There is no committed binding
41
+
42
+ 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`:
43
+
44
+ - **`config.yaml` does not exist either** → **HALT** and report:
45
+
46
+ > **ma-agents — stopping: this project is not configured.**
47
+ > 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.
48
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
49
+
50
+ - **`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:
51
+
52
+ > **ma-agents — stopping: the committed knowledge binding is missing.**
53
+ > `_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.
54
+ > Restore the file (for example `git checkout -- _bmad-output/project-layout.yaml`), or run `ma-agents bind` to write a fresh one.
55
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
56
+
57
+ 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.
58
+
59
+ - **`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.
60
+
61
+ ### Step P3 — The binding is readable: check what `config.yaml` was generated from
62
+
63
+ Read `_bmad/bmm/config.yaml`.
64
+
65
+ - **`config.yaml` does not exist** → **HALT** and report:
66
+
67
+ > **ma-agents — stopping: the generated config is missing.**
68
+ > `_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.
69
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
70
+
71
+ - **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:
72
+
73
+ > **ma-agents — stopping: `_bmad/bmm/config.yaml` does not record which binding it came from.**
74
+ > `_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.
75
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
76
+
77
+ - **`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:
78
+
79
+ > **ma-agents — stopping: `_bmad/bmm/config.yaml` claims to have been generated and the claim cannot be read.**
80
+ > 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`.
81
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
82
+
83
+ - **`project_layout_stamp:` is well-formed** → go to **Step P4**.
84
+
85
+ ### Step P4 — Compare the committed binding against the generated fields
86
+
87
+ 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.
88
+
89
+ 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.
90
+
91
+ 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.
92
+
93
+ | When | Binding record it is derived from | Field(s) it generates in `config.yaml` |
94
+ | --- | --- | --- |
95
+ | 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:` |
96
+ | 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:` |
97
+ | `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:` |
98
+ | `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:` |
99
+
100
+ **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.
101
+
102
+ 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.
103
+
104
+ **Any disagreement is drift** → **HALT** and report, naming every store that moved and both of its paths:
105
+
106
+ > **ma-agents — stopping: `_bmad/bmm/config.yaml` is stale.**
107
+ > The committed binding in `_bmad-output/project-layout.yaml` no longer matches the state `_bmad/bmm/config.yaml` was generated from:
108
+ > - {store name}: was `{path in config.yaml}`, is now `{path in project-layout.yaml}`
109
+ > (one line per store that moved)
110
+ > 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.
111
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
112
+
113
+ **If everything in the table agrees** → go to **Step P5**.
114
+
115
+ 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:
116
+
117
+ ```bash
118
+ 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)}"
119
+ ```
120
+
121
+ `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.
122
+
123
+ ### Step P5 — PROCEED
124
+
125
+ 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.
126
+
15
127
  ## Prerequisites
16
128
 
17
129
  - The project must have a `_bmad-output/` directory (created by BMAD install) and/or a `_bmad/bmm/config.yaml` pointing at where its knowledge actually lives.
@@ -6,6 +6,167 @@
6
6
 
7
7
  ---
8
8
 
9
+ ## Knowledge Store Binding Preflight
10
+
11
+ **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.
12
+
13
+ `_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.
14
+
15
+ **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.
16
+
17
+ **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.
18
+
19
+ 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.
20
+
21
+ ### Step P1 — Read `_bmad-output/project-layout.yaml`
22
+
23
+ - **Missing, or present but blank / comments-only** → go to **Step P2**.
24
+ - **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:
25
+
26
+ > **ma-agents — stopping: the committed knowledge binding cannot be read.**
27
+ > `_bmad-output/project-layout.yaml` exists but this build cannot use it: {say exactly what is wrong}.
28
+ > 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.
29
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
30
+
31
+ 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.
32
+ - **Present and resolves cleanly** → go to **Step P3**.
33
+
34
+ ### Step P2 — There is no committed binding
35
+
36
+ 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`:
37
+
38
+ - **`config.yaml` does not exist either** → **HALT** and report:
39
+
40
+ > **ma-agents — stopping: this project is not configured.**
41
+ > 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.
42
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
43
+
44
+ - **`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:
45
+
46
+ > **ma-agents — stopping: the committed knowledge binding is missing.**
47
+ > `_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.
48
+ > Restore the file (for example `git checkout -- _bmad-output/project-layout.yaml`), or run `ma-agents bind` to write a fresh one.
49
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
50
+
51
+ 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.
52
+
53
+ - **`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.
54
+
55
+ ### Step P3 — The binding is readable: check what `config.yaml` was generated from
56
+
57
+ Read `_bmad/bmm/config.yaml`.
58
+
59
+ - **`config.yaml` does not exist** → **HALT** and report:
60
+
61
+ > **ma-agents — stopping: the generated config is missing.**
62
+ > `_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.
63
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
64
+
65
+ - **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:
66
+
67
+ > **ma-agents — stopping: `_bmad/bmm/config.yaml` does not record which binding it came from.**
68
+ > `_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.
69
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
70
+
71
+ - **`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:
72
+
73
+ > **ma-agents — stopping: `_bmad/bmm/config.yaml` claims to have been generated and the claim cannot be read.**
74
+ > 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`.
75
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
76
+
77
+ - **`project_layout_stamp:` is well-formed** → go to **Step P4**.
78
+
79
+ ### Step P4 — Compare the committed binding against the generated fields
80
+
81
+ 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.
82
+
83
+ 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.
84
+
85
+ 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.
86
+
87
+ | When | Binding record it is derived from | Field(s) it generates in `config.yaml` |
88
+ | --- | --- | --- |
89
+ | 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:` |
90
+ | 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:` |
91
+ | `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:` |
92
+ | `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:` |
93
+
94
+ **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.
95
+
96
+ 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.
97
+
98
+ **Any disagreement is drift** → **HALT** and report, naming every store that moved and both of its paths:
99
+
100
+ > **ma-agents — stopping: `_bmad/bmm/config.yaml` is stale.**
101
+ > The committed binding in `_bmad-output/project-layout.yaml` no longer matches the state `_bmad/bmm/config.yaml` was generated from:
102
+ > - {store name}: was `{path in config.yaml}`, is now `{path in project-layout.yaml}`
103
+ > (one line per store that moved)
104
+ > 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.
105
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
106
+
107
+ **If everything in the table agrees** → go to **Step P5**.
108
+
109
+ 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:
110
+
111
+ ```bash
112
+ 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)}"
113
+ ```
114
+
115
+ `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.
116
+
117
+ ### Step P5 — PROCEED
118
+
119
+ 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.
120
+
121
+ ## Knowledge Store Backend Routing
122
+
123
+ **Gateway check — run this immediately after the preflight above, and before anything else.** Before any read, any write, any user prompt, and before the sprint-tracking `## Backend Routing` check below. No step of this skill executes until this check returns PROCEED.
124
+
125
+ This is the SAME routing pattern the sprint skills use for Jira, applied to a knowledge store instead of to sprint tracking. It is not a second mechanism: read the backend from the resolved configuration, branch on its value, and where it cannot be honoured say exactly what is wrong and stop. `mcp-detection-pattern-spec.md` defines the shape — §2.6 (this is a gateway check, not an afterthought), §2.7 (absent or empty means `file-system`), §6.6 (a backend is added by adding a branch, and the `file-system` branch is untouched).
126
+
127
+ **Which store gates this skill.** This skill resolves epics, and epics are a *software requirements* artifact (FR256), so the store that gates it is the **software requirements store**. Read these two fields from `_bmad/bmm/config.yaml`, each only from a line at column 0, stripping one balanced pair of surrounding quotes from the value:
128
+
129
+ - `software_requirements_backend:` — the backend of the store this skill reads its epics from.
130
+ - `software_requirements_space_key:` — that store's Confluence space, when it has one.
131
+
132
+ A skill that resolves a *system requirements* artifact reads `system_requirements_backend:` and `system_requirements_space_key:` instead and takes exactly these branches; nothing else about the check changes. Sprint execution state — story files, `sprint-status.yaml`, bugs, retrospectives — belongs to NEITHER knowledge store (FR267), so nothing this skill does with those artifacts is affected by any branch below.
133
+
134
+ Take the first branch that matches, and stop there.
135
+
136
+ ### Step B1 — `file-system`, or the field is absent, or its value is empty
137
+
138
+ **PROCEED** with the rest of this skill exactly as written below, unchanged. Say NOTHING about the backend: a file-system project gets no message from this section, no prompt, and no offer. This is the default (§2.7), and it is what every project that has not configured a Confluence store gets — including every project generated before the field existed. Nothing in this section reads, writes, resolves or reports anything on this path.
139
+
140
+ ### Step B2 — `confluence`
141
+
142
+ That store's artifacts live in Confluence, and Confluence is the source of truth for them. Before resolving, reading or writing ANY artifact belonging to that store, determine whether a Confluence-capable tool is available to you in your current tool context — an MCP server, plugin or native integration connected to the Confluence instance that hosts the space named above. Do not assume one is present because the backend says `confluence`: the backend is a statement of configuration, and availability is a fact about this run.
143
+
144
+ - **A Confluence-capable tool IS available** → route this store's reads and writes to it, in the space named by the space-key field, and treat what it returns as the source of truth for that store. Do NOT read that store's artifacts from a local directory instead, and do NOT write, copy, cache, export or reconcile a local copy of anything Confluence returns. There is no mirror and no cache, so there is nothing to synchronise and nothing to fall out of date.
145
+
146
+ **Where that store's artifacts live in the space, and how to write one.** ONE page per markdown artifact, nested under the store's root page so the pages match the source folder structure: `epics.md` is the page titled `epics` directly under that root page, and `prd/capabilities/skills-platform.md` is `skills-platform` under `capabilities` under `prd`, which is also how a sharded document becomes an index page with its shards as children. A page title is unique within a space, so the title identifies the page and the parent only says where it hangs. Run `ma-agents confluence-plan` to print that mapping for THIS project together with the complete set of operations a publish performs and the order to perform them in, parents first; it enumerates the plan and performs none of it, because the Confluence-capable tool is in your context and not in that process. To write, take the pages in the order it prints and, for each, look its title up in the space: update that page when it is already there and create it when it is not. That is an idempotent write, not a reconciliation — there are never two versions to compare, so a second publish of the same artifacts lands on the same pages and their history and comments survive. Report the root page by name to the user, and when it was created rather than found, record the id Confluence gave it by running `ma-agents confluence-record-root-page` so later runs address it directly. If any write does not report success, STOP, say which page failed, and report no publish.
147
+
148
+ - **No Confluence-capable tool is available** → **HALT** and report:
149
+
150
+ > **ma-agents — stopping: this knowledge store is Confluence-backed and no Confluence MCP was detected.**
151
+ > Store: the software requirements store. Space: {the value of the space-key field, or `not recorded` when that field is absent or empty}.
152
+ > No Confluence MCP server is available in this agent's tool context, so that store cannot be read or written.
153
+ > To configure one, add a Confluence-capable MCP server to your agent's MCP configuration, pointed at the Confluence instance that hosts that space, and authorise it; then run this skill again. Alternatively, re-run the installer (or `ma-agents bind`) to bind that store to the file system instead.
154
+ > Nothing has been read or written on your behalf, and nothing has been written locally. Stopping rather than guessing.
155
+
156
+ Then STOP. Do NOT fall back to the file system. Do NOT read, create or offer to create a local copy of that store's artifacts. Do NOT continue with a partial or empty result as though the store were empty. Do NOT change the configured backend on the user's behalf. A Confluence-backed store with no Confluence MCP has no degraded mode — halting IS the behaviour, not a failure to find one.
157
+
158
+ ### Step B3 — any other value
159
+
160
+ **HALT** and report:
161
+
162
+ > **ma-agents — stopping: unrecognised knowledge-store backend.**
163
+ > Store: the software requirements store. Backend: `{the value you read}`. The backends this build understands are `file-system` and `confluence`.
164
+ > `_bmad/bmm/config.yaml` is generated, so a value outside that set means it was hand-edited or written by a different build — and every artifact this skill would resolve for that store depends on which of the two it actually is.
165
+ > Run `ma-agents bind` to regenerate `_bmad/bmm/config.yaml`, or re-run the installer.
166
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
167
+
168
+ **Default-deny, deliberately.** This is the one place this block departs from `mcp-detection-pattern-spec.md`, which for an unrecognised *sprint tracking* value prints a message and falls back to the file system (§2.5). A knowledge store does not get that treatment: falling back would resolve a store this build cannot identify to local files, which is exactly the silent degradation FR262 forbids. Step B1's default is for a field that was never stated. It is not a catch-all, and it must never absorb a value that IS stated and is not understood.
169
+
9
170
  ## INITIALIZATION
10
171
 
11
172
  ### Configuration Loading
@@ -8,6 +8,118 @@
8
8
 
9
9
  ---
10
10
 
11
+ ## Knowledge Store Binding Preflight
12
+
13
+ **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.
14
+
15
+ `_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.
16
+
17
+ **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.
18
+
19
+ **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.
20
+
21
+ 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.
22
+
23
+ ### Step P1 — Read `_bmad-output/project-layout.yaml`
24
+
25
+ - **Missing, or present but blank / comments-only** → go to **Step P2**.
26
+ - **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:
27
+
28
+ > **ma-agents — stopping: the committed knowledge binding cannot be read.**
29
+ > `_bmad-output/project-layout.yaml` exists but this build cannot use it: {say exactly what is wrong}.
30
+ > 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.
31
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
32
+
33
+ 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.
34
+ - **Present and resolves cleanly** → go to **Step P3**.
35
+
36
+ ### Step P2 — There is no committed binding
37
+
38
+ 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`:
39
+
40
+ - **`config.yaml` does not exist either** → **HALT** and report:
41
+
42
+ > **ma-agents — stopping: this project is not configured.**
43
+ > 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.
44
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
45
+
46
+ - **`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:
47
+
48
+ > **ma-agents — stopping: the committed knowledge binding is missing.**
49
+ > `_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.
50
+ > Restore the file (for example `git checkout -- _bmad-output/project-layout.yaml`), or run `ma-agents bind` to write a fresh one.
51
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
52
+
53
+ 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.
54
+
55
+ - **`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.
56
+
57
+ ### Step P3 — The binding is readable: check what `config.yaml` was generated from
58
+
59
+ Read `_bmad/bmm/config.yaml`.
60
+
61
+ - **`config.yaml` does not exist** → **HALT** and report:
62
+
63
+ > **ma-agents — stopping: the generated config is missing.**
64
+ > `_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.
65
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
66
+
67
+ - **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:
68
+
69
+ > **ma-agents — stopping: `_bmad/bmm/config.yaml` does not record which binding it came from.**
70
+ > `_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.
71
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
72
+
73
+ - **`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:
74
+
75
+ > **ma-agents — stopping: `_bmad/bmm/config.yaml` claims to have been generated and the claim cannot be read.**
76
+ > 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`.
77
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
78
+
79
+ - **`project_layout_stamp:` is well-formed** → go to **Step P4**.
80
+
81
+ ### Step P4 — Compare the committed binding against the generated fields
82
+
83
+ 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.
84
+
85
+ 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.
86
+
87
+ 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.
88
+
89
+ | When | Binding record it is derived from | Field(s) it generates in `config.yaml` |
90
+ | --- | --- | --- |
91
+ | 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:` |
92
+ | 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:` |
93
+ | `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:` |
94
+ | `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:` |
95
+
96
+ **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.
97
+
98
+ 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.
99
+
100
+ **Any disagreement is drift** → **HALT** and report, naming every store that moved and both of its paths:
101
+
102
+ > **ma-agents — stopping: `_bmad/bmm/config.yaml` is stale.**
103
+ > The committed binding in `_bmad-output/project-layout.yaml` no longer matches the state `_bmad/bmm/config.yaml` was generated from:
104
+ > - {store name}: was `{path in config.yaml}`, is now `{path in project-layout.yaml}`
105
+ > (one line per store that moved)
106
+ > 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.
107
+ > Nothing has been read or written on your behalf. Stopping rather than guessing.
108
+
109
+ **If everything in the table agrees** → go to **Step P5**.
110
+
111
+ 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:
112
+
113
+ ```bash
114
+ 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)}"
115
+ ```
116
+
117
+ `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.
118
+
119
+ ### Step P5 — PROCEED
120
+
121
+ 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.
122
+
11
123
  ## INITIALIZATION
12
124
 
13
125
  ### Configuration Loading