ma-agents 3.12.0 → 3.12.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 (1310) hide show
  1. package/CONTRIBUTING.md +235 -235
  2. package/LICENSE +20 -20
  3. package/QUICK_START.md +154 -154
  4. package/README.md +731 -731
  5. package/SKILLS_STRUCTURE.md +392 -392
  6. package/bin/cli.js +1681 -1573
  7. package/docs/architecture.md +284 -284
  8. package/docs/deployment/vllm-nemotron.md +132 -132
  9. package/docs/development-guide.md +122 -122
  10. package/docs/index.md +48 -48
  11. package/docs/project-overview.md +56 -56
  12. package/docs/project-scan-report.json +50 -50
  13. package/docs/source-tree-analysis.md +84 -84
  14. package/docs/technical-notes/context-persistence-research.md +434 -434
  15. package/docs/validation/bundled-installation-validation.md +52 -52
  16. package/examples/programmatic-usage.js +62 -62
  17. package/index.js +22 -22
  18. package/lib/agents.js +370 -370
  19. package/lib/bmad-cache/bmb/.claude-plugin/marketplace.json +50 -50
  20. package/lib/bmad-cache/bmb/.markdownlint-cli2.yaml +36 -36
  21. package/lib/bmad-cache/bmb/.prettierignore +9 -9
  22. package/lib/bmad-cache/bmb/CNAME +1 -1
  23. package/lib/bmad-cache/bmb/LICENSE +30 -30
  24. package/lib/bmad-cache/bmb/README.md +75 -75
  25. package/lib/bmad-cache/bmb/_git_preserved/HEAD +1 -1
  26. package/lib/bmad-cache/bmb/_git_preserved/config +13 -13
  27. package/lib/bmad-cache/bmb/_git_preserved/description +1 -1
  28. package/lib/bmad-cache/bmb/_git_preserved/hooks/applypatch-msg.sample +15 -15
  29. package/lib/bmad-cache/bmb/_git_preserved/hooks/commit-msg.sample +24 -24
  30. package/lib/bmad-cache/bmb/_git_preserved/hooks/fsmonitor-watchman.sample +174 -174
  31. package/lib/bmad-cache/bmb/_git_preserved/hooks/post-update.sample +8 -8
  32. package/lib/bmad-cache/bmb/_git_preserved/hooks/pre-applypatch.sample +14 -14
  33. package/lib/bmad-cache/bmb/_git_preserved/hooks/pre-commit.sample +49 -49
  34. package/lib/bmad-cache/bmb/_git_preserved/hooks/pre-merge-commit.sample +13 -13
  35. package/lib/bmad-cache/bmb/_git_preserved/hooks/pre-push.sample +53 -53
  36. package/lib/bmad-cache/bmb/_git_preserved/hooks/pre-rebase.sample +169 -169
  37. package/lib/bmad-cache/bmb/_git_preserved/hooks/pre-receive.sample +24 -24
  38. package/lib/bmad-cache/bmb/_git_preserved/hooks/prepare-commit-msg.sample +42 -42
  39. package/lib/bmad-cache/bmb/_git_preserved/hooks/push-to-checkout.sample +78 -78
  40. package/lib/bmad-cache/bmb/_git_preserved/hooks/sendemail-validate.sample +77 -77
  41. package/lib/bmad-cache/bmb/_git_preserved/hooks/update.sample +128 -128
  42. package/lib/bmad-cache/bmb/_git_preserved/info/exclude +6 -6
  43. package/lib/bmad-cache/bmb/_git_preserved/packed-refs +2 -2
  44. package/lib/bmad-cache/bmb/_git_preserved/refs/heads/main +1 -1
  45. package/lib/bmad-cache/bmb/_git_preserved/refs/remotes/origin/HEAD +1 -1
  46. package/lib/bmad-cache/bmb/_git_preserved/refs/tags/v1.7.0 +1 -1
  47. package/lib/bmad-cache/bmb/_git_preserved/shallow +1 -1
  48. package/lib/bmad-cache/bmb/eslint.config.mjs +141 -141
  49. package/lib/bmad-cache/bmb/package-lock.json +15283 -15283
  50. package/lib/bmad-cache/bmb/package.json +86 -86
  51. package/lib/bmad-cache/bmb/prettier.config.mjs +32 -32
  52. package/lib/bmad-cache/bmb/samples/bmad-agent-code-coach/scripts/init-sanctum.py +288 -288
  53. package/lib/bmad-cache/bmb/samples/bmad-agent-creative-muse/scripts/init-sanctum.py +274 -274
  54. package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/assets/module-help.csv +9 -9
  55. package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/assets/module.yaml +8 -8
  56. package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/scripts/merge-config.py +408 -408
  57. package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/scripts/merge-help-csv.py +218 -218
  58. package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/scripts/recall_metrics.py +229 -229
  59. package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/scripts/seed_tracker.py +156 -156
  60. package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/scripts/symbol_stats.py +162 -162
  61. package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/scripts/tests/test_recall_metrics.py +115 -115
  62. package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/scripts/tests/test_seed_tracker.py +140 -140
  63. package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/scripts/tests/test_symbol_stats.py +113 -113
  64. package/lib/bmad-cache/bmb/samples/bmad-agent-sentinel/scripts/init-sanctum.py +285 -285
  65. package/lib/bmad-cache/bmb/samples/bmad-agent-sentinel/scripts/tests/test-init-sanctum.py +174 -174
  66. package/lib/bmad-cache/bmb/samples/bmad-excalidraw/scripts/generate_excalidraw.py +605 -605
  67. package/lib/bmad-cache/bmb/samples/bmad-excalidraw/scripts/tests/test_generate_excalidraw.py +360 -360
  68. package/lib/bmad-cache/bmb/samples/bmad-excalidraw/scripts/tests/test_validate_excalidraw.py +246 -246
  69. package/lib/bmad-cache/bmb/samples/bmad-excalidraw/scripts/validate_excalidraw.py +264 -264
  70. package/lib/bmad-cache/bmb/samples/sample-module-setup/assets/module-help.csv +16 -16
  71. package/lib/bmad-cache/bmb/samples/sample-module-setup/assets/module.yaml +13 -13
  72. package/lib/bmad-cache/bmb/samples/sample-module-setup/scripts/cleanup-legacy.py +259 -259
  73. package/lib/bmad-cache/bmb/samples/sample-module-setup/scripts/merge-config.py +408 -408
  74. package/lib/bmad-cache/bmb/samples/sample-module-setup/scripts/merge-help-csv.py +218 -218
  75. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/assets/customize-template.toml +62 -62
  76. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/assets/init-sanctum-template.py +277 -277
  77. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/assets/sample-customize-analyst.toml +87 -87
  78. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/references/sample-init-sanctum.py +274 -274
  79. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/generate-html-report.py +534 -534
  80. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass-execution-deps.py +337 -337
  81. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass-prompt-metrics.py +425 -425
  82. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass-sanctum-architecture.py +385 -385
  83. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass-structure-capabilities.py +482 -482
  84. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/process-template.py +190 -190
  85. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/scan-path-standards.py +324 -324
  86. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/scan-scripts.py +747 -747
  87. package/lib/bmad-cache/bmb/skills/bmad-bmb-setup/assets/module-help.csv +10 -10
  88. package/lib/bmad-cache/bmb/skills/bmad-bmb-setup/assets/module.yaml +20 -20
  89. package/lib/bmad-cache/bmb/skills/bmad-bmb-setup/scripts/cleanup-legacy.py +259 -259
  90. package/lib/bmad-cache/bmb/skills/bmad-bmb-setup/scripts/merge-config.py +408 -408
  91. package/lib/bmad-cache/bmb/skills/bmad-bmb-setup/scripts/merge-help-csv.py +218 -218
  92. package/lib/bmad-cache/bmb/skills/bmad-module-builder/assets/setup-skill-template/assets/module-help.csv +1 -1
  93. package/lib/bmad-cache/bmb/skills/bmad-module-builder/assets/setup-skill-template/assets/module.yaml +6 -6
  94. package/lib/bmad-cache/bmb/skills/bmad-module-builder/assets/setup-skill-template/scripts/cleanup-legacy.py +259 -259
  95. package/lib/bmad-cache/bmb/skills/bmad-module-builder/assets/setup-skill-template/scripts/merge-config.py +408 -408
  96. package/lib/bmad-cache/bmb/skills/bmad-module-builder/assets/setup-skill-template/scripts/merge-help-csv.py +218 -218
  97. package/lib/bmad-cache/bmb/skills/bmad-module-builder/assets/standalone-module-template/merge-config.py +408 -408
  98. package/lib/bmad-cache/bmb/skills/bmad-module-builder/assets/standalone-module-template/merge-help-csv.py +218 -218
  99. package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/scaffold-setup-skill.py +124 -124
  100. package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/scaffold-standalone-module.py +190 -190
  101. package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/tests/test-scaffold-setup-skill.py +230 -230
  102. package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/tests/test-scaffold-standalone-module.py +266 -266
  103. package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/tests/test-validate-module.py +314 -314
  104. package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/validate-module.py +293 -293
  105. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/assets/customize-template.toml +56 -56
  106. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/assets/sample-customize-product-brief.toml +51 -51
  107. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/generate-convert-report.py +406 -406
  108. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/generate-html-report.py +539 -539
  109. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/prepass-execution-deps.py +288 -288
  110. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/prepass-prompt-metrics.py +285 -285
  111. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/prepass-workflow-integrity.py +475 -475
  112. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/scan-path-standards.py +298 -298
  113. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/scan-scripts.py +745 -745
  114. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/tests/test_generate_convert_report.py +243 -243
  115. package/lib/bmad-cache/bmb/skills/module-help.csv +11 -11
  116. package/lib/bmad-cache/bmb/skills/module.yaml +20 -20
  117. package/lib/bmad-cache/bmb/tools/build-docs.mjs +448 -448
  118. package/lib/bmad-cache/bmb/tools/validate-doc-links.cjs +412 -412
  119. package/lib/bmad-cache/bmb/tools/validate-file-refs.mjs +657 -657
  120. package/lib/bmad-cache/bmb/website/astro.config.mjs +142 -142
  121. package/lib/bmad-cache/bmb/website/src/components/Banner.astro +57 -57
  122. package/lib/bmad-cache/bmb/website/src/components/Header.astro +94 -94
  123. package/lib/bmad-cache/bmb/website/src/components/MobileMenuFooter.astro +33 -33
  124. package/lib/bmad-cache/bmb/website/src/content/config.ts +6 -6
  125. package/lib/bmad-cache/bmb/website/src/lib/site-url.mjs +25 -25
  126. package/lib/bmad-cache/bmb/website/src/rehype-base-paths.js +88 -88
  127. package/lib/bmad-cache/bmb/website/src/rehype-markdown-links.js +117 -117
  128. package/lib/bmad-cache/bmb/website/src/styles/custom.css +502 -502
  129. package/lib/bmad-cache/cache-manifest.json +37 -37
  130. package/lib/bmad-cache/cis/.claude-plugin/marketplace.json +33 -33
  131. package/lib/bmad-cache/cis/.markdownlint-cli2.yaml +35 -35
  132. package/lib/bmad-cache/cis/.prettierignore +9 -9
  133. package/lib/bmad-cache/cis/CNAME +1 -1
  134. package/lib/bmad-cache/cis/LICENSE +26 -26
  135. package/lib/bmad-cache/cis/README.md +114 -114
  136. package/lib/bmad-cache/cis/_git_preserved/HEAD +1 -1
  137. package/lib/bmad-cache/cis/_git_preserved/config +13 -13
  138. package/lib/bmad-cache/cis/_git_preserved/description +1 -1
  139. package/lib/bmad-cache/cis/_git_preserved/hooks/applypatch-msg.sample +15 -15
  140. package/lib/bmad-cache/cis/_git_preserved/hooks/commit-msg.sample +24 -24
  141. package/lib/bmad-cache/cis/_git_preserved/hooks/fsmonitor-watchman.sample +174 -174
  142. package/lib/bmad-cache/cis/_git_preserved/hooks/post-update.sample +8 -8
  143. package/lib/bmad-cache/cis/_git_preserved/hooks/pre-applypatch.sample +14 -14
  144. package/lib/bmad-cache/cis/_git_preserved/hooks/pre-commit.sample +49 -49
  145. package/lib/bmad-cache/cis/_git_preserved/hooks/pre-merge-commit.sample +13 -13
  146. package/lib/bmad-cache/cis/_git_preserved/hooks/pre-push.sample +53 -53
  147. package/lib/bmad-cache/cis/_git_preserved/hooks/pre-rebase.sample +169 -169
  148. package/lib/bmad-cache/cis/_git_preserved/hooks/pre-receive.sample +24 -24
  149. package/lib/bmad-cache/cis/_git_preserved/hooks/prepare-commit-msg.sample +42 -42
  150. package/lib/bmad-cache/cis/_git_preserved/hooks/push-to-checkout.sample +78 -78
  151. package/lib/bmad-cache/cis/_git_preserved/hooks/sendemail-validate.sample +77 -77
  152. package/lib/bmad-cache/cis/_git_preserved/hooks/update.sample +128 -128
  153. package/lib/bmad-cache/cis/_git_preserved/info/exclude +6 -6
  154. package/lib/bmad-cache/cis/_git_preserved/packed-refs +2 -2
  155. package/lib/bmad-cache/cis/_git_preserved/refs/heads/main +1 -1
  156. package/lib/bmad-cache/cis/_git_preserved/refs/remotes/origin/HEAD +1 -1
  157. package/lib/bmad-cache/cis/_git_preserved/shallow +1 -1
  158. package/lib/bmad-cache/cis/eslint.config.mjs +141 -141
  159. package/lib/bmad-cache/cis/package-lock.json +17015 -17015
  160. package/lib/bmad-cache/cis/package.json +91 -91
  161. package/lib/bmad-cache/cis/prettier.config.mjs +32 -32
  162. package/lib/bmad-cache/cis/src/module-help.csv +7 -7
  163. package/lib/bmad-cache/cis/src/module.yaml +76 -76
  164. package/lib/bmad-cache/cis/src/skills/bmad-cis-agent-brainstorming-coach/customize.toml +38 -38
  165. package/lib/bmad-cache/cis/src/skills/bmad-cis-agent-creative-problem-solver/customize.toml +38 -38
  166. package/lib/bmad-cache/cis/src/skills/bmad-cis-agent-design-thinking-coach/customize.toml +39 -39
  167. package/lib/bmad-cache/cis/src/skills/bmad-cis-agent-innovation-strategist/customize.toml +38 -38
  168. package/lib/bmad-cache/cis/src/skills/bmad-cis-agent-presentation-master/customize.toml +73 -73
  169. package/lib/bmad-cache/cis/src/skills/bmad-cis-agent-storyteller/customize.toml +60 -60
  170. package/lib/bmad-cache/cis/src/skills/bmad-cis-design-thinking/customize.toml +41 -41
  171. package/lib/bmad-cache/cis/src/skills/bmad-cis-design-thinking/design-methods.csv +30 -30
  172. package/lib/bmad-cache/cis/src/skills/bmad-cis-innovation-strategy/customize.toml +41 -41
  173. package/lib/bmad-cache/cis/src/skills/bmad-cis-innovation-strategy/innovation-frameworks.csv +30 -30
  174. package/lib/bmad-cache/cis/src/skills/bmad-cis-problem-solving/customize.toml +42 -42
  175. package/lib/bmad-cache/cis/src/skills/bmad-cis-problem-solving/solving-methods.csv +30 -30
  176. package/lib/bmad-cache/cis/src/skills/bmad-cis-storytelling/customize.toml +41 -41
  177. package/lib/bmad-cache/cis/src/skills/bmad-cis-storytelling/story-types.csv +25 -25
  178. package/lib/bmad-cache/cis/tools/build-docs.mjs +456 -456
  179. package/lib/bmad-cache/cis/website/astro.config.mjs +172 -172
  180. package/lib/bmad-cache/cis/website/src/components/Banner.astro +71 -71
  181. package/lib/bmad-cache/cis/website/src/components/Header.astro +94 -94
  182. package/lib/bmad-cache/cis/website/src/components/MobileMenuFooter.astro +33 -33
  183. package/lib/bmad-cache/cis/website/src/content/config.ts +7 -7
  184. package/lib/bmad-cache/cis/website/src/content/i18n/zh-CN.json +28 -28
  185. package/lib/bmad-cache/cis/website/src/lib/locales.mjs +27 -27
  186. package/lib/bmad-cache/cis/website/src/lib/site-url.mjs +25 -25
  187. package/lib/bmad-cache/cis/website/src/rehype-base-paths.js +88 -88
  188. package/lib/bmad-cache/cis/website/src/rehype-markdown-links.js +117 -117
  189. package/lib/bmad-cache/cis/website/src/styles/custom.css +503 -503
  190. package/lib/bmad-cache/gds/.claude-plugin/marketplace.json +59 -59
  191. package/lib/bmad-cache/gds/.markdownlint-cli2.yaml +35 -35
  192. package/lib/bmad-cache/gds/.prettierignore +9 -9
  193. package/lib/bmad-cache/gds/CNAME +1 -1
  194. package/lib/bmad-cache/gds/LICENSE +26 -26
  195. package/lib/bmad-cache/gds/README.md +132 -132
  196. package/lib/bmad-cache/gds/_git_preserved/HEAD +1 -1
  197. package/lib/bmad-cache/gds/_git_preserved/config +13 -13
  198. package/lib/bmad-cache/gds/_git_preserved/description +1 -1
  199. package/lib/bmad-cache/gds/_git_preserved/hooks/applypatch-msg.sample +15 -15
  200. package/lib/bmad-cache/gds/_git_preserved/hooks/commit-msg.sample +24 -24
  201. package/lib/bmad-cache/gds/_git_preserved/hooks/fsmonitor-watchman.sample +174 -174
  202. package/lib/bmad-cache/gds/_git_preserved/hooks/post-update.sample +8 -8
  203. package/lib/bmad-cache/gds/_git_preserved/hooks/pre-applypatch.sample +14 -14
  204. package/lib/bmad-cache/gds/_git_preserved/hooks/pre-commit.sample +49 -49
  205. package/lib/bmad-cache/gds/_git_preserved/hooks/pre-merge-commit.sample +13 -13
  206. package/lib/bmad-cache/gds/_git_preserved/hooks/pre-push.sample +53 -53
  207. package/lib/bmad-cache/gds/_git_preserved/hooks/pre-rebase.sample +169 -169
  208. package/lib/bmad-cache/gds/_git_preserved/hooks/pre-receive.sample +24 -24
  209. package/lib/bmad-cache/gds/_git_preserved/hooks/prepare-commit-msg.sample +42 -42
  210. package/lib/bmad-cache/gds/_git_preserved/hooks/push-to-checkout.sample +78 -78
  211. package/lib/bmad-cache/gds/_git_preserved/hooks/sendemail-validate.sample +77 -77
  212. package/lib/bmad-cache/gds/_git_preserved/hooks/update.sample +128 -128
  213. package/lib/bmad-cache/gds/_git_preserved/info/exclude +6 -6
  214. package/lib/bmad-cache/gds/_git_preserved/packed-refs +2 -2
  215. package/lib/bmad-cache/gds/_git_preserved/refs/heads/main +1 -1
  216. package/lib/bmad-cache/gds/_git_preserved/refs/remotes/origin/HEAD +1 -1
  217. package/lib/bmad-cache/gds/_git_preserved/shallow +1 -1
  218. package/lib/bmad-cache/gds/eslint.config.mjs +141 -141
  219. package/lib/bmad-cache/gds/package.json +91 -91
  220. package/lib/bmad-cache/gds/prettier.config.mjs +32 -32
  221. package/lib/bmad-cache/gds/src/agents/gds-agent-game-architect/customize.toml +57 -57
  222. package/lib/bmad-cache/gds/src/agents/gds-agent-game-designer/customize.toml +59 -59
  223. package/lib/bmad-cache/gds/src/agents/gds-agent-game-dev/customize.toml +129 -129
  224. package/lib/bmad-cache/gds/src/agents/gds-agent-game-dev/gametest/qa-index.csv +18 -18
  225. package/lib/bmad-cache/gds/src/agents/gds-agent-game-solo-dev/customize.toml +60 -60
  226. package/lib/bmad-cache/gds/src/agents/gds-agent-tech-writer/customize.toml +65 -65
  227. package/lib/bmad-cache/gds/src/module-help.csv +36 -36
  228. package/lib/bmad-cache/gds/src/module.yaml +113 -113
  229. package/lib/bmad-cache/gds/src/workflows/1-preproduction/gds-brainstorm-game/customize.toml +41 -41
  230. package/lib/bmad-cache/gds/src/workflows/1-preproduction/gds-brainstorm-game/game-brain-methods.csv +25 -25
  231. package/lib/bmad-cache/gds/src/workflows/1-preproduction/gds-create-game-brief/customize.toml +41 -41
  232. package/lib/bmad-cache/gds/src/workflows/1-preproduction/research/gds-domain-research/customize.toml +41 -41
  233. package/lib/bmad-cache/gds/src/workflows/2-design/gds-create-gdd/customize.toml +41 -41
  234. package/lib/bmad-cache/gds/src/workflows/2-design/gds-create-gdd/game-types.csv +24 -24
  235. package/lib/bmad-cache/gds/src/workflows/2-design/gds-create-narrative/customize.toml +41 -41
  236. package/lib/bmad-cache/gds/src/workflows/2-design/gds-create-prd/customize.toml +41 -41
  237. package/lib/bmad-cache/gds/src/workflows/2-design/gds-create-prd/data/domain-complexity.csv +14 -14
  238. package/lib/bmad-cache/gds/src/workflows/2-design/gds-create-prd/data/project-types.csv +10 -10
  239. package/lib/bmad-cache/gds/src/workflows/2-design/gds-create-ux-design/customize.toml +41 -41
  240. package/lib/bmad-cache/gds/src/workflows/2-design/gds-edit-gdd/customize.toml +41 -41
  241. package/lib/bmad-cache/gds/src/workflows/2-design/gds-edit-prd/customize.toml +41 -41
  242. package/lib/bmad-cache/gds/src/workflows/2-design/gds-validate-gdd/customize.toml +41 -41
  243. package/lib/bmad-cache/gds/src/workflows/2-design/gds-validate-gdd/data/genre-complexity.csv +26 -26
  244. package/lib/bmad-cache/gds/src/workflows/2-design/gds-validate-prd/customize.toml +41 -41
  245. package/lib/bmad-cache/gds/src/workflows/2-design/gds-validate-prd/data/domain-complexity.csv +14 -14
  246. package/lib/bmad-cache/gds/src/workflows/2-design/gds-validate-prd/data/project-types.csv +10 -10
  247. package/lib/bmad-cache/gds/src/workflows/3-technical/gds-check-implementation-readiness/customize.toml +41 -41
  248. package/lib/bmad-cache/gds/src/workflows/3-technical/gds-create-epics-and-stories/customize.toml +41 -41
  249. package/lib/bmad-cache/gds/src/workflows/3-technical/gds-game-architecture/architecture-patterns.yaml +507 -507
  250. package/lib/bmad-cache/gds/src/workflows/3-technical/gds-game-architecture/customize.toml +41 -41
  251. package/lib/bmad-cache/gds/src/workflows/3-technical/gds-game-architecture/decision-catalog.yaml +340 -340
  252. package/lib/bmad-cache/gds/src/workflows/3-technical/gds-game-architecture/engine-mcps.yaml +270 -270
  253. package/lib/bmad-cache/gds/src/workflows/3-technical/gds-game-architecture/pattern-categories.csv +12 -12
  254. package/lib/bmad-cache/gds/src/workflows/3-technical/gds-generate-project-context/customize.toml +41 -41
  255. package/lib/bmad-cache/gds/src/workflows/4-production/gds-code-review/customize.toml +41 -41
  256. package/lib/bmad-cache/gds/src/workflows/4-production/gds-correct-course/customize.toml +41 -41
  257. package/lib/bmad-cache/gds/src/workflows/4-production/gds-create-story/customize.toml +41 -41
  258. package/lib/bmad-cache/gds/src/workflows/4-production/gds-dev-story/customize.toml +41 -41
  259. package/lib/bmad-cache/gds/src/workflows/4-production/gds-retrospective/customize.toml +41 -41
  260. package/lib/bmad-cache/gds/src/workflows/4-production/gds-sprint-planning/customize.toml +41 -41
  261. package/lib/bmad-cache/gds/src/workflows/4-production/gds-sprint-planning/sprint-status-template.yaml +55 -55
  262. package/lib/bmad-cache/gds/src/workflows/4-production/gds-sprint-status/customize.toml +41 -41
  263. package/lib/bmad-cache/gds/src/workflows/gametest/gds-e2e-scaffold/customize.toml +41 -41
  264. package/lib/bmad-cache/gds/src/workflows/gametest/gds-performance-test/customize.toml +41 -41
  265. package/lib/bmad-cache/gds/src/workflows/gametest/gds-playtest-plan/customize.toml +41 -41
  266. package/lib/bmad-cache/gds/src/workflows/gametest/gds-test-automate/customize.toml +41 -41
  267. package/lib/bmad-cache/gds/src/workflows/gametest/gds-test-design/customize.toml +41 -41
  268. package/lib/bmad-cache/gds/src/workflows/gametest/gds-test-framework/customize.toml +41 -41
  269. package/lib/bmad-cache/gds/src/workflows/gametest/gds-test-review/customize.toml +41 -41
  270. package/lib/bmad-cache/gds/src/workflows/gds-document-project/customize.toml +41 -41
  271. package/lib/bmad-cache/gds/src/workflows/gds-document-project/documentation-requirements.csv +12 -12
  272. package/lib/bmad-cache/gds/src/workflows/gds-document-project/templates/project-scan-report-schema.json +160 -160
  273. package/lib/bmad-cache/gds/src/workflows/gds-quick-flow/gds-quick-dev/customize.toml +41 -41
  274. package/lib/bmad-cache/gds/tools/build-docs.mjs +450 -450
  275. package/lib/bmad-cache/gds/website/astro.config.mjs +142 -142
  276. package/lib/bmad-cache/gds/website/src/components/Banner.astro +71 -71
  277. package/lib/bmad-cache/gds/website/src/components/Header.astro +94 -94
  278. package/lib/bmad-cache/gds/website/src/components/MobileMenuFooter.astro +33 -33
  279. package/lib/bmad-cache/gds/website/src/content/config.ts +6 -6
  280. package/lib/bmad-cache/gds/website/src/lib/site-url.mjs +25 -25
  281. package/lib/bmad-cache/gds/website/src/rehype-base-paths.js +88 -88
  282. package/lib/bmad-cache/gds/website/src/rehype-markdown-links.js +117 -117
  283. package/lib/bmad-cache/gds/website/src/styles/custom.css +503 -503
  284. package/lib/bmad-cache/tea/.claude-plugin/marketplace.json +33 -33
  285. package/lib/bmad-cache/tea/.coderabbit.yaml +40 -40
  286. package/lib/bmad-cache/tea/.github/CODE_OF_CONDUCT.md +128 -128
  287. package/lib/bmad-cache/tea/.github/FUNDING.yaml +15 -15
  288. package/lib/bmad-cache/tea/.github/ISSUE_TEMPLATE/config.yaml +11 -11
  289. package/lib/bmad-cache/tea/.github/ISSUE_TEMPLATE/feature_request.md +70 -70
  290. package/lib/bmad-cache/tea/.github/ISSUE_TEMPLATE/issue.md +61 -61
  291. package/lib/bmad-cache/tea/.github/workflows/docs.yaml +66 -66
  292. package/lib/bmad-cache/tea/.github/workflows/quality.yaml +117 -117
  293. package/lib/bmad-cache/tea/.husky/pre-commit +20 -20
  294. package/lib/bmad-cache/tea/.markdownlint-cli2.yaml +36 -36
  295. package/lib/bmad-cache/tea/.prettierignore +9 -9
  296. package/lib/bmad-cache/tea/CHANGELOG.md +241 -241
  297. package/lib/bmad-cache/tea/CONTRIBUTING.md +268 -268
  298. package/lib/bmad-cache/tea/LICENSE +26 -26
  299. package/lib/bmad-cache/tea/README.md +416 -416
  300. package/lib/bmad-cache/tea/SECURITY.md +85 -85
  301. package/lib/bmad-cache/tea/_git_preserved/HEAD +1 -1
  302. package/lib/bmad-cache/tea/_git_preserved/config +13 -13
  303. package/lib/bmad-cache/tea/_git_preserved/description +1 -1
  304. package/lib/bmad-cache/tea/_git_preserved/hooks/applypatch-msg.sample +15 -15
  305. package/lib/bmad-cache/tea/_git_preserved/hooks/commit-msg.sample +24 -24
  306. package/lib/bmad-cache/tea/_git_preserved/hooks/fsmonitor-watchman.sample +174 -174
  307. package/lib/bmad-cache/tea/_git_preserved/hooks/post-update.sample +8 -8
  308. package/lib/bmad-cache/tea/_git_preserved/hooks/pre-applypatch.sample +14 -14
  309. package/lib/bmad-cache/tea/_git_preserved/hooks/pre-commit.sample +49 -49
  310. package/lib/bmad-cache/tea/_git_preserved/hooks/pre-merge-commit.sample +13 -13
  311. package/lib/bmad-cache/tea/_git_preserved/hooks/pre-push.sample +53 -53
  312. package/lib/bmad-cache/tea/_git_preserved/hooks/pre-rebase.sample +169 -169
  313. package/lib/bmad-cache/tea/_git_preserved/hooks/pre-receive.sample +24 -24
  314. package/lib/bmad-cache/tea/_git_preserved/hooks/prepare-commit-msg.sample +42 -42
  315. package/lib/bmad-cache/tea/_git_preserved/hooks/push-to-checkout.sample +78 -78
  316. package/lib/bmad-cache/tea/_git_preserved/hooks/sendemail-validate.sample +77 -77
  317. package/lib/bmad-cache/tea/_git_preserved/hooks/update.sample +128 -128
  318. package/lib/bmad-cache/tea/_git_preserved/info/exclude +6 -6
  319. package/lib/bmad-cache/tea/_git_preserved/packed-refs +2 -2
  320. package/lib/bmad-cache/tea/_git_preserved/refs/heads/main +1 -1
  321. package/lib/bmad-cache/tea/_git_preserved/refs/remotes/origin/HEAD +1 -1
  322. package/lib/bmad-cache/tea/_git_preserved/shallow +1 -1
  323. package/lib/bmad-cache/tea/docs/404.md +20 -20
  324. package/lib/bmad-cache/tea/docs/explanation/engagement-models.md +767 -767
  325. package/lib/bmad-cache/tea/docs/explanation/fixture-architecture.md +484 -484
  326. package/lib/bmad-cache/tea/docs/explanation/knowledge-base-system.md +601 -601
  327. package/lib/bmad-cache/tea/docs/explanation/network-first-patterns.md +884 -884
  328. package/lib/bmad-cache/tea/docs/explanation/risk-based-testing.md +628 -628
  329. package/lib/bmad-cache/tea/docs/explanation/step-file-architecture.md +599 -599
  330. package/lib/bmad-cache/tea/docs/explanation/subagent-architecture.md +189 -189
  331. package/lib/bmad-cache/tea/docs/explanation/tea-overview.md +474 -474
  332. package/lib/bmad-cache/tea/docs/explanation/test-quality-standards.md +965 -965
  333. package/lib/bmad-cache/tea/docs/explanation/testing-as-engineering.md +115 -115
  334. package/lib/bmad-cache/tea/docs/glossary/index.md +160 -160
  335. package/lib/bmad-cache/tea/docs/how-to/brownfield/use-tea-for-enterprise.md +571 -571
  336. package/lib/bmad-cache/tea/docs/how-to/brownfield/use-tea-with-existing-tests.md +631 -631
  337. package/lib/bmad-cache/tea/docs/how-to/customization/configure-browser-automation.md +243 -243
  338. package/lib/bmad-cache/tea/docs/how-to/customization/extend-tea-with-custom-workflows.md +102 -102
  339. package/lib/bmad-cache/tea/docs/how-to/customization/integrate-playwright-utils.md +846 -846
  340. package/lib/bmad-cache/tea/docs/how-to/workflows/run-atdd.md +462 -462
  341. package/lib/bmad-cache/tea/docs/how-to/workflows/run-automate.md +693 -693
  342. package/lib/bmad-cache/tea/docs/how-to/workflows/run-nfr-assess.md +731 -731
  343. package/lib/bmad-cache/tea/docs/how-to/workflows/run-test-design.md +144 -144
  344. package/lib/bmad-cache/tea/docs/how-to/workflows/run-test-review.md +634 -634
  345. package/lib/bmad-cache/tea/docs/how-to/workflows/run-trace.md +966 -966
  346. package/lib/bmad-cache/tea/docs/how-to/workflows/setup-ci.md +763 -763
  347. package/lib/bmad-cache/tea/docs/how-to/workflows/setup-test-framework.md +122 -122
  348. package/lib/bmad-cache/tea/docs/how-to/workflows/teach-me-testing.md +302 -302
  349. package/lib/bmad-cache/tea/docs/index.md +65 -65
  350. package/lib/bmad-cache/tea/docs/reference/commands.md +356 -356
  351. package/lib/bmad-cache/tea/docs/reference/configuration.md +1144 -1144
  352. package/lib/bmad-cache/tea/docs/reference/knowledge-base.md +406 -406
  353. package/lib/bmad-cache/tea/docs/reference/troubleshooting.md +837 -837
  354. package/lib/bmad-cache/tea/docs/tutorials/learn-testing-tea-academy.md +266 -266
  355. package/lib/bmad-cache/tea/docs/tutorials/tea-lite-quickstart.md +465 -465
  356. package/lib/bmad-cache/tea/eslint.config.mjs +141 -141
  357. package/lib/bmad-cache/tea/package-lock.json +16046 -16046
  358. package/lib/bmad-cache/tea/package.json +118 -118
  359. package/lib/bmad-cache/tea/prettier.config.mjs +32 -32
  360. package/lib/bmad-cache/tea/src/agents/bmad-tea/SKILL.md +80 -80
  361. package/lib/bmad-cache/tea/src/agents/bmad-tea/customize.toml +104 -104
  362. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/adr-quality-readiness-checklist.md +377 -377
  363. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/api-request.md +563 -563
  364. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/api-testing-patterns.md +915 -915
  365. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/auth-session.md +548 -548
  366. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/burn-in.md +273 -273
  367. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/ci-burn-in.md +717 -717
  368. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/component-tdd.md +486 -486
  369. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/contract-testing.md +1066 -1066
  370. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/data-factories.md +500 -500
  371. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/email-auth.md +721 -721
  372. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/error-handling.md +725 -725
  373. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/feature-flags.md +750 -750
  374. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/file-utils.md +456 -456
  375. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/fixture-architecture.md +401 -401
  376. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/fixtures-composition.md +382 -382
  377. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/intercept-network-call.md +426 -426
  378. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/log.md +426 -426
  379. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/network-error-monitor.md +401 -401
  380. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/network-first.md +486 -486
  381. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/network-recorder.md +527 -527
  382. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/nfr-criteria.md +670 -670
  383. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/overview.md +286 -286
  384. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/pact-broker-webhooks.md +237 -237
  385. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/pact-consumer-di.md +310 -310
  386. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/pact-consumer-framework-setup.md +704 -704
  387. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/pact-mcp.md +205 -205
  388. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/pactjs-utils-consumer-helpers.md +379 -379
  389. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/pactjs-utils-overview.md +219 -219
  390. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/pactjs-utils-provider-verifier.md +397 -397
  391. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/pactjs-utils-request-filter.md +224 -224
  392. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/pactjs-utils-zod-to-pact.md +262 -262
  393. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/playwright-cli.md +280 -280
  394. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/playwright-config.md +734 -734
  395. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/probability-impact.md +601 -601
  396. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/recurse.md +421 -421
  397. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/risk-governance.md +615 -615
  398. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/selective-testing.md +732 -732
  399. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/selector-resilience.md +527 -527
  400. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/test-healing-patterns.md +644 -644
  401. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/test-levels-framework.md +473 -473
  402. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/test-priorities-matrix.md +373 -373
  403. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/test-quality.md +664 -664
  404. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/timing-debugging.md +372 -372
  405. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/visual-debugging.md +527 -527
  406. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/webhook-module-setup.md +122 -122
  407. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/webhook-providers.md +155 -155
  408. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/webhook-risk-guidance.md +114 -114
  409. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/webhook-template-matchers.md +160 -160
  410. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/webhook-testing-fundamentals.md +42 -42
  411. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/webhook-timeout-error.md +130 -130
  412. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/webhook-waiting-querying.md +167 -167
  413. package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/tea-index.csv +52 -52
  414. package/lib/bmad-cache/tea/src/module-help.csv +11 -11
  415. package/lib/bmad-cache/tea/src/module.yaml +307 -307
  416. package/lib/bmad-cache/tea/src/workflows/testarch/README.md +76 -76
  417. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/SKILL.md +129 -129
  418. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/checklist.md +198 -198
  419. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/customize.toml +40 -40
  420. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/data/curriculum.yaml +129 -129
  421. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/data/quiz-questions.yaml +206 -206
  422. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/data/role-paths.yaml +136 -136
  423. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/data/session-content-map.yaml +219 -219
  424. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/data/tea-resources-index.yaml +394 -394
  425. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/instructions.md +137 -137
  426. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-01-init.md +235 -235
  427. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-01b-continue.md +147 -147
  428. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-02-assess.md +258 -258
  429. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-03-session-menu.md +219 -219
  430. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-04-session-01.md +460 -460
  431. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-04-session-02.md +465 -465
  432. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-04-session-03.md +301 -301
  433. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-04-session-04.md +234 -234
  434. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-04-session-05.md +234 -234
  435. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-04-session-06.md +209 -209
  436. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-04-session-07.md +220 -220
  437. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-05-completion.md +347 -347
  438. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-e/step-e-01-assess-workflow.md +141 -141
  439. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-e/step-e-02-apply-edits.md +130 -130
  440. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-v/step-v-01-validate.md +272 -272
  441. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/templates/certificate-template.md +86 -86
  442. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/templates/progress-template.yaml +95 -95
  443. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/templates/session-notes-template.md +83 -83
  444. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/workflow-plan-teach-me-testing.md +950 -950
  445. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/SKILL.md +85 -85
  446. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/atdd-checklist-template.md +394 -394
  447. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/checklist.md +375 -375
  448. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/customize.toml +40 -40
  449. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/instructions.md +44 -44
  450. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/adr-quality-readiness-checklist.md +377 -377
  451. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/api-request.md +563 -563
  452. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/api-testing-patterns.md +915 -915
  453. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/auth-session.md +548 -548
  454. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/burn-in.md +273 -273
  455. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/ci-burn-in.md +717 -717
  456. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/component-tdd.md +486 -486
  457. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/contract-testing.md +1067 -1067
  458. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/data-factories.md +500 -500
  459. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/email-auth.md +721 -721
  460. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/error-handling.md +725 -725
  461. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/feature-flags.md +750 -750
  462. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/file-utils.md +456 -456
  463. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/fixture-architecture.md +401 -401
  464. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/fixtures-composition.md +382 -382
  465. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/intercept-network-call.md +426 -426
  466. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/log.md +426 -426
  467. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/network-error-monitor.md +401 -401
  468. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/network-first.md +486 -486
  469. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/network-recorder.md +527 -527
  470. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/nfr-criteria.md +670 -670
  471. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/overview.md +286 -286
  472. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/pact-broker-webhooks.md +237 -237
  473. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/pact-consumer-di.md +310 -310
  474. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/pact-consumer-framework-setup.md +757 -757
  475. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/pact-mcp.md +205 -205
  476. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/pactjs-utils-consumer-helpers.md +380 -380
  477. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/pactjs-utils-overview.md +219 -219
  478. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/pactjs-utils-provider-verifier.md +397 -397
  479. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/pactjs-utils-request-filter.md +224 -224
  480. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/pactjs-utils-zod-to-pact.md +262 -262
  481. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/playwright-cli.md +280 -280
  482. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/playwright-config.md +734 -734
  483. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/probability-impact.md +601 -601
  484. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/recurse.md +421 -421
  485. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/risk-governance.md +615 -615
  486. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/selective-testing.md +732 -732
  487. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/selector-resilience.md +527 -527
  488. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/test-healing-patterns.md +644 -644
  489. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/test-levels-framework.md +473 -473
  490. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/test-priorities-matrix.md +373 -373
  491. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/test-quality.md +664 -664
  492. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/timing-debugging.md +372 -372
  493. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/visual-debugging.md +527 -527
  494. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/webhook-module-setup.md +122 -122
  495. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/webhook-providers.md +155 -155
  496. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/webhook-risk-guidance.md +114 -114
  497. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/webhook-template-matchers.md +160 -160
  498. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/webhook-testing-fundamentals.md +42 -42
  499. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/webhook-timeout-error.md +130 -130
  500. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/webhook-waiting-querying.md +167 -167
  501. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/tea-index.csv +52 -52
  502. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-01-preflight-and-context.md +244 -244
  503. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-01b-resume.md +96 -96
  504. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-02-generation-mode.md +125 -125
  505. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-03-test-strategy.md +110 -110
  506. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-04-generate-tests.md +335 -335
  507. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-04a-subagent-api-failing.md +294 -294
  508. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-04b-subagent-e2e-failing.md +244 -244
  509. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-04c-aggregate.md +394 -394
  510. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-05-validate-and-complete.md +123 -123
  511. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-e/step-01-assess.md +65 -65
  512. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-e/step-02-apply-edit.md +68 -68
  513. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-v/step-01-validate.md +75 -75
  514. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/validation-report-20260127-095021.md +73 -73
  515. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/validation-report-20260127-102401.md +116 -116
  516. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/workflow-plan.md +21 -21
  517. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/workflow.yaml +46 -46
  518. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/SKILL.md +85 -85
  519. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/checklist.md +611 -611
  520. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/customize.toml +40 -40
  521. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/instructions.md +49 -49
  522. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/adr-quality-readiness-checklist.md +377 -377
  523. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/api-request.md +563 -563
  524. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/api-testing-patterns.md +915 -915
  525. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/auth-session.md +548 -548
  526. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/burn-in.md +273 -273
  527. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/ci-burn-in.md +717 -717
  528. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/component-tdd.md +486 -486
  529. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/contract-testing.md +1066 -1066
  530. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/data-factories.md +500 -500
  531. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/email-auth.md +721 -721
  532. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/error-handling.md +725 -725
  533. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/feature-flags.md +750 -750
  534. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/file-utils.md +456 -456
  535. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/fixture-architecture.md +401 -401
  536. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/fixtures-composition.md +382 -382
  537. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/intercept-network-call.md +426 -426
  538. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/log.md +426 -426
  539. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/network-error-monitor.md +401 -401
  540. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/network-first.md +486 -486
  541. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/network-recorder.md +527 -527
  542. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/nfr-criteria.md +670 -670
  543. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/overview.md +286 -286
  544. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/pact-broker-webhooks.md +237 -237
  545. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/pact-consumer-di.md +310 -310
  546. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/pact-consumer-framework-setup.md +757 -757
  547. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/pact-mcp.md +205 -205
  548. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/pactjs-utils-consumer-helpers.md +380 -380
  549. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/pactjs-utils-overview.md +216 -216
  550. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/pactjs-utils-provider-verifier.md +397 -397
  551. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/pactjs-utils-request-filter.md +224 -224
  552. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/playwright-cli.md +280 -280
  553. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/playwright-config.md +734 -734
  554. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/probability-impact.md +601 -601
  555. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/recurse.md +421 -421
  556. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/risk-governance.md +615 -615
  557. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/selective-testing.md +732 -732
  558. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/selector-resilience.md +527 -527
  559. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/test-healing-patterns.md +644 -644
  560. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/test-levels-framework.md +473 -473
  561. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/test-priorities-matrix.md +373 -373
  562. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/test-quality.md +664 -664
  563. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/timing-debugging.md +372 -372
  564. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/visual-debugging.md +527 -527
  565. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/webhook-module-setup.md +122 -122
  566. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/webhook-providers.md +155 -155
  567. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/webhook-risk-guidance.md +114 -114
  568. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/webhook-template-matchers.md +160 -160
  569. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/webhook-testing-fundamentals.md +42 -42
  570. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/webhook-timeout-error.md +130 -130
  571. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/webhook-waiting-querying.md +167 -167
  572. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/tea-index.csv +51 -51
  573. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-c/step-01-preflight-and-context.md +237 -237
  574. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-c/step-01b-resume.md +94 -94
  575. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-c/step-02-identify-targets.md +169 -169
  576. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-c/step-03-generate-tests.md +394 -394
  577. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-c/step-03a-subagent-api.md +271 -271
  578. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-c/step-03b-subagent-backend.md +246 -246
  579. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-c/step-03b-subagent-e2e.md +213 -213
  580. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-c/step-03c-aggregate.md +398 -398
  581. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-c/step-04-validate-and-summarize.md +114 -114
  582. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-e/step-01-assess.md +65 -65
  583. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-e/step-02-apply-edit.md +68 -68
  584. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-v/step-01-validate.md +75 -75
  585. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/validation-report-20260127-095021.md +72 -72
  586. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/validation-report-20260127-102401.md +114 -114
  587. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/workflow-plan.md +20 -20
  588. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/workflow.yaml +53 -53
  589. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/SKILL.md +85 -85
  590. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/azure-pipelines-template.yaml +155 -155
  591. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/checklist.md +289 -289
  592. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/customize.toml +40 -40
  593. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/github-actions-template.yaml +328 -328
  594. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/gitlab-ci-template.yaml +158 -158
  595. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/harness-pipeline-template.yaml +160 -160
  596. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/instructions.md +44 -44
  597. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/jenkins-pipeline-template.groovy +129 -129
  598. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/adr-quality-readiness-checklist.md +377 -377
  599. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/api-request.md +563 -563
  600. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/api-testing-patterns.md +915 -915
  601. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/auth-session.md +548 -548
  602. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/burn-in.md +273 -273
  603. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/ci-burn-in.md +717 -717
  604. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/component-tdd.md +486 -486
  605. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/contract-testing.md +1066 -1066
  606. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/data-factories.md +500 -500
  607. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/email-auth.md +721 -721
  608. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/error-handling.md +725 -725
  609. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/feature-flags.md +750 -750
  610. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/file-utils.md +456 -456
  611. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/fixture-architecture.md +401 -401
  612. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/fixtures-composition.md +382 -382
  613. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/intercept-network-call.md +426 -426
  614. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/log.md +426 -426
  615. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/network-error-monitor.md +401 -401
  616. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/network-first.md +486 -486
  617. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/network-recorder.md +527 -527
  618. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/nfr-criteria.md +670 -670
  619. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/overview.md +286 -286
  620. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/pact-broker-webhooks.md +237 -237
  621. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/pact-consumer-di.md +310 -310
  622. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/pact-consumer-framework-setup.md +757 -757
  623. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/pact-mcp.md +205 -205
  624. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/pactjs-utils-consumer-helpers.md +380 -380
  625. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/pactjs-utils-overview.md +216 -216
  626. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/pactjs-utils-provider-verifier.md +397 -397
  627. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/pactjs-utils-request-filter.md +224 -224
  628. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/playwright-cli.md +280 -280
  629. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/playwright-config.md +734 -734
  630. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/probability-impact.md +601 -601
  631. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/recurse.md +421 -421
  632. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/risk-governance.md +615 -615
  633. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/selective-testing.md +732 -732
  634. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/selector-resilience.md +527 -527
  635. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/test-healing-patterns.md +644 -644
  636. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/test-levels-framework.md +473 -473
  637. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/test-priorities-matrix.md +373 -373
  638. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/test-quality.md +664 -664
  639. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/timing-debugging.md +372 -372
  640. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/visual-debugging.md +527 -527
  641. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/webhook-module-setup.md +122 -122
  642. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/webhook-providers.md +155 -155
  643. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/webhook-risk-guidance.md +114 -114
  644. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/webhook-template-matchers.md +160 -160
  645. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/webhook-testing-fundamentals.md +42 -42
  646. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/webhook-timeout-error.md +130 -130
  647. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/webhook-waiting-querying.md +167 -167
  648. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/tea-index.csv +51 -51
  649. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/steps-c/step-01-preflight.md +158 -158
  650. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/steps-c/step-01b-resume.md +110 -110
  651. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/steps-c/step-02-generate-pipeline.md +293 -293
  652. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/steps-c/step-03-configure-quality-gates.md +145 -145
  653. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/steps-c/step-04-validate-and-summary.md +100 -100
  654. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/steps-e/step-01-assess.md +65 -65
  655. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/steps-e/step-02-apply-edit.md +68 -68
  656. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/steps-v/step-01-validate.md +89 -89
  657. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/validation-report-20260127-095021.md +72 -72
  658. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/validation-report-20260127-102401.md +114 -114
  659. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/workflow-plan.md +20 -20
  660. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/workflow.yaml +48 -48
  661. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/SKILL.md +85 -85
  662. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/checklist.md +345 -345
  663. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/customize.toml +40 -40
  664. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/instructions.md +44 -44
  665. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/adr-quality-readiness-checklist.md +377 -377
  666. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/api-request.md +563 -563
  667. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/api-testing-patterns.md +915 -915
  668. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/auth-session.md +548 -548
  669. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/burn-in.md +273 -273
  670. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/ci-burn-in.md +717 -717
  671. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/component-tdd.md +486 -486
  672. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/contract-testing.md +1066 -1066
  673. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/data-factories.md +500 -500
  674. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/email-auth.md +721 -721
  675. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/error-handling.md +725 -725
  676. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/feature-flags.md +750 -750
  677. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/file-utils.md +456 -456
  678. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/fixture-architecture.md +401 -401
  679. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/fixtures-composition.md +382 -382
  680. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/intercept-network-call.md +426 -426
  681. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/log.md +426 -426
  682. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/network-error-monitor.md +401 -401
  683. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/network-first.md +486 -486
  684. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/network-recorder.md +527 -527
  685. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/nfr-criteria.md +670 -670
  686. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/overview.md +286 -286
  687. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/pact-broker-webhooks.md +237 -237
  688. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/pact-consumer-di.md +310 -310
  689. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/pact-consumer-framework-setup.md +757 -757
  690. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/pact-mcp.md +205 -205
  691. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/pactjs-utils-consumer-helpers.md +380 -380
  692. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/pactjs-utils-overview.md +216 -216
  693. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/pactjs-utils-provider-verifier.md +397 -397
  694. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/pactjs-utils-request-filter.md +224 -224
  695. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/playwright-cli.md +280 -280
  696. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/playwright-config.md +734 -734
  697. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/probability-impact.md +601 -601
  698. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/recurse.md +421 -421
  699. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/risk-governance.md +615 -615
  700. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/selective-testing.md +732 -732
  701. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/selector-resilience.md +527 -527
  702. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/test-healing-patterns.md +644 -644
  703. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/test-levels-framework.md +473 -473
  704. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/test-priorities-matrix.md +373 -373
  705. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/test-quality.md +664 -664
  706. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/timing-debugging.md +372 -372
  707. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/visual-debugging.md +527 -527
  708. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/webhook-module-setup.md +122 -122
  709. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/webhook-providers.md +155 -155
  710. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/webhook-risk-guidance.md +114 -114
  711. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/webhook-template-matchers.md +160 -160
  712. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/webhook-testing-fundamentals.md +42 -42
  713. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/webhook-timeout-error.md +130 -130
  714. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/webhook-waiting-querying.md +167 -167
  715. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/tea-index.csv +51 -51
  716. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/steps-c/step-01-preflight.md +132 -132
  717. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/steps-c/step-01b-resume.md +116 -116
  718. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/steps-c/step-02-select-framework.md +117 -117
  719. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/steps-c/step-03-scaffold-framework.md +328 -328
  720. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/steps-c/step-04-docs-and-scripts.md +105 -105
  721. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/steps-c/step-05-validate-and-summary.md +101 -101
  722. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/steps-e/step-01-assess.md +65 -65
  723. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/steps-e/step-02-apply-edit.md +68 -68
  724. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/steps-v/step-01-validate.md +75 -75
  725. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/validation-report-20260127-095021.md +73 -73
  726. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/validation-report-20260127-102401.md +116 -116
  727. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/workflow-plan.md +22 -22
  728. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/workflow.yaml +48 -48
  729. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/SKILL.md +85 -85
  730. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/checklist.md +407 -407
  731. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/customize.toml +40 -40
  732. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/instructions.md +43 -43
  733. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/nfr-report-template.md +470 -470
  734. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/adr-quality-readiness-checklist.md +377 -377
  735. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/api-request.md +563 -563
  736. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/api-testing-patterns.md +915 -915
  737. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/auth-session.md +548 -548
  738. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/burn-in.md +273 -273
  739. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/ci-burn-in.md +717 -717
  740. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/component-tdd.md +486 -486
  741. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/contract-testing.md +1066 -1066
  742. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/data-factories.md +500 -500
  743. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/email-auth.md +721 -721
  744. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/error-handling.md +725 -725
  745. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/feature-flags.md +750 -750
  746. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/file-utils.md +456 -456
  747. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/fixture-architecture.md +401 -401
  748. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/fixtures-composition.md +382 -382
  749. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/intercept-network-call.md +426 -426
  750. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/log.md +426 -426
  751. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/network-error-monitor.md +401 -401
  752. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/network-first.md +486 -486
  753. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/network-recorder.md +527 -527
  754. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/nfr-criteria.md +670 -670
  755. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/overview.md +286 -286
  756. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/pact-broker-webhooks.md +237 -237
  757. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/pact-consumer-di.md +310 -310
  758. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/pact-consumer-framework-setup.md +757 -757
  759. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/pact-mcp.md +205 -205
  760. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/pactjs-utils-consumer-helpers.md +380 -380
  761. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/pactjs-utils-overview.md +216 -216
  762. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/pactjs-utils-provider-verifier.md +397 -397
  763. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/pactjs-utils-request-filter.md +224 -224
  764. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/playwright-cli.md +280 -280
  765. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/playwright-config.md +734 -734
  766. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/probability-impact.md +601 -601
  767. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/recurse.md +421 -421
  768. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/risk-governance.md +615 -615
  769. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/selective-testing.md +732 -732
  770. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/selector-resilience.md +527 -527
  771. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/test-healing-patterns.md +644 -644
  772. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/test-levels-framework.md +473 -473
  773. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/test-priorities-matrix.md +373 -373
  774. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/test-quality.md +664 -664
  775. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/timing-debugging.md +372 -372
  776. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/visual-debugging.md +527 -527
  777. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/webhook-module-setup.md +122 -122
  778. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/webhook-providers.md +155 -155
  779. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/webhook-risk-guidance.md +114 -114
  780. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/webhook-template-matchers.md +160 -160
  781. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/webhook-testing-fundamentals.md +42 -42
  782. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/webhook-timeout-error.md +130 -130
  783. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/webhook-waiting-querying.md +167 -167
  784. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/tea-index.csv +51 -51
  785. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-01-load-context.md +138 -138
  786. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-01b-resume.md +106 -106
  787. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-02-define-thresholds.md +107 -107
  788. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-03-gather-evidence.md +108 -108
  789. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-04-evaluate-and-score.md +254 -254
  790. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-04a-subagent-security.md +138 -138
  791. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-04b-subagent-performance.md +84 -84
  792. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-04c-subagent-reliability.md +85 -85
  793. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-04d-subagent-scalability.md +88 -88
  794. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-04e-aggregate-nfr.md +264 -264
  795. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-05-generate-report.md +116 -116
  796. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-e/step-01-assess.md +65 -65
  797. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-e/step-02-apply-edit.md +68 -68
  798. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-v/step-01-validate.md +75 -75
  799. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/validation-report-20260127-095021.md +73 -73
  800. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/validation-report-20260127-102401.md +116 -116
  801. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/workflow-plan.md +19 -19
  802. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/workflow.yaml +48 -48
  803. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/SKILL.md +87 -87
  804. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/checklist.md +464 -464
  805. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/customize.toml +40 -40
  806. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/instructions.md +104 -104
  807. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/adr-quality-readiness-checklist.md +377 -377
  808. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/api-request.md +563 -563
  809. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/api-testing-patterns.md +915 -915
  810. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/auth-session.md +548 -548
  811. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/burn-in.md +273 -273
  812. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/ci-burn-in.md +717 -717
  813. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/component-tdd.md +486 -486
  814. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/contract-testing.md +1066 -1066
  815. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/data-factories.md +500 -500
  816. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/email-auth.md +721 -721
  817. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/error-handling.md +725 -725
  818. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/feature-flags.md +750 -750
  819. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/file-utils.md +456 -456
  820. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/fixture-architecture.md +401 -401
  821. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/fixtures-composition.md +382 -382
  822. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/intercept-network-call.md +426 -426
  823. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/log.md +426 -426
  824. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/network-error-monitor.md +401 -401
  825. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/network-first.md +486 -486
  826. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/network-recorder.md +527 -527
  827. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/nfr-criteria.md +670 -670
  828. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/overview.md +286 -286
  829. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/pact-broker-webhooks.md +237 -237
  830. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/pact-consumer-di.md +310 -310
  831. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/pact-consumer-framework-setup.md +757 -757
  832. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/pact-mcp.md +205 -205
  833. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/pactjs-utils-consumer-helpers.md +380 -380
  834. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/pactjs-utils-overview.md +216 -216
  835. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/pactjs-utils-provider-verifier.md +397 -397
  836. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/pactjs-utils-request-filter.md +224 -224
  837. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/playwright-cli.md +280 -280
  838. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/playwright-config.md +734 -734
  839. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/probability-impact.md +601 -601
  840. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/recurse.md +421 -421
  841. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/risk-governance.md +615 -615
  842. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/selective-testing.md +732 -732
  843. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/selector-resilience.md +527 -527
  844. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/test-healing-patterns.md +644 -644
  845. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/test-levels-framework.md +473 -473
  846. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/test-priorities-matrix.md +373 -373
  847. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/test-quality.md +664 -664
  848. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/timing-debugging.md +372 -372
  849. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/visual-debugging.md +527 -527
  850. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/webhook-module-setup.md +122 -122
  851. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/webhook-providers.md +155 -155
  852. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/webhook-risk-guidance.md +114 -114
  853. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/webhook-template-matchers.md +160 -160
  854. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/webhook-testing-fundamentals.md +42 -42
  855. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/webhook-timeout-error.md +130 -130
  856. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/webhook-waiting-querying.md +167 -167
  857. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/tea-index.csv +51 -51
  858. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-c/step-01-detect-mode.md +140 -140
  859. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-c/step-01b-resume.md +116 -116
  860. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-c/step-02-load-context.md +248 -248
  861. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-c/step-03-risk-and-testability.md +116 -116
  862. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-c/step-04-coverage-plan.md +129 -129
  863. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-c/step-05-generate-output.md +236 -236
  864. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-e/step-01-assess.md +65 -65
  865. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-e/step-02-apply-edit.md +68 -68
  866. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-v/step-01-validate.md +75 -75
  867. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/test-design-architecture-template.md +233 -233
  868. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/test-design-handoff-template.md +70 -70
  869. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/test-design-qa-template.md +399 -399
  870. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/test-design-template.md +347 -347
  871. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/validation-report-20260127-095021.md +73 -73
  872. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/validation-report-20260127-102401.md +116 -116
  873. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/workflow-plan.md +22 -22
  874. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/workflow.yaml +77 -77
  875. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/SKILL.md +85 -85
  876. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/checklist.md +475 -475
  877. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/customize.toml +40 -40
  878. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/instructions.md +45 -45
  879. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/adr-quality-readiness-checklist.md +377 -377
  880. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/api-request.md +563 -563
  881. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/api-testing-patterns.md +915 -915
  882. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/auth-session.md +548 -548
  883. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/burn-in.md +273 -273
  884. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/ci-burn-in.md +717 -717
  885. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/component-tdd.md +486 -486
  886. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/contract-testing.md +1066 -1066
  887. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/data-factories.md +500 -500
  888. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/email-auth.md +721 -721
  889. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/error-handling.md +725 -725
  890. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/feature-flags.md +750 -750
  891. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/file-utils.md +456 -456
  892. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/fixture-architecture.md +401 -401
  893. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/fixtures-composition.md +382 -382
  894. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/intercept-network-call.md +426 -426
  895. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/log.md +426 -426
  896. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/network-error-monitor.md +401 -401
  897. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/network-first.md +486 -486
  898. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/network-recorder.md +527 -527
  899. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/nfr-criteria.md +670 -670
  900. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/overview.md +286 -286
  901. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/pact-broker-webhooks.md +237 -237
  902. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/pact-consumer-di.md +310 -310
  903. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/pact-consumer-framework-setup.md +757 -757
  904. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/pact-mcp.md +205 -205
  905. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/pactjs-utils-consumer-helpers.md +380 -380
  906. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/pactjs-utils-overview.md +216 -216
  907. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/pactjs-utils-provider-verifier.md +397 -397
  908. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/pactjs-utils-request-filter.md +224 -224
  909. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/playwright-cli.md +280 -280
  910. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/playwright-config.md +734 -734
  911. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/probability-impact.md +601 -601
  912. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/recurse.md +421 -421
  913. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/risk-governance.md +615 -615
  914. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/selective-testing.md +732 -732
  915. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/selector-resilience.md +527 -527
  916. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/test-healing-patterns.md +644 -644
  917. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/test-levels-framework.md +473 -473
  918. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/test-priorities-matrix.md +373 -373
  919. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/test-quality.md +664 -664
  920. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/timing-debugging.md +372 -372
  921. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/visual-debugging.md +527 -527
  922. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/webhook-module-setup.md +122 -122
  923. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/webhook-providers.md +155 -155
  924. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/webhook-risk-guidance.md +114 -114
  925. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/webhook-template-matchers.md +160 -160
  926. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/webhook-testing-fundamentals.md +42 -42
  927. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/webhook-timeout-error.md +130 -130
  928. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/webhook-waiting-querying.md +167 -167
  929. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/tea-index.csv +51 -51
  930. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-c/step-01-load-context.md +197 -197
  931. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-c/step-01b-resume.md +104 -104
  932. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-c/step-02-discover-tests.md +120 -120
  933. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-c/step-03-quality-evaluation.md +274 -274
  934. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-c/step-03a-subagent-determinism.md +257 -257
  935. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-c/step-03b-subagent-isolation.md +125 -125
  936. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-c/step-03c-subagent-maintainability.md +102 -102
  937. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-c/step-03e-subagent-performance.md +117 -117
  938. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-c/step-03f-aggregate-scores.md +277 -277
  939. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-c/step-04-generate-report.md +119 -119
  940. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-e/step-01-assess.md +65 -65
  941. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-e/step-02-apply-edit.md +68 -68
  942. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-v/step-01-validate.md +75 -75
  943. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/test-review-template.md +387 -387
  944. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/validation-report-20260127-095021.md +72 -72
  945. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/validation-report-20260127-102401.md +114 -114
  946. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/workflow-plan.md +18 -18
  947. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/workflow.yaml +48 -48
  948. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/SKILL.md +87 -87
  949. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/checklist.md +671 -671
  950. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/customize.toml +40 -40
  951. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/instructions.md +45 -45
  952. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/adr-quality-readiness-checklist.md +377 -377
  953. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/api-request.md +563 -563
  954. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/api-testing-patterns.md +915 -915
  955. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/auth-session.md +548 -548
  956. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/burn-in.md +273 -273
  957. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/ci-burn-in.md +717 -717
  958. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/component-tdd.md +486 -486
  959. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/contract-testing.md +1066 -1066
  960. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/data-factories.md +500 -500
  961. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/email-auth.md +721 -721
  962. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/error-handling.md +725 -725
  963. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/feature-flags.md +750 -750
  964. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/file-utils.md +456 -456
  965. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/fixture-architecture.md +401 -401
  966. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/fixtures-composition.md +382 -382
  967. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/intercept-network-call.md +426 -426
  968. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/log.md +426 -426
  969. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/network-error-monitor.md +401 -401
  970. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/network-first.md +486 -486
  971. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/network-recorder.md +527 -527
  972. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/nfr-criteria.md +670 -670
  973. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/overview.md +286 -286
  974. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/pact-broker-webhooks.md +237 -237
  975. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/pact-consumer-di.md +310 -310
  976. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/pact-consumer-framework-setup.md +757 -757
  977. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/pact-mcp.md +205 -205
  978. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/pactjs-utils-consumer-helpers.md +380 -380
  979. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/pactjs-utils-overview.md +216 -216
  980. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/pactjs-utils-provider-verifier.md +397 -397
  981. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/pactjs-utils-request-filter.md +224 -224
  982. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/playwright-cli.md +280 -280
  983. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/playwright-config.md +734 -734
  984. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/probability-impact.md +601 -601
  985. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/recurse.md +421 -421
  986. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/risk-governance.md +615 -615
  987. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/selective-testing.md +732 -732
  988. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/selector-resilience.md +527 -527
  989. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/test-healing-patterns.md +644 -644
  990. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/test-levels-framework.md +473 -473
  991. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/test-priorities-matrix.md +373 -373
  992. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/test-quality.md +664 -664
  993. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/timing-debugging.md +372 -372
  994. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/visual-debugging.md +527 -527
  995. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/webhook-module-setup.md +122 -122
  996. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/webhook-providers.md +155 -155
  997. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/webhook-risk-guidance.md +114 -114
  998. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/webhook-template-matchers.md +160 -160
  999. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/webhook-testing-fundamentals.md +42 -42
  1000. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/webhook-timeout-error.md +130 -130
  1001. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/webhook-waiting-querying.md +167 -167
  1002. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/tea-index.csv +51 -51
  1003. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/steps-c/step-01-load-context.md +166 -166
  1004. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/steps-c/step-01b-resume.md +102 -102
  1005. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/steps-c/step-02-discover-tests.md +132 -132
  1006. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/steps-c/step-03-map-criteria.md +101 -101
  1007. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/steps-c/step-04-analyze-gaps.md +628 -628
  1008. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/steps-c/step-05-gate-decision.md +681 -681
  1009. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/steps-e/step-01-assess.md +65 -65
  1010. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/steps-e/step-02-apply-edit.md +68 -68
  1011. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/steps-v/step-01-validate.md +75 -75
  1012. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/trace-template.md +716 -716
  1013. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/validation-report-20260127-095021.md +73 -73
  1014. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/validation-report-20260127-102401.md +116 -116
  1015. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/workflow-plan.md +24 -24
  1016. package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/workflow.yaml +80 -80
  1017. package/lib/bmad-cache/tea/test/README.md +23 -23
  1018. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/critical-actions/actions-as-string.agent.yaml +27 -27
  1019. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/critical-actions/empty-string-in-actions.agent.yaml +30 -30
  1020. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu/empty-menu.agent.yaml +22 -22
  1021. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu/missing-menu.agent.yaml +20 -20
  1022. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu-commands/empty-command-target.agent.yaml +25 -25
  1023. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu-commands/no-command-target.agent.yaml +24 -24
  1024. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu-triggers/camel-case.agent.yaml +25 -25
  1025. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu-triggers/compound-invalid-format.agent.yaml +25 -25
  1026. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu-triggers/compound-mismatched-kebab.agent.yaml +25 -25
  1027. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu-triggers/duplicate-triggers.agent.yaml +31 -31
  1028. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu-triggers/empty-trigger.agent.yaml +25 -25
  1029. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu-triggers/leading-asterisk.agent.yaml +25 -25
  1030. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu-triggers/snake-case.agent.yaml +25 -25
  1031. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu-triggers/trigger-with-spaces.agent.yaml +25 -25
  1032. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/metadata/empty-module-string.agent.yaml +26 -26
  1033. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/metadata/empty-name.agent.yaml +24 -24
  1034. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/metadata/extra-metadata-fields.agent.yaml +27 -27
  1035. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/metadata/missing-id.agent.yaml +23 -23
  1036. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/persona/empty-principles-array.agent.yaml +24 -24
  1037. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/persona/empty-string-in-principles.agent.yaml +27 -27
  1038. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/persona/extra-persona-fields.agent.yaml +27 -27
  1039. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/persona/missing-role.agent.yaml +24 -24
  1040. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/prompts/empty-content.agent.yaml +29 -29
  1041. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/prompts/extra-prompt-fields.agent.yaml +31 -31
  1042. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/prompts/missing-content.agent.yaml +28 -28
  1043. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/prompts/missing-id.agent.yaml +28 -28
  1044. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/top-level/empty-file.agent.yaml +5 -5
  1045. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/top-level/extra-top-level-keys.agent.yaml +28 -28
  1046. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/top-level/missing-agent-key.agent.yaml +11 -11
  1047. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/yaml-errors/invalid-indentation.agent.yaml +19 -19
  1048. package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/yaml-errors/malformed-yaml.agent.yaml +18 -18
  1049. package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/critical-actions/empty-critical-actions.agent.yaml +24 -24
  1050. package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/critical-actions/no-critical-actions.agent.yaml +22 -22
  1051. package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/critical-actions/valid-critical-actions.agent.yaml +27 -27
  1052. package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/menu/multiple-menu-items.agent.yaml +31 -31
  1053. package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/menu/single-menu-item.agent.yaml +22 -22
  1054. package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/menu-commands/all-command-types.agent.yaml +37 -37
  1055. package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/menu-commands/multiple-commands.agent.yaml +24 -24
  1056. package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/menu-triggers/compound-triggers.agent.yaml +31 -31
  1057. package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/menu-triggers/kebab-case-triggers.agent.yaml +34 -34
  1058. package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/metadata/core-agent-with-module.agent.yaml +24 -24
  1059. package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/metadata/empty-module-name-in-path.agent.yaml +24 -24
  1060. package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/metadata/malformed-path-treated-as-core.agent.yaml +24 -24
  1061. package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/metadata/module-agent-correct.agent.yaml +24 -24
  1062. package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/metadata/module-agent-missing-module.agent.yaml +23 -23
  1063. package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/metadata/wrong-module-value.agent.yaml +24 -24
  1064. package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/persona/complete-persona.agent.yaml +24 -24
  1065. package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/prompts/empty-prompts.agent.yaml +24 -24
  1066. package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/prompts/no-prompts.agent.yaml +22 -22
  1067. package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/prompts/valid-prompts-minimal.agent.yaml +28 -28
  1068. package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/prompts/valid-prompts-with-description.agent.yaml +30 -30
  1069. package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/top-level/minimal-core-agent.agent.yaml +24 -24
  1070. package/lib/bmad-cache/tea/test/schema/agent.js +491 -491
  1071. package/lib/bmad-cache/tea/test/test-agent-schema.js +387 -387
  1072. package/lib/bmad-cache/tea/test/test-installation-components.js +422 -422
  1073. package/lib/bmad-cache/tea/test/test-knowledge-base.js +213 -213
  1074. package/lib/bmad-cache/tea/test/test-release-metadata.js +71 -71
  1075. package/lib/bmad-cache/tea/test/unit-test-schema.js +133 -133
  1076. package/lib/bmad-cache/tea/test/validate-agent-schema.js +110 -110
  1077. package/lib/bmad-cache/tea/tools/build-docs.js +575 -575
  1078. package/lib/bmad-cache/tea/tools/fix-doc-links.js +288 -288
  1079. package/lib/bmad-cache/tea/tools/schema/agent.js +491 -491
  1080. package/lib/bmad-cache/tea/tools/validate-agent-schema.js +284 -284
  1081. package/lib/bmad-cache/tea/tools/validate-doc-links.js +371 -371
  1082. package/lib/bmad-cache/tea/tools/validate-tea-workflow-descriptions.js +122 -122
  1083. package/lib/bmad-cache/tea/tools/verify-paths.js +100 -100
  1084. package/lib/bmad-cache/tea/website/README.md +137 -137
  1085. package/lib/bmad-cache/tea/website/astro.config.mjs +183 -183
  1086. package/lib/bmad-cache/tea/website/package-lock.json +6856 -6856
  1087. package/lib/bmad-cache/tea/website/package.json +24 -24
  1088. package/lib/bmad-cache/tea/website/public/img/tea-logo.svg +7 -7
  1089. package/lib/bmad-cache/tea/website/public/robots.txt +37 -37
  1090. package/lib/bmad-cache/tea/website/src/components/Banner.astro +74 -74
  1091. package/lib/bmad-cache/tea/website/src/components/Header.astro +121 -121
  1092. package/lib/bmad-cache/tea/website/src/components/MobileMenuFooter.astro +53 -53
  1093. package/lib/bmad-cache/tea/website/src/content/config.ts +6 -6
  1094. package/lib/bmad-cache/tea/website/src/lib/site-url.js +25 -25
  1095. package/lib/bmad-cache/tea/website/src/pages/404.astro +11 -11
  1096. package/lib/bmad-cache/tea/website/src/rehype-base-paths.js +89 -89
  1097. package/lib/bmad-cache/tea/website/src/rehype-markdown-links.js +117 -117
  1098. package/lib/bmad-cache/tea/website/src/styles/custom.css +518 -518
  1099. package/lib/bmad-cache/tea/website/tsconfig.json +9 -9
  1100. package/lib/bmad-cache/wds/.claude-plugin/marketplace.json +35 -35
  1101. package/lib/bmad-cache/wds/.markdownlint-cli2.yaml +38 -38
  1102. package/lib/bmad-cache/wds/.prettierignore +9 -9
  1103. package/lib/bmad-cache/wds/LICENSE +27 -27
  1104. package/lib/bmad-cache/wds/README.md +139 -139
  1105. package/lib/bmad-cache/wds/_git_preserved/HEAD +1 -1
  1106. package/lib/bmad-cache/wds/_git_preserved/config +13 -13
  1107. package/lib/bmad-cache/wds/_git_preserved/description +1 -1
  1108. package/lib/bmad-cache/wds/_git_preserved/hooks/applypatch-msg.sample +15 -15
  1109. package/lib/bmad-cache/wds/_git_preserved/hooks/commit-msg.sample +24 -24
  1110. package/lib/bmad-cache/wds/_git_preserved/hooks/fsmonitor-watchman.sample +174 -174
  1111. package/lib/bmad-cache/wds/_git_preserved/hooks/post-update.sample +8 -8
  1112. package/lib/bmad-cache/wds/_git_preserved/hooks/pre-applypatch.sample +14 -14
  1113. package/lib/bmad-cache/wds/_git_preserved/hooks/pre-commit.sample +49 -49
  1114. package/lib/bmad-cache/wds/_git_preserved/hooks/pre-merge-commit.sample +13 -13
  1115. package/lib/bmad-cache/wds/_git_preserved/hooks/pre-push.sample +53 -53
  1116. package/lib/bmad-cache/wds/_git_preserved/hooks/pre-rebase.sample +169 -169
  1117. package/lib/bmad-cache/wds/_git_preserved/hooks/pre-receive.sample +24 -24
  1118. package/lib/bmad-cache/wds/_git_preserved/hooks/prepare-commit-msg.sample +42 -42
  1119. package/lib/bmad-cache/wds/_git_preserved/hooks/push-to-checkout.sample +78 -78
  1120. package/lib/bmad-cache/wds/_git_preserved/hooks/sendemail-validate.sample +77 -77
  1121. package/lib/bmad-cache/wds/_git_preserved/hooks/update.sample +128 -128
  1122. package/lib/bmad-cache/wds/_git_preserved/info/exclude +6 -6
  1123. package/lib/bmad-cache/wds/_git_preserved/packed-refs +2 -2
  1124. package/lib/bmad-cache/wds/_git_preserved/refs/heads/main +1 -1
  1125. package/lib/bmad-cache/wds/_git_preserved/refs/remotes/origin/HEAD +1 -1
  1126. package/lib/bmad-cache/wds/_git_preserved/shallow +1 -1
  1127. package/lib/bmad-cache/wds/eslint.config.mjs +152 -152
  1128. package/lib/bmad-cache/wds/package.json +82 -82
  1129. package/lib/bmad-cache/wds/prettier.config.mjs +32 -32
  1130. package/lib/bmad-cache/wds/src/agents/wds-agent-freya-ux/bmad-skill-manifest.yaml +12 -12
  1131. package/lib/bmad-cache/wds/src/agents/wds-agent-saga-analyst/bmad-skill-manifest.yaml +12 -12
  1132. package/lib/bmad-cache/wds/src/module-help.csv +19 -19
  1133. package/lib/bmad-cache/wds/src/module.yaml +145 -145
  1134. package/lib/bmad-cache/wds/src/workflows/wds-0-alignment-signoff/bmad-skill-manifest.yaml +1 -1
  1135. package/lib/bmad-cache/wds/src/workflows/wds-0-project-setup/bmad-skill-manifest.yaml +1 -1
  1136. package/lib/bmad-cache/wds/src/workflows/wds-0-project-setup/resources/wds-1-project-brief/templates/platform-requirements.template.yaml +69 -69
  1137. package/lib/bmad-cache/wds/src/workflows/wds-0-project-setup/resources/wds-7-design-system/templates/catalog.template.html +363 -363
  1138. package/lib/bmad-cache/wds/src/workflows/wds-1-project-brief/bmad-skill-manifest.yaml +1 -1
  1139. package/lib/bmad-cache/wds/src/workflows/wds-1-project-brief/templates/platform-requirements.template.yaml +69 -69
  1140. package/lib/bmad-cache/wds/src/workflows/wds-2-trigger-mapping/bmad-skill-manifest.yaml +1 -1
  1141. package/lib/bmad-cache/wds/src/workflows/wds-3-scenarios/bmad-skill-manifest.yaml +1 -1
  1142. package/lib/bmad-cache/wds/src/workflows/wds-4-ux-design/bmad-skill-manifest.yaml +1 -1
  1143. package/lib/bmad-cache/wds/src/workflows/wds-4-ux-design/templates/design-delivery.template.yaml +104 -104
  1144. package/lib/bmad-cache/wds/src/workflows/wds-4-ux-design/templates/test-scenario.template.yaml +192 -192
  1145. package/lib/bmad-cache/wds/src/workflows/wds-5-agentic-development/bmad-skill-manifest.yaml +1 -1
  1146. package/lib/bmad-cache/wds/src/workflows/wds-5-agentic-development/templates/components/dev-mode.css +164 -164
  1147. package/lib/bmad-cache/wds/src/workflows/wds-5-agentic-development/templates/components/dev-mode.html +18 -18
  1148. package/lib/bmad-cache/wds/src/workflows/wds-5-agentic-development/templates/components/dev-mode.js +430 -430
  1149. package/lib/bmad-cache/wds/src/workflows/wds-5-agentic-development/templates/demo-data-template.json +63 -63
  1150. package/lib/bmad-cache/wds/src/workflows/wds-5-agentic-development/templates/page-template.html +465 -465
  1151. package/lib/bmad-cache/wds/src/workflows/wds-5-agentic-development/templates/work-file-template.yaml +264 -264
  1152. package/lib/bmad-cache/wds/src/workflows/wds-6-asset-generation/bmad-skill-manifest.yaml +1 -1
  1153. package/lib/bmad-cache/wds/src/workflows/wds-7-design-system/bmad-skill-manifest.yaml +1 -1
  1154. package/lib/bmad-cache/wds/src/workflows/wds-7-design-system/templates/catalog.template.html +363 -363
  1155. package/lib/bmad-cache/wds/src/workflows/wds-8-product-evolution/bmad-skill-manifest.yaml +1 -1
  1156. package/lib/bmad-customize/bmm-analyst.customize.yaml +8 -8
  1157. package/lib/bmad-customize/bmm-architect.customize.yaml +8 -8
  1158. package/lib/bmad-customize/bmm-bmad-master.customize.yaml +6 -6
  1159. package/lib/bmad-customize/bmm-dev.customize.yaml +8 -8
  1160. package/lib/bmad-customize/bmm-pm.customize.yaml +8 -8
  1161. package/lib/bmad-customize/bmm-qa.customize.yaml +26 -26
  1162. package/lib/bmad-customize/bmm-sm.customize.yaml +8 -8
  1163. package/lib/bmad-customize/bmm-tech-writer.customize.yaml +8 -8
  1164. package/lib/bmad-customize/bmm-ux-designer.customize.yaml +8 -8
  1165. package/lib/bmad-extension-plugin/.claude-plugin/marketplace.json +1 -1
  1166. package/lib/bmad.js +191 -1
  1167. package/lib/installer.js +2024 -2024
  1168. package/lib/merge/roomodes.js +125 -125
  1169. package/lib/methodology/version.json +7 -7
  1170. package/lib/mil498-templates/OCD.md +169 -169
  1171. package/lib/mil498-templates/README.md +4 -4
  1172. package/lib/mil498-templates/SDD.md +163 -163
  1173. package/lib/mil498-templates/SDP.md +307 -307
  1174. package/lib/mil498-templates/SRS.md +219 -219
  1175. package/lib/mil498-templates/SSDD.md +154 -154
  1176. package/lib/mil498-templates/SSS.md +225 -225
  1177. package/lib/mil498-templates/STD.md +188 -188
  1178. package/lib/profile.js +130 -130
  1179. package/lib/reconfigure.js +334 -334
  1180. package/lib/skill-authoring.js +732 -732
  1181. package/lib/templates/agents-md.template.md +67 -67
  1182. package/lib/templates/clinerules.template.md +13 -13
  1183. package/lib/templates/instruction-block-onprem.template.md +86 -86
  1184. package/lib/templates/instruction-block-universal.template.md +29 -29
  1185. package/lib/templates/project-context.template.md +47 -47
  1186. package/lib/templates/roomodes.template.yaml +96 -96
  1187. package/lib/uninstall.js +314 -314
  1188. package/lib/warning-filter.js +245 -245
  1189. package/mil498/OCD.md +169 -169
  1190. package/mil498/README.md +4 -4
  1191. package/mil498/SDP.md +307 -307
  1192. package/mil498/SRS.md +219 -219
  1193. package/mil498/SSDD.md +154 -154
  1194. package/mil498/SSS.md +225 -225
  1195. package/mil498/STD.md +188 -188
  1196. package/package.json +57 -57
  1197. package/scripts/build-bmad-cache.js +494 -494
  1198. package/skills/README.md +473 -473
  1199. package/skills/add-sprint/SKILL.md +204 -204
  1200. package/skills/add-sprint/skill.json +7 -7
  1201. package/skills/add-to-sprint/SKILL.md +270 -270
  1202. package/skills/add-to-sprint/skill.json +7 -7
  1203. package/skills/ai-audit-trail/SKILL.md +19 -19
  1204. package/skills/ai-audit-trail/skill.json +20 -20
  1205. package/skills/auto-bug-detection/SKILL.md +165 -165
  1206. package/skills/auto-bug-detection/skill.json +8 -8
  1207. package/skills/bmad-sprint-planning/SKILL.md +362 -362
  1208. package/skills/bmad-sprint-planning/skill.json +7 -7
  1209. package/skills/bmad-sprint-status/SKILL.md +312 -312
  1210. package/skills/bmad-sprint-status/skill.json +7 -7
  1211. package/skills/cleanup-done/SKILL.md +242 -242
  1212. package/skills/cleanup-done/skill.json +7 -7
  1213. package/skills/close-sprint/SKILL.md +409 -409
  1214. package/skills/close-sprint/skill.json +7 -7
  1215. package/skills/code-review/SKILL.md +79 -79
  1216. package/skills/code-review/claude-code.md +64 -64
  1217. package/skills/code-review/cline.md +55 -55
  1218. package/skills/code-review/generic.md +39 -39
  1219. package/skills/code-review/skill.json +7 -7
  1220. package/skills/commit-message/SKILL.md +75 -75
  1221. package/skills/commit-message/generic.md +75 -75
  1222. package/skills/commit-message/skill.json +7 -7
  1223. package/skills/cpp-best-practices/SKILL.md +230 -230
  1224. package/skills/cpp-best-practices/examples/modern-idioms.md +189 -189
  1225. package/skills/cpp-best-practices/examples/naming-and-organization.md +102 -102
  1226. package/skills/cpp-best-practices/skill.json +25 -25
  1227. package/skills/create-hardened-docker-skill/README.md +85 -85
  1228. package/skills/create-hardened-docker-skill/SKILL.md +633 -633
  1229. package/skills/create-hardened-docker-skill/scripts/create-all.sh +489 -489
  1230. package/skills/create-hardened-docker-skill/skill.json +7 -7
  1231. package/skills/csharp-best-practices/SKILL.md +274 -274
  1232. package/skills/csharp-best-practices/skill.json +23 -23
  1233. package/skills/docker-hardening-verification/scripts/verify-hardening.sh +39 -39
  1234. package/skills/docker-image-signing/scripts/sign-image.sh +33 -33
  1235. package/skills/document-revision-history/SKILL.md +100 -100
  1236. package/skills/document-revision-history/skill.json +18 -18
  1237. package/skills/generate-backlog/SKILL.md +219 -219
  1238. package/skills/generate-backlog/skill.json +7 -7
  1239. package/skills/git-workflow-skill/README.md +135 -135
  1240. package/skills/git-workflow-skill/SKILL.md +190 -190
  1241. package/skills/git-workflow-skill/hooks/commit-msg +61 -61
  1242. package/skills/git-workflow-skill/hooks/pre-commit +38 -38
  1243. package/skills/git-workflow-skill/hooks/prepare-commit-msg +56 -56
  1244. package/skills/git-workflow-skill/scripts/finish-feature.sh +192 -192
  1245. package/skills/git-workflow-skill/scripts/install-hooks.sh +55 -55
  1246. package/skills/git-workflow-skill/scripts/start-feature.sh +110 -110
  1247. package/skills/git-workflow-skill/scripts/validate-workflow.sh +229 -229
  1248. package/skills/git-workflow-skill/skill.json +21 -21
  1249. package/skills/js-ts-security-skill/scripts/verify-security.sh +136 -136
  1250. package/skills/js-ts-security-skill/skill.json +17 -17
  1251. package/skills/modify-sprint/SKILL.md +341 -341
  1252. package/skills/modify-sprint/skill.json +7 -7
  1253. package/skills/open-presentation/SKILL.md +31 -31
  1254. package/skills/open-presentation/skill.json +11 -11
  1255. package/skills/prioritize-backlog/SKILL.md +242 -242
  1256. package/skills/prioritize-backlog/skill.json +7 -7
  1257. package/skills/python-best-practices/SKILL.md +381 -381
  1258. package/skills/python-best-practices/skill.json +26 -26
  1259. package/skills/remove-from-sprint/SKILL.md +213 -213
  1260. package/skills/remove-from-sprint/skill.json +7 -7
  1261. package/skills/self-signed-cert/scripts/generate-cert.sh +43 -43
  1262. package/skills/skill-creator/SKILL.md +211 -211
  1263. package/skills/skill-creator/claude-code.md +64 -64
  1264. package/skills/skill-creator/generic.md +192 -192
  1265. package/skills/skill-creator/references/output-patterns.md +82 -82
  1266. package/skills/skill-creator/references/workflows.md +28 -28
  1267. package/skills/skill-creator/scripts/init_skill.py +208 -208
  1268. package/skills/skill-creator/scripts/package_skill.py +99 -99
  1269. package/skills/skill-creator/scripts/quick_validate.py +113 -113
  1270. package/skills/skill-creator/skill.json +8 -8
  1271. package/skills/sprint-status-view/SKILL.md +212 -212
  1272. package/skills/sprint-status-view/skill.json +7 -7
  1273. package/skills/story-status-lookup/SKILL.md +106 -106
  1274. package/skills/story-status-lookup/skill.json +8 -8
  1275. package/skills/test-generator/SKILL.md +74 -74
  1276. package/skills/test-generator/claude-code.md +103 -103
  1277. package/skills/test-generator/cline.md +69 -69
  1278. package/skills/test-generator/generic.md +61 -61
  1279. package/skills/test-generator/skill.json +18 -18
  1280. package/skills/vercel-react-best-practices/SKILL.md +105 -105
  1281. package/skills/vercel-react-best-practices/claude-code.md +80 -80
  1282. package/skills/vercel-react-best-practices/generic.md +105 -105
  1283. package/skills/vercel-react-best-practices/skill.json +19 -19
  1284. package/skills/verify-hardened-docker-skill/README.md +85 -85
  1285. package/skills/verify-hardened-docker-skill/SKILL.md +438 -438
  1286. package/skills/verify-hardened-docker-skill/scripts/verify-docker-hardening.sh +439 -439
  1287. package/skills/verify-hardened-docker-skill/skill.json +7 -7
  1288. package/lib/.bmad-extension-plugin.build-1264-1777348888201/.claude-plugin/marketplace.json +0 -109
  1289. package/lib/.bmad-extension-plugin.build-1264-1777348888201/skills/module-help.csv +0 -62
  1290. package/lib/.bmad-extension-plugin.build-1264-1777348888201/skills/module.yaml +0 -20
  1291. package/lib/.bmad-extension-plugin.build-24696-1777348768444/.claude-plugin/marketplace.json +0 -109
  1292. package/lib/.bmad-extension-plugin.build-24696-1777348768444/skills/module-help.csv +0 -62
  1293. package/lib/.bmad-extension-plugin.build-24696-1777348768444/skills/module.yaml +0 -20
  1294. package/lib/.bmad-extension-plugin.build-25428-1777348694953/.claude-plugin/marketplace.json +0 -109
  1295. package/lib/.bmad-extension-plugin.build-25428-1777348694953/skills/module-help.csv +0 -62
  1296. package/lib/.bmad-extension-plugin.build-25428-1777348694953/skills/module.yaml +0 -20
  1297. package/lib/bmad-cache/bmb/_git_preserved/logs/HEAD +0 -1
  1298. package/lib/bmad-cache/bmb/_git_preserved/logs/refs/heads/main +0 -1
  1299. package/lib/bmad-cache/bmb/_git_preserved/logs/refs/remotes/origin/HEAD +0 -1
  1300. package/lib/bmad-cache/cis/_git_preserved/logs/HEAD +0 -1
  1301. package/lib/bmad-cache/cis/_git_preserved/logs/refs/heads/main +0 -1
  1302. package/lib/bmad-cache/cis/_git_preserved/logs/refs/remotes/origin/HEAD +0 -1
  1303. package/lib/bmad-cache/gds/_git_preserved/logs/HEAD +0 -1
  1304. package/lib/bmad-cache/gds/_git_preserved/logs/refs/heads/main +0 -1
  1305. package/lib/bmad-cache/gds/_git_preserved/logs/refs/remotes/origin/HEAD +0 -1
  1306. package/lib/bmad-cache/tea/.github/workflows/publish.yaml +0 -168
  1307. package/lib/bmad-cache/tea/.vscode/settings.json +0 -47
  1308. package/lib/bmad-cache/wds/_git_preserved/logs/HEAD +0 -1
  1309. package/lib/bmad-cache/wds/_git_preserved/logs/refs/heads/main +0 -1
  1310. package/lib/bmad-cache/wds/_git_preserved/logs/refs/remotes/origin/HEAD +0 -1
package/lib/installer.js CHANGED
@@ -1,2024 +1,2024 @@
1
- const fs = require('fs-extra');
2
- const path = require('path');
3
- const chalk = require('chalk');
4
- const prompts = require('prompts');
5
- const { getAgent, getAllAgents } = require('./agents');
6
- // Story 22.8 — reuse the canonical stage-directory name so the gitignore policy
7
- // stays in lock-step with lib/bmad.js#stagePlugin / cleanupStage.
8
- const { PLUGIN_STAGE_DIR_NAME } = require('./bmad');
9
-
10
- const MANIFEST_FILE = '.ma-agents.json';
11
- const MANIFEST_VERSION = '1.2.0';
12
- const MA_AGENTS_SOURCE = 'ma-agents';
13
- const TEMPLATE_PATH = path.join(__dirname, 'templates', 'project-context.template.md');
14
- const UNIVERSAL_INSTRUCTION_TEMPLATE_PATH = path.join(__dirname, 'templates', 'instruction-block-universal.template.md');
15
- const ONPREM_INSTRUCTION_TEMPLATE_PATH = path.join(__dirname, 'templates', 'instruction-block-onprem.template.md');
16
- const CLINERULES_TEMPLATE_PATH = path.join(__dirname, 'templates', 'clinerules.template.md');
17
- const EXTRA_TEMPLATE_DIR = path.join(__dirname, 'templates');
18
-
19
- /**
20
- * Story 21.5 AC #6 — Dual-file drift detection error.
21
- *
22
- * Thrown by the installer when the in-marker contents of `.cline/clinerules.md`
23
- * and `.clinerules` diverge (non-whitespace diff). `--yes` does NOT bypass this
24
- * check — reconciliation between the two Cline rule files is user work, and
25
- * silently picking a "winner" could discard intentional edits.
26
- *
27
- * Follows the error-class naming pattern introduced by Story 21.3/21.10's
28
- * RoomodesSlugDivergenceError.
29
- */
30
- class ClinerulesDualFileDriftError extends Error {
31
- constructor({ fileA, fileB, diff }) {
32
- const header = `Cline dual-file drift detected between ${fileA} and ${fileB}.`;
33
- const guidance = 'Reconcile the two files manually (copy the correct marker-block content into both) before re-running install. `--yes` does NOT bypass this check.';
34
- super(`${header}\n${guidance}\n\n--- diff (${fileA} vs. ${fileB}) ---\n${diff}`);
35
- this.name = 'ClinerulesDualFileDriftError';
36
- this.fileA = fileA;
37
- this.fileB = fileB;
38
- this.diff = diff;
39
- }
40
- }
41
-
42
- /**
43
- * Extract the content between MA-AGENTS markers in a file, without the marker
44
- * lines themselves. Returns null when the file does not exist OR has no marker
45
- * pair. Whitespace-only leading/trailing runs inside the block are preserved —
46
- * caller decides whether to normalize before comparing.
47
- */
48
- function _extractMarkerBlockInner(filePath) {
49
- if (!fs.existsSync(filePath)) return null;
50
- const content = fs.readFileSync(filePath, 'utf-8');
51
- const m = content.match(/<!-- MA-AGENTS-START -->([\s\S]*?)<!-- MA-AGENTS-END -->/);
52
- if (!m) return null;
53
- return m[1];
54
- }
55
-
56
- /**
57
- * Story 21.5 AC #6 — Compare in-marker content of `.cline/clinerules.md` and
58
- * `.clinerules` (when both exist). Non-whitespace divergence throws
59
- * ClinerulesDualFileDriftError. If only one file exists, drift detection is
60
- * skipped (AC #6, Task 3.4 "render once, write twice" invariant).
61
- *
62
- * Exposed for tests via module.exports; called internally by
63
- * updateAgentInstructions on the Cline agent path.
64
- */
65
- function checkClinerulesDualFileDrift(projectRoot) {
66
- const pathA = path.join(projectRoot, '.cline', 'clinerules.md');
67
- const pathB = path.join(projectRoot, '.clinerules');
68
- // .clinerules is now a directory (Cline's correct structure) — no file to drift-check.
69
- if (fs.existsSync(pathB) && fs.lstatSync(pathB).isDirectory()) return;
70
- const innerA = _extractMarkerBlockInner(pathA);
71
- const innerB = _extractMarkerBlockInner(pathB);
72
- if (innerA == null || innerB == null) return; // one (or both) absent — skip
73
- const normalize = (s) => s.replace(/\s+/g, ' ').trim();
74
- if (normalize(innerA) === normalize(innerB)) return;
75
- // Build a minimal unified diff (line-level) for the message.
76
- const linesA = innerA.split('\n');
77
- const linesB = innerB.split('\n');
78
- const diffLines = [];
79
- const maxLen = Math.max(linesA.length, linesB.length);
80
- for (let i = 0; i < maxLen; i++) {
81
- const a = linesA[i];
82
- const b = linesB[i];
83
- if (a === b) continue;
84
- if (a !== undefined) diffLines.push(`- ${a}`);
85
- if (b !== undefined) diffLines.push(`+ ${b}`);
86
- }
87
- throw new ClinerulesDualFileDriftError({
88
- fileA: path.relative(projectRoot, pathA).replace(/\\/g, '/'),
89
- fileB: path.relative(projectRoot, pathB).replace(/\\/g, '/'),
90
- diff: diffLines.join('\n')
91
- });
92
- }
93
-
94
- // Story 21.4 — memoize resolved BMAD-output dirs per projectRoot so the
95
- // install loop resolves once (AC #10c) and logs once.
96
- const _bmadOutputDirsCache = new Map();
97
- const _bmadOutputDirsLogged = new Set();
98
-
99
- /**
100
- * Story 21.2 — Universal per-tool instruction block composer.
101
- *
102
- * Reads lib/templates/instruction-block-universal.template.md (always).
103
- * If profile === 'on-prem', appends lib/templates/instruction-block-onprem.template.md
104
- * separated by a single blank line. If the on-prem template is missing when required,
105
- * THROWS — there is no silent fallback (Decision A, AC #3).
106
- *
107
- * Templates contain NO substitution of {{...}} placeholders inside this function; any
108
- * substitution (e.g., {{MANIFEST_PATH}}) is the caller's responsibility, applied AFTER
109
- * composition. This keeps the composer a single-owner entry point that downstream
110
- * stories 21.3/21.4/21.5/21.6 consume without duplication.
111
- *
112
- * @param {{profile: string|undefined, projectRoot: string}} args
113
- * @returns {string} composed template content (with placeholders intact)
114
- */
115
- function composeInstructionBlock({ profile, projectRoot } = {}) {
116
- // projectRoot is accepted for API-stability with downstream stories even though
117
- // the current implementation does not read from it (templates ship with the
118
- // package). Keeping the parameter documented avoids signature churn in 21.6.
119
- void projectRoot;
120
-
121
- let universal;
122
- try {
123
- universal = fs.readFileSync(UNIVERSAL_INSTRUCTION_TEMPLATE_PATH, 'utf-8');
124
- } catch (err) {
125
- throw new Error(
126
- `universal instruction template not found at ${UNIVERSAL_INSTRUCTION_TEMPLATE_PATH} — ma-agents installation may be corrupted: ${err.message}`
127
- );
128
- }
129
-
130
- if (!universal.includes('{{MANIFEST_PATH}}')) {
131
- throw new Error(
132
- `universal instruction template at ${UNIVERSAL_INSTRUCTION_TEMPLATE_PATH} is missing the required {{MANIFEST_PATH}} placeholder`
133
- );
134
- }
135
-
136
- if (profile === 'on-prem') {
137
- if (!fs.existsSync(ONPREM_INSTRUCTION_TEMPLATE_PATH)) {
138
- throw new Error('on-prem profile selected but instruction-block-onprem.template.md is missing');
139
- }
140
- const onprem = fs.readFileSync(ONPREM_INSTRUCTION_TEMPLATE_PATH, 'utf-8');
141
- // Normalize both pieces so the concatenation has exactly one blank line between them
142
- // and no trailing whitespace — feeds NFR46 byte-identity.
143
- return universal.replace(/\s+$/, '') + '\n\n' + onprem.replace(/\s+$/, '') + '\n';
144
- }
145
-
146
- // Normalize trailing whitespace so first-insert and in-place-replace both
147
- // produce byte-identical content inside the markers (NFR46, AC #6).
148
- return universal.replace(/\s+$/, '') + '\n';
149
- }
150
-
151
- /**
152
- * Story 21.4 AC #10 — resolve BMAD output directories once per install.
153
- * Story 22.7 — reads the canonical v6.3.0 config at `_bmad/bmm/config.yaml`.
154
- *
155
- * Precedence:
156
- * a) If `_bmad/bmm/config.yaml` exists AND has explicit `planning_artifacts`,
157
- * `architecture_artifacts`, and `implementation_artifacts`, use those.
158
- * b) Otherwise, fall back to the documented defaults:
159
- * planning: _bmad-output/planning-artifacts
160
- * architecture: _bmad-output/planning-artifacts (co-located default)
161
- * stories: _bmad-output/implementation-artifacts
162
- *
163
- * This resolver NEVER consults the legacy `_bmad/_config/manifest.yaml`.
164
- * If a legacy install is present without the canonical file, the defaults
165
- * are used; the install flow (`lib/bmad.js::installBmad`) invokes
166
- * `ensureCanonicalConfigLocation()` earlier so bmad-method regenerates the
167
- * canonical layout during the install that triggered this call.
168
- *
169
- * YAML parsing is intentionally minimal — we only need a handful of
170
- * top-level scalar keys. Adding a full YAML dependency just for this is
171
- * overkill and would widen the supply chain for one helper.
172
- *
173
- * @param {string} projectRoot
174
- * @returns {{planning: string, architecture: string, stories: string}}
175
- */
176
- function resolveBmadOutputDirs(projectRoot) {
177
- if (_bmadOutputDirsCache.has(projectRoot)) {
178
- return _bmadOutputDirsCache.get(projectRoot);
179
- }
180
-
181
- const defaults = {
182
- planning: '_bmad-output/planning-artifacts',
183
- architecture: '_bmad-output/planning-artifacts',
184
- stories: '_bmad-output/implementation-artifacts'
185
- };
186
-
187
- const configPath = path.join(projectRoot, '_bmad', 'bmm', 'config.yaml');
188
- const dirs = { ...defaults };
189
-
190
- if (fs.existsSync(configPath)) {
191
- try {
192
- const yamlText = fs.readFileSync(configPath, 'utf-8');
193
- const scanScalar = (key) => {
194
- // Match `key: "value"` or `key: value` at line start (indent tolerated),
195
- // ignoring commented lines. Value is the quoted string or the bare token.
196
- const re = new RegExp(`^[\\t ]*${key}\\s*:\\s*(?:"([^"]*)"|'([^']*)'|([^#\\n\\r]+?))\\s*(?:#.*)?$`, 'm');
197
- const m = yamlText.match(re);
198
- if (!m) return null;
199
- const raw = m[1] || m[2] || m[3] || '';
200
- return raw.trim().replace(/\\/g, '/');
201
- };
202
- const planning = scanScalar('planning_artifacts');
203
- const architecture = scanScalar('architecture_artifacts');
204
- const stories = scanScalar('implementation_artifacts');
205
- if (planning) dirs.planning = planning;
206
- if (architecture) dirs.architecture = architecture;
207
- if (stories) dirs.stories = stories;
208
- } catch {
209
- // Malformed or unreadable config — fall back silently to defaults.
210
- }
211
- }
212
-
213
- _bmadOutputDirsCache.set(projectRoot, dirs);
214
- if (!_bmadOutputDirsLogged.has(projectRoot)) {
215
- _bmadOutputDirsLogged.add(projectRoot);
216
- console.log(
217
- `Resolved BMAD output dirs: planning=${dirs.planning}, architecture=${dirs.architecture}, stories=${dirs.stories}`
218
- );
219
- }
220
- return dirs;
221
- }
222
-
223
- // Claude Code hook configuration for MANIFEST verification
224
- const CLAUDE_CODE_HOOK_ID = 'ma-agents-verify-manifest';
225
- const CLAUDE_CODE_SETTINGS_FILE = '.claude/settings.json';
226
-
227
- function getPackageVersion() {
228
- const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
229
- return pkg.version;
230
- }
231
-
232
- function getBmadVersion() {
233
- try {
234
- const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'bmad-method', 'package.json'), 'utf-8'));
235
- return pkg.version;
236
- } catch {
237
- return null;
238
- }
239
- }
240
-
241
- // --- Manifest functions ---
242
-
243
- function readManifest(installPath) {
244
- const manifestPath = path.join(installPath, MANIFEST_FILE);
245
- if (!fs.existsSync(manifestPath)) {
246
- return null;
247
- }
248
- try {
249
- return JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
250
- } catch {
251
- return null;
252
- }
253
- }
254
-
255
- function writeManifest(installPath, manifest) {
256
- const manifestPath = path.join(installPath, MANIFEST_FILE);
257
- // Stamp version fields on every write so they always reflect the last run.
258
- manifest.toolVersion = getPackageVersion();
259
- manifest.bmadVersion = getBmadVersion();
260
- fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
261
- }
262
-
263
- const BMAD_OUTPUT_PATTERNS = ['_bmad-output', '_bmad-output/', '/_bmad-output', '/_bmad-output/'];
264
-
265
- function ensureBmadOutputTracked(projectRoot) {
266
- const gitignorePath = path.join(projectRoot, '.gitignore');
267
- let content;
268
- try {
269
- content = fs.readFileSync(gitignorePath, 'utf-8');
270
- } catch (err) {
271
- if (err.code === 'ENOENT') return; // no .gitignore — nothing to do
272
- throw err;
273
- }
274
-
275
- const lines = content.split(/\r?\n/);
276
- const filtered = lines.filter(line => !BMAD_OUTPUT_PATTERNS.includes(line.trim()));
277
-
278
- if (filtered.length === lines.length) return; // nothing removed — do not write
279
-
280
- fs.writeFileSync(gitignorePath, filtered.join('\n'), 'utf-8');
281
- console.log(chalk.green('_bmad-output is now tracked as project knowledge (not gitignored)'));
282
- }
283
-
284
- // Story 22.8 — plugin-stage directory gitignore policy.
285
- //
286
- // Sister-policy to BMAD_OUTPUT_PATTERNS above. `_bmad-output/` is intentionally
287
- // NOT gitignored (it holds tracked project knowledge); by contrast the
288
- // project-local plugin stage (`<projectRoot>/.ma-agents-plugin-stage/`, see
289
- // lib/bmad.js#stagePlugin) is a transient install artifact that should NEVER
290
- // be committed. Both forms (trailing / or not) match the directory per git's
291
- // pathspec rules, and the leading-slash variants anchor to the project root.
292
- const PLUGIN_STAGE_PATTERNS = [
293
- PLUGIN_STAGE_DIR_NAME, // .ma-agents-plugin-stage
294
- `${PLUGIN_STAGE_DIR_NAME}/`, // .ma-agents-plugin-stage/
295
- `/${PLUGIN_STAGE_DIR_NAME}`, // /.ma-agents-plugin-stage
296
- `/${PLUGIN_STAGE_DIR_NAME}/`, // /.ma-agents-plugin-stage/
297
- ];
298
-
299
- // Canonical entry we write when the pattern is absent — trailing slash makes
300
- // the "directory-only" intent explicit for human readers.
301
- const PLUGIN_STAGE_CANONICAL_ENTRY = `${PLUGIN_STAGE_DIR_NAME}/`;
302
-
303
- /**
304
- * Ensure `.ma-agents-plugin-stage/` is present in the target project's
305
- * `.gitignore`. Append-only and idempotent:
306
- *
307
- * - If `.gitignore` is absent, create it with the single canonical entry.
308
- * - If any active (non-comment) line already matches any PLUGIN_STAGE_PATTERNS
309
- * variant, do nothing.
310
- * - Otherwise, append `PLUGIN_STAGE_CANONICAL_ENTRY` preserving the file's
311
- * existing line endings (CRLF vs LF) and guaranteeing a trailing newline
312
- * so the new entry is not concatenated to the previous line.
313
- *
314
- * Unrelated lines are never modified — this is strictly additive. Commented
315
- * variants (lines starting with `#`) are ignored for the "already present"
316
- * check, matching git's own semantics.
317
- *
318
- * F2a contract — the caller MUST pass the **project root** (the directory
319
- * that contains the target project's top-level `.gitignore`), NOT a per-agent
320
- * skills install path (e.g., `.claude/skills/`). Earlier (pre-F2a) the call
321
- * site in `installSkill()` passed `installPath`, which produced per-skill-dir
322
- * `.gitignore` files instead of one project-root file. The fix moved the
323
- * single invocation next to `stagePlugin(projectRoot)` in `lib/bmad.js`.
324
- *
325
- * Defensive behavior — if `projectRoot` is falsy or not a string, this
326
- * function is a no-op. The `ensurePluginStageGitignoredForProject()` helper
327
- * in `lib/bmad.js` is the canonical caller; this guard exists so a stray
328
- * call from misconfigured tests or third-party code cannot accidentally write
329
- * to `cwd`.
330
- *
331
- * @param {string} projectRoot - Absolute path to the target project.
332
- */
333
- function ensurePluginStageIgnored(projectRoot) {
334
- // F2a — defensive guard: reject undefined/null/non-string input rather than
335
- // silently writing to `path.join(undefined, '.gitignore')` (which throws on
336
- // older Node) or to whatever `path.join('')` resolves to (cwd).
337
- if (typeof projectRoot !== 'string' || projectRoot.length === 0) {
338
- return;
339
- }
340
-
341
- const gitignorePath = path.join(projectRoot, '.gitignore');
342
-
343
- let content = '';
344
- let fileExists = true;
345
- try {
346
- content = fs.readFileSync(gitignorePath, 'utf-8');
347
- } catch (err) {
348
- if (err.code !== 'ENOENT') throw err;
349
- fileExists = false;
350
- }
351
-
352
- if (fileExists) {
353
- const alreadyPresent = content.split(/\r?\n/).some(rawLine => {
354
- const line = rawLine.trim();
355
- if (!line || line.startsWith('#')) return false; // skip blanks & comments
356
- return PLUGIN_STAGE_PATTERNS.includes(line);
357
- });
358
- if (alreadyPresent) return; // idempotent no-op
359
- }
360
-
361
- // Preserve the file's existing line-ending style; default to LF for new files.
362
- const usesCrlf = fileExists && /\r\n/.test(content);
363
- const eol = usesCrlf ? '\r\n' : '\n';
364
-
365
- let next = content;
366
- if (next && !next.endsWith('\n') && !next.endsWith('\r\n')) {
367
- next += eol; // ensure the append lands on its own line
368
- }
369
- next += PLUGIN_STAGE_CANONICAL_ENTRY + eol;
370
-
371
- fs.writeFileSync(gitignorePath, next, 'utf-8');
372
- console.log(
373
- chalk.green(
374
- `${PLUGIN_STAGE_CANONICAL_ENTRY} added to .gitignore (transient plugin stage — safe to ignore)`
375
- )
376
- );
377
- }
378
-
379
- function ensureManifest(installPath, agentId, scope) {
380
- let manifest = readManifest(installPath);
381
- if (!manifest) {
382
- manifest = {
383
- manifestVersion: MANIFEST_VERSION,
384
- agent: agentId,
385
- agents: [agentId],
386
- scope: scope,
387
- skills: {}
388
- };
389
- } else {
390
- // Migrate v1.0.0 manifests: add agents array if missing
391
- if (!manifest.agents) {
392
- manifest.agents = manifest.agent ? [manifest.agent] : [];
393
- }
394
- // Add current agent if not already present
395
- if (agentId && !manifest.agents.includes(agentId)) {
396
- manifest.agents.push(agentId);
397
- }
398
- // Keep backward-compat agent field as first agent
399
- manifest.agent = manifest.agents[0] || null;
400
- }
401
- return manifest;
402
- }
403
-
404
- function getManifestAgents(manifest) {
405
- if (!manifest) return [];
406
- if (manifest.agents && Array.isArray(manifest.agents)) {
407
- return manifest.agents;
408
- }
409
- return manifest.agent ? [manifest.agent] : [];
410
- }
411
-
412
- function getInstalledSkillInfo(installPath, skillId) {
413
- const manifest = readManifest(installPath);
414
- if (!manifest || !manifest.skills || !manifest.skills[skillId]) {
415
- return null;
416
- }
417
- return manifest.skills[skillId];
418
- }
419
-
420
- async function generateSkillsManifest(installPath) {
421
- const skills = listSkills();
422
- const manifest = readManifest(installPath);
423
- if (!manifest || !manifest.skills) return;
424
-
425
- const manifestYamlPath = path.join(installPath, 'MANIFEST.yaml');
426
- let yamlContent = '# MANIFEST.yaml\n\nskills:\n';
427
-
428
- const skillIds = Object.keys(manifest.skills).sort();
429
- for (const skillId of skillIds) {
430
- const skill = skills.find(s => s.id === skillId);
431
- if (!skill) continue;
432
-
433
- yamlContent += ` - id: ${skillId}\n`;
434
- yamlContent += ` file: ${skillId}/SKILL.md\n`;
435
- yamlContent += ` description: ${skill.description}\n`;
436
-
437
- if (skill.applies_when && Array.isArray(skill.applies_when)) {
438
- yamlContent += ' applies_when:\n';
439
- skill.applies_when.forEach(cond => {
440
- yamlContent += ` - ${cond}\n`;
441
- });
442
- }
443
-
444
- if (skill.always_load) {
445
- yamlContent += ' always_load: true\n';
446
- }
447
-
448
- yamlContent += '\n';
449
- }
450
-
451
- await fs.writeFile(manifestYamlPath, yamlContent, 'utf-8');
452
- console.log(chalk.cyan(` + Generated ${manifestYamlPath}`));
453
- }
454
-
455
- /**
456
- * Generate project-context.md content by stamping the template with installed agent MANIFEST paths.
457
- * @param {string} projectRoot - Absolute path to project root (unused for path generation but part of API)
458
- * @param {Array<{skillsDir: string}>} installedAgents - Agent objects with skillsDir property
459
- * @param {Object|null} [layout=null] - Repository layout from collectRepoLayout() (null for single-repo)
460
- * @returns {Promise<string>} Stamped template content string (does NOT write any file)
461
- */
462
- async function generateProjectContext(projectRoot, installedAgents, layout = null) {
463
- let template;
464
- try {
465
- template = await fs.readFile(TEMPLATE_PATH, 'utf8');
466
- } catch (err) {
467
- throw new Error(`project-context template not found at ${TEMPLATE_PATH} — ma-agents installation may be corrupted: ${err.message}`);
468
- }
469
-
470
- const validAgents = installedAgents ? installedAgents.filter(a => a && a.skillsDir) : [];
471
- let manifestList;
472
- if (validAgents.length === 0) {
473
- manifestList = ' - (no agents installed — run ma-agents to install skills)';
474
- } else {
475
- manifestList = validAgents
476
- .map(a => ` - \`${a.skillsDir}/MANIFEST.yaml\``)
477
- .join('\n');
478
- }
479
-
480
- let content = template.replace('{{MANIFEST_PATHS_LIST}}', manifestList);
481
-
482
- // Replace repo layout placeholder (Story 16.3)
483
- const layoutSection = generateRepoLayoutSection(layout);
484
- if (layoutSection) {
485
- content = content.replace('{{REPO_LAYOUT_SECTION}}', '\n' + layoutSection + '\n');
486
- } else {
487
- // Clean removal: remove placeholder and avoid double blank lines
488
- content = content.replace(/\{\{REPO_LAYOUT_SECTION\}\}\r?\n/, '');
489
- }
490
-
491
- return content;
492
- }
493
-
494
- /**
495
- * Generate the Repository Layout markdown section for project-context.md.
496
- * Returns the section with markers for multi-repo, or empty string for single-repo/null layout.
497
- * @param {Object|null} layout - Layout object from collectRepoLayout()
498
- * @returns {string} Markdown section with markers, or empty string
499
- */
500
- function generateRepoLayoutSection(layout) {
501
- if (!layout || !layout.knowledgebase || !layout.sprintManagement) return '';
502
- if (layout.knowledgebase.mode === 'same' && layout.sprintManagement.mode === 'same') return '';
503
-
504
- const normPath = (p) => (p || '.').replace(/\\/g, '/');
505
- const kbDisplay = layout.knowledgebase.mode === 'same'
506
- ? 'current repository (default)'
507
- : normPath(layout.knowledgebase.path);
508
- const spDisplay = layout.sprintManagement.mode === 'same'
509
- ? 'current repository (default)'
510
- : normPath(layout.sprintManagement.path);
511
-
512
- let lines = [
513
- '<!-- ma-agents:repo-layout-start -->',
514
- '### Repository Layout',
515
- `- **Knowledgebase:** ${kbDisplay}`,
516
- `- **Sprint Management:** ${spDisplay}`,
517
- ];
518
-
519
- if (layout.knowledgebase.mode !== 'same') {
520
- lines.push('- When creating or reading planning artifacts, use the knowledgebase path');
521
- }
522
- if (layout.sprintManagement.mode !== 'same') {
523
- lines.push('- When creating or reading sprint/story artifacts, use the sprint management path');
524
- }
525
-
526
- lines.push('<!-- ma-agents:repo-layout-end -->');
527
- return lines.join('\n');
528
- }
529
-
530
- /**
531
- * Update the Repository Layout section in an existing project-context.md.
532
- * Uses marker-based update pattern (same as manifest paths).
533
- * @param {string} outputPath - Absolute path to existing project-context.md
534
- * @param {Object|null} layout - Layout object from collectRepoLayout()
535
- * @returns {Promise<boolean>} true if file was written, false otherwise
536
- */
537
- async function updateProjectContextRepoLayout(outputPath, layout) {
538
- let content;
539
- try {
540
- content = await fs.readFile(outputPath, 'utf8');
541
- } catch {
542
- return false;
543
- }
544
-
545
- const newSection = generateRepoLayoutSection(layout);
546
- const START = '<!-- ma-agents:repo-layout-start -->';
547
- const END = '<!-- ma-agents:repo-layout-end -->';
548
- const startIdx = content.indexOf(START);
549
- const endIdx = content.indexOf(END);
550
-
551
- if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
552
- // Markers exist — replace content between them
553
- if (newSection) {
554
- const newContent = content.slice(0, startIdx) + newSection + content.slice(endIdx + END.length);
555
- if (newContent === content) return false;
556
- await fs.writeFile(outputPath, newContent, 'utf8');
557
- return true;
558
- } else {
559
- // Single-repo: remove the entire section including markers and surrounding blank lines
560
- let before = content.slice(0, startIdx);
561
- let after = content.slice(endIdx + END.length);
562
- // Clean up trailing newline from before and leading newline from after
563
- if (before.endsWith('\n')) before = before.slice(0, -1);
564
- if (after.startsWith('\n')) after = after.slice(1);
565
- const newContent = before + '\n' + after;
566
- if (newContent === content) return false;
567
- await fs.writeFile(outputPath, newContent, 'utf8');
568
- return true;
569
- }
570
- }
571
-
572
- // Markers don't exist
573
- if (!newSection) return false; // single-repo, nothing to insert
574
-
575
- // Find insertion point: before "## Technology Stack"
576
- const techStackIdx = content.indexOf('## Technology Stack');
577
- if (techStackIdx === -1) {
578
- // Can't find expected structure — skip with info
579
- return false;
580
- }
581
-
582
- const newContent = content.slice(0, techStackIdx) + newSection + '\n\n' + content.slice(techStackIdx);
583
- await fs.writeFile(outputPath, newContent, 'utf8');
584
- return true;
585
- }
586
-
587
- /**
588
- * Update the MANIFEST paths section in an existing project-context.md.
589
- * Locates content between <!-- ma-agents:manifest-paths-start --> and
590
- * <!-- ma-agents:manifest-paths-end --> markers and replaces it with the
591
- * current agent list. Returns true if the file was updated, false if
592
- * markers were not found (old-format file — backward compatible) or if
593
- * the content was already up to date.
594
- * @param {string} outputPath - Absolute path to the existing project-context.md
595
- * @param {Array<{skillsDir: string}>} installedAgents - Agent objects with skillsDir property
596
- * @returns {Promise<boolean>} true if file was written, false otherwise
597
- */
598
- async function updateProjectContextManifestPaths(outputPath, installedAgents) {
599
- const content = await fs.readFile(outputPath, 'utf8');
600
- const validAgents = installedAgents ? installedAgents.filter(a => a && a.skillsDir) : [];
601
- const newList = validAgents.length === 0
602
- ? ' - (no agents installed — run ma-agents to install skills)'
603
- : validAgents.map(a => ` - \`${a.skillsDir}/MANIFEST.yaml\``).join('\n');
604
-
605
- const START = '<!-- ma-agents:manifest-paths-start -->';
606
- const END = '<!-- ma-agents:manifest-paths-end -->';
607
- const startIdx = content.indexOf(START);
608
- const endIdx = content.indexOf(END);
609
-
610
- if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
611
- return false; // no markers — old-format file, skip silently (backward compatible)
612
- }
613
-
614
- const before = content.slice(0, startIdx + START.length);
615
- const after = content.slice(endIdx);
616
- const newContent = `${before}\n${newList}\n${after}`;
617
-
618
- if (newContent === content) {
619
- return false; // already up to date, no write needed
620
- }
621
-
622
- await fs.writeFile(outputPath, newContent, 'utf8');
623
- return true;
624
- }
625
-
626
- /**
627
- * Find the insertion point in content after all skipped headers.
628
- * For '---' pattern, skips YAML frontmatter block (opening --- on first line through closing ---).
629
- * @param {string} content - File content
630
- * @param {string[]} [skipPatterns] - Patterns to skip (currently supports '---' for YAML frontmatter)
631
- * @returns {number} Character index where injection should be inserted
632
- */
633
- function findInsertionPoint(content, skipPatterns) {
634
- if (!content || !skipPatterns || skipPatterns.length === 0) {
635
- return 0;
636
- }
637
-
638
- let idx = 0;
639
-
640
- for (const pattern of skipPatterns) {
641
- if (pattern === '---') {
642
- // YAML frontmatter: must start at position 0 (or after leading whitespace)
643
- const trimmedStart = content.slice(idx).trimStart();
644
- const leadingWhitespace = content.length - idx - trimmedStart.length;
645
-
646
- if (!trimmedStart.startsWith('---')) {
647
- continue;
648
- }
649
-
650
- // Find the opening ---
651
- const openIdx = idx + leadingWhitespace;
652
- const afterOpen = content.indexOf('\n', openIdx);
653
- if (afterOpen === -1) {
654
- continue;
655
- }
656
-
657
- // Find the closing ---
658
- const closeIdx = content.indexOf('\n---', afterOpen);
659
- if (closeIdx === -1) {
660
- continue;
661
- }
662
-
663
- // Move past the closing --- line
664
- const afterClose = content.indexOf('\n', closeIdx + 1);
665
- if (afterClose === -1) {
666
- idx = content.length;
667
- } else {
668
- idx = afterClose + 1;
669
- }
670
- }
671
- }
672
-
673
- return idx;
674
- }
675
-
676
- /**
677
- * Story 21.2 AC #10 — canonical backup filename format.
678
- * Format: <target>.backup-<ISO-8601-timestamp> with colons replaced by hyphens
679
- * for Windows filename safety (e.g., .claude/CLAUDE.md.backup-2026-04-15T12-30-00Z).
680
- * Story 21.2 OWNS this format; stories 21.10 (reconfigure) and 21.11 (uninstall)
681
- * consume it. The date source is injectable to keep tests deterministic.
682
- */
683
- function formatBackupTimestamp(date = new Date()) {
684
- // ISO 8601 with hyphens instead of colons and no milliseconds:
685
- // 2026-04-15T12-30-00Z
686
- return date.toISOString().replace(/\.\d{3}Z$/, 'Z').replace(/:/g, '-');
687
- }
688
-
689
- function buildBackupFilename(targetPath, date = new Date()) {
690
- const base = `${targetPath}.backup-${formatBackupTimestamp(date)}`;
691
- // Guard against sub-second re-runs clobbering a prior backup. If the canonical
692
- // name already exists on disk, append a ".N" suffix until we find a free slot.
693
- if (!fs.existsSync(base)) return base;
694
- let i = 1;
695
- while (fs.existsSync(`${base}.${i}`)) i++;
696
- return `${base}.${i}`;
697
- }
698
-
699
- /**
700
- * Story 21.2 AC #10 — marker-block drift handler.
701
- *
702
- * Called when the existing in-marker content differs from what
703
- * composeInstructionBlock would produce for the current profile. In interactive
704
- * mode (no --yes), prompts the user for confirmation. With --yes or when stdin
705
- * is not a TTY, emits the pinned WARNING line and proceeds. In all drift cases
706
- * where we proceed with the overwrite, a backup sibling file is written
707
- * containing ONLY the marker-block region (markers included).
708
- *
709
- * Throws if the user declines the interactive prompt, short-circuiting the
710
- * write in the caller.
711
- */
712
- async function handleMarkerBlockDrift({ filePath, existingBlock, expectedBlock, yesMode }) {
713
- const backupPath = buildBackupFilename(filePath);
714
-
715
- // In non-interactive mode (yes mode or non-TTY), emit pinned warning and proceed.
716
- // In interactive mode, show diff preview and prompt for confirmation.
717
- const interactive = !yesMode && process.stdin.isTTY;
718
-
719
- if (interactive) {
720
- // Show a compact diff-style preview. We intentionally do not require a diff
721
- // library — the on-screen diff is informational only.
722
- console.log(chalk.yellow(`\nma-agents marker-block in ${filePath} was modified since last install.`));
723
- console.log(chalk.gray('--- current on-disk (inside markers) ---'));
724
- console.log(existingBlock);
725
- console.log(chalk.gray('--- expected (ma-agents) ---'));
726
- console.log(expectedBlock);
727
- const { proceed } = await prompts({
728
- type: 'confirm',
729
- name: 'proceed',
730
- message: `Overwrite and back up previous content to ${backupPath}?`,
731
- initial: false
732
- });
733
- if (!proceed) {
734
- throw new Error(`User declined to overwrite ma-agents marker block in ${filePath}`);
735
- }
736
- } else {
737
- // AC #10 pinned WARNING line (verbatim).
738
- console.log(
739
- `WARNING: ma-agents marker-block content modified since last install — overwriting. Previous content backed up to ${backupPath}`
740
- );
741
- }
742
-
743
- // Backup contains only the marker-block region (markers included).
744
- await fs.outputFile(backupPath, existingBlock, 'utf-8');
745
- }
746
-
747
- /**
748
- * Story 21.4 — markdown-markers merger for extraInstructionTemplates.
749
- *
750
- * Writes a marker-wrapped instruction block into a markdown target file. This
751
- * is the same marker-wrap contract used by updateAgentInstructions for
752
- * single-instructionFile agents (AC #5), lifted into a reusable helper so the
753
- * extraInstructionTemplates processor can dispatch on merger = 'markdown-markers'.
754
- *
755
- * Behavior (AC #5):
756
- * - If target file does not exist: create it by writing the full template
757
- * contents (including the leading "Generated by ma-agents" comment and
758
- * the markers), with `composedBlock` placed between the MA-AGENTS markers.
759
- * - If target exists with markers: replace in-marker content only; content
760
- * outside markers is preserved byte-for-byte. Hand-edit drift detection
761
- * (AC #11) uses the same handleMarkerBlockDrift helper as Story 21.2.
762
- * - If target exists WITHOUT markers: append the marker block at EOF
763
- * separated by one blank line. Existing content preserved.
764
- *
765
- * @param {string} targetPath - absolute path to the target file
766
- * @param {string} templateBody - static template text (from lib/templates/)
767
- * @param {string} composedBlock - the output of composeInstructionBlock(...)
768
- * @param {{ yesMode?: boolean }} opts
769
- * @returns {Promise<'created'|'updated'|'appended'|'skipped'>}
770
- */
771
- async function markdownMarkersMerger(targetPath, templateBody, composedBlock, opts = {}) {
772
- const markerStart = '<!-- MA-AGENTS-START -->';
773
- const markerEnd = '<!-- MA-AGENTS-END -->';
774
- const yesMode = !!(opts.yesMode || process.env.MA_AGENTS_YES === '1');
775
-
776
- // Normalize composedBlock trailing whitespace once so first-insert and
777
- // in-place-replace both produce byte-identical in-marker content (NFR46).
778
- const normalized = composedBlock.replace(/\s+$/, '') + '\n';
779
- const wrappedBlock = `${markerStart}\n${normalized}${markerEnd}`;
780
-
781
- if (!fs.existsSync(targetPath)) {
782
- // AC #5 first bullet: fresh create — write leading comment + template, with
783
- // markers replaced to carry the composed content.
784
- const leadingComment = '<!-- Generated by ma-agents. Edit outside the MA-AGENTS-START/END markers to preserve your changes. -->\n';
785
- // The template already contains placeholder empty markers ("<!-- MA-AGENTS-START -->\n<!-- MA-AGENTS-END -->").
786
- // Replace the first such pair with the wrapped block. If the template has no markers,
787
- // the block is appended at EOF separated by one blank line (defensive — template ships with markers).
788
- const emptyMarkerPair = new RegExp(`${markerStart}\\s*\\n\\s*${markerEnd}`);
789
- let body;
790
- if (emptyMarkerPair.test(templateBody)) {
791
- body = templateBody.replace(emptyMarkerPair, wrappedBlock);
792
- } else {
793
- const trimmed = templateBody.replace(/\s+$/, '');
794
- body = trimmed + '\n\n' + wrappedBlock + '\n';
795
- }
796
- // Ensure single trailing newline.
797
- const finalBody = body.replace(/\s+$/, '') + '\n';
798
- await fs.outputFile(targetPath, leadingComment + finalBody, 'utf-8');
799
- console.log(chalk.cyan(` + Created ${path.relative(path.dirname(targetPath), targetPath) === path.basename(targetPath) ? path.basename(targetPath) : targetPath}`));
800
- return 'created';
801
- }
802
-
803
- const content = await fs.readFile(targetPath, 'utf-8');
804
- const regex = new RegExp(`${markerStart}[\\s\\S]*?${markerEnd}`);
805
- const existingMatch = content.match(regex);
806
-
807
- if (existingMatch) {
808
- // AC #5 second bullet + AC #11 drift detection.
809
- const existingBlock = existingMatch[0];
810
- if (existingBlock === wrappedBlock) {
811
- // Byte-identical — idempotent no-op write to preserve mtime semantics would be
812
- // wasteful; just return. Outside-markers content is unchanged.
813
- return 'skipped';
814
- }
815
- // Drift: previous content differs from expected.
816
- try {
817
- await handleMarkerBlockDrift({
818
- filePath: targetPath,
819
- existingBlock,
820
- expectedBlock: wrappedBlock,
821
- yesMode
822
- });
823
- } catch (declineErr) {
824
- console.log(chalk.gray(` Skipped ${path.basename(targetPath)} (user declined marker-block overwrite)`));
825
- return 'skipped';
826
- }
827
- const replaced = content.replace(regex, wrappedBlock);
828
- await fs.writeFile(targetPath, replaced, 'utf-8');
829
- console.log(chalk.cyan(` + Updated ${path.basename(targetPath)}`));
830
- return 'updated';
831
- }
832
-
833
- // AC #5 third bullet: existing file, no markers — append marker block at EOF
834
- // separated by one blank line.
835
- const base = content.replace(/\s+$/, '');
836
- const appended = (base.length ? base + '\n\n' : '') + wrappedBlock + '\n';
837
- await fs.writeFile(targetPath, appended, 'utf-8');
838
- console.log(chalk.cyan(` + Appended marker block to ${path.basename(targetPath)}`));
839
- return 'appended';
840
- }
841
-
842
- /**
843
- * Story 21.4 — process extraInstructionTemplates entries on an agent.
844
- *
845
- * For each { template, target, merger } entry:
846
- * 1. Resolve the source template file under lib/templates/.
847
- * 2. Call composeInstructionBlock({ profile, projectRoot }) exactly once per
848
- * entry (canonical composer contract — Story 21.2, decision A).
849
- * 3. Dispatch on `merger`:
850
- * - 'markdown-markers' → markdownMarkersMerger (this story)
851
- * - 'yaml-customModes' → reserved for Story 21.3 (.roomodes)
852
- * 4. Per-entry MANIFEST_PATH substitution is applied to the composed string
853
- * BEFORE the merger receives it (caller-owned per Story 21.2 AC #3).
854
- *
855
- * @param {object} agent - lib/agents.js entry
856
- * @param {string} projectRoot
857
- * @param {{ yesMode?: boolean }} opts
858
- */
859
- async function stampExtraInstructionTemplates(agent, projectRoot, opts = {}) {
860
- if (!Array.isArray(agent.extraInstructionTemplates) || agent.extraInstructionTemplates.length === 0) {
861
- return;
862
- }
863
-
864
- // Resolve BMAD output dirs once per projectRoot (memoized + logged once).
865
- resolveBmadOutputDirs(projectRoot);
866
-
867
- const { getProfile } = require('./profile');
868
- const resolvedProfile = getProfile(projectRoot) || 'standard';
869
- const agentProjectPath = agent.getProjectPath();
870
- const relManifestPath = path.relative(projectRoot, path.join(agentProjectPath, 'MANIFEST.yaml')).replace(/\\/g, '/');
871
-
872
- for (const entry of agent.extraInstructionTemplates) {
873
- if (!entry || !entry.template || !entry.target || !entry.merger) {
874
- console.log(chalk.yellow(` Warning: malformed extraInstructionTemplates entry on ${agent.id}, skipping`));
875
- continue;
876
- }
877
- const templatePath = path.join(EXTRA_TEMPLATE_DIR, entry.template);
878
- if (!fs.existsSync(templatePath)) {
879
- console.log(chalk.yellow(` Warning: template not found at ${templatePath}, skipping`));
880
- continue;
881
- }
882
- const templateBody = fs.readFileSync(templatePath, 'utf-8');
883
-
884
- // Canonical composer contract: called exactly once per artifact.
885
- let composed = composeInstructionBlock({ profile: resolvedProfile, projectRoot });
886
- // Post-composition substitution is caller-owned (Story 21.2 AC #3).
887
- composed = composed.replace(/\{\{MANIFEST_PATH\}\}/g, relManifestPath);
888
-
889
- const targetPath = path.join(projectRoot, entry.target);
890
-
891
- if (entry.merger === 'markdown-markers') {
892
- await markdownMarkersMerger(targetPath, templateBody, composed, { yesMode: opts.yesMode });
893
- } else if (entry.merger === 'yaml-customModes') {
894
- // Story 21.3 — .roomodes YAML splice. The template contains
895
- // {{UNIVERSAL_BLOCK}} sentinels that must be expanded with the
896
- // composed block BEFORE the merger (caller-owned substitution, AC #2/#4).
897
- // Indent-preserving expansion keeps each line of the multi-line block
898
- // aligned with the YAML block-scalar indent of the sentinel.
899
- const composedTemplate = templateBody.replace(
900
- /^([ \t]*)\{\{UNIVERSAL_BLOCK\}\}/gm,
901
- (_m, indent) => composed
902
- .replace(/\s+$/, '')
903
- .split('\n')
904
- .map(line => indent + line)
905
- .join('\n')
906
- );
907
- const { mergeRoomodes } = require('./merge/roomodes');
908
- const existingYaml = fs.existsSync(targetPath)
909
- ? fs.readFileSync(targetPath, 'utf-8')
910
- : '';
911
- const mergedContent = mergeRoomodes(existingYaml, composedTemplate);
912
-
913
- // Backup if the target exists and content differs (Story 21.2 canonical format).
914
- if (fs.existsSync(targetPath)) {
915
- const existing = fs.readFileSync(targetPath, 'utf-8');
916
- if (existing !== mergedContent) {
917
- const backupPath = buildBackupFilename(targetPath);
918
- await fs.outputFile(backupPath, existing, 'utf-8');
919
- }
920
- }
921
-
922
- const tmpPath = targetPath + '.tmp';
923
- await fs.outputFile(tmpPath, mergedContent, 'utf-8');
924
- await fs.rename(tmpPath, targetPath);
925
- console.log(chalk.cyan(` + Updated ${entry.target}`));
926
- } else {
927
- console.log(chalk.yellow(` Warning: unknown merger '${entry.merger}' for ${agent.id}, skipping ${entry.target}`));
928
- }
929
- }
930
- }
931
-
932
- async function updateAgentInstructions(agent, projectRoot, opts = {}) {
933
- if (!agent.instructionFiles || agent.instructionFiles.length === 0) return;
934
-
935
- // Story 21.2 — yesMode resolution (AC #10): explicit opts.yesMode wins,
936
- // fall back to MA_AGENTS_YES env var (used by tests and subprocess flows).
937
- // The CLI passes yesMode via opts when --yes is set so non-interactive
938
- // installs do not hang on the drift-confirmation prompt.
939
- const yesMode = !!(opts.yesMode || process.env.MA_AGENTS_YES === '1');
940
-
941
- // Story 21.2 — resolve profile once per stamped artifact; compose the universal
942
- // (+ on-prem when applicable) block once; mergers consume the already-composed string.
943
- // Lazy require avoids potential circular-import pitfalls at module load (profile.js
944
- // already lazy-requires installer for the ensureManifest bootstrap path).
945
- const { getProfile } = require('./profile');
946
- const resolvedProfile = getProfile(projectRoot) || 'standard';
947
- const composedTemplate = composeInstructionBlock({ profile: resolvedProfile, projectRoot });
948
-
949
- // JSON merge strategy (e.g., OpenCode)
950
- // OpenCode expects instructions to be plain strings, not objects.
951
- // We identify our entries by a marker prefix in the string content,
952
- // and also clean up legacy object-format entries from older versions.
953
- if (agent.injectionStrategy?.position === 'json-merge') {
954
- const targetKey = agent.injectionStrategy.targetKey || 'instructions';
955
- const filePath = path.join(projectRoot, agent.instructionFiles[0]);
956
- const agentProjectPath = agent.getProjectPath();
957
- const relManifestPath = path.relative(projectRoot, path.join(agentProjectPath, 'MANIFEST.yaml')).replace(/\\/g, '/');
958
- // Story 21.2 — substitute {{MANIFEST_PATH}} AFTER composition (caller-owned, per AC #3).
959
- // Prefix with [ma-agents] tag so the isMaEntry filter identifies the entry on re-install.
960
- const instructionBody = composedTemplate.replace(/\{\{MANIFEST_PATH\}\}/g, relManifestPath);
961
- const instructionText = `[${MA_AGENTS_SOURCE}] ${instructionBody}`.replace(/\s+$/, '');
962
-
963
- const isMaEntry = (entry) =>
964
- (typeof entry === 'string' && entry.startsWith(`[${MA_AGENTS_SOURCE}]`)) ||
965
- (typeof entry === 'object' && entry != null && entry._source === MA_AGENTS_SOURCE);
966
-
967
- // Story 21.4 AC #6, #7 — collect any extra path-string entries that must be
968
- // appended to the JSON array (e.g., literal "AGENTS.md" for OpenCode). These
969
- // entries are USER-OWNED after first install (no [ma-agents] prefix → the
970
- // isMaEntry filter does NOT match them → never re-appended, never removed
971
- // by subsequent installs). Dedup uses strict string equality per AC #6.
972
- const extraJsonEntries = [];
973
- if (Array.isArray(agent.extraInstructionTemplates)) {
974
- for (const tpl of agent.extraInstructionTemplates) {
975
- if (tpl && tpl.merger === 'markdown-markers' && typeof tpl.target === 'string') {
976
- extraJsonEntries.push(tpl.target);
977
- }
978
- }
979
- }
980
-
981
- if (!fs.existsSync(filePath)) {
982
- // File absent: create fresh (atomic write)
983
- const data = { [targetKey]: [instructionText, ...extraJsonEntries] };
984
- const tmpPath = filePath + '.tmp';
985
- await fs.outputFile(tmpPath, JSON.stringify(data, null, 2), 'utf-8');
986
- await fs.rename(tmpPath, filePath);
987
- console.log(chalk.cyan(` + Created ${agent.instructionFiles[0]}`));
988
- // Continue to stamp extra templates below (e.g., AGENTS.md).
989
- } else {
990
- // File present: read and merge
991
- try {
992
- const content = await fs.readFile(filePath, 'utf-8');
993
- const data = JSON.parse(content);
994
- if (!Array.isArray(data[targetKey])) {
995
- data[targetKey] = [];
996
- }
997
- // Filter out stale ma-agents entries (string or legacy object format), keep user entries
998
- const userEntries = data[targetKey].filter(entry => entry != null && !isMaEntry(entry));
999
- // Story 21.4 AC #6 — append extraJsonEntries (e.g., "AGENTS.md") using strict
1000
- // string-equality dedup against userEntries so pre-existing user additions
1001
- // are not duplicated. AC #7: entries lack the [ma-agents] prefix and are
1002
- // therefore user-owned after first install.
1003
- const missingExtras = extraJsonEntries.filter(e => !userEntries.includes(e));
1004
- data[targetKey] = [...userEntries, instructionText, ...missingExtras];
1005
- // Atomic write: temp file then rename
1006
- const tmpPath = filePath + '.tmp';
1007
- await fs.outputFile(tmpPath, JSON.stringify(data, null, 2), 'utf-8');
1008
- await fs.rename(tmpPath, filePath);
1009
- console.log(chalk.cyan(` + Updated ${agent.instructionFiles[0]}`));
1010
- } catch (err) {
1011
- console.error(`[${MA_AGENTS_SOURCE}] ERROR: Could not parse ${filePath} — ${err.message}. File not modified.`);
1012
- return; // non-fatal: do NOT re-throw
1013
- }
1014
- }
1015
-
1016
- // Story 21.4 — stamp any extraInstructionTemplates (e.g., AGENTS.md) after
1017
- // the JSON-merge branch. This path does not fall through to the markdown
1018
- // marker-wrap below because `instructionFiles: ['opencode.json']` is JSON.
1019
- await stampExtraInstructionTemplates(agent, projectRoot, { yesMode });
1020
- return;
1021
- }
1022
-
1023
- const agentProjectPath = agent.getProjectPath();
1024
- const relManifestPath = path.relative(projectRoot, path.join(agentProjectPath, 'MANIFEST.yaml')).replace(/\\/g, '/');
1025
-
1026
- // Story 21.2 — replace the previously-hardcoded planningInstruction with the
1027
- // composed block. {{MANIFEST_PATH}} substitution happens AFTER composition
1028
- // (caller-owned per AC #3). The leading "\n" preserves the historical shape
1029
- // of the wrapped instruction so existing markers keep the same layout.
1030
- const planningInstruction = '\n' + composedTemplate.replace(/\{\{MANIFEST_PATH\}\}/g, relManifestPath);
1031
-
1032
- const markerStart = '<!-- MA-AGENTS-START -->';
1033
- const markerEnd = '<!-- MA-AGENTS-END -->';
1034
- // NFR46: normalize once so first-insert and in-place-replace produce
1035
- // byte-identical marker-block content. Both paths now use `.trim()` + '\n'.
1036
- const wrappedInstruction = `${markerStart}${planningInstruction}${markerEnd}`;
1037
- const wrappedInstructionWithTrailingNewline = wrappedInstruction + '\n';
1038
-
1039
- // Story 21.5 AC #6 — Cline dual-file drift detection.
1040
- // Runs BEFORE the file loop so we abort cleanly without partial writes.
1041
- // `--yes` does NOT bypass (explicit documented exception, AC #6).
1042
- if (agent.id === 'cline') {
1043
- checkClinerulesDualFileDrift(projectRoot);
1044
- }
1045
-
1046
- // Story 21.5 AC #1, #2 — optional framing template for fresh Cline file creation.
1047
- // The composer produces the universal body once; the framing supplies the
1048
- // Cline-flavored header paragraph + Architect-mode guidance line. Framing is
1049
- // only used when creating a NEW file — existing files go through the normal
1050
- // marker-replace path so user content outside markers is preserved (AC #4).
1051
- let frameworkTemplate = null;
1052
- if (agent.id === 'cline' && fs.existsSync(CLINERULES_TEMPLATE_PATH)) {
1053
- frameworkTemplate = fs.readFileSync(CLINERULES_TEMPLATE_PATH, 'utf-8');
1054
- }
1055
-
1056
- for (const fileName of agent.instructionFiles) {
1057
- const filePath = path.join(projectRoot, fileName);
1058
- let content = '';
1059
-
1060
- if (fs.existsSync(filePath)) {
1061
- content = await fs.readFile(filePath, 'utf-8');
1062
-
1063
- const regex = new RegExp(`${markerStart}[\\s\\S]*?${markerEnd}`, 'g');
1064
- const existingMatch = content.match(regex);
1065
- if (existingMatch) {
1066
- // AC #10 — upgrade-safety: detect hand-edits to the marker block and back up.
1067
- const existingBlock = existingMatch[0];
1068
- if (existingBlock !== wrappedInstruction) {
1069
- let proceedWithOverwrite = true;
1070
- try {
1071
- await handleMarkerBlockDrift({
1072
- filePath,
1073
- existingBlock,
1074
- expectedBlock: wrappedInstruction,
1075
- yesMode
1076
- });
1077
- } catch (declineErr) {
1078
- // User declined in interactive mode — leave file unchanged for this agent file.
1079
- proceedWithOverwrite = false;
1080
- console.log(chalk.gray(` Skipped ${fileName} (user declined marker-block overwrite)`));
1081
- }
1082
- if (!proceedWithOverwrite) continue;
1083
- }
1084
- // Replace existing block in-place (AC #2)
1085
- content = content.replace(regex, wrappedInstruction);
1086
- } else {
1087
- // Top-insert: place after skipped headers (AC #1, #3)
1088
- const strategy = agent.injectionStrategy || { position: 'top', skipPatterns: [] };
1089
- const insertIdx = findInsertionPoint(content, strategy.skipPatterns);
1090
- content = content.slice(0, insertIdx) + wrappedInstructionWithTrailingNewline + '\n' + content.slice(insertIdx);
1091
- }
1092
- } else if (agent.category === 'bmad') {
1093
- // BMAD agent instruction files ARE the agent definitions (persona, menu, activation).
1094
- // Never create them from scratch — they must be deployed by applyCustomizations().
1095
- // Creating them here would produce a file with ONLY the MA-AGENTS block,
1096
- // wiping the entire agent definition.
1097
- console.log(chalk.gray(` Skipped ${fileName} (BMAD agent file not yet deployed)`));
1098
- continue;
1099
- } else if (frameworkTemplate) {
1100
- // Story 21.5 AC #1/#2 — fresh Cline file: wrap the composed block in
1101
- // the Cline-flavored framing template (header paragraph + Architect-mode
1102
- // guidance line). The framing contains empty MA-AGENTS markers that we
1103
- // replace with the wrapped block. Cross-file byte-identity by construction
1104
- // is preserved because both `.cline/clinerules.md` and `.clinerules`
1105
- // receive the SAME rendered string in this single loop pass.
1106
- const emptyMarkerPair = new RegExp(`${markerStart}\\s*\\n\\s*${markerEnd}`);
1107
- let framed;
1108
- if (emptyMarkerPair.test(frameworkTemplate)) {
1109
- framed = frameworkTemplate.replace(emptyMarkerPair, wrappedInstruction);
1110
- } else {
1111
- // Defensive: framing shipped without markers — append block at EOF.
1112
- const trimmed = frameworkTemplate.replace(/\s+$/, '');
1113
- framed = trimmed + '\n\n' + wrappedInstruction + '\n';
1114
- }
1115
- content = framed.replace(/\s+$/, '') + '\n';
1116
- } else {
1117
- // New non-BMAD file: block is sole content (AC #1, #3)
1118
- content = wrappedInstructionWithTrailingNewline;
1119
- }
1120
-
1121
- await fs.outputFile(filePath, content, 'utf-8');
1122
- console.log(chalk.cyan(` + Updated ${fileName}`));
1123
- }
1124
-
1125
- // Story 21.4 — stamp any extraInstructionTemplates on non-JSON-merge agents
1126
- // (forward-compat for Story 21.3 .roomodes and Story 21.5 .clinerules).
1127
- await stampExtraInstructionTemplates(agent, projectRoot, { yesMode });
1128
- }
1129
-
1130
- // Story 21.3's parallel applyExtraInstructionTemplates was consolidated during
1131
- // rebase onto Story 21.4's canonical stampExtraInstructionTemplates dispatcher
1132
- // (see above) — the yaml-customModes merger branch lives there now.
1133
-
1134
- /**
1135
- * Compare two semver strings. Returns -1, 0, or 1.
1136
- */
1137
- function compareSemver(a, b) {
1138
- const pa = (a || '0.0.0').split('.').map(Number);
1139
- const pb = (b || '0.0.0').split('.').map(Number);
1140
- for (let i = 0; i < 3; i++) {
1141
- if (pa[i] > pb[i]) return 1;
1142
- if (pa[i] < pb[i]) return -1;
1143
- }
1144
- return 0;
1145
- }
1146
-
1147
- // --- Skill listing ---
1148
-
1149
- function listSkills() {
1150
- const skillsDir = path.join(__dirname, '..', 'skills');
1151
-
1152
- if (!fs.existsSync(skillsDir)) {
1153
- return [];
1154
- }
1155
-
1156
- const skillDirs = fs.readdirSync(skillsDir, { withFileTypes: true })
1157
- .filter(dirent => dirent.isDirectory())
1158
- .map(dirent => dirent.name);
1159
-
1160
- return skillDirs.map(skillDir => {
1161
- const metaPath = path.join(skillsDir, skillDir, 'skill.json');
1162
-
1163
- if (!fs.existsSync(metaPath)) {
1164
- return null;
1165
- }
1166
-
1167
- const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
1168
- return {
1169
- id: skillDir,
1170
- ...meta
1171
- };
1172
- }).filter(Boolean);
1173
- }
1174
-
1175
- function listAgents() {
1176
- return getAllAgents();
1177
- }
1178
-
1179
- // --- Core install logic (no prompts) ---
1180
-
1181
- async function performInstall(skillId, skill, agent, installPath) {
1182
- const skillSourceDir = path.join(__dirname, '..', 'skills', skillId);
1183
- let sourceFile = path.join(skillSourceDir, `${agent.template}${agent.fileExtension}`);
1184
-
1185
- if (!fs.existsSync(sourceFile)) {
1186
- sourceFile = path.join(skillSourceDir, `generic${agent.fileExtension}`);
1187
- }
1188
-
1189
- if (!fs.existsSync(sourceFile)) {
1190
- const skillMdPath = path.join(skillSourceDir, 'SKILL.md');
1191
- if (fs.existsSync(skillMdPath)) {
1192
- sourceFile = skillMdPath;
1193
- }
1194
- }
1195
-
1196
- if (!fs.existsSync(sourceFile)) {
1197
- console.log(chalk.yellow(` Warning: No template found for ${agent.name}, skipping`));
1198
- return false;
1199
- }
1200
-
1201
- const skillDir = path.join(installPath, skillId);
1202
- await fs.ensureDir(skillDir);
1203
-
1204
- let content = await fs.readFile(sourceFile, 'utf-8');
1205
-
1206
- // Strip any existing YAML frontmatter from the source
1207
- const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
1208
- content = content.replace(frontmatterRegex, '');
1209
-
1210
- // Inject YAML frontmatter from skill.json (single source of truth)
1211
- const frontmatter = [
1212
- '---',
1213
- `name: ${skill.name}`,
1214
- `description: ${skill.description}`,
1215
- '---',
1216
- ''
1217
- ].join('\n');
1218
- content = frontmatter + content;
1219
-
1220
- const targetFile = path.join(skillDir, 'SKILL.md');
1221
- await fs.writeFile(targetFile, content, 'utf-8');
1222
- console.log(chalk.green(` + Installed to ${targetFile}`));
1223
-
1224
- // Copy bundled resources
1225
- const resourceMap = agent.resourceMap || {};
1226
- const resourceDirs = ['scripts', 'references', 'assets', 'examples', 'hooks', 'docs', 'templates'];
1227
- for (const dir of resourceDirs) {
1228
- const resourceSource = path.join(skillSourceDir, dir);
1229
- if (fs.existsSync(resourceSource)) {
1230
- const targetDirName = resourceMap[dir] || dir;
1231
- const resourceTarget = path.join(skillDir, targetDirName);
1232
- await fs.copy(resourceSource, resourceTarget);
1233
- console.log(chalk.green(` + Copied ${dir}/ → ${targetDirName}/`));
1234
- }
1235
- }
1236
-
1237
- // Copy template.md if it exists
1238
- const templateSource = path.join(skillSourceDir, 'template.md');
1239
- if (fs.existsSync(templateSource)) {
1240
- await fs.copy(templateSource, path.join(skillDir, 'template.md'));
1241
- console.log(chalk.green(` + Copied template.md`));
1242
- }
1243
-
1244
- return true;
1245
- }
1246
-
1247
- // --- Install with upgrade detection ---
1248
-
1249
- async function installSkill(skillId, agentIds, customPath = '', scope = 'project', options = {}) {
1250
- const { force = false, yes = false } = options;
1251
-
1252
- // Task 4.3: Pre-set batch global action for non-interactive mode (shared batchState reference)
1253
- if (yes && options.batchState && !options.batchState.globalAction) {
1254
- options.batchState.globalAction = 'update';
1255
- }
1256
-
1257
- const skills = listSkills();
1258
- const skill = skills.find(s => s.id === skillId);
1259
-
1260
- if (!skill) {
1261
- throw new Error(`Skill '${skillId}' not found. Run "list" to see available skills.`);
1262
- }
1263
-
1264
- console.log(chalk.cyan(`\nInstalling skill: ${skill.name} v${skill.version}`));
1265
-
1266
- // Group agents by their resolved install path to avoid redundant installs
1267
- const pathGroups = new Map();
1268
- for (const agentId of agentIds) {
1269
- const agent = getAgent(agentId);
1270
- if (!agent) {
1271
- console.log(chalk.yellow(` Warning: Skipping unknown agent: ${agentId}`));
1272
- continue;
1273
- }
1274
- const installPath = customPath || (scope === 'global' ? agent.getGlobalPath() : agent.getProjectPath());
1275
- if (!installPath) {
1276
- console.log(chalk.yellow(` Warning: Agent '${agent.name}' does not support ${scope} installation, skipping`));
1277
- continue;
1278
- }
1279
- if (!pathGroups.has(installPath)) {
1280
- pathGroups.set(installPath, []);
1281
- }
1282
- pathGroups.get(installPath).push({ agentId, agent });
1283
- }
1284
-
1285
- // Guard: project-context generation runs at most once per installSkill() call.
1286
- // pathGroups can have multiple entries when agents share paths; without this flag
1287
- // the generation would fire once per path group, producing redundant file I/O.
1288
- let projectContextHandled = false;
1289
-
1290
- for (const [installPath, agentEntries] of pathGroups) {
1291
- const primaryAgent = agentEntries[0].agent;
1292
- const agentNames = agentEntries.map(e => e.agent.name).join(', ');
1293
-
1294
- try {
1295
- await fs.ensureDir(installPath);
1296
-
1297
- const installed = getInstalledSkillInfo(installPath, skillId);
1298
-
1299
- if (installed && !force) {
1300
- const cmp = compareSemver(skill.version, installed.version);
1301
-
1302
- let action;
1303
-
1304
- // Check if a global action was already chosen for this batch
1305
- const batchState = options.batchState || {};
1306
-
1307
- if (batchState.globalAction) {
1308
- action = batchState.globalAction;
1309
- } else if (cmp > 0) {
1310
- // Upgrade available
1311
- console.log(chalk.yellow(` ${skill.name} v${installed.version} → v${skill.version} update available for ${agentNames}`));
1312
- const { choice } = await prompts({
1313
- type: 'select',
1314
- name: 'choice',
1315
- message: 'What would you like to do?',
1316
- choices: [
1317
- { title: 'Update (recommended)', value: 'update' },
1318
- { title: 'Update all remaining', value: 'update-all' },
1319
- { title: 'Skip (keep current)', value: 'skip' },
1320
- { title: 'Skip all remaining', value: 'skip-all' },
1321
- { title: 'Clean reinstall', value: 'reinstall' },
1322
- { title: 'Remove (uninstall)', value: 'remove' }
1323
- ]
1324
- });
1325
- // Task 4.4: Guard against unexpected prompts in non-interactive mode
1326
- if (yes && choice === undefined) {
1327
- console.error(chalk.red('Error: Unexpected interactive prompt in non-interactive mode'));
1328
- process.exit(1);
1329
- }
1330
- action = choice;
1331
- } else if (cmp === 0) {
1332
- // Same version
1333
- console.log(chalk.gray(` ${skill.name} v${installed.version} already installed for ${agentNames}`));
1334
- const { choice } = await prompts({
1335
- type: 'select',
1336
- name: 'choice',
1337
- message: 'What would you like to do?',
1338
- choices: [
1339
- { title: 'Skip (keep current)', value: 'skip' },
1340
- { title: 'Skip all remaining', value: 'skip-all' },
1341
- { title: 'Clean reinstall', value: 'reinstall' },
1342
- { title: 'Remove (uninstall)', value: 'remove' }
1343
- ]
1344
- });
1345
- // Task 4.4: Guard against unexpected prompts in non-interactive mode
1346
- if (yes && choice === undefined) {
1347
- console.error(chalk.red('Error: Unexpected interactive prompt in non-interactive mode'));
1348
- process.exit(1);
1349
- }
1350
- action = choice;
1351
- } else {
1352
- // Downgrade
1353
- console.log(chalk.yellow(` ${skill.name} v${installed.version} installed, package has v${skill.version} for ${agentNames}`));
1354
- const { choice } = await prompts({
1355
- type: 'select',
1356
- name: 'choice',
1357
- message: 'What would you like to do?',
1358
- choices: [
1359
- { title: 'Skip (keep current)', value: 'skip' },
1360
- { title: 'Skip all remaining', value: 'skip-all' },
1361
- { title: `Downgrade to v${skill.version}`, value: 'update' },
1362
- { title: `Downgrade all to package version`, value: 'update-all' },
1363
- { title: 'Remove (uninstall)', value: 'remove' }
1364
- ]
1365
- });
1366
- // Task 4.4: Guard against unexpected prompts in non-interactive mode
1367
- if (yes && choice === undefined) {
1368
- console.error(chalk.red('Error: Unexpected interactive prompt in non-interactive mode'));
1369
- process.exit(1);
1370
- }
1371
- action = choice;
1372
- }
1373
-
1374
- if (!action) {
1375
- console.log(chalk.gray(` Skipped`));
1376
- continue;
1377
- }
1378
-
1379
- // Handle global actions
1380
- if (action === 'update-all') {
1381
- batchState.globalAction = 'update';
1382
- action = 'update';
1383
- } else if (action === 'skip-all') {
1384
- batchState.globalAction = 'skip';
1385
- action = 'skip';
1386
- }
1387
-
1388
- if (action === 'skip') {
1389
- console.log(chalk.gray(` Skipped`));
1390
- continue;
1391
- }
1392
-
1393
- if (action === 'remove') {
1394
- await performUninstall(skillId, installPath);
1395
- const manifest = ensureManifest(installPath, agentEntries[0].agentId, scope);
1396
- delete manifest.skills[skillId];
1397
- writeManifest(installPath, manifest);
1398
- console.log(chalk.green(` - Removed ${skill.name} from ${agentNames}`));
1399
-
1400
- // Generate MANIFEST.yaml and update agent instruction files
1401
- await generateSkillsManifest(installPath);
1402
- if (scope === 'project') {
1403
- for (const entry of agentEntries) {
1404
- await updateAgentInstructions(entry.agent, process.cwd(), { yesMode: yes });
1405
- }
1406
- }
1407
- continue;
1408
- }
1409
-
1410
- if (action === 'reinstall') {
1411
- await performUninstall(skillId, installPath);
1412
- }
1413
-
1414
- // action === 'update' or 'reinstall' → proceed to install below
1415
- }
1416
-
1417
- if (!installed || force) {
1418
- console.log(chalk.gray(` Installing for ${agentNames}...`));
1419
- }
1420
-
1421
- // Perform the install ONCE for this shared path
1422
- const success = await performInstall(skillId, skill, primaryAgent, installPath);
1423
-
1424
- if (success) {
1425
- // Update manifest with ALL agents that share this path
1426
- const manifest = ensureManifest(installPath, agentEntries[0].agentId, scope);
1427
- for (const entry of agentEntries) {
1428
- if (!manifest.agents.includes(entry.agentId)) {
1429
- manifest.agents.push(entry.agentId);
1430
- }
1431
- }
1432
- manifest.agent = manifest.agents[0];
1433
-
1434
- const now = new Date().toISOString();
1435
- const existing = manifest.skills[skillId];
1436
- manifest.skills[skillId] = {
1437
- version: skill.version,
1438
- installedAt: existing ? existing.installedAt : now,
1439
- updatedAt: now,
1440
- installerVersion: getPackageVersion(),
1441
- agentVersion: primaryAgent.version
1442
- };
1443
- writeManifest(installPath, manifest);
1444
- ensureBmadOutputTracked(installPath);
1445
- // F2a — the plugin-stage gitignore policy was previously applied here
1446
- // with `installPath` (the per-agent skills directory), which produced
1447
- // per-skill-dir `.gitignore` files instead of one project-root file.
1448
- // The single invocation now lives next to `stagePlugin(projectRoot)` in
1449
- // `lib/bmad.js` (see `ensurePluginStageGitignoredForProject` callers in
1450
- // installBmad / runMigration / updateBmad). Story 22.8 AC #1 / AC #5
1451
- // are satisfied at the project-root level; the per-agent path here
1452
- // would never have been the correct write target.
1453
-
1454
- // Generate MANIFEST.yaml and update instruction files for ALL agents
1455
- await generateSkillsManifest(installPath);
1456
- if (scope === 'project') {
1457
- for (const entry of agentEntries) {
1458
- await updateAgentInstructions(entry.agent, process.cwd(), { yesMode: yes });
1459
- // Story 21.4 — updateAgentInstructions now internally invokes
1460
- // stampExtraInstructionTemplates, which handles per-agent extra
1461
- // templates (e.g., Roo Code .roomodes via merger 'yaml-customModes',
1462
- // OpenCode AGENTS.md via merger 'markdown-markers'). No sibling
1463
- // call needed here.
1464
- }
1465
- // Deploy Claude Code hook when skills are installed for claude-code
1466
- if (includesClaudeCode(agentEntries)) {
1467
- await deployClaudeCodeHook(process.cwd());
1468
- }
1469
- // Generate project-context.md on first install; update manifest paths on subsequent installs.
1470
- // Bug B2 — also run obsolete-tool-dir cleanup on the first successful path-group iteration.
1471
- // Guard: runs at most once per installSkill() call — pathGroups may have multiple entries
1472
- // when agents share install paths, so without this flag these would fire redundantly.
1473
- if (!projectContextHandled) {
1474
- projectContextHandled = true;
1475
- // Bug B2 — clean up stale tool-level skill directories left by earlier routing migrations.
1476
- try {
1477
- await migrateObsoleteToolDirs(process.cwd());
1478
- } catch (err) {
1479
- console.log(chalk.yellow(`⚠ obsolete-tool-dir cleanup failed: ${err.message} — continuing install`));
1480
- }
1481
- const projectRoot = process.cwd();
1482
- const outputPath = path.join(projectRoot, '_bmad-output', 'project-context.md');
1483
- const allAgents = getManifestAgents(manifest).map(id => getAgent(id)).filter(Boolean);
1484
- try {
1485
- await fs.ensureDir(path.join(projectRoot, '_bmad-output'));
1486
- if (await fs.pathExists(outputPath)) {
1487
- const updated = await updateProjectContextManifestPaths(outputPath, allAgents);
1488
- if (updated) {
1489
- console.log(chalk.green('✓ project-context.md manifest paths updated'));
1490
- } else {
1491
- console.log(chalk.blue('ℹ project-context.md already up to date'));
1492
- }
1493
- } else {
1494
- const content = await generateProjectContext(projectRoot, allAgents);
1495
- await fs.writeFile(outputPath, content, 'utf8');
1496
- console.log(chalk.green('✓ project-context.md generated at _bmad-output/project-context.md'));
1497
- }
1498
- } catch (err) {
1499
- console.log(chalk.yellow(`⚠ project-context generation failed: ${err.message} — continuing install`));
1500
- }
1501
- }
1502
- }
1503
- }
1504
- } catch (error) {
1505
- console.log(chalk.red(` x Failed: ${error.message}`));
1506
- }
1507
- }
1508
- }
1509
-
1510
- // --- Uninstall ---
1511
-
1512
- async function performUninstall(skillId, installPath) {
1513
- const skillDir = path.join(installPath, skillId);
1514
- if (fs.existsSync(skillDir)) {
1515
- await fs.remove(skillDir);
1516
- }
1517
- }
1518
-
1519
- /**
1520
- * Story 22.3 / 22.2 — Legacy skill retirement + rename migration (AC 7).
1521
- *
1522
- * BMAD v6.3.0 retired four upstream skills:
1523
- * - bmad-init (upstream PR #2159 — agents read _bmad/bmm/config.yaml directly)
1524
- * - bmad-agent-qa (Quinn — merged into Amelia in PR #2186)
1525
- * - bmad-agent-sm (Bob — merged into Amelia in PR #2179)
1526
- * - bmad-agent-quick-flow-solo-dev (Barry — merged into Amelia in PR #2177)
1527
- *
1528
- * Story 22.2 additionally renamed the five ma-agents custom-agent skills out of
1529
- * the upstream-reserved `bmad-*` prefix:
1530
- * - bmad-ma-agent-cyber → ma-agent-cyber
1531
- * - bmad-ma-agent-devops → ma-agent-devops
1532
- * - bmad-ma-agent-sre → ma-agent-sre
1533
- * - bmad-ma-agent-ml → ma-agent-ml
1534
- * - bmad-ma-agent-sqa → ma-agent-sqa
1535
- *
1536
- * Both retirements and renames produce the same stale-artifact problem: existing
1537
- * ma-agents installs may still carry the old IDs in `.ma-agents.json` and still
1538
- * have the old folders deployed under each agent's skill root. This helper sweeps
1539
- * every agent install path and, for every obsolete ID (retired or renamed):
1540
- * 1. removes the ID from the manifest's `skills` map
1541
- * 2. deletes the installed skill directory under the agent's skill root
1542
- * 3. regenerates MANIFEST.yaml so the deployed manifest drops the obsolete IDs
1543
- *
1544
- * The follow-up install step re-adds the renamed skills under their new IDs, so
1545
- * users effectively see a one-shot upgrade — no manual cleanup, no dangling
1546
- * duplicates. The migration is idempotent — running it again on a clean install
1547
- * is a no-op.
1548
- *
1549
- * Returns the list of per-path migration actions (for testability and logging).
1550
- */
1551
- const RETIRED_SKILL_IDS = Object.freeze([
1552
- 'bmad-init',
1553
- 'bmad-agent-qa',
1554
- 'bmad-agent-sm',
1555
- 'bmad-agent-quick-flow-solo-dev'
1556
- ]);
1557
-
1558
- // Skills that were renamed (not retired). The new ID is installed fresh by
1559
- // the regular install flow after migration clears the old ID.
1560
- const RENAMED_SKILL_IDS = Object.freeze([
1561
- 'bmad-ma-agent-cyber',
1562
- 'bmad-ma-agent-devops',
1563
- 'bmad-ma-agent-sre',
1564
- 'bmad-ma-agent-ml',
1565
- 'bmad-ma-agent-sqa'
1566
- ]);
1567
-
1568
- // Union of every obsolete ID the sweep must purge from existing installs.
1569
- // Kept as a frozen array so accidental mutation is surfaced at test time.
1570
- const OBSOLETE_SKILL_IDS = Object.freeze([...RETIRED_SKILL_IDS, ...RENAMED_SKILL_IDS]);
1571
-
1572
- // Registry of old tool-level skill directories that earlier ma-agents versions
1573
- // wrote to but no longer use. Each entry describes one migration:
1574
- // obsolete — the legacy path (relative to project root)
1575
- // replacedBy — the current path that skills are now written to
1576
- // sinceVersion — the ma-agents release that made the change (informational)
1577
- //
1578
- // Story 24.11 (AC5) will add additional entries to this list when .agents/skills/
1579
- // cross-tool routing is assessed. Define new entries here; `migrateObsoleteToolDirs`
1580
- // iterates the registry automatically.
1581
- //
1582
- // Kept frozen so accidental mutation is surfaced at test time.
1583
- const OBSOLETE_TOOL_SKILL_DIRS = Object.freeze([
1584
- // Pre-F2b (PR #70) copilot wrote skills to .github/copilot/skills;
1585
- // F2b realigned the target to .github/skills to match the BMAD upstream path.
1586
- { obsolete: '.github/copilot/skills', replacedBy: '.github/skills', sinceVersion: '3.7.0' },
1587
- // bmad-method 6.5.0 moved github-copilot, roo, and kilo platforms from
1588
- // tool-specific paths to the shared .agents/skills/ directory.
1589
- { obsolete: '.github/skills', replacedBy: '.agents/skills', sinceVersion: '3.8.1' },
1590
- { obsolete: '.roo/skills', replacedBy: '.agents/skills', sinceVersion: '3.8.1' },
1591
- { obsolete: '.kilocode/skills', replacedBy: '.agents/skills', sinceVersion: '3.8.1' }
1592
- ]);
1593
-
1594
- async function migrateRetiredSkills(options = {}) {
1595
- const { scope = 'project', customPath = '' } = options;
1596
- const results = [];
1597
-
1598
- // Build the list of install paths to sweep. We consider every registered
1599
- // agent for the requested scope — the retirement/rename is agent-agnostic.
1600
- const pathsSeen = new Set();
1601
- const targetPaths = [];
1602
-
1603
- if (customPath) {
1604
- targetPaths.push(customPath);
1605
- } else {
1606
- for (const agent of getAllAgents()) {
1607
- const p = scope === 'global' ? agent.getGlobalPath() : agent.getProjectPath();
1608
- if (p && !pathsSeen.has(p)) {
1609
- pathsSeen.add(p);
1610
- targetPaths.push(p);
1611
- }
1612
- }
1613
- }
1614
-
1615
- for (const installPath of targetPaths) {
1616
- let manifestMutated = false;
1617
- let filesRemoved = 0;
1618
- const removedIds = [];
1619
- let pathError = null;
1620
-
1621
- try {
1622
- // Read manifest (if any). Missing manifest is fine — we still check for
1623
- // orphaned skill directories left behind by a partial uninstall.
1624
- const manifest = readManifest(installPath);
1625
-
1626
- for (const obsoleteId of OBSOLETE_SKILL_IDS) {
1627
- // 1. Manifest entry cleanup
1628
- if (manifest && manifest.skills && Object.prototype.hasOwnProperty.call(manifest.skills, obsoleteId)) {
1629
- delete manifest.skills[obsoleteId];
1630
- manifestMutated = true;
1631
- removedIds.push(obsoleteId);
1632
- }
1633
-
1634
- // 2. Installed-directory cleanup — guard against ENOENT (idempotency)
1635
- const skillDir = path.join(installPath, obsoleteId);
1636
- if (fs.existsSync(skillDir)) {
1637
- try {
1638
- await fs.remove(skillDir);
1639
- filesRemoved++;
1640
- if (!removedIds.includes(obsoleteId)) {
1641
- removedIds.push(obsoleteId);
1642
- }
1643
- } catch (err) {
1644
- // Non-fatal — surface the path so the user can clean up manually.
1645
- console.log(chalk.yellow(` ! Could not remove ${skillDir}: ${err.message}`));
1646
- }
1647
- }
1648
- }
1649
-
1650
- if (manifestMutated) {
1651
- writeManifest(installPath, manifest);
1652
- }
1653
-
1654
- if (manifestMutated || filesRemoved > 0) {
1655
- // Regenerate the deployed MANIFEST.yaml so AC 1 is satisfied for
1656
- // existing installs — the file is derived from `.ma-agents.json::skills`.
1657
- if (manifest && manifest.skills) {
1658
- try {
1659
- await generateSkillsManifest(installPath);
1660
- } catch (err) {
1661
- console.log(chalk.yellow(` ! MANIFEST.yaml regeneration skipped for ${installPath}: ${err.message}`));
1662
- }
1663
- }
1664
- console.log(chalk.green(
1665
- ` - Cleaned ${removedIds.length} obsolete skill(s) at ${installPath}: ${removedIds.join(', ')}`
1666
- ));
1667
- }
1668
- } catch (err) {
1669
- // Isolate per-path failures so one broken install path does not
1670
- // abort the sweep across all other agents.
1671
- pathError = err;
1672
- console.log(chalk.yellow(
1673
- ` ! Obsolete-skill migration failed for ${installPath}: ${err.message}`
1674
- ));
1675
- }
1676
-
1677
- results.push({ installPath, removedIds, filesRemoved, manifestMutated, error: pathError });
1678
- }
1679
-
1680
- return results;
1681
- }
1682
-
1683
- /**
1684
- * Bug B2 — Stale copilot skills dir cleanup (and future obsolete tool-dir migrations).
1685
- *
1686
- * Iterates `OBSOLETE_TOOL_SKILL_DIRS` and, for each entry, checks whether the
1687
- * legacy directory exists under `projectRoot`. Behaviour per entry:
1688
- *
1689
- * - Obsolete dir absent → no-op (idempotent)
1690
- * - Obsolete dir present AND
1691
- * replacedBy dir has content → remove obsolete dir, log info message
1692
- * - Obsolete dir present BUT
1693
- * replacedBy dir is empty/gone → preserve obsolete dir, log warning
1694
- * (user may have local edits; safer to keep)
1695
- *
1696
- * @param {string} projectRoot — absolute path to the project root (process.cwd() in production)
1697
- * @returns {Promise<Array<{entry, action, obsoletePath, replacedByPath, error}>>}
1698
- */
1699
- async function migrateObsoleteToolDirs(projectRoot) {
1700
- const results = [];
1701
-
1702
- for (const entry of OBSOLETE_TOOL_SKILL_DIRS) {
1703
- const obsoletePath = path.join(projectRoot, entry.obsolete);
1704
- const replacedByPath = path.join(projectRoot, entry.replacedBy);
1705
- let action = 'noop';
1706
- let error = null;
1707
-
1708
- try {
1709
- if (!fs.existsSync(obsoletePath)) {
1710
- // Nothing to do — already clean.
1711
- action = 'noop';
1712
- } else {
1713
- // Check whether the replacement directory has any content.
1714
- let replacedByHasContent = false;
1715
- if (fs.existsSync(replacedByPath)) {
1716
- const entries = fs.readdirSync(replacedByPath);
1717
- replacedByHasContent = entries.length > 0;
1718
- }
1719
-
1720
- if (replacedByHasContent) {
1721
- // Safe to remove — skills have been migrated to the new location.
1722
- await fs.remove(obsoletePath);
1723
- action = 'removed';
1724
- console.log(chalk.green(
1725
- ` - Removed legacy tool skills directory ${entry.obsolete} ` +
1726
- `(moved to ${entry.replacedBy} in v${entry.sinceVersion})`
1727
- ));
1728
- } else {
1729
- // Replacement is absent or empty — do not silently destroy user content.
1730
- action = 'preserved';
1731
- console.log(chalk.yellow(
1732
- ` ! Legacy tool skills directory ${entry.obsolete} was not removed ` +
1733
- `because ${entry.replacedBy} is empty or absent. ` +
1734
- `Remove it manually once you have confirmed there is nothing to keep: ` +
1735
- `rm -rf ${entry.obsolete}`
1736
- ));
1737
- }
1738
- }
1739
- } catch (err) {
1740
- action = 'error';
1741
- error = err;
1742
- console.log(chalk.yellow(
1743
- ` ! Could not migrate obsolete tool dir ${entry.obsolete}: ${err.message}`
1744
- ));
1745
- }
1746
-
1747
- results.push({ entry, action, obsoletePath, replacedByPath, error });
1748
- }
1749
-
1750
- return results;
1751
- }
1752
-
1753
- async function uninstallSkill(skillId, agentIds, customPath = '', scope = 'project') {
1754
- console.log(chalk.cyan(`\nUninstalling skill: ${skillId}`));
1755
-
1756
- // Group agents by their resolved install path
1757
- const pathGroups = new Map();
1758
- for (const agentId of agentIds) {
1759
- const agent = getAgent(agentId);
1760
- if (!agent) {
1761
- console.log(chalk.yellow(` Warning: Skipping unknown agent: ${agentId}`));
1762
- continue;
1763
- }
1764
- const installPath = customPath || (scope === 'global' ? agent.getGlobalPath() : agent.getProjectPath());
1765
- if (!installPath) {
1766
- console.log(chalk.yellow(` Warning: Agent '${agent.name}' does not support ${scope} scope, skipping`));
1767
- continue;
1768
- }
1769
- if (!pathGroups.has(installPath)) {
1770
- pathGroups.set(installPath, []);
1771
- }
1772
- pathGroups.get(installPath).push({ agentId, agent });
1773
- }
1774
-
1775
- for (const [installPath, agentEntries] of pathGroups) {
1776
- try {
1777
- const installed = getInstalledSkillInfo(installPath, skillId);
1778
-
1779
- if (!installed) {
1780
- const skillDir = path.join(installPath, skillId);
1781
- if (fs.existsSync(skillDir)) {
1782
- await performUninstall(skillId, installPath);
1783
- console.log(chalk.green(` - Removed ${skillId} from ${installPath} (legacy install)`));
1784
- } else {
1785
- console.log(chalk.gray(` ${skillId} is not installed at ${installPath}`));
1786
- }
1787
- continue;
1788
- }
1789
-
1790
- // Read current manifest to check which agents still need this path
1791
- const manifest = readManifest(installPath) || { agents: [], skills: {} };
1792
- const currentAgents = getManifestAgents(manifest);
1793
-
1794
- // Remove the requested agents from the manifest's agent list
1795
- const agentIdsToRemove = new Set(agentEntries.map(e => e.agentId));
1796
- const remainingAgents = currentAgents.filter(id => !agentIdsToRemove.has(id));
1797
-
1798
- if (remainingAgents.length === 0) {
1799
- // No agents left that need this skill at this path -> delete the files
1800
- await performUninstall(skillId, installPath);
1801
- delete manifest.skills[skillId];
1802
- console.log(chalk.green(` - Removed ${skillId} v${installed.version} from ${installPath}`));
1803
- } else {
1804
- // Other agents still need the files, just update the manifest
1805
- console.log(chalk.gray(` ${skillId} still needed by ${remainingAgents.join(', ')} at ${installPath}, keeping files`));
1806
- }
1807
-
1808
- // Update the agents list in manifest
1809
- manifest.agents = remainingAgents;
1810
- manifest.agent = remainingAgents[0] || null;
1811
- writeManifest(installPath, manifest);
1812
-
1813
- // Regenerate MANIFEST.yaml and update instruction files
1814
- await generateSkillsManifest(installPath);
1815
- if (scope === 'project') {
1816
- for (const entry of agentEntries) {
1817
- await updateAgentInstructions(entry.agent, process.cwd());
1818
- }
1819
- // Remove Claude Code hook when no skills remain for claude-code
1820
- if (includesClaudeCode(agentEntries)) {
1821
- const currentManifest = readManifest(installPath);
1822
- const hasRemainingSkills = currentManifest && currentManifest.skills && Object.keys(currentManifest.skills).length > 0;
1823
- if (!hasRemainingSkills) {
1824
- await removeClaudeCodeHook(process.cwd());
1825
- }
1826
- }
1827
- }
1828
- } catch (error) {
1829
- console.log(chalk.red(` x Failed: ${error.message}`));
1830
- }
1831
- }
1832
- }
1833
-
1834
- // --- Status ---
1835
-
1836
- function getStatus(agentIds, customPath = '', scope = 'project') {
1837
- const results = [];
1838
-
1839
- const targetAgents = agentIds && agentIds.length > 0
1840
- ? agentIds.map(id => getAgent(id)).filter(Boolean)
1841
- : getAllAgents();
1842
-
1843
- for (const agent of targetAgents) {
1844
- const projectPath = agent.getProjectPath();
1845
- const globalPath = agent.getGlobalPath();
1846
-
1847
- // Filter paths based on scope
1848
- let pathsToCheck;
1849
- if (customPath) {
1850
- pathsToCheck = [{ path: customPath, scope: 'custom' }];
1851
- } else if (scope === 'global') {
1852
- pathsToCheck = globalPath ? [{ path: globalPath, scope: 'global' }] : [];
1853
- } else if (scope === 'project') {
1854
- pathsToCheck = [{ path: projectPath, scope: 'project' }];
1855
- } else {
1856
- pathsToCheck = [{ path: projectPath, scope: 'project' }];
1857
- if (globalPath) {
1858
- pathsToCheck.push({ path: globalPath, scope: 'global' });
1859
- }
1860
- }
1861
-
1862
- for (const { path: checkPath, scope: checkScope } of pathsToCheck) {
1863
- const manifest = readManifest(checkPath);
1864
- if (!manifest || !manifest.skills || Object.keys(manifest.skills).length === 0) {
1865
- continue;
1866
- }
1867
-
1868
- results.push({
1869
- agent: agent,
1870
- installPath: checkPath,
1871
- scope: checkScope,
1872
- skills: manifest.skills
1873
- });
1874
- }
1875
- }
1876
-
1877
- return results;
1878
- }
1879
-
1880
- // --- Claude Code Hook Management ---
1881
-
1882
- /**
1883
- * Deploy the verify-manifest SessionStart hook into .claude/settings.json.
1884
- * Performs a JSON merge — preserves existing settings and hooks.
1885
- */
1886
- async function deployClaudeCodeHook(projectRoot) {
1887
- const settingsPath = path.join(projectRoot, CLAUDE_CODE_SETTINGS_FILE);
1888
- let settings = {};
1889
-
1890
- if (fs.existsSync(settingsPath)) {
1891
- try {
1892
- settings = JSON.parse(await fs.readFile(settingsPath, 'utf-8'));
1893
- } catch {
1894
- console.log(chalk.yellow(' Warning: Could not parse .claude/settings.json, skipping hook deployment'));
1895
- return;
1896
- }
1897
- }
1898
-
1899
- if (!settings.hooks) {
1900
- settings.hooks = {};
1901
- }
1902
- if (!settings.hooks.SessionStart) {
1903
- settings.hooks.SessionStart = [];
1904
- }
1905
-
1906
- // Check if our hook is already present
1907
- const hookCommand = `node "$CLAUDE_PROJECT_DIR/lib/hooks/claude-code/verify-manifest.js"`;
1908
- const alreadyInstalled = settings.hooks.SessionStart.some(group =>
1909
- group.hooks && group.hooks.some(h => h.command === hookCommand)
1910
- );
1911
-
1912
- if (alreadyInstalled) {
1913
- return; // Already deployed
1914
- }
1915
-
1916
- settings.hooks.SessionStart.push({
1917
- matcher: 'startup',
1918
- hooks: [
1919
- {
1920
- type: 'command',
1921
- command: hookCommand,
1922
- _id: CLAUDE_CODE_HOOK_ID
1923
- }
1924
- ]
1925
- });
1926
-
1927
- await fs.ensureDir(path.dirname(settingsPath));
1928
- await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
1929
- console.log(chalk.cyan(' + Deployed Claude Code verify-manifest hook'));
1930
- }
1931
-
1932
- /**
1933
- * Remove the verify-manifest hook from .claude/settings.json.
1934
- */
1935
- async function removeClaudeCodeHook(projectRoot) {
1936
- const settingsPath = path.join(projectRoot, CLAUDE_CODE_SETTINGS_FILE);
1937
-
1938
- if (!fs.existsSync(settingsPath)) return;
1939
-
1940
- let settings;
1941
- try {
1942
- settings = JSON.parse(await fs.readFile(settingsPath, 'utf-8'));
1943
- } catch {
1944
- return;
1945
- }
1946
-
1947
- if (!settings.hooks || !settings.hooks.SessionStart) return;
1948
-
1949
- const hookCommand = `node "$CLAUDE_PROJECT_DIR/lib/hooks/claude-code/verify-manifest.js"`;
1950
- settings.hooks.SessionStart = settings.hooks.SessionStart.filter(group => {
1951
- if (!group.hooks) return true;
1952
- group.hooks = group.hooks.filter(h => h.command !== hookCommand && h._id !== CLAUDE_CODE_HOOK_ID);
1953
- return group.hooks.length > 0;
1954
- });
1955
-
1956
- // Clean up empty arrays
1957
- if (settings.hooks.SessionStart.length === 0) {
1958
- delete settings.hooks.SessionStart;
1959
- }
1960
- if (Object.keys(settings.hooks).length === 0) {
1961
- delete settings.hooks;
1962
- }
1963
-
1964
- await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
1965
- console.log(chalk.cyan(' - Removed Claude Code verify-manifest hook'));
1966
- }
1967
-
1968
- /**
1969
- * Check if any agents in the list include claude-code.
1970
- */
1971
- function includesClaudeCode(agentEntries) {
1972
- return agentEntries.some(e => e.agentId === 'claude-code');
1973
- }
1974
-
1975
- module.exports = {
1976
- listSkills,
1977
- listAgents,
1978
- installSkill,
1979
- uninstallSkill,
1980
- getStatus,
1981
- readManifest,
1982
- ensureManifest,
1983
- getInstalledSkillInfo,
1984
- getManifestAgents,
1985
- compareSemver,
1986
- findInsertionPoint,
1987
- deployClaudeCodeHook,
1988
- removeClaudeCodeHook,
1989
- ensureBmadOutputTracked,
1990
- ensurePluginStageIgnored,
1991
- generateSkillsManifest,
1992
- generateProjectContext,
1993
- generateRepoLayoutSection,
1994
- updateProjectContextRepoLayout,
1995
- _updateProjectContextManifestPaths: updateProjectContextManifestPaths,
1996
- _testUpdateAgentInstructions: updateAgentInstructions,
1997
- _MA_AGENTS_SOURCE: MA_AGENTS_SOURCE,
1998
- // Story 21.2 — universal instruction-block composer and helpers.
1999
- composeInstructionBlock,
2000
- buildBackupFilename,
2001
- formatBackupTimestamp,
2002
- UNIVERSAL_INSTRUCTION_TEMPLATE_PATH,
2003
- ONPREM_INSTRUCTION_TEMPLATE_PATH,
2004
- // Story 21.4 — AGENTS.md template, markdown-markers merger, extraInstructionTemplates processor.
2005
- // Story 21.3 (rebased) — yaml-customModes merger dispatch is integrated into stampExtraInstructionTemplates.
2006
- resolveBmadOutputDirs,
2007
- markdownMarkersMerger,
2008
- stampExtraInstructionTemplates,
2009
- EXTRA_TEMPLATE_DIR,
2010
- // Story 21.5 — Cline dual-file drift detection + framing template path.
2011
- CLINERULES_TEMPLATE_PATH,
2012
- ClinerulesDualFileDriftError,
2013
- checkClinerulesDualFileDrift,
2014
- // Story 22.3 — legacy skill retirement migration (AC 7)
2015
- // Story 22.2 — extended to also sweep renamed skill IDs (bmad-ma-agent-*)
2016
- migrateRetiredSkills,
2017
- RETIRED_SKILL_IDS,
2018
- RENAMED_SKILL_IDS,
2019
- OBSOLETE_SKILL_IDS,
2020
- // Bug B2 — stale copilot skills dir cleanup + future tool-dir migrations.
2021
- // Story 24.11 (AC5) will extend OBSOLETE_TOOL_SKILL_DIRS with additional entries.
2022
- OBSOLETE_TOOL_SKILL_DIRS,
2023
- migrateObsoleteToolDirs
2024
- };
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+ const prompts = require('prompts');
5
+ const { getAgent, getAllAgents } = require('./agents');
6
+ // Story 22.8 — reuse the canonical stage-directory name so the gitignore policy
7
+ // stays in lock-step with lib/bmad.js#stagePlugin / cleanupStage.
8
+ const { PLUGIN_STAGE_DIR_NAME } = require('./bmad');
9
+
10
+ const MANIFEST_FILE = '.ma-agents.json';
11
+ const MANIFEST_VERSION = '1.2.0';
12
+ const MA_AGENTS_SOURCE = 'ma-agents';
13
+ const TEMPLATE_PATH = path.join(__dirname, 'templates', 'project-context.template.md');
14
+ const UNIVERSAL_INSTRUCTION_TEMPLATE_PATH = path.join(__dirname, 'templates', 'instruction-block-universal.template.md');
15
+ const ONPREM_INSTRUCTION_TEMPLATE_PATH = path.join(__dirname, 'templates', 'instruction-block-onprem.template.md');
16
+ const CLINERULES_TEMPLATE_PATH = path.join(__dirname, 'templates', 'clinerules.template.md');
17
+ const EXTRA_TEMPLATE_DIR = path.join(__dirname, 'templates');
18
+
19
+ /**
20
+ * Story 21.5 AC #6 — Dual-file drift detection error.
21
+ *
22
+ * Thrown by the installer when the in-marker contents of `.cline/clinerules.md`
23
+ * and `.clinerules` diverge (non-whitespace diff). `--yes` does NOT bypass this
24
+ * check — reconciliation between the two Cline rule files is user work, and
25
+ * silently picking a "winner" could discard intentional edits.
26
+ *
27
+ * Follows the error-class naming pattern introduced by Story 21.3/21.10's
28
+ * RoomodesSlugDivergenceError.
29
+ */
30
+ class ClinerulesDualFileDriftError extends Error {
31
+ constructor({ fileA, fileB, diff }) {
32
+ const header = `Cline dual-file drift detected between ${fileA} and ${fileB}.`;
33
+ const guidance = 'Reconcile the two files manually (copy the correct marker-block content into both) before re-running install. `--yes` does NOT bypass this check.';
34
+ super(`${header}\n${guidance}\n\n--- diff (${fileA} vs. ${fileB}) ---\n${diff}`);
35
+ this.name = 'ClinerulesDualFileDriftError';
36
+ this.fileA = fileA;
37
+ this.fileB = fileB;
38
+ this.diff = diff;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Extract the content between MA-AGENTS markers in a file, without the marker
44
+ * lines themselves. Returns null when the file does not exist OR has no marker
45
+ * pair. Whitespace-only leading/trailing runs inside the block are preserved —
46
+ * caller decides whether to normalize before comparing.
47
+ */
48
+ function _extractMarkerBlockInner(filePath) {
49
+ if (!fs.existsSync(filePath)) return null;
50
+ const content = fs.readFileSync(filePath, 'utf-8');
51
+ const m = content.match(/<!-- MA-AGENTS-START -->([\s\S]*?)<!-- MA-AGENTS-END -->/);
52
+ if (!m) return null;
53
+ return m[1];
54
+ }
55
+
56
+ /**
57
+ * Story 21.5 AC #6 — Compare in-marker content of `.cline/clinerules.md` and
58
+ * `.clinerules` (when both exist). Non-whitespace divergence throws
59
+ * ClinerulesDualFileDriftError. If only one file exists, drift detection is
60
+ * skipped (AC #6, Task 3.4 "render once, write twice" invariant).
61
+ *
62
+ * Exposed for tests via module.exports; called internally by
63
+ * updateAgentInstructions on the Cline agent path.
64
+ */
65
+ function checkClinerulesDualFileDrift(projectRoot) {
66
+ const pathA = path.join(projectRoot, '.cline', 'clinerules.md');
67
+ const pathB = path.join(projectRoot, '.clinerules');
68
+ // .clinerules is now a directory (Cline's correct structure) — no file to drift-check.
69
+ if (fs.existsSync(pathB) && fs.lstatSync(pathB).isDirectory()) return;
70
+ const innerA = _extractMarkerBlockInner(pathA);
71
+ const innerB = _extractMarkerBlockInner(pathB);
72
+ if (innerA == null || innerB == null) return; // one (or both) absent — skip
73
+ const normalize = (s) => s.replace(/\s+/g, ' ').trim();
74
+ if (normalize(innerA) === normalize(innerB)) return;
75
+ // Build a minimal unified diff (line-level) for the message.
76
+ const linesA = innerA.split('\n');
77
+ const linesB = innerB.split('\n');
78
+ const diffLines = [];
79
+ const maxLen = Math.max(linesA.length, linesB.length);
80
+ for (let i = 0; i < maxLen; i++) {
81
+ const a = linesA[i];
82
+ const b = linesB[i];
83
+ if (a === b) continue;
84
+ if (a !== undefined) diffLines.push(`- ${a}`);
85
+ if (b !== undefined) diffLines.push(`+ ${b}`);
86
+ }
87
+ throw new ClinerulesDualFileDriftError({
88
+ fileA: path.relative(projectRoot, pathA).replace(/\\/g, '/'),
89
+ fileB: path.relative(projectRoot, pathB).replace(/\\/g, '/'),
90
+ diff: diffLines.join('\n')
91
+ });
92
+ }
93
+
94
+ // Story 21.4 — memoize resolved BMAD-output dirs per projectRoot so the
95
+ // install loop resolves once (AC #10c) and logs once.
96
+ const _bmadOutputDirsCache = new Map();
97
+ const _bmadOutputDirsLogged = new Set();
98
+
99
+ /**
100
+ * Story 21.2 — Universal per-tool instruction block composer.
101
+ *
102
+ * Reads lib/templates/instruction-block-universal.template.md (always).
103
+ * If profile === 'on-prem', appends lib/templates/instruction-block-onprem.template.md
104
+ * separated by a single blank line. If the on-prem template is missing when required,
105
+ * THROWS — there is no silent fallback (Decision A, AC #3).
106
+ *
107
+ * Templates contain NO substitution of {{...}} placeholders inside this function; any
108
+ * substitution (e.g., {{MANIFEST_PATH}}) is the caller's responsibility, applied AFTER
109
+ * composition. This keeps the composer a single-owner entry point that downstream
110
+ * stories 21.3/21.4/21.5/21.6 consume without duplication.
111
+ *
112
+ * @param {{profile: string|undefined, projectRoot: string}} args
113
+ * @returns {string} composed template content (with placeholders intact)
114
+ */
115
+ function composeInstructionBlock({ profile, projectRoot } = {}) {
116
+ // projectRoot is accepted for API-stability with downstream stories even though
117
+ // the current implementation does not read from it (templates ship with the
118
+ // package). Keeping the parameter documented avoids signature churn in 21.6.
119
+ void projectRoot;
120
+
121
+ let universal;
122
+ try {
123
+ universal = fs.readFileSync(UNIVERSAL_INSTRUCTION_TEMPLATE_PATH, 'utf-8');
124
+ } catch (err) {
125
+ throw new Error(
126
+ `universal instruction template not found at ${UNIVERSAL_INSTRUCTION_TEMPLATE_PATH} — ma-agents installation may be corrupted: ${err.message}`
127
+ );
128
+ }
129
+
130
+ if (!universal.includes('{{MANIFEST_PATH}}')) {
131
+ throw new Error(
132
+ `universal instruction template at ${UNIVERSAL_INSTRUCTION_TEMPLATE_PATH} is missing the required {{MANIFEST_PATH}} placeholder`
133
+ );
134
+ }
135
+
136
+ if (profile === 'on-prem') {
137
+ if (!fs.existsSync(ONPREM_INSTRUCTION_TEMPLATE_PATH)) {
138
+ throw new Error('on-prem profile selected but instruction-block-onprem.template.md is missing');
139
+ }
140
+ const onprem = fs.readFileSync(ONPREM_INSTRUCTION_TEMPLATE_PATH, 'utf-8');
141
+ // Normalize both pieces so the concatenation has exactly one blank line between them
142
+ // and no trailing whitespace — feeds NFR46 byte-identity.
143
+ return universal.replace(/\s+$/, '') + '\n\n' + onprem.replace(/\s+$/, '') + '\n';
144
+ }
145
+
146
+ // Normalize trailing whitespace so first-insert and in-place-replace both
147
+ // produce byte-identical content inside the markers (NFR46, AC #6).
148
+ return universal.replace(/\s+$/, '') + '\n';
149
+ }
150
+
151
+ /**
152
+ * Story 21.4 AC #10 — resolve BMAD output directories once per install.
153
+ * Story 22.7 — reads the canonical v6.3.0 config at `_bmad/bmm/config.yaml`.
154
+ *
155
+ * Precedence:
156
+ * a) If `_bmad/bmm/config.yaml` exists AND has explicit `planning_artifacts`,
157
+ * `architecture_artifacts`, and `implementation_artifacts`, use those.
158
+ * b) Otherwise, fall back to the documented defaults:
159
+ * planning: _bmad-output/planning-artifacts
160
+ * architecture: _bmad-output/planning-artifacts (co-located default)
161
+ * stories: _bmad-output/implementation-artifacts
162
+ *
163
+ * This resolver NEVER consults the legacy `_bmad/_config/manifest.yaml`.
164
+ * If a legacy install is present without the canonical file, the defaults
165
+ * are used; the install flow (`lib/bmad.js::installBmad`) invokes
166
+ * `ensureCanonicalConfigLocation()` earlier so bmad-method regenerates the
167
+ * canonical layout during the install that triggered this call.
168
+ *
169
+ * YAML parsing is intentionally minimal — we only need a handful of
170
+ * top-level scalar keys. Adding a full YAML dependency just for this is
171
+ * overkill and would widen the supply chain for one helper.
172
+ *
173
+ * @param {string} projectRoot
174
+ * @returns {{planning: string, architecture: string, stories: string}}
175
+ */
176
+ function resolveBmadOutputDirs(projectRoot) {
177
+ if (_bmadOutputDirsCache.has(projectRoot)) {
178
+ return _bmadOutputDirsCache.get(projectRoot);
179
+ }
180
+
181
+ const defaults = {
182
+ planning: '_bmad-output/planning-artifacts',
183
+ architecture: '_bmad-output/planning-artifacts',
184
+ stories: '_bmad-output/implementation-artifacts'
185
+ };
186
+
187
+ const configPath = path.join(projectRoot, '_bmad', 'bmm', 'config.yaml');
188
+ const dirs = { ...defaults };
189
+
190
+ if (fs.existsSync(configPath)) {
191
+ try {
192
+ const yamlText = fs.readFileSync(configPath, 'utf-8');
193
+ const scanScalar = (key) => {
194
+ // Match `key: "value"` or `key: value` at line start (indent tolerated),
195
+ // ignoring commented lines. Value is the quoted string or the bare token.
196
+ const re = new RegExp(`^[\\t ]*${key}\\s*:\\s*(?:"([^"]*)"|'([^']*)'|([^#\\n\\r]+?))\\s*(?:#.*)?$`, 'm');
197
+ const m = yamlText.match(re);
198
+ if (!m) return null;
199
+ const raw = m[1] || m[2] || m[3] || '';
200
+ return raw.trim().replace(/\\/g, '/');
201
+ };
202
+ const planning = scanScalar('planning_artifacts');
203
+ const architecture = scanScalar('architecture_artifacts');
204
+ const stories = scanScalar('implementation_artifacts');
205
+ if (planning) dirs.planning = planning;
206
+ if (architecture) dirs.architecture = architecture;
207
+ if (stories) dirs.stories = stories;
208
+ } catch {
209
+ // Malformed or unreadable config — fall back silently to defaults.
210
+ }
211
+ }
212
+
213
+ _bmadOutputDirsCache.set(projectRoot, dirs);
214
+ if (!_bmadOutputDirsLogged.has(projectRoot)) {
215
+ _bmadOutputDirsLogged.add(projectRoot);
216
+ console.log(
217
+ `Resolved BMAD output dirs: planning=${dirs.planning}, architecture=${dirs.architecture}, stories=${dirs.stories}`
218
+ );
219
+ }
220
+ return dirs;
221
+ }
222
+
223
+ // Claude Code hook configuration for MANIFEST verification
224
+ const CLAUDE_CODE_HOOK_ID = 'ma-agents-verify-manifest';
225
+ const CLAUDE_CODE_SETTINGS_FILE = '.claude/settings.json';
226
+
227
+ function getPackageVersion() {
228
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
229
+ return pkg.version;
230
+ }
231
+
232
+ function getBmadVersion() {
233
+ try {
234
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'bmad-method', 'package.json'), 'utf-8'));
235
+ return pkg.version;
236
+ } catch {
237
+ return null;
238
+ }
239
+ }
240
+
241
+ // --- Manifest functions ---
242
+
243
+ function readManifest(installPath) {
244
+ const manifestPath = path.join(installPath, MANIFEST_FILE);
245
+ if (!fs.existsSync(manifestPath)) {
246
+ return null;
247
+ }
248
+ try {
249
+ return JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
250
+ } catch {
251
+ return null;
252
+ }
253
+ }
254
+
255
+ function writeManifest(installPath, manifest) {
256
+ const manifestPath = path.join(installPath, MANIFEST_FILE);
257
+ // Stamp version fields on every write so they always reflect the last run.
258
+ manifest.toolVersion = getPackageVersion();
259
+ manifest.bmadVersion = getBmadVersion();
260
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
261
+ }
262
+
263
+ const BMAD_OUTPUT_PATTERNS = ['_bmad-output', '_bmad-output/', '/_bmad-output', '/_bmad-output/'];
264
+
265
+ function ensureBmadOutputTracked(projectRoot) {
266
+ const gitignorePath = path.join(projectRoot, '.gitignore');
267
+ let content;
268
+ try {
269
+ content = fs.readFileSync(gitignorePath, 'utf-8');
270
+ } catch (err) {
271
+ if (err.code === 'ENOENT') return; // no .gitignore — nothing to do
272
+ throw err;
273
+ }
274
+
275
+ const lines = content.split(/\r?\n/);
276
+ const filtered = lines.filter(line => !BMAD_OUTPUT_PATTERNS.includes(line.trim()));
277
+
278
+ if (filtered.length === lines.length) return; // nothing removed — do not write
279
+
280
+ fs.writeFileSync(gitignorePath, filtered.join('\n'), 'utf-8');
281
+ console.log(chalk.green('_bmad-output is now tracked as project knowledge (not gitignored)'));
282
+ }
283
+
284
+ // Story 22.8 — plugin-stage directory gitignore policy.
285
+ //
286
+ // Sister-policy to BMAD_OUTPUT_PATTERNS above. `_bmad-output/` is intentionally
287
+ // NOT gitignored (it holds tracked project knowledge); by contrast the
288
+ // project-local plugin stage (`<projectRoot>/.ma-agents-plugin-stage/`, see
289
+ // lib/bmad.js#stagePlugin) is a transient install artifact that should NEVER
290
+ // be committed. Both forms (trailing / or not) match the directory per git's
291
+ // pathspec rules, and the leading-slash variants anchor to the project root.
292
+ const PLUGIN_STAGE_PATTERNS = [
293
+ PLUGIN_STAGE_DIR_NAME, // .ma-agents-plugin-stage
294
+ `${PLUGIN_STAGE_DIR_NAME}/`, // .ma-agents-plugin-stage/
295
+ `/${PLUGIN_STAGE_DIR_NAME}`, // /.ma-agents-plugin-stage
296
+ `/${PLUGIN_STAGE_DIR_NAME}/`, // /.ma-agents-plugin-stage/
297
+ ];
298
+
299
+ // Canonical entry we write when the pattern is absent — trailing slash makes
300
+ // the "directory-only" intent explicit for human readers.
301
+ const PLUGIN_STAGE_CANONICAL_ENTRY = `${PLUGIN_STAGE_DIR_NAME}/`;
302
+
303
+ /**
304
+ * Ensure `.ma-agents-plugin-stage/` is present in the target project's
305
+ * `.gitignore`. Append-only and idempotent:
306
+ *
307
+ * - If `.gitignore` is absent, create it with the single canonical entry.
308
+ * - If any active (non-comment) line already matches any PLUGIN_STAGE_PATTERNS
309
+ * variant, do nothing.
310
+ * - Otherwise, append `PLUGIN_STAGE_CANONICAL_ENTRY` preserving the file's
311
+ * existing line endings (CRLF vs LF) and guaranteeing a trailing newline
312
+ * so the new entry is not concatenated to the previous line.
313
+ *
314
+ * Unrelated lines are never modified — this is strictly additive. Commented
315
+ * variants (lines starting with `#`) are ignored for the "already present"
316
+ * check, matching git's own semantics.
317
+ *
318
+ * F2a contract — the caller MUST pass the **project root** (the directory
319
+ * that contains the target project's top-level `.gitignore`), NOT a per-agent
320
+ * skills install path (e.g., `.claude/skills/`). Earlier (pre-F2a) the call
321
+ * site in `installSkill()` passed `installPath`, which produced per-skill-dir
322
+ * `.gitignore` files instead of one project-root file. The fix moved the
323
+ * single invocation next to `stagePlugin(projectRoot)` in `lib/bmad.js`.
324
+ *
325
+ * Defensive behavior — if `projectRoot` is falsy or not a string, this
326
+ * function is a no-op. The `ensurePluginStageGitignoredForProject()` helper
327
+ * in `lib/bmad.js` is the canonical caller; this guard exists so a stray
328
+ * call from misconfigured tests or third-party code cannot accidentally write
329
+ * to `cwd`.
330
+ *
331
+ * @param {string} projectRoot - Absolute path to the target project.
332
+ */
333
+ function ensurePluginStageIgnored(projectRoot) {
334
+ // F2a — defensive guard: reject undefined/null/non-string input rather than
335
+ // silently writing to `path.join(undefined, '.gitignore')` (which throws on
336
+ // older Node) or to whatever `path.join('')` resolves to (cwd).
337
+ if (typeof projectRoot !== 'string' || projectRoot.length === 0) {
338
+ return;
339
+ }
340
+
341
+ const gitignorePath = path.join(projectRoot, '.gitignore');
342
+
343
+ let content = '';
344
+ let fileExists = true;
345
+ try {
346
+ content = fs.readFileSync(gitignorePath, 'utf-8');
347
+ } catch (err) {
348
+ if (err.code !== 'ENOENT') throw err;
349
+ fileExists = false;
350
+ }
351
+
352
+ if (fileExists) {
353
+ const alreadyPresent = content.split(/\r?\n/).some(rawLine => {
354
+ const line = rawLine.trim();
355
+ if (!line || line.startsWith('#')) return false; // skip blanks & comments
356
+ return PLUGIN_STAGE_PATTERNS.includes(line);
357
+ });
358
+ if (alreadyPresent) return; // idempotent no-op
359
+ }
360
+
361
+ // Preserve the file's existing line-ending style; default to LF for new files.
362
+ const usesCrlf = fileExists && /\r\n/.test(content);
363
+ const eol = usesCrlf ? '\r\n' : '\n';
364
+
365
+ let next = content;
366
+ if (next && !next.endsWith('\n') && !next.endsWith('\r\n')) {
367
+ next += eol; // ensure the append lands on its own line
368
+ }
369
+ next += PLUGIN_STAGE_CANONICAL_ENTRY + eol;
370
+
371
+ fs.writeFileSync(gitignorePath, next, 'utf-8');
372
+ console.log(
373
+ chalk.green(
374
+ `${PLUGIN_STAGE_CANONICAL_ENTRY} added to .gitignore (transient plugin stage — safe to ignore)`
375
+ )
376
+ );
377
+ }
378
+
379
+ function ensureManifest(installPath, agentId, scope) {
380
+ let manifest = readManifest(installPath);
381
+ if (!manifest) {
382
+ manifest = {
383
+ manifestVersion: MANIFEST_VERSION,
384
+ agent: agentId,
385
+ agents: [agentId],
386
+ scope: scope,
387
+ skills: {}
388
+ };
389
+ } else {
390
+ // Migrate v1.0.0 manifests: add agents array if missing
391
+ if (!manifest.agents) {
392
+ manifest.agents = manifest.agent ? [manifest.agent] : [];
393
+ }
394
+ // Add current agent if not already present
395
+ if (agentId && !manifest.agents.includes(agentId)) {
396
+ manifest.agents.push(agentId);
397
+ }
398
+ // Keep backward-compat agent field as first agent
399
+ manifest.agent = manifest.agents[0] || null;
400
+ }
401
+ return manifest;
402
+ }
403
+
404
+ function getManifestAgents(manifest) {
405
+ if (!manifest) return [];
406
+ if (manifest.agents && Array.isArray(manifest.agents)) {
407
+ return manifest.agents;
408
+ }
409
+ return manifest.agent ? [manifest.agent] : [];
410
+ }
411
+
412
+ function getInstalledSkillInfo(installPath, skillId) {
413
+ const manifest = readManifest(installPath);
414
+ if (!manifest || !manifest.skills || !manifest.skills[skillId]) {
415
+ return null;
416
+ }
417
+ return manifest.skills[skillId];
418
+ }
419
+
420
+ async function generateSkillsManifest(installPath) {
421
+ const skills = listSkills();
422
+ const manifest = readManifest(installPath);
423
+ if (!manifest || !manifest.skills) return;
424
+
425
+ const manifestYamlPath = path.join(installPath, 'MANIFEST.yaml');
426
+ let yamlContent = '# MANIFEST.yaml\n\nskills:\n';
427
+
428
+ const skillIds = Object.keys(manifest.skills).sort();
429
+ for (const skillId of skillIds) {
430
+ const skill = skills.find(s => s.id === skillId);
431
+ if (!skill) continue;
432
+
433
+ yamlContent += ` - id: ${skillId}\n`;
434
+ yamlContent += ` file: ${skillId}/SKILL.md\n`;
435
+ yamlContent += ` description: ${skill.description}\n`;
436
+
437
+ if (skill.applies_when && Array.isArray(skill.applies_when)) {
438
+ yamlContent += ' applies_when:\n';
439
+ skill.applies_when.forEach(cond => {
440
+ yamlContent += ` - ${cond}\n`;
441
+ });
442
+ }
443
+
444
+ if (skill.always_load) {
445
+ yamlContent += ' always_load: true\n';
446
+ }
447
+
448
+ yamlContent += '\n';
449
+ }
450
+
451
+ await fs.writeFile(manifestYamlPath, yamlContent, 'utf-8');
452
+ console.log(chalk.cyan(` + Generated ${manifestYamlPath}`));
453
+ }
454
+
455
+ /**
456
+ * Generate project-context.md content by stamping the template with installed agent MANIFEST paths.
457
+ * @param {string} projectRoot - Absolute path to project root (unused for path generation but part of API)
458
+ * @param {Array<{skillsDir: string}>} installedAgents - Agent objects with skillsDir property
459
+ * @param {Object|null} [layout=null] - Repository layout from collectRepoLayout() (null for single-repo)
460
+ * @returns {Promise<string>} Stamped template content string (does NOT write any file)
461
+ */
462
+ async function generateProjectContext(projectRoot, installedAgents, layout = null) {
463
+ let template;
464
+ try {
465
+ template = await fs.readFile(TEMPLATE_PATH, 'utf8');
466
+ } catch (err) {
467
+ throw new Error(`project-context template not found at ${TEMPLATE_PATH} — ma-agents installation may be corrupted: ${err.message}`);
468
+ }
469
+
470
+ const validAgents = installedAgents ? installedAgents.filter(a => a && a.skillsDir) : [];
471
+ let manifestList;
472
+ if (validAgents.length === 0) {
473
+ manifestList = ' - (no agents installed — run ma-agents to install skills)';
474
+ } else {
475
+ manifestList = validAgents
476
+ .map(a => ` - \`${a.skillsDir}/MANIFEST.yaml\``)
477
+ .join('\n');
478
+ }
479
+
480
+ let content = template.replace('{{MANIFEST_PATHS_LIST}}', manifestList);
481
+
482
+ // Replace repo layout placeholder (Story 16.3)
483
+ const layoutSection = generateRepoLayoutSection(layout);
484
+ if (layoutSection) {
485
+ content = content.replace('{{REPO_LAYOUT_SECTION}}', '\n' + layoutSection + '\n');
486
+ } else {
487
+ // Clean removal: remove placeholder and avoid double blank lines
488
+ content = content.replace(/\{\{REPO_LAYOUT_SECTION\}\}\r?\n/, '');
489
+ }
490
+
491
+ return content;
492
+ }
493
+
494
+ /**
495
+ * Generate the Repository Layout markdown section for project-context.md.
496
+ * Returns the section with markers for multi-repo, or empty string for single-repo/null layout.
497
+ * @param {Object|null} layout - Layout object from collectRepoLayout()
498
+ * @returns {string} Markdown section with markers, or empty string
499
+ */
500
+ function generateRepoLayoutSection(layout) {
501
+ if (!layout || !layout.knowledgebase || !layout.sprintManagement) return '';
502
+ if (layout.knowledgebase.mode === 'same' && layout.sprintManagement.mode === 'same') return '';
503
+
504
+ const normPath = (p) => (p || '.').replace(/\\/g, '/');
505
+ const kbDisplay = layout.knowledgebase.mode === 'same'
506
+ ? 'current repository (default)'
507
+ : normPath(layout.knowledgebase.path);
508
+ const spDisplay = layout.sprintManagement.mode === 'same'
509
+ ? 'current repository (default)'
510
+ : normPath(layout.sprintManagement.path);
511
+
512
+ let lines = [
513
+ '<!-- ma-agents:repo-layout-start -->',
514
+ '### Repository Layout',
515
+ `- **Knowledgebase:** ${kbDisplay}`,
516
+ `- **Sprint Management:** ${spDisplay}`,
517
+ ];
518
+
519
+ if (layout.knowledgebase.mode !== 'same') {
520
+ lines.push('- When creating or reading planning artifacts, use the knowledgebase path');
521
+ }
522
+ if (layout.sprintManagement.mode !== 'same') {
523
+ lines.push('- When creating or reading sprint/story artifacts, use the sprint management path');
524
+ }
525
+
526
+ lines.push('<!-- ma-agents:repo-layout-end -->');
527
+ return lines.join('\n');
528
+ }
529
+
530
+ /**
531
+ * Update the Repository Layout section in an existing project-context.md.
532
+ * Uses marker-based update pattern (same as manifest paths).
533
+ * @param {string} outputPath - Absolute path to existing project-context.md
534
+ * @param {Object|null} layout - Layout object from collectRepoLayout()
535
+ * @returns {Promise<boolean>} true if file was written, false otherwise
536
+ */
537
+ async function updateProjectContextRepoLayout(outputPath, layout) {
538
+ let content;
539
+ try {
540
+ content = await fs.readFile(outputPath, 'utf8');
541
+ } catch {
542
+ return false;
543
+ }
544
+
545
+ const newSection = generateRepoLayoutSection(layout);
546
+ const START = '<!-- ma-agents:repo-layout-start -->';
547
+ const END = '<!-- ma-agents:repo-layout-end -->';
548
+ const startIdx = content.indexOf(START);
549
+ const endIdx = content.indexOf(END);
550
+
551
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
552
+ // Markers exist — replace content between them
553
+ if (newSection) {
554
+ const newContent = content.slice(0, startIdx) + newSection + content.slice(endIdx + END.length);
555
+ if (newContent === content) return false;
556
+ await fs.writeFile(outputPath, newContent, 'utf8');
557
+ return true;
558
+ } else {
559
+ // Single-repo: remove the entire section including markers and surrounding blank lines
560
+ let before = content.slice(0, startIdx);
561
+ let after = content.slice(endIdx + END.length);
562
+ // Clean up trailing newline from before and leading newline from after
563
+ if (before.endsWith('\n')) before = before.slice(0, -1);
564
+ if (after.startsWith('\n')) after = after.slice(1);
565
+ const newContent = before + '\n' + after;
566
+ if (newContent === content) return false;
567
+ await fs.writeFile(outputPath, newContent, 'utf8');
568
+ return true;
569
+ }
570
+ }
571
+
572
+ // Markers don't exist
573
+ if (!newSection) return false; // single-repo, nothing to insert
574
+
575
+ // Find insertion point: before "## Technology Stack"
576
+ const techStackIdx = content.indexOf('## Technology Stack');
577
+ if (techStackIdx === -1) {
578
+ // Can't find expected structure — skip with info
579
+ return false;
580
+ }
581
+
582
+ const newContent = content.slice(0, techStackIdx) + newSection + '\n\n' + content.slice(techStackIdx);
583
+ await fs.writeFile(outputPath, newContent, 'utf8');
584
+ return true;
585
+ }
586
+
587
+ /**
588
+ * Update the MANIFEST paths section in an existing project-context.md.
589
+ * Locates content between <!-- ma-agents:manifest-paths-start --> and
590
+ * <!-- ma-agents:manifest-paths-end --> markers and replaces it with the
591
+ * current agent list. Returns true if the file was updated, false if
592
+ * markers were not found (old-format file — backward compatible) or if
593
+ * the content was already up to date.
594
+ * @param {string} outputPath - Absolute path to the existing project-context.md
595
+ * @param {Array<{skillsDir: string}>} installedAgents - Agent objects with skillsDir property
596
+ * @returns {Promise<boolean>} true if file was written, false otherwise
597
+ */
598
+ async function updateProjectContextManifestPaths(outputPath, installedAgents) {
599
+ const content = await fs.readFile(outputPath, 'utf8');
600
+ const validAgents = installedAgents ? installedAgents.filter(a => a && a.skillsDir) : [];
601
+ const newList = validAgents.length === 0
602
+ ? ' - (no agents installed — run ma-agents to install skills)'
603
+ : validAgents.map(a => ` - \`${a.skillsDir}/MANIFEST.yaml\``).join('\n');
604
+
605
+ const START = '<!-- ma-agents:manifest-paths-start -->';
606
+ const END = '<!-- ma-agents:manifest-paths-end -->';
607
+ const startIdx = content.indexOf(START);
608
+ const endIdx = content.indexOf(END);
609
+
610
+ if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
611
+ return false; // no markers — old-format file, skip silently (backward compatible)
612
+ }
613
+
614
+ const before = content.slice(0, startIdx + START.length);
615
+ const after = content.slice(endIdx);
616
+ const newContent = `${before}\n${newList}\n${after}`;
617
+
618
+ if (newContent === content) {
619
+ return false; // already up to date, no write needed
620
+ }
621
+
622
+ await fs.writeFile(outputPath, newContent, 'utf8');
623
+ return true;
624
+ }
625
+
626
+ /**
627
+ * Find the insertion point in content after all skipped headers.
628
+ * For '---' pattern, skips YAML frontmatter block (opening --- on first line through closing ---).
629
+ * @param {string} content - File content
630
+ * @param {string[]} [skipPatterns] - Patterns to skip (currently supports '---' for YAML frontmatter)
631
+ * @returns {number} Character index where injection should be inserted
632
+ */
633
+ function findInsertionPoint(content, skipPatterns) {
634
+ if (!content || !skipPatterns || skipPatterns.length === 0) {
635
+ return 0;
636
+ }
637
+
638
+ let idx = 0;
639
+
640
+ for (const pattern of skipPatterns) {
641
+ if (pattern === '---') {
642
+ // YAML frontmatter: must start at position 0 (or after leading whitespace)
643
+ const trimmedStart = content.slice(idx).trimStart();
644
+ const leadingWhitespace = content.length - idx - trimmedStart.length;
645
+
646
+ if (!trimmedStart.startsWith('---')) {
647
+ continue;
648
+ }
649
+
650
+ // Find the opening ---
651
+ const openIdx = idx + leadingWhitespace;
652
+ const afterOpen = content.indexOf('\n', openIdx);
653
+ if (afterOpen === -1) {
654
+ continue;
655
+ }
656
+
657
+ // Find the closing ---
658
+ const closeIdx = content.indexOf('\n---', afterOpen);
659
+ if (closeIdx === -1) {
660
+ continue;
661
+ }
662
+
663
+ // Move past the closing --- line
664
+ const afterClose = content.indexOf('\n', closeIdx + 1);
665
+ if (afterClose === -1) {
666
+ idx = content.length;
667
+ } else {
668
+ idx = afterClose + 1;
669
+ }
670
+ }
671
+ }
672
+
673
+ return idx;
674
+ }
675
+
676
+ /**
677
+ * Story 21.2 AC #10 — canonical backup filename format.
678
+ * Format: <target>.backup-<ISO-8601-timestamp> with colons replaced by hyphens
679
+ * for Windows filename safety (e.g., .claude/CLAUDE.md.backup-2026-04-15T12-30-00Z).
680
+ * Story 21.2 OWNS this format; stories 21.10 (reconfigure) and 21.11 (uninstall)
681
+ * consume it. The date source is injectable to keep tests deterministic.
682
+ */
683
+ function formatBackupTimestamp(date = new Date()) {
684
+ // ISO 8601 with hyphens instead of colons and no milliseconds:
685
+ // 2026-04-15T12-30-00Z
686
+ return date.toISOString().replace(/\.\d{3}Z$/, 'Z').replace(/:/g, '-');
687
+ }
688
+
689
+ function buildBackupFilename(targetPath, date = new Date()) {
690
+ const base = `${targetPath}.backup-${formatBackupTimestamp(date)}`;
691
+ // Guard against sub-second re-runs clobbering a prior backup. If the canonical
692
+ // name already exists on disk, append a ".N" suffix until we find a free slot.
693
+ if (!fs.existsSync(base)) return base;
694
+ let i = 1;
695
+ while (fs.existsSync(`${base}.${i}`)) i++;
696
+ return `${base}.${i}`;
697
+ }
698
+
699
+ /**
700
+ * Story 21.2 AC #10 — marker-block drift handler.
701
+ *
702
+ * Called when the existing in-marker content differs from what
703
+ * composeInstructionBlock would produce for the current profile. In interactive
704
+ * mode (no --yes), prompts the user for confirmation. With --yes or when stdin
705
+ * is not a TTY, emits the pinned WARNING line and proceeds. In all drift cases
706
+ * where we proceed with the overwrite, a backup sibling file is written
707
+ * containing ONLY the marker-block region (markers included).
708
+ *
709
+ * Throws if the user declines the interactive prompt, short-circuiting the
710
+ * write in the caller.
711
+ */
712
+ async function handleMarkerBlockDrift({ filePath, existingBlock, expectedBlock, yesMode }) {
713
+ const backupPath = buildBackupFilename(filePath);
714
+
715
+ // In non-interactive mode (yes mode or non-TTY), emit pinned warning and proceed.
716
+ // In interactive mode, show diff preview and prompt for confirmation.
717
+ const interactive = !yesMode && process.stdin.isTTY;
718
+
719
+ if (interactive) {
720
+ // Show a compact diff-style preview. We intentionally do not require a diff
721
+ // library — the on-screen diff is informational only.
722
+ console.log(chalk.yellow(`\nma-agents marker-block in ${filePath} was modified since last install.`));
723
+ console.log(chalk.gray('--- current on-disk (inside markers) ---'));
724
+ console.log(existingBlock);
725
+ console.log(chalk.gray('--- expected (ma-agents) ---'));
726
+ console.log(expectedBlock);
727
+ const { proceed } = await prompts({
728
+ type: 'confirm',
729
+ name: 'proceed',
730
+ message: `Overwrite and back up previous content to ${backupPath}?`,
731
+ initial: false
732
+ });
733
+ if (!proceed) {
734
+ throw new Error(`User declined to overwrite ma-agents marker block in ${filePath}`);
735
+ }
736
+ } else {
737
+ // AC #10 pinned WARNING line (verbatim).
738
+ console.log(
739
+ `WARNING: ma-agents marker-block content modified since last install — overwriting. Previous content backed up to ${backupPath}`
740
+ );
741
+ }
742
+
743
+ // Backup contains only the marker-block region (markers included).
744
+ await fs.outputFile(backupPath, existingBlock, 'utf-8');
745
+ }
746
+
747
+ /**
748
+ * Story 21.4 — markdown-markers merger for extraInstructionTemplates.
749
+ *
750
+ * Writes a marker-wrapped instruction block into a markdown target file. This
751
+ * is the same marker-wrap contract used by updateAgentInstructions for
752
+ * single-instructionFile agents (AC #5), lifted into a reusable helper so the
753
+ * extraInstructionTemplates processor can dispatch on merger = 'markdown-markers'.
754
+ *
755
+ * Behavior (AC #5):
756
+ * - If target file does not exist: create it by writing the full template
757
+ * contents (including the leading "Generated by ma-agents" comment and
758
+ * the markers), with `composedBlock` placed between the MA-AGENTS markers.
759
+ * - If target exists with markers: replace in-marker content only; content
760
+ * outside markers is preserved byte-for-byte. Hand-edit drift detection
761
+ * (AC #11) uses the same handleMarkerBlockDrift helper as Story 21.2.
762
+ * - If target exists WITHOUT markers: append the marker block at EOF
763
+ * separated by one blank line. Existing content preserved.
764
+ *
765
+ * @param {string} targetPath - absolute path to the target file
766
+ * @param {string} templateBody - static template text (from lib/templates/)
767
+ * @param {string} composedBlock - the output of composeInstructionBlock(...)
768
+ * @param {{ yesMode?: boolean }} opts
769
+ * @returns {Promise<'created'|'updated'|'appended'|'skipped'>}
770
+ */
771
+ async function markdownMarkersMerger(targetPath, templateBody, composedBlock, opts = {}) {
772
+ const markerStart = '<!-- MA-AGENTS-START -->';
773
+ const markerEnd = '<!-- MA-AGENTS-END -->';
774
+ const yesMode = !!(opts.yesMode || process.env.MA_AGENTS_YES === '1');
775
+
776
+ // Normalize composedBlock trailing whitespace once so first-insert and
777
+ // in-place-replace both produce byte-identical in-marker content (NFR46).
778
+ const normalized = composedBlock.replace(/\s+$/, '') + '\n';
779
+ const wrappedBlock = `${markerStart}\n${normalized}${markerEnd}`;
780
+
781
+ if (!fs.existsSync(targetPath)) {
782
+ // AC #5 first bullet: fresh create — write leading comment + template, with
783
+ // markers replaced to carry the composed content.
784
+ const leadingComment = '<!-- Generated by ma-agents. Edit outside the MA-AGENTS-START/END markers to preserve your changes. -->\n';
785
+ // The template already contains placeholder empty markers ("<!-- MA-AGENTS-START -->\n<!-- MA-AGENTS-END -->").
786
+ // Replace the first such pair with the wrapped block. If the template has no markers,
787
+ // the block is appended at EOF separated by one blank line (defensive — template ships with markers).
788
+ const emptyMarkerPair = new RegExp(`${markerStart}\\s*\\n\\s*${markerEnd}`);
789
+ let body;
790
+ if (emptyMarkerPair.test(templateBody)) {
791
+ body = templateBody.replace(emptyMarkerPair, wrappedBlock);
792
+ } else {
793
+ const trimmed = templateBody.replace(/\s+$/, '');
794
+ body = trimmed + '\n\n' + wrappedBlock + '\n';
795
+ }
796
+ // Ensure single trailing newline.
797
+ const finalBody = body.replace(/\s+$/, '') + '\n';
798
+ await fs.outputFile(targetPath, leadingComment + finalBody, 'utf-8');
799
+ console.log(chalk.cyan(` + Created ${path.relative(path.dirname(targetPath), targetPath) === path.basename(targetPath) ? path.basename(targetPath) : targetPath}`));
800
+ return 'created';
801
+ }
802
+
803
+ const content = await fs.readFile(targetPath, 'utf-8');
804
+ const regex = new RegExp(`${markerStart}[\\s\\S]*?${markerEnd}`);
805
+ const existingMatch = content.match(regex);
806
+
807
+ if (existingMatch) {
808
+ // AC #5 second bullet + AC #11 drift detection.
809
+ const existingBlock = existingMatch[0];
810
+ if (existingBlock === wrappedBlock) {
811
+ // Byte-identical — idempotent no-op write to preserve mtime semantics would be
812
+ // wasteful; just return. Outside-markers content is unchanged.
813
+ return 'skipped';
814
+ }
815
+ // Drift: previous content differs from expected.
816
+ try {
817
+ await handleMarkerBlockDrift({
818
+ filePath: targetPath,
819
+ existingBlock,
820
+ expectedBlock: wrappedBlock,
821
+ yesMode
822
+ });
823
+ } catch (declineErr) {
824
+ console.log(chalk.gray(` Skipped ${path.basename(targetPath)} (user declined marker-block overwrite)`));
825
+ return 'skipped';
826
+ }
827
+ const replaced = content.replace(regex, wrappedBlock);
828
+ await fs.writeFile(targetPath, replaced, 'utf-8');
829
+ console.log(chalk.cyan(` + Updated ${path.basename(targetPath)}`));
830
+ return 'updated';
831
+ }
832
+
833
+ // AC #5 third bullet: existing file, no markers — append marker block at EOF
834
+ // separated by one blank line.
835
+ const base = content.replace(/\s+$/, '');
836
+ const appended = (base.length ? base + '\n\n' : '') + wrappedBlock + '\n';
837
+ await fs.writeFile(targetPath, appended, 'utf-8');
838
+ console.log(chalk.cyan(` + Appended marker block to ${path.basename(targetPath)}`));
839
+ return 'appended';
840
+ }
841
+
842
+ /**
843
+ * Story 21.4 — process extraInstructionTemplates entries on an agent.
844
+ *
845
+ * For each { template, target, merger } entry:
846
+ * 1. Resolve the source template file under lib/templates/.
847
+ * 2. Call composeInstructionBlock({ profile, projectRoot }) exactly once per
848
+ * entry (canonical composer contract — Story 21.2, decision A).
849
+ * 3. Dispatch on `merger`:
850
+ * - 'markdown-markers' → markdownMarkersMerger (this story)
851
+ * - 'yaml-customModes' → reserved for Story 21.3 (.roomodes)
852
+ * 4. Per-entry MANIFEST_PATH substitution is applied to the composed string
853
+ * BEFORE the merger receives it (caller-owned per Story 21.2 AC #3).
854
+ *
855
+ * @param {object} agent - lib/agents.js entry
856
+ * @param {string} projectRoot
857
+ * @param {{ yesMode?: boolean }} opts
858
+ */
859
+ async function stampExtraInstructionTemplates(agent, projectRoot, opts = {}) {
860
+ if (!Array.isArray(agent.extraInstructionTemplates) || agent.extraInstructionTemplates.length === 0) {
861
+ return;
862
+ }
863
+
864
+ // Resolve BMAD output dirs once per projectRoot (memoized + logged once).
865
+ resolveBmadOutputDirs(projectRoot);
866
+
867
+ const { getProfile } = require('./profile');
868
+ const resolvedProfile = getProfile(projectRoot) || 'standard';
869
+ const agentProjectPath = agent.getProjectPath();
870
+ const relManifestPath = path.relative(projectRoot, path.join(agentProjectPath, 'MANIFEST.yaml')).replace(/\\/g, '/');
871
+
872
+ for (const entry of agent.extraInstructionTemplates) {
873
+ if (!entry || !entry.template || !entry.target || !entry.merger) {
874
+ console.log(chalk.yellow(` Warning: malformed extraInstructionTemplates entry on ${agent.id}, skipping`));
875
+ continue;
876
+ }
877
+ const templatePath = path.join(EXTRA_TEMPLATE_DIR, entry.template);
878
+ if (!fs.existsSync(templatePath)) {
879
+ console.log(chalk.yellow(` Warning: template not found at ${templatePath}, skipping`));
880
+ continue;
881
+ }
882
+ const templateBody = fs.readFileSync(templatePath, 'utf-8');
883
+
884
+ // Canonical composer contract: called exactly once per artifact.
885
+ let composed = composeInstructionBlock({ profile: resolvedProfile, projectRoot });
886
+ // Post-composition substitution is caller-owned (Story 21.2 AC #3).
887
+ composed = composed.replace(/\{\{MANIFEST_PATH\}\}/g, relManifestPath);
888
+
889
+ const targetPath = path.join(projectRoot, entry.target);
890
+
891
+ if (entry.merger === 'markdown-markers') {
892
+ await markdownMarkersMerger(targetPath, templateBody, composed, { yesMode: opts.yesMode });
893
+ } else if (entry.merger === 'yaml-customModes') {
894
+ // Story 21.3 — .roomodes YAML splice. The template contains
895
+ // {{UNIVERSAL_BLOCK}} sentinels that must be expanded with the
896
+ // composed block BEFORE the merger (caller-owned substitution, AC #2/#4).
897
+ // Indent-preserving expansion keeps each line of the multi-line block
898
+ // aligned with the YAML block-scalar indent of the sentinel.
899
+ const composedTemplate = templateBody.replace(
900
+ /^([ \t]*)\{\{UNIVERSAL_BLOCK\}\}/gm,
901
+ (_m, indent) => composed
902
+ .replace(/\s+$/, '')
903
+ .split('\n')
904
+ .map(line => indent + line)
905
+ .join('\n')
906
+ );
907
+ const { mergeRoomodes } = require('./merge/roomodes');
908
+ const existingYaml = fs.existsSync(targetPath)
909
+ ? fs.readFileSync(targetPath, 'utf-8')
910
+ : '';
911
+ const mergedContent = mergeRoomodes(existingYaml, composedTemplate);
912
+
913
+ // Backup if the target exists and content differs (Story 21.2 canonical format).
914
+ if (fs.existsSync(targetPath)) {
915
+ const existing = fs.readFileSync(targetPath, 'utf-8');
916
+ if (existing !== mergedContent) {
917
+ const backupPath = buildBackupFilename(targetPath);
918
+ await fs.outputFile(backupPath, existing, 'utf-8');
919
+ }
920
+ }
921
+
922
+ const tmpPath = targetPath + '.tmp';
923
+ await fs.outputFile(tmpPath, mergedContent, 'utf-8');
924
+ await fs.rename(tmpPath, targetPath);
925
+ console.log(chalk.cyan(` + Updated ${entry.target}`));
926
+ } else {
927
+ console.log(chalk.yellow(` Warning: unknown merger '${entry.merger}' for ${agent.id}, skipping ${entry.target}`));
928
+ }
929
+ }
930
+ }
931
+
932
+ async function updateAgentInstructions(agent, projectRoot, opts = {}) {
933
+ if (!agent.instructionFiles || agent.instructionFiles.length === 0) return;
934
+
935
+ // Story 21.2 — yesMode resolution (AC #10): explicit opts.yesMode wins,
936
+ // fall back to MA_AGENTS_YES env var (used by tests and subprocess flows).
937
+ // The CLI passes yesMode via opts when --yes is set so non-interactive
938
+ // installs do not hang on the drift-confirmation prompt.
939
+ const yesMode = !!(opts.yesMode || process.env.MA_AGENTS_YES === '1');
940
+
941
+ // Story 21.2 — resolve profile once per stamped artifact; compose the universal
942
+ // (+ on-prem when applicable) block once; mergers consume the already-composed string.
943
+ // Lazy require avoids potential circular-import pitfalls at module load (profile.js
944
+ // already lazy-requires installer for the ensureManifest bootstrap path).
945
+ const { getProfile } = require('./profile');
946
+ const resolvedProfile = getProfile(projectRoot) || 'standard';
947
+ const composedTemplate = composeInstructionBlock({ profile: resolvedProfile, projectRoot });
948
+
949
+ // JSON merge strategy (e.g., OpenCode)
950
+ // OpenCode expects instructions to be plain strings, not objects.
951
+ // We identify our entries by a marker prefix in the string content,
952
+ // and also clean up legacy object-format entries from older versions.
953
+ if (agent.injectionStrategy?.position === 'json-merge') {
954
+ const targetKey = agent.injectionStrategy.targetKey || 'instructions';
955
+ const filePath = path.join(projectRoot, agent.instructionFiles[0]);
956
+ const agentProjectPath = agent.getProjectPath();
957
+ const relManifestPath = path.relative(projectRoot, path.join(agentProjectPath, 'MANIFEST.yaml')).replace(/\\/g, '/');
958
+ // Story 21.2 — substitute {{MANIFEST_PATH}} AFTER composition (caller-owned, per AC #3).
959
+ // Prefix with [ma-agents] tag so the isMaEntry filter identifies the entry on re-install.
960
+ const instructionBody = composedTemplate.replace(/\{\{MANIFEST_PATH\}\}/g, relManifestPath);
961
+ const instructionText = `[${MA_AGENTS_SOURCE}] ${instructionBody}`.replace(/\s+$/, '');
962
+
963
+ const isMaEntry = (entry) =>
964
+ (typeof entry === 'string' && entry.startsWith(`[${MA_AGENTS_SOURCE}]`)) ||
965
+ (typeof entry === 'object' && entry != null && entry._source === MA_AGENTS_SOURCE);
966
+
967
+ // Story 21.4 AC #6, #7 — collect any extra path-string entries that must be
968
+ // appended to the JSON array (e.g., literal "AGENTS.md" for OpenCode). These
969
+ // entries are USER-OWNED after first install (no [ma-agents] prefix → the
970
+ // isMaEntry filter does NOT match them → never re-appended, never removed
971
+ // by subsequent installs). Dedup uses strict string equality per AC #6.
972
+ const extraJsonEntries = [];
973
+ if (Array.isArray(agent.extraInstructionTemplates)) {
974
+ for (const tpl of agent.extraInstructionTemplates) {
975
+ if (tpl && tpl.merger === 'markdown-markers' && typeof tpl.target === 'string') {
976
+ extraJsonEntries.push(tpl.target);
977
+ }
978
+ }
979
+ }
980
+
981
+ if (!fs.existsSync(filePath)) {
982
+ // File absent: create fresh (atomic write)
983
+ const data = { [targetKey]: [instructionText, ...extraJsonEntries] };
984
+ const tmpPath = filePath + '.tmp';
985
+ await fs.outputFile(tmpPath, JSON.stringify(data, null, 2), 'utf-8');
986
+ await fs.rename(tmpPath, filePath);
987
+ console.log(chalk.cyan(` + Created ${agent.instructionFiles[0]}`));
988
+ // Continue to stamp extra templates below (e.g., AGENTS.md).
989
+ } else {
990
+ // File present: read and merge
991
+ try {
992
+ const content = await fs.readFile(filePath, 'utf-8');
993
+ const data = JSON.parse(content);
994
+ if (!Array.isArray(data[targetKey])) {
995
+ data[targetKey] = [];
996
+ }
997
+ // Filter out stale ma-agents entries (string or legacy object format), keep user entries
998
+ const userEntries = data[targetKey].filter(entry => entry != null && !isMaEntry(entry));
999
+ // Story 21.4 AC #6 — append extraJsonEntries (e.g., "AGENTS.md") using strict
1000
+ // string-equality dedup against userEntries so pre-existing user additions
1001
+ // are not duplicated. AC #7: entries lack the [ma-agents] prefix and are
1002
+ // therefore user-owned after first install.
1003
+ const missingExtras = extraJsonEntries.filter(e => !userEntries.includes(e));
1004
+ data[targetKey] = [...userEntries, instructionText, ...missingExtras];
1005
+ // Atomic write: temp file then rename
1006
+ const tmpPath = filePath + '.tmp';
1007
+ await fs.outputFile(tmpPath, JSON.stringify(data, null, 2), 'utf-8');
1008
+ await fs.rename(tmpPath, filePath);
1009
+ console.log(chalk.cyan(` + Updated ${agent.instructionFiles[0]}`));
1010
+ } catch (err) {
1011
+ console.error(`[${MA_AGENTS_SOURCE}] ERROR: Could not parse ${filePath} — ${err.message}. File not modified.`);
1012
+ return; // non-fatal: do NOT re-throw
1013
+ }
1014
+ }
1015
+
1016
+ // Story 21.4 — stamp any extraInstructionTemplates (e.g., AGENTS.md) after
1017
+ // the JSON-merge branch. This path does not fall through to the markdown
1018
+ // marker-wrap below because `instructionFiles: ['opencode.json']` is JSON.
1019
+ await stampExtraInstructionTemplates(agent, projectRoot, { yesMode });
1020
+ return;
1021
+ }
1022
+
1023
+ const agentProjectPath = agent.getProjectPath();
1024
+ const relManifestPath = path.relative(projectRoot, path.join(agentProjectPath, 'MANIFEST.yaml')).replace(/\\/g, '/');
1025
+
1026
+ // Story 21.2 — replace the previously-hardcoded planningInstruction with the
1027
+ // composed block. {{MANIFEST_PATH}} substitution happens AFTER composition
1028
+ // (caller-owned per AC #3). The leading "\n" preserves the historical shape
1029
+ // of the wrapped instruction so existing markers keep the same layout.
1030
+ const planningInstruction = '\n' + composedTemplate.replace(/\{\{MANIFEST_PATH\}\}/g, relManifestPath);
1031
+
1032
+ const markerStart = '<!-- MA-AGENTS-START -->';
1033
+ const markerEnd = '<!-- MA-AGENTS-END -->';
1034
+ // NFR46: normalize once so first-insert and in-place-replace produce
1035
+ // byte-identical marker-block content. Both paths now use `.trim()` + '\n'.
1036
+ const wrappedInstruction = `${markerStart}${planningInstruction}${markerEnd}`;
1037
+ const wrappedInstructionWithTrailingNewline = wrappedInstruction + '\n';
1038
+
1039
+ // Story 21.5 AC #6 — Cline dual-file drift detection.
1040
+ // Runs BEFORE the file loop so we abort cleanly without partial writes.
1041
+ // `--yes` does NOT bypass (explicit documented exception, AC #6).
1042
+ if (agent.id === 'cline') {
1043
+ checkClinerulesDualFileDrift(projectRoot);
1044
+ }
1045
+
1046
+ // Story 21.5 AC #1, #2 — optional framing template for fresh Cline file creation.
1047
+ // The composer produces the universal body once; the framing supplies the
1048
+ // Cline-flavored header paragraph + Architect-mode guidance line. Framing is
1049
+ // only used when creating a NEW file — existing files go through the normal
1050
+ // marker-replace path so user content outside markers is preserved (AC #4).
1051
+ let frameworkTemplate = null;
1052
+ if (agent.id === 'cline' && fs.existsSync(CLINERULES_TEMPLATE_PATH)) {
1053
+ frameworkTemplate = fs.readFileSync(CLINERULES_TEMPLATE_PATH, 'utf-8');
1054
+ }
1055
+
1056
+ for (const fileName of agent.instructionFiles) {
1057
+ const filePath = path.join(projectRoot, fileName);
1058
+ let content = '';
1059
+
1060
+ if (fs.existsSync(filePath)) {
1061
+ content = await fs.readFile(filePath, 'utf-8');
1062
+
1063
+ const regex = new RegExp(`${markerStart}[\\s\\S]*?${markerEnd}`, 'g');
1064
+ const existingMatch = content.match(regex);
1065
+ if (existingMatch) {
1066
+ // AC #10 — upgrade-safety: detect hand-edits to the marker block and back up.
1067
+ const existingBlock = existingMatch[0];
1068
+ if (existingBlock !== wrappedInstruction) {
1069
+ let proceedWithOverwrite = true;
1070
+ try {
1071
+ await handleMarkerBlockDrift({
1072
+ filePath,
1073
+ existingBlock,
1074
+ expectedBlock: wrappedInstruction,
1075
+ yesMode
1076
+ });
1077
+ } catch (declineErr) {
1078
+ // User declined in interactive mode — leave file unchanged for this agent file.
1079
+ proceedWithOverwrite = false;
1080
+ console.log(chalk.gray(` Skipped ${fileName} (user declined marker-block overwrite)`));
1081
+ }
1082
+ if (!proceedWithOverwrite) continue;
1083
+ }
1084
+ // Replace existing block in-place (AC #2)
1085
+ content = content.replace(regex, wrappedInstruction);
1086
+ } else {
1087
+ // Top-insert: place after skipped headers (AC #1, #3)
1088
+ const strategy = agent.injectionStrategy || { position: 'top', skipPatterns: [] };
1089
+ const insertIdx = findInsertionPoint(content, strategy.skipPatterns);
1090
+ content = content.slice(0, insertIdx) + wrappedInstructionWithTrailingNewline + '\n' + content.slice(insertIdx);
1091
+ }
1092
+ } else if (agent.category === 'bmad') {
1093
+ // BMAD agent instruction files ARE the agent definitions (persona, menu, activation).
1094
+ // Never create them from scratch — they must be deployed by applyCustomizations().
1095
+ // Creating them here would produce a file with ONLY the MA-AGENTS block,
1096
+ // wiping the entire agent definition.
1097
+ console.log(chalk.gray(` Skipped ${fileName} (BMAD agent file not yet deployed)`));
1098
+ continue;
1099
+ } else if (frameworkTemplate) {
1100
+ // Story 21.5 AC #1/#2 — fresh Cline file: wrap the composed block in
1101
+ // the Cline-flavored framing template (header paragraph + Architect-mode
1102
+ // guidance line). The framing contains empty MA-AGENTS markers that we
1103
+ // replace with the wrapped block. Cross-file byte-identity by construction
1104
+ // is preserved because both `.cline/clinerules.md` and `.clinerules`
1105
+ // receive the SAME rendered string in this single loop pass.
1106
+ const emptyMarkerPair = new RegExp(`${markerStart}\\s*\\n\\s*${markerEnd}`);
1107
+ let framed;
1108
+ if (emptyMarkerPair.test(frameworkTemplate)) {
1109
+ framed = frameworkTemplate.replace(emptyMarkerPair, wrappedInstruction);
1110
+ } else {
1111
+ // Defensive: framing shipped without markers — append block at EOF.
1112
+ const trimmed = frameworkTemplate.replace(/\s+$/, '');
1113
+ framed = trimmed + '\n\n' + wrappedInstruction + '\n';
1114
+ }
1115
+ content = framed.replace(/\s+$/, '') + '\n';
1116
+ } else {
1117
+ // New non-BMAD file: block is sole content (AC #1, #3)
1118
+ content = wrappedInstructionWithTrailingNewline;
1119
+ }
1120
+
1121
+ await fs.outputFile(filePath, content, 'utf-8');
1122
+ console.log(chalk.cyan(` + Updated ${fileName}`));
1123
+ }
1124
+
1125
+ // Story 21.4 — stamp any extraInstructionTemplates on non-JSON-merge agents
1126
+ // (forward-compat for Story 21.3 .roomodes and Story 21.5 .clinerules).
1127
+ await stampExtraInstructionTemplates(agent, projectRoot, { yesMode });
1128
+ }
1129
+
1130
+ // Story 21.3's parallel applyExtraInstructionTemplates was consolidated during
1131
+ // rebase onto Story 21.4's canonical stampExtraInstructionTemplates dispatcher
1132
+ // (see above) — the yaml-customModes merger branch lives there now.
1133
+
1134
+ /**
1135
+ * Compare two semver strings. Returns -1, 0, or 1.
1136
+ */
1137
+ function compareSemver(a, b) {
1138
+ const pa = (a || '0.0.0').split('.').map(Number);
1139
+ const pb = (b || '0.0.0').split('.').map(Number);
1140
+ for (let i = 0; i < 3; i++) {
1141
+ if (pa[i] > pb[i]) return 1;
1142
+ if (pa[i] < pb[i]) return -1;
1143
+ }
1144
+ return 0;
1145
+ }
1146
+
1147
+ // --- Skill listing ---
1148
+
1149
+ function listSkills() {
1150
+ const skillsDir = path.join(__dirname, '..', 'skills');
1151
+
1152
+ if (!fs.existsSync(skillsDir)) {
1153
+ return [];
1154
+ }
1155
+
1156
+ const skillDirs = fs.readdirSync(skillsDir, { withFileTypes: true })
1157
+ .filter(dirent => dirent.isDirectory())
1158
+ .map(dirent => dirent.name);
1159
+
1160
+ return skillDirs.map(skillDir => {
1161
+ const metaPath = path.join(skillsDir, skillDir, 'skill.json');
1162
+
1163
+ if (!fs.existsSync(metaPath)) {
1164
+ return null;
1165
+ }
1166
+
1167
+ const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
1168
+ return {
1169
+ id: skillDir,
1170
+ ...meta
1171
+ };
1172
+ }).filter(Boolean);
1173
+ }
1174
+
1175
+ function listAgents() {
1176
+ return getAllAgents();
1177
+ }
1178
+
1179
+ // --- Core install logic (no prompts) ---
1180
+
1181
+ async function performInstall(skillId, skill, agent, installPath) {
1182
+ const skillSourceDir = path.join(__dirname, '..', 'skills', skillId);
1183
+ let sourceFile = path.join(skillSourceDir, `${agent.template}${agent.fileExtension}`);
1184
+
1185
+ if (!fs.existsSync(sourceFile)) {
1186
+ sourceFile = path.join(skillSourceDir, `generic${agent.fileExtension}`);
1187
+ }
1188
+
1189
+ if (!fs.existsSync(sourceFile)) {
1190
+ const skillMdPath = path.join(skillSourceDir, 'SKILL.md');
1191
+ if (fs.existsSync(skillMdPath)) {
1192
+ sourceFile = skillMdPath;
1193
+ }
1194
+ }
1195
+
1196
+ if (!fs.existsSync(sourceFile)) {
1197
+ console.log(chalk.yellow(` Warning: No template found for ${agent.name}, skipping`));
1198
+ return false;
1199
+ }
1200
+
1201
+ const skillDir = path.join(installPath, skillId);
1202
+ await fs.ensureDir(skillDir);
1203
+
1204
+ let content = await fs.readFile(sourceFile, 'utf-8');
1205
+
1206
+ // Strip any existing YAML frontmatter from the source
1207
+ const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
1208
+ content = content.replace(frontmatterRegex, '');
1209
+
1210
+ // Inject YAML frontmatter from skill.json (single source of truth)
1211
+ const frontmatter = [
1212
+ '---',
1213
+ `name: ${skill.name}`,
1214
+ `description: ${skill.description}`,
1215
+ '---',
1216
+ ''
1217
+ ].join('\n');
1218
+ content = frontmatter + content;
1219
+
1220
+ const targetFile = path.join(skillDir, 'SKILL.md');
1221
+ await fs.writeFile(targetFile, content, 'utf-8');
1222
+ console.log(chalk.green(` + Installed to ${targetFile}`));
1223
+
1224
+ // Copy bundled resources
1225
+ const resourceMap = agent.resourceMap || {};
1226
+ const resourceDirs = ['scripts', 'references', 'assets', 'examples', 'hooks', 'docs', 'templates'];
1227
+ for (const dir of resourceDirs) {
1228
+ const resourceSource = path.join(skillSourceDir, dir);
1229
+ if (fs.existsSync(resourceSource)) {
1230
+ const targetDirName = resourceMap[dir] || dir;
1231
+ const resourceTarget = path.join(skillDir, targetDirName);
1232
+ await fs.copy(resourceSource, resourceTarget);
1233
+ console.log(chalk.green(` + Copied ${dir}/ → ${targetDirName}/`));
1234
+ }
1235
+ }
1236
+
1237
+ // Copy template.md if it exists
1238
+ const templateSource = path.join(skillSourceDir, 'template.md');
1239
+ if (fs.existsSync(templateSource)) {
1240
+ await fs.copy(templateSource, path.join(skillDir, 'template.md'));
1241
+ console.log(chalk.green(` + Copied template.md`));
1242
+ }
1243
+
1244
+ return true;
1245
+ }
1246
+
1247
+ // --- Install with upgrade detection ---
1248
+
1249
+ async function installSkill(skillId, agentIds, customPath = '', scope = 'project', options = {}) {
1250
+ const { force = false, yes = false } = options;
1251
+
1252
+ // Task 4.3: Pre-set batch global action for non-interactive mode (shared batchState reference)
1253
+ if (yes && options.batchState && !options.batchState.globalAction) {
1254
+ options.batchState.globalAction = 'update';
1255
+ }
1256
+
1257
+ const skills = listSkills();
1258
+ const skill = skills.find(s => s.id === skillId);
1259
+
1260
+ if (!skill) {
1261
+ throw new Error(`Skill '${skillId}' not found. Run "list" to see available skills.`);
1262
+ }
1263
+
1264
+ console.log(chalk.cyan(`\nInstalling skill: ${skill.name} v${skill.version}`));
1265
+
1266
+ // Group agents by their resolved install path to avoid redundant installs
1267
+ const pathGroups = new Map();
1268
+ for (const agentId of agentIds) {
1269
+ const agent = getAgent(agentId);
1270
+ if (!agent) {
1271
+ console.log(chalk.yellow(` Warning: Skipping unknown agent: ${agentId}`));
1272
+ continue;
1273
+ }
1274
+ const installPath = customPath || (scope === 'global' ? agent.getGlobalPath() : agent.getProjectPath());
1275
+ if (!installPath) {
1276
+ console.log(chalk.yellow(` Warning: Agent '${agent.name}' does not support ${scope} installation, skipping`));
1277
+ continue;
1278
+ }
1279
+ if (!pathGroups.has(installPath)) {
1280
+ pathGroups.set(installPath, []);
1281
+ }
1282
+ pathGroups.get(installPath).push({ agentId, agent });
1283
+ }
1284
+
1285
+ // Guard: project-context generation runs at most once per installSkill() call.
1286
+ // pathGroups can have multiple entries when agents share paths; without this flag
1287
+ // the generation would fire once per path group, producing redundant file I/O.
1288
+ let projectContextHandled = false;
1289
+
1290
+ for (const [installPath, agentEntries] of pathGroups) {
1291
+ const primaryAgent = agentEntries[0].agent;
1292
+ const agentNames = agentEntries.map(e => e.agent.name).join(', ');
1293
+
1294
+ try {
1295
+ await fs.ensureDir(installPath);
1296
+
1297
+ const installed = getInstalledSkillInfo(installPath, skillId);
1298
+
1299
+ if (installed && !force) {
1300
+ const cmp = compareSemver(skill.version, installed.version);
1301
+
1302
+ let action;
1303
+
1304
+ // Check if a global action was already chosen for this batch
1305
+ const batchState = options.batchState || {};
1306
+
1307
+ if (batchState.globalAction) {
1308
+ action = batchState.globalAction;
1309
+ } else if (cmp > 0) {
1310
+ // Upgrade available
1311
+ console.log(chalk.yellow(` ${skill.name} v${installed.version} → v${skill.version} update available for ${agentNames}`));
1312
+ const { choice } = await prompts({
1313
+ type: 'select',
1314
+ name: 'choice',
1315
+ message: 'What would you like to do?',
1316
+ choices: [
1317
+ { title: 'Update (recommended)', value: 'update' },
1318
+ { title: 'Update all remaining', value: 'update-all' },
1319
+ { title: 'Skip (keep current)', value: 'skip' },
1320
+ { title: 'Skip all remaining', value: 'skip-all' },
1321
+ { title: 'Clean reinstall', value: 'reinstall' },
1322
+ { title: 'Remove (uninstall)', value: 'remove' }
1323
+ ]
1324
+ });
1325
+ // Task 4.4: Guard against unexpected prompts in non-interactive mode
1326
+ if (yes && choice === undefined) {
1327
+ console.error(chalk.red('Error: Unexpected interactive prompt in non-interactive mode'));
1328
+ process.exit(1);
1329
+ }
1330
+ action = choice;
1331
+ } else if (cmp === 0) {
1332
+ // Same version
1333
+ console.log(chalk.gray(` ${skill.name} v${installed.version} already installed for ${agentNames}`));
1334
+ const { choice } = await prompts({
1335
+ type: 'select',
1336
+ name: 'choice',
1337
+ message: 'What would you like to do?',
1338
+ choices: [
1339
+ { title: 'Skip (keep current)', value: 'skip' },
1340
+ { title: 'Skip all remaining', value: 'skip-all' },
1341
+ { title: 'Clean reinstall', value: 'reinstall' },
1342
+ { title: 'Remove (uninstall)', value: 'remove' }
1343
+ ]
1344
+ });
1345
+ // Task 4.4: Guard against unexpected prompts in non-interactive mode
1346
+ if (yes && choice === undefined) {
1347
+ console.error(chalk.red('Error: Unexpected interactive prompt in non-interactive mode'));
1348
+ process.exit(1);
1349
+ }
1350
+ action = choice;
1351
+ } else {
1352
+ // Downgrade
1353
+ console.log(chalk.yellow(` ${skill.name} v${installed.version} installed, package has v${skill.version} for ${agentNames}`));
1354
+ const { choice } = await prompts({
1355
+ type: 'select',
1356
+ name: 'choice',
1357
+ message: 'What would you like to do?',
1358
+ choices: [
1359
+ { title: 'Skip (keep current)', value: 'skip' },
1360
+ { title: 'Skip all remaining', value: 'skip-all' },
1361
+ { title: `Downgrade to v${skill.version}`, value: 'update' },
1362
+ { title: `Downgrade all to package version`, value: 'update-all' },
1363
+ { title: 'Remove (uninstall)', value: 'remove' }
1364
+ ]
1365
+ });
1366
+ // Task 4.4: Guard against unexpected prompts in non-interactive mode
1367
+ if (yes && choice === undefined) {
1368
+ console.error(chalk.red('Error: Unexpected interactive prompt in non-interactive mode'));
1369
+ process.exit(1);
1370
+ }
1371
+ action = choice;
1372
+ }
1373
+
1374
+ if (!action) {
1375
+ console.log(chalk.gray(` Skipped`));
1376
+ continue;
1377
+ }
1378
+
1379
+ // Handle global actions
1380
+ if (action === 'update-all') {
1381
+ batchState.globalAction = 'update';
1382
+ action = 'update';
1383
+ } else if (action === 'skip-all') {
1384
+ batchState.globalAction = 'skip';
1385
+ action = 'skip';
1386
+ }
1387
+
1388
+ if (action === 'skip') {
1389
+ console.log(chalk.gray(` Skipped`));
1390
+ continue;
1391
+ }
1392
+
1393
+ if (action === 'remove') {
1394
+ await performUninstall(skillId, installPath);
1395
+ const manifest = ensureManifest(installPath, agentEntries[0].agentId, scope);
1396
+ delete manifest.skills[skillId];
1397
+ writeManifest(installPath, manifest);
1398
+ console.log(chalk.green(` - Removed ${skill.name} from ${agentNames}`));
1399
+
1400
+ // Generate MANIFEST.yaml and update agent instruction files
1401
+ await generateSkillsManifest(installPath);
1402
+ if (scope === 'project') {
1403
+ for (const entry of agentEntries) {
1404
+ await updateAgentInstructions(entry.agent, process.cwd(), { yesMode: yes });
1405
+ }
1406
+ }
1407
+ continue;
1408
+ }
1409
+
1410
+ if (action === 'reinstall') {
1411
+ await performUninstall(skillId, installPath);
1412
+ }
1413
+
1414
+ // action === 'update' or 'reinstall' → proceed to install below
1415
+ }
1416
+
1417
+ if (!installed || force) {
1418
+ console.log(chalk.gray(` Installing for ${agentNames}...`));
1419
+ }
1420
+
1421
+ // Perform the install ONCE for this shared path
1422
+ const success = await performInstall(skillId, skill, primaryAgent, installPath);
1423
+
1424
+ if (success) {
1425
+ // Update manifest with ALL agents that share this path
1426
+ const manifest = ensureManifest(installPath, agentEntries[0].agentId, scope);
1427
+ for (const entry of agentEntries) {
1428
+ if (!manifest.agents.includes(entry.agentId)) {
1429
+ manifest.agents.push(entry.agentId);
1430
+ }
1431
+ }
1432
+ manifest.agent = manifest.agents[0];
1433
+
1434
+ const now = new Date().toISOString();
1435
+ const existing = manifest.skills[skillId];
1436
+ manifest.skills[skillId] = {
1437
+ version: skill.version,
1438
+ installedAt: existing ? existing.installedAt : now,
1439
+ updatedAt: now,
1440
+ installerVersion: getPackageVersion(),
1441
+ agentVersion: primaryAgent.version
1442
+ };
1443
+ writeManifest(installPath, manifest);
1444
+ ensureBmadOutputTracked(installPath);
1445
+ // F2a — the plugin-stage gitignore policy was previously applied here
1446
+ // with `installPath` (the per-agent skills directory), which produced
1447
+ // per-skill-dir `.gitignore` files instead of one project-root file.
1448
+ // The single invocation now lives next to `stagePlugin(projectRoot)` in
1449
+ // `lib/bmad.js` (see `ensurePluginStageGitignoredForProject` callers in
1450
+ // installBmad / runMigration / updateBmad). Story 22.8 AC #1 / AC #5
1451
+ // are satisfied at the project-root level; the per-agent path here
1452
+ // would never have been the correct write target.
1453
+
1454
+ // Generate MANIFEST.yaml and update instruction files for ALL agents
1455
+ await generateSkillsManifest(installPath);
1456
+ if (scope === 'project') {
1457
+ for (const entry of agentEntries) {
1458
+ await updateAgentInstructions(entry.agent, process.cwd(), { yesMode: yes });
1459
+ // Story 21.4 — updateAgentInstructions now internally invokes
1460
+ // stampExtraInstructionTemplates, which handles per-agent extra
1461
+ // templates (e.g., Roo Code .roomodes via merger 'yaml-customModes',
1462
+ // OpenCode AGENTS.md via merger 'markdown-markers'). No sibling
1463
+ // call needed here.
1464
+ }
1465
+ // Deploy Claude Code hook when skills are installed for claude-code
1466
+ if (includesClaudeCode(agentEntries)) {
1467
+ await deployClaudeCodeHook(process.cwd());
1468
+ }
1469
+ // Generate project-context.md on first install; update manifest paths on subsequent installs.
1470
+ // Bug B2 — also run obsolete-tool-dir cleanup on the first successful path-group iteration.
1471
+ // Guard: runs at most once per installSkill() call — pathGroups may have multiple entries
1472
+ // when agents share install paths, so without this flag these would fire redundantly.
1473
+ if (!projectContextHandled) {
1474
+ projectContextHandled = true;
1475
+ // Bug B2 — clean up stale tool-level skill directories left by earlier routing migrations.
1476
+ try {
1477
+ await migrateObsoleteToolDirs(process.cwd());
1478
+ } catch (err) {
1479
+ console.log(chalk.yellow(`⚠ obsolete-tool-dir cleanup failed: ${err.message} — continuing install`));
1480
+ }
1481
+ const projectRoot = process.cwd();
1482
+ const outputPath = path.join(projectRoot, '_bmad-output', 'project-context.md');
1483
+ const allAgents = getManifestAgents(manifest).map(id => getAgent(id)).filter(Boolean);
1484
+ try {
1485
+ await fs.ensureDir(path.join(projectRoot, '_bmad-output'));
1486
+ if (await fs.pathExists(outputPath)) {
1487
+ const updated = await updateProjectContextManifestPaths(outputPath, allAgents);
1488
+ if (updated) {
1489
+ console.log(chalk.green('✓ project-context.md manifest paths updated'));
1490
+ } else {
1491
+ console.log(chalk.blue('ℹ project-context.md already up to date'));
1492
+ }
1493
+ } else {
1494
+ const content = await generateProjectContext(projectRoot, allAgents);
1495
+ await fs.writeFile(outputPath, content, 'utf8');
1496
+ console.log(chalk.green('✓ project-context.md generated at _bmad-output/project-context.md'));
1497
+ }
1498
+ } catch (err) {
1499
+ console.log(chalk.yellow(`⚠ project-context generation failed: ${err.message} — continuing install`));
1500
+ }
1501
+ }
1502
+ }
1503
+ }
1504
+ } catch (error) {
1505
+ console.log(chalk.red(` x Failed: ${error.message}`));
1506
+ }
1507
+ }
1508
+ }
1509
+
1510
+ // --- Uninstall ---
1511
+
1512
+ async function performUninstall(skillId, installPath) {
1513
+ const skillDir = path.join(installPath, skillId);
1514
+ if (fs.existsSync(skillDir)) {
1515
+ await fs.remove(skillDir);
1516
+ }
1517
+ }
1518
+
1519
+ /**
1520
+ * Story 22.3 / 22.2 — Legacy skill retirement + rename migration (AC 7).
1521
+ *
1522
+ * BMAD v6.3.0 retired four upstream skills:
1523
+ * - bmad-init (upstream PR #2159 — agents read _bmad/bmm/config.yaml directly)
1524
+ * - bmad-agent-qa (Quinn — merged into Amelia in PR #2186)
1525
+ * - bmad-agent-sm (Bob — merged into Amelia in PR #2179)
1526
+ * - bmad-agent-quick-flow-solo-dev (Barry — merged into Amelia in PR #2177)
1527
+ *
1528
+ * Story 22.2 additionally renamed the five ma-agents custom-agent skills out of
1529
+ * the upstream-reserved `bmad-*` prefix:
1530
+ * - bmad-ma-agent-cyber → ma-agent-cyber
1531
+ * - bmad-ma-agent-devops → ma-agent-devops
1532
+ * - bmad-ma-agent-sre → ma-agent-sre
1533
+ * - bmad-ma-agent-ml → ma-agent-ml
1534
+ * - bmad-ma-agent-sqa → ma-agent-sqa
1535
+ *
1536
+ * Both retirements and renames produce the same stale-artifact problem: existing
1537
+ * ma-agents installs may still carry the old IDs in `.ma-agents.json` and still
1538
+ * have the old folders deployed under each agent's skill root. This helper sweeps
1539
+ * every agent install path and, for every obsolete ID (retired or renamed):
1540
+ * 1. removes the ID from the manifest's `skills` map
1541
+ * 2. deletes the installed skill directory under the agent's skill root
1542
+ * 3. regenerates MANIFEST.yaml so the deployed manifest drops the obsolete IDs
1543
+ *
1544
+ * The follow-up install step re-adds the renamed skills under their new IDs, so
1545
+ * users effectively see a one-shot upgrade — no manual cleanup, no dangling
1546
+ * duplicates. The migration is idempotent — running it again on a clean install
1547
+ * is a no-op.
1548
+ *
1549
+ * Returns the list of per-path migration actions (for testability and logging).
1550
+ */
1551
+ const RETIRED_SKILL_IDS = Object.freeze([
1552
+ 'bmad-init',
1553
+ 'bmad-agent-qa',
1554
+ 'bmad-agent-sm',
1555
+ 'bmad-agent-quick-flow-solo-dev'
1556
+ ]);
1557
+
1558
+ // Skills that were renamed (not retired). The new ID is installed fresh by
1559
+ // the regular install flow after migration clears the old ID.
1560
+ const RENAMED_SKILL_IDS = Object.freeze([
1561
+ 'bmad-ma-agent-cyber',
1562
+ 'bmad-ma-agent-devops',
1563
+ 'bmad-ma-agent-sre',
1564
+ 'bmad-ma-agent-ml',
1565
+ 'bmad-ma-agent-sqa'
1566
+ ]);
1567
+
1568
+ // Union of every obsolete ID the sweep must purge from existing installs.
1569
+ // Kept as a frozen array so accidental mutation is surfaced at test time.
1570
+ const OBSOLETE_SKILL_IDS = Object.freeze([...RETIRED_SKILL_IDS, ...RENAMED_SKILL_IDS]);
1571
+
1572
+ // Registry of old tool-level skill directories that earlier ma-agents versions
1573
+ // wrote to but no longer use. Each entry describes one migration:
1574
+ // obsolete — the legacy path (relative to project root)
1575
+ // replacedBy — the current path that skills are now written to
1576
+ // sinceVersion — the ma-agents release that made the change (informational)
1577
+ //
1578
+ // Story 24.11 (AC5) will add additional entries to this list when .agents/skills/
1579
+ // cross-tool routing is assessed. Define new entries here; `migrateObsoleteToolDirs`
1580
+ // iterates the registry automatically.
1581
+ //
1582
+ // Kept frozen so accidental mutation is surfaced at test time.
1583
+ const OBSOLETE_TOOL_SKILL_DIRS = Object.freeze([
1584
+ // Pre-F2b (PR #70) copilot wrote skills to .github/copilot/skills;
1585
+ // F2b realigned the target to .github/skills to match the BMAD upstream path.
1586
+ { obsolete: '.github/copilot/skills', replacedBy: '.github/skills', sinceVersion: '3.7.0' },
1587
+ // bmad-method 6.5.0 moved github-copilot, roo, and kilo platforms from
1588
+ // tool-specific paths to the shared .agents/skills/ directory.
1589
+ { obsolete: '.github/skills', replacedBy: '.agents/skills', sinceVersion: '3.8.1' },
1590
+ { obsolete: '.roo/skills', replacedBy: '.agents/skills', sinceVersion: '3.8.1' },
1591
+ { obsolete: '.kilocode/skills', replacedBy: '.agents/skills', sinceVersion: '3.8.1' }
1592
+ ]);
1593
+
1594
+ async function migrateRetiredSkills(options = {}) {
1595
+ const { scope = 'project', customPath = '' } = options;
1596
+ const results = [];
1597
+
1598
+ // Build the list of install paths to sweep. We consider every registered
1599
+ // agent for the requested scope — the retirement/rename is agent-agnostic.
1600
+ const pathsSeen = new Set();
1601
+ const targetPaths = [];
1602
+
1603
+ if (customPath) {
1604
+ targetPaths.push(customPath);
1605
+ } else {
1606
+ for (const agent of getAllAgents()) {
1607
+ const p = scope === 'global' ? agent.getGlobalPath() : agent.getProjectPath();
1608
+ if (p && !pathsSeen.has(p)) {
1609
+ pathsSeen.add(p);
1610
+ targetPaths.push(p);
1611
+ }
1612
+ }
1613
+ }
1614
+
1615
+ for (const installPath of targetPaths) {
1616
+ let manifestMutated = false;
1617
+ let filesRemoved = 0;
1618
+ const removedIds = [];
1619
+ let pathError = null;
1620
+
1621
+ try {
1622
+ // Read manifest (if any). Missing manifest is fine — we still check for
1623
+ // orphaned skill directories left behind by a partial uninstall.
1624
+ const manifest = readManifest(installPath);
1625
+
1626
+ for (const obsoleteId of OBSOLETE_SKILL_IDS) {
1627
+ // 1. Manifest entry cleanup
1628
+ if (manifest && manifest.skills && Object.prototype.hasOwnProperty.call(manifest.skills, obsoleteId)) {
1629
+ delete manifest.skills[obsoleteId];
1630
+ manifestMutated = true;
1631
+ removedIds.push(obsoleteId);
1632
+ }
1633
+
1634
+ // 2. Installed-directory cleanup — guard against ENOENT (idempotency)
1635
+ const skillDir = path.join(installPath, obsoleteId);
1636
+ if (fs.existsSync(skillDir)) {
1637
+ try {
1638
+ await fs.remove(skillDir);
1639
+ filesRemoved++;
1640
+ if (!removedIds.includes(obsoleteId)) {
1641
+ removedIds.push(obsoleteId);
1642
+ }
1643
+ } catch (err) {
1644
+ // Non-fatal — surface the path so the user can clean up manually.
1645
+ console.log(chalk.yellow(` ! Could not remove ${skillDir}: ${err.message}`));
1646
+ }
1647
+ }
1648
+ }
1649
+
1650
+ if (manifestMutated) {
1651
+ writeManifest(installPath, manifest);
1652
+ }
1653
+
1654
+ if (manifestMutated || filesRemoved > 0) {
1655
+ // Regenerate the deployed MANIFEST.yaml so AC 1 is satisfied for
1656
+ // existing installs — the file is derived from `.ma-agents.json::skills`.
1657
+ if (manifest && manifest.skills) {
1658
+ try {
1659
+ await generateSkillsManifest(installPath);
1660
+ } catch (err) {
1661
+ console.log(chalk.yellow(` ! MANIFEST.yaml regeneration skipped for ${installPath}: ${err.message}`));
1662
+ }
1663
+ }
1664
+ console.log(chalk.green(
1665
+ ` - Cleaned ${removedIds.length} obsolete skill(s) at ${installPath}: ${removedIds.join(', ')}`
1666
+ ));
1667
+ }
1668
+ } catch (err) {
1669
+ // Isolate per-path failures so one broken install path does not
1670
+ // abort the sweep across all other agents.
1671
+ pathError = err;
1672
+ console.log(chalk.yellow(
1673
+ ` ! Obsolete-skill migration failed for ${installPath}: ${err.message}`
1674
+ ));
1675
+ }
1676
+
1677
+ results.push({ installPath, removedIds, filesRemoved, manifestMutated, error: pathError });
1678
+ }
1679
+
1680
+ return results;
1681
+ }
1682
+
1683
+ /**
1684
+ * Bug B2 — Stale copilot skills dir cleanup (and future obsolete tool-dir migrations).
1685
+ *
1686
+ * Iterates `OBSOLETE_TOOL_SKILL_DIRS` and, for each entry, checks whether the
1687
+ * legacy directory exists under `projectRoot`. Behaviour per entry:
1688
+ *
1689
+ * - Obsolete dir absent → no-op (idempotent)
1690
+ * - Obsolete dir present AND
1691
+ * replacedBy dir has content → remove obsolete dir, log info message
1692
+ * - Obsolete dir present BUT
1693
+ * replacedBy dir is empty/gone → preserve obsolete dir, log warning
1694
+ * (user may have local edits; safer to keep)
1695
+ *
1696
+ * @param {string} projectRoot — absolute path to the project root (process.cwd() in production)
1697
+ * @returns {Promise<Array<{entry, action, obsoletePath, replacedByPath, error}>>}
1698
+ */
1699
+ async function migrateObsoleteToolDirs(projectRoot) {
1700
+ const results = [];
1701
+
1702
+ for (const entry of OBSOLETE_TOOL_SKILL_DIRS) {
1703
+ const obsoletePath = path.join(projectRoot, entry.obsolete);
1704
+ const replacedByPath = path.join(projectRoot, entry.replacedBy);
1705
+ let action = 'noop';
1706
+ let error = null;
1707
+
1708
+ try {
1709
+ if (!fs.existsSync(obsoletePath)) {
1710
+ // Nothing to do — already clean.
1711
+ action = 'noop';
1712
+ } else {
1713
+ // Check whether the replacement directory has any content.
1714
+ let replacedByHasContent = false;
1715
+ if (fs.existsSync(replacedByPath)) {
1716
+ const entries = fs.readdirSync(replacedByPath);
1717
+ replacedByHasContent = entries.length > 0;
1718
+ }
1719
+
1720
+ if (replacedByHasContent) {
1721
+ // Safe to remove — skills have been migrated to the new location.
1722
+ await fs.remove(obsoletePath);
1723
+ action = 'removed';
1724
+ console.log(chalk.green(
1725
+ ` - Removed legacy tool skills directory ${entry.obsolete} ` +
1726
+ `(moved to ${entry.replacedBy} in v${entry.sinceVersion})`
1727
+ ));
1728
+ } else {
1729
+ // Replacement is absent or empty — do not silently destroy user content.
1730
+ action = 'preserved';
1731
+ console.log(chalk.yellow(
1732
+ ` ! Legacy tool skills directory ${entry.obsolete} was not removed ` +
1733
+ `because ${entry.replacedBy} is empty or absent. ` +
1734
+ `Remove it manually once you have confirmed there is nothing to keep: ` +
1735
+ `rm -rf ${entry.obsolete}`
1736
+ ));
1737
+ }
1738
+ }
1739
+ } catch (err) {
1740
+ action = 'error';
1741
+ error = err;
1742
+ console.log(chalk.yellow(
1743
+ ` ! Could not migrate obsolete tool dir ${entry.obsolete}: ${err.message}`
1744
+ ));
1745
+ }
1746
+
1747
+ results.push({ entry, action, obsoletePath, replacedByPath, error });
1748
+ }
1749
+
1750
+ return results;
1751
+ }
1752
+
1753
+ async function uninstallSkill(skillId, agentIds, customPath = '', scope = 'project') {
1754
+ console.log(chalk.cyan(`\nUninstalling skill: ${skillId}`));
1755
+
1756
+ // Group agents by their resolved install path
1757
+ const pathGroups = new Map();
1758
+ for (const agentId of agentIds) {
1759
+ const agent = getAgent(agentId);
1760
+ if (!agent) {
1761
+ console.log(chalk.yellow(` Warning: Skipping unknown agent: ${agentId}`));
1762
+ continue;
1763
+ }
1764
+ const installPath = customPath || (scope === 'global' ? agent.getGlobalPath() : agent.getProjectPath());
1765
+ if (!installPath) {
1766
+ console.log(chalk.yellow(` Warning: Agent '${agent.name}' does not support ${scope} scope, skipping`));
1767
+ continue;
1768
+ }
1769
+ if (!pathGroups.has(installPath)) {
1770
+ pathGroups.set(installPath, []);
1771
+ }
1772
+ pathGroups.get(installPath).push({ agentId, agent });
1773
+ }
1774
+
1775
+ for (const [installPath, agentEntries] of pathGroups) {
1776
+ try {
1777
+ const installed = getInstalledSkillInfo(installPath, skillId);
1778
+
1779
+ if (!installed) {
1780
+ const skillDir = path.join(installPath, skillId);
1781
+ if (fs.existsSync(skillDir)) {
1782
+ await performUninstall(skillId, installPath);
1783
+ console.log(chalk.green(` - Removed ${skillId} from ${installPath} (legacy install)`));
1784
+ } else {
1785
+ console.log(chalk.gray(` ${skillId} is not installed at ${installPath}`));
1786
+ }
1787
+ continue;
1788
+ }
1789
+
1790
+ // Read current manifest to check which agents still need this path
1791
+ const manifest = readManifest(installPath) || { agents: [], skills: {} };
1792
+ const currentAgents = getManifestAgents(manifest);
1793
+
1794
+ // Remove the requested agents from the manifest's agent list
1795
+ const agentIdsToRemove = new Set(agentEntries.map(e => e.agentId));
1796
+ const remainingAgents = currentAgents.filter(id => !agentIdsToRemove.has(id));
1797
+
1798
+ if (remainingAgents.length === 0) {
1799
+ // No agents left that need this skill at this path -> delete the files
1800
+ await performUninstall(skillId, installPath);
1801
+ delete manifest.skills[skillId];
1802
+ console.log(chalk.green(` - Removed ${skillId} v${installed.version} from ${installPath}`));
1803
+ } else {
1804
+ // Other agents still need the files, just update the manifest
1805
+ console.log(chalk.gray(` ${skillId} still needed by ${remainingAgents.join(', ')} at ${installPath}, keeping files`));
1806
+ }
1807
+
1808
+ // Update the agents list in manifest
1809
+ manifest.agents = remainingAgents;
1810
+ manifest.agent = remainingAgents[0] || null;
1811
+ writeManifest(installPath, manifest);
1812
+
1813
+ // Regenerate MANIFEST.yaml and update instruction files
1814
+ await generateSkillsManifest(installPath);
1815
+ if (scope === 'project') {
1816
+ for (const entry of agentEntries) {
1817
+ await updateAgentInstructions(entry.agent, process.cwd());
1818
+ }
1819
+ // Remove Claude Code hook when no skills remain for claude-code
1820
+ if (includesClaudeCode(agentEntries)) {
1821
+ const currentManifest = readManifest(installPath);
1822
+ const hasRemainingSkills = currentManifest && currentManifest.skills && Object.keys(currentManifest.skills).length > 0;
1823
+ if (!hasRemainingSkills) {
1824
+ await removeClaudeCodeHook(process.cwd());
1825
+ }
1826
+ }
1827
+ }
1828
+ } catch (error) {
1829
+ console.log(chalk.red(` x Failed: ${error.message}`));
1830
+ }
1831
+ }
1832
+ }
1833
+
1834
+ // --- Status ---
1835
+
1836
+ function getStatus(agentIds, customPath = '', scope = 'project') {
1837
+ const results = [];
1838
+
1839
+ const targetAgents = agentIds && agentIds.length > 0
1840
+ ? agentIds.map(id => getAgent(id)).filter(Boolean)
1841
+ : getAllAgents();
1842
+
1843
+ for (const agent of targetAgents) {
1844
+ const projectPath = agent.getProjectPath();
1845
+ const globalPath = agent.getGlobalPath();
1846
+
1847
+ // Filter paths based on scope
1848
+ let pathsToCheck;
1849
+ if (customPath) {
1850
+ pathsToCheck = [{ path: customPath, scope: 'custom' }];
1851
+ } else if (scope === 'global') {
1852
+ pathsToCheck = globalPath ? [{ path: globalPath, scope: 'global' }] : [];
1853
+ } else if (scope === 'project') {
1854
+ pathsToCheck = [{ path: projectPath, scope: 'project' }];
1855
+ } else {
1856
+ pathsToCheck = [{ path: projectPath, scope: 'project' }];
1857
+ if (globalPath) {
1858
+ pathsToCheck.push({ path: globalPath, scope: 'global' });
1859
+ }
1860
+ }
1861
+
1862
+ for (const { path: checkPath, scope: checkScope } of pathsToCheck) {
1863
+ const manifest = readManifest(checkPath);
1864
+ if (!manifest || !manifest.skills || Object.keys(manifest.skills).length === 0) {
1865
+ continue;
1866
+ }
1867
+
1868
+ results.push({
1869
+ agent: agent,
1870
+ installPath: checkPath,
1871
+ scope: checkScope,
1872
+ skills: manifest.skills
1873
+ });
1874
+ }
1875
+ }
1876
+
1877
+ return results;
1878
+ }
1879
+
1880
+ // --- Claude Code Hook Management ---
1881
+
1882
+ /**
1883
+ * Deploy the verify-manifest SessionStart hook into .claude/settings.json.
1884
+ * Performs a JSON merge — preserves existing settings and hooks.
1885
+ */
1886
+ async function deployClaudeCodeHook(projectRoot) {
1887
+ const settingsPath = path.join(projectRoot, CLAUDE_CODE_SETTINGS_FILE);
1888
+ let settings = {};
1889
+
1890
+ if (fs.existsSync(settingsPath)) {
1891
+ try {
1892
+ settings = JSON.parse(await fs.readFile(settingsPath, 'utf-8'));
1893
+ } catch {
1894
+ console.log(chalk.yellow(' Warning: Could not parse .claude/settings.json, skipping hook deployment'));
1895
+ return;
1896
+ }
1897
+ }
1898
+
1899
+ if (!settings.hooks) {
1900
+ settings.hooks = {};
1901
+ }
1902
+ if (!settings.hooks.SessionStart) {
1903
+ settings.hooks.SessionStart = [];
1904
+ }
1905
+
1906
+ // Check if our hook is already present
1907
+ const hookCommand = `node "$CLAUDE_PROJECT_DIR/lib/hooks/claude-code/verify-manifest.js"`;
1908
+ const alreadyInstalled = settings.hooks.SessionStart.some(group =>
1909
+ group.hooks && group.hooks.some(h => h.command === hookCommand)
1910
+ );
1911
+
1912
+ if (alreadyInstalled) {
1913
+ return; // Already deployed
1914
+ }
1915
+
1916
+ settings.hooks.SessionStart.push({
1917
+ matcher: 'startup',
1918
+ hooks: [
1919
+ {
1920
+ type: 'command',
1921
+ command: hookCommand,
1922
+ _id: CLAUDE_CODE_HOOK_ID
1923
+ }
1924
+ ]
1925
+ });
1926
+
1927
+ await fs.ensureDir(path.dirname(settingsPath));
1928
+ await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
1929
+ console.log(chalk.cyan(' + Deployed Claude Code verify-manifest hook'));
1930
+ }
1931
+
1932
+ /**
1933
+ * Remove the verify-manifest hook from .claude/settings.json.
1934
+ */
1935
+ async function removeClaudeCodeHook(projectRoot) {
1936
+ const settingsPath = path.join(projectRoot, CLAUDE_CODE_SETTINGS_FILE);
1937
+
1938
+ if (!fs.existsSync(settingsPath)) return;
1939
+
1940
+ let settings;
1941
+ try {
1942
+ settings = JSON.parse(await fs.readFile(settingsPath, 'utf-8'));
1943
+ } catch {
1944
+ return;
1945
+ }
1946
+
1947
+ if (!settings.hooks || !settings.hooks.SessionStart) return;
1948
+
1949
+ const hookCommand = `node "$CLAUDE_PROJECT_DIR/lib/hooks/claude-code/verify-manifest.js"`;
1950
+ settings.hooks.SessionStart = settings.hooks.SessionStart.filter(group => {
1951
+ if (!group.hooks) return true;
1952
+ group.hooks = group.hooks.filter(h => h.command !== hookCommand && h._id !== CLAUDE_CODE_HOOK_ID);
1953
+ return group.hooks.length > 0;
1954
+ });
1955
+
1956
+ // Clean up empty arrays
1957
+ if (settings.hooks.SessionStart.length === 0) {
1958
+ delete settings.hooks.SessionStart;
1959
+ }
1960
+ if (Object.keys(settings.hooks).length === 0) {
1961
+ delete settings.hooks;
1962
+ }
1963
+
1964
+ await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
1965
+ console.log(chalk.cyan(' - Removed Claude Code verify-manifest hook'));
1966
+ }
1967
+
1968
+ /**
1969
+ * Check if any agents in the list include claude-code.
1970
+ */
1971
+ function includesClaudeCode(agentEntries) {
1972
+ return agentEntries.some(e => e.agentId === 'claude-code');
1973
+ }
1974
+
1975
+ module.exports = {
1976
+ listSkills,
1977
+ listAgents,
1978
+ installSkill,
1979
+ uninstallSkill,
1980
+ getStatus,
1981
+ readManifest,
1982
+ ensureManifest,
1983
+ getInstalledSkillInfo,
1984
+ getManifestAgents,
1985
+ compareSemver,
1986
+ findInsertionPoint,
1987
+ deployClaudeCodeHook,
1988
+ removeClaudeCodeHook,
1989
+ ensureBmadOutputTracked,
1990
+ ensurePluginStageIgnored,
1991
+ generateSkillsManifest,
1992
+ generateProjectContext,
1993
+ generateRepoLayoutSection,
1994
+ updateProjectContextRepoLayout,
1995
+ _updateProjectContextManifestPaths: updateProjectContextManifestPaths,
1996
+ _testUpdateAgentInstructions: updateAgentInstructions,
1997
+ _MA_AGENTS_SOURCE: MA_AGENTS_SOURCE,
1998
+ // Story 21.2 — universal instruction-block composer and helpers.
1999
+ composeInstructionBlock,
2000
+ buildBackupFilename,
2001
+ formatBackupTimestamp,
2002
+ UNIVERSAL_INSTRUCTION_TEMPLATE_PATH,
2003
+ ONPREM_INSTRUCTION_TEMPLATE_PATH,
2004
+ // Story 21.4 — AGENTS.md template, markdown-markers merger, extraInstructionTemplates processor.
2005
+ // Story 21.3 (rebased) — yaml-customModes merger dispatch is integrated into stampExtraInstructionTemplates.
2006
+ resolveBmadOutputDirs,
2007
+ markdownMarkersMerger,
2008
+ stampExtraInstructionTemplates,
2009
+ EXTRA_TEMPLATE_DIR,
2010
+ // Story 21.5 — Cline dual-file drift detection + framing template path.
2011
+ CLINERULES_TEMPLATE_PATH,
2012
+ ClinerulesDualFileDriftError,
2013
+ checkClinerulesDualFileDrift,
2014
+ // Story 22.3 — legacy skill retirement migration (AC 7)
2015
+ // Story 22.2 — extended to also sweep renamed skill IDs (bmad-ma-agent-*)
2016
+ migrateRetiredSkills,
2017
+ RETIRED_SKILL_IDS,
2018
+ RENAMED_SKILL_IDS,
2019
+ OBSOLETE_SKILL_IDS,
2020
+ // Bug B2 — stale copilot skills dir cleanup + future tool-dir migrations.
2021
+ // Story 24.11 (AC5) will extend OBSOLETE_TOOL_SKILL_DIRS with additional entries.
2022
+ OBSOLETE_TOOL_SKILL_DIRS,
2023
+ migrateObsoleteToolDirs
2024
+ };