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.
- package/CONTRIBUTING.md +235 -235
- package/LICENSE +20 -20
- package/QUICK_START.md +154 -154
- package/README.md +731 -731
- package/SKILLS_STRUCTURE.md +392 -392
- package/bin/cli.js +1681 -1573
- package/docs/architecture.md +284 -284
- package/docs/deployment/vllm-nemotron.md +132 -132
- package/docs/development-guide.md +122 -122
- package/docs/index.md +48 -48
- package/docs/project-overview.md +56 -56
- package/docs/project-scan-report.json +50 -50
- package/docs/source-tree-analysis.md +84 -84
- package/docs/technical-notes/context-persistence-research.md +434 -434
- package/docs/validation/bundled-installation-validation.md +52 -52
- package/examples/programmatic-usage.js +62 -62
- package/index.js +22 -22
- package/lib/agents.js +370 -370
- package/lib/bmad-cache/bmb/.claude-plugin/marketplace.json +50 -50
- package/lib/bmad-cache/bmb/.markdownlint-cli2.yaml +36 -36
- package/lib/bmad-cache/bmb/.prettierignore +9 -9
- package/lib/bmad-cache/bmb/CNAME +1 -1
- package/lib/bmad-cache/bmb/LICENSE +30 -30
- package/lib/bmad-cache/bmb/README.md +75 -75
- package/lib/bmad-cache/bmb/_git_preserved/HEAD +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/config +13 -13
- package/lib/bmad-cache/bmb/_git_preserved/description +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/hooks/applypatch-msg.sample +15 -15
- package/lib/bmad-cache/bmb/_git_preserved/hooks/commit-msg.sample +24 -24
- package/lib/bmad-cache/bmb/_git_preserved/hooks/fsmonitor-watchman.sample +174 -174
- package/lib/bmad-cache/bmb/_git_preserved/hooks/post-update.sample +8 -8
- package/lib/bmad-cache/bmb/_git_preserved/hooks/pre-applypatch.sample +14 -14
- package/lib/bmad-cache/bmb/_git_preserved/hooks/pre-commit.sample +49 -49
- package/lib/bmad-cache/bmb/_git_preserved/hooks/pre-merge-commit.sample +13 -13
- package/lib/bmad-cache/bmb/_git_preserved/hooks/pre-push.sample +53 -53
- package/lib/bmad-cache/bmb/_git_preserved/hooks/pre-rebase.sample +169 -169
- package/lib/bmad-cache/bmb/_git_preserved/hooks/pre-receive.sample +24 -24
- package/lib/bmad-cache/bmb/_git_preserved/hooks/prepare-commit-msg.sample +42 -42
- package/lib/bmad-cache/bmb/_git_preserved/hooks/push-to-checkout.sample +78 -78
- package/lib/bmad-cache/bmb/_git_preserved/hooks/sendemail-validate.sample +77 -77
- package/lib/bmad-cache/bmb/_git_preserved/hooks/update.sample +128 -128
- package/lib/bmad-cache/bmb/_git_preserved/info/exclude +6 -6
- package/lib/bmad-cache/bmb/_git_preserved/packed-refs +2 -2
- package/lib/bmad-cache/bmb/_git_preserved/refs/heads/main +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/refs/remotes/origin/HEAD +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/refs/tags/v1.7.0 +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/shallow +1 -1
- package/lib/bmad-cache/bmb/eslint.config.mjs +141 -141
- package/lib/bmad-cache/bmb/package-lock.json +15283 -15283
- package/lib/bmad-cache/bmb/package.json +86 -86
- package/lib/bmad-cache/bmb/prettier.config.mjs +32 -32
- package/lib/bmad-cache/bmb/samples/bmad-agent-code-coach/scripts/init-sanctum.py +288 -288
- package/lib/bmad-cache/bmb/samples/bmad-agent-creative-muse/scripts/init-sanctum.py +274 -274
- package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/assets/module-help.csv +9 -9
- package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/assets/module.yaml +8 -8
- package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/scripts/merge-config.py +408 -408
- package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/scripts/merge-help-csv.py +218 -218
- package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/scripts/recall_metrics.py +229 -229
- package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/scripts/seed_tracker.py +156 -156
- package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/scripts/symbol_stats.py +162 -162
- package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/scripts/tests/test_recall_metrics.py +115 -115
- package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/scripts/tests/test_seed_tracker.py +140 -140
- package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/scripts/tests/test_symbol_stats.py +113 -113
- package/lib/bmad-cache/bmb/samples/bmad-agent-sentinel/scripts/init-sanctum.py +285 -285
- package/lib/bmad-cache/bmb/samples/bmad-agent-sentinel/scripts/tests/test-init-sanctum.py +174 -174
- package/lib/bmad-cache/bmb/samples/bmad-excalidraw/scripts/generate_excalidraw.py +605 -605
- package/lib/bmad-cache/bmb/samples/bmad-excalidraw/scripts/tests/test_generate_excalidraw.py +360 -360
- package/lib/bmad-cache/bmb/samples/bmad-excalidraw/scripts/tests/test_validate_excalidraw.py +246 -246
- package/lib/bmad-cache/bmb/samples/bmad-excalidraw/scripts/validate_excalidraw.py +264 -264
- package/lib/bmad-cache/bmb/samples/sample-module-setup/assets/module-help.csv +16 -16
- package/lib/bmad-cache/bmb/samples/sample-module-setup/assets/module.yaml +13 -13
- package/lib/bmad-cache/bmb/samples/sample-module-setup/scripts/cleanup-legacy.py +259 -259
- package/lib/bmad-cache/bmb/samples/sample-module-setup/scripts/merge-config.py +408 -408
- package/lib/bmad-cache/bmb/samples/sample-module-setup/scripts/merge-help-csv.py +218 -218
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/assets/customize-template.toml +62 -62
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/assets/init-sanctum-template.py +277 -277
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/assets/sample-customize-analyst.toml +87 -87
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/references/sample-init-sanctum.py +274 -274
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/generate-html-report.py +534 -534
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass-execution-deps.py +337 -337
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass-prompt-metrics.py +425 -425
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass-sanctum-architecture.py +385 -385
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass-structure-capabilities.py +482 -482
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/process-template.py +190 -190
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/scan-path-standards.py +324 -324
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/scan-scripts.py +747 -747
- package/lib/bmad-cache/bmb/skills/bmad-bmb-setup/assets/module-help.csv +10 -10
- package/lib/bmad-cache/bmb/skills/bmad-bmb-setup/assets/module.yaml +20 -20
- package/lib/bmad-cache/bmb/skills/bmad-bmb-setup/scripts/cleanup-legacy.py +259 -259
- package/lib/bmad-cache/bmb/skills/bmad-bmb-setup/scripts/merge-config.py +408 -408
- package/lib/bmad-cache/bmb/skills/bmad-bmb-setup/scripts/merge-help-csv.py +218 -218
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/assets/setup-skill-template/assets/module-help.csv +1 -1
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/assets/setup-skill-template/assets/module.yaml +6 -6
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/assets/setup-skill-template/scripts/cleanup-legacy.py +259 -259
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/assets/setup-skill-template/scripts/merge-config.py +408 -408
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/assets/setup-skill-template/scripts/merge-help-csv.py +218 -218
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/assets/standalone-module-template/merge-config.py +408 -408
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/assets/standalone-module-template/merge-help-csv.py +218 -218
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/scaffold-setup-skill.py +124 -124
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/scaffold-standalone-module.py +190 -190
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/tests/test-scaffold-setup-skill.py +230 -230
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/tests/test-scaffold-standalone-module.py +266 -266
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/tests/test-validate-module.py +314 -314
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/validate-module.py +293 -293
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/assets/customize-template.toml +56 -56
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/assets/sample-customize-product-brief.toml +51 -51
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/generate-convert-report.py +406 -406
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/generate-html-report.py +539 -539
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/prepass-execution-deps.py +288 -288
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/prepass-prompt-metrics.py +285 -285
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/prepass-workflow-integrity.py +475 -475
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/scan-path-standards.py +298 -298
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/scan-scripts.py +745 -745
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/tests/test_generate_convert_report.py +243 -243
- package/lib/bmad-cache/bmb/skills/module-help.csv +11 -11
- package/lib/bmad-cache/bmb/skills/module.yaml +20 -20
- package/lib/bmad-cache/bmb/tools/build-docs.mjs +448 -448
- package/lib/bmad-cache/bmb/tools/validate-doc-links.cjs +412 -412
- package/lib/bmad-cache/bmb/tools/validate-file-refs.mjs +657 -657
- package/lib/bmad-cache/bmb/website/astro.config.mjs +142 -142
- package/lib/bmad-cache/bmb/website/src/components/Banner.astro +57 -57
- package/lib/bmad-cache/bmb/website/src/components/Header.astro +94 -94
- package/lib/bmad-cache/bmb/website/src/components/MobileMenuFooter.astro +33 -33
- package/lib/bmad-cache/bmb/website/src/content/config.ts +6 -6
- package/lib/bmad-cache/bmb/website/src/lib/site-url.mjs +25 -25
- package/lib/bmad-cache/bmb/website/src/rehype-base-paths.js +88 -88
- package/lib/bmad-cache/bmb/website/src/rehype-markdown-links.js +117 -117
- package/lib/bmad-cache/bmb/website/src/styles/custom.css +502 -502
- package/lib/bmad-cache/cache-manifest.json +37 -37
- package/lib/bmad-cache/cis/.claude-plugin/marketplace.json +33 -33
- package/lib/bmad-cache/cis/.markdownlint-cli2.yaml +35 -35
- package/lib/bmad-cache/cis/.prettierignore +9 -9
- package/lib/bmad-cache/cis/CNAME +1 -1
- package/lib/bmad-cache/cis/LICENSE +26 -26
- package/lib/bmad-cache/cis/README.md +114 -114
- package/lib/bmad-cache/cis/_git_preserved/HEAD +1 -1
- package/lib/bmad-cache/cis/_git_preserved/config +13 -13
- package/lib/bmad-cache/cis/_git_preserved/description +1 -1
- package/lib/bmad-cache/cis/_git_preserved/hooks/applypatch-msg.sample +15 -15
- package/lib/bmad-cache/cis/_git_preserved/hooks/commit-msg.sample +24 -24
- package/lib/bmad-cache/cis/_git_preserved/hooks/fsmonitor-watchman.sample +174 -174
- package/lib/bmad-cache/cis/_git_preserved/hooks/post-update.sample +8 -8
- package/lib/bmad-cache/cis/_git_preserved/hooks/pre-applypatch.sample +14 -14
- package/lib/bmad-cache/cis/_git_preserved/hooks/pre-commit.sample +49 -49
- package/lib/bmad-cache/cis/_git_preserved/hooks/pre-merge-commit.sample +13 -13
- package/lib/bmad-cache/cis/_git_preserved/hooks/pre-push.sample +53 -53
- package/lib/bmad-cache/cis/_git_preserved/hooks/pre-rebase.sample +169 -169
- package/lib/bmad-cache/cis/_git_preserved/hooks/pre-receive.sample +24 -24
- package/lib/bmad-cache/cis/_git_preserved/hooks/prepare-commit-msg.sample +42 -42
- package/lib/bmad-cache/cis/_git_preserved/hooks/push-to-checkout.sample +78 -78
- package/lib/bmad-cache/cis/_git_preserved/hooks/sendemail-validate.sample +77 -77
- package/lib/bmad-cache/cis/_git_preserved/hooks/update.sample +128 -128
- package/lib/bmad-cache/cis/_git_preserved/info/exclude +6 -6
- package/lib/bmad-cache/cis/_git_preserved/packed-refs +2 -2
- package/lib/bmad-cache/cis/_git_preserved/refs/heads/main +1 -1
- package/lib/bmad-cache/cis/_git_preserved/refs/remotes/origin/HEAD +1 -1
- package/lib/bmad-cache/cis/_git_preserved/shallow +1 -1
- package/lib/bmad-cache/cis/eslint.config.mjs +141 -141
- package/lib/bmad-cache/cis/package-lock.json +17015 -17015
- package/lib/bmad-cache/cis/package.json +91 -91
- package/lib/bmad-cache/cis/prettier.config.mjs +32 -32
- package/lib/bmad-cache/cis/src/module-help.csv +7 -7
- package/lib/bmad-cache/cis/src/module.yaml +76 -76
- package/lib/bmad-cache/cis/src/skills/bmad-cis-agent-brainstorming-coach/customize.toml +38 -38
- package/lib/bmad-cache/cis/src/skills/bmad-cis-agent-creative-problem-solver/customize.toml +38 -38
- package/lib/bmad-cache/cis/src/skills/bmad-cis-agent-design-thinking-coach/customize.toml +39 -39
- package/lib/bmad-cache/cis/src/skills/bmad-cis-agent-innovation-strategist/customize.toml +38 -38
- package/lib/bmad-cache/cis/src/skills/bmad-cis-agent-presentation-master/customize.toml +73 -73
- package/lib/bmad-cache/cis/src/skills/bmad-cis-agent-storyteller/customize.toml +60 -60
- package/lib/bmad-cache/cis/src/skills/bmad-cis-design-thinking/customize.toml +41 -41
- package/lib/bmad-cache/cis/src/skills/bmad-cis-design-thinking/design-methods.csv +30 -30
- package/lib/bmad-cache/cis/src/skills/bmad-cis-innovation-strategy/customize.toml +41 -41
- package/lib/bmad-cache/cis/src/skills/bmad-cis-innovation-strategy/innovation-frameworks.csv +30 -30
- package/lib/bmad-cache/cis/src/skills/bmad-cis-problem-solving/customize.toml +42 -42
- package/lib/bmad-cache/cis/src/skills/bmad-cis-problem-solving/solving-methods.csv +30 -30
- package/lib/bmad-cache/cis/src/skills/bmad-cis-storytelling/customize.toml +41 -41
- package/lib/bmad-cache/cis/src/skills/bmad-cis-storytelling/story-types.csv +25 -25
- package/lib/bmad-cache/cis/tools/build-docs.mjs +456 -456
- package/lib/bmad-cache/cis/website/astro.config.mjs +172 -172
- package/lib/bmad-cache/cis/website/src/components/Banner.astro +71 -71
- package/lib/bmad-cache/cis/website/src/components/Header.astro +94 -94
- package/lib/bmad-cache/cis/website/src/components/MobileMenuFooter.astro +33 -33
- package/lib/bmad-cache/cis/website/src/content/config.ts +7 -7
- package/lib/bmad-cache/cis/website/src/content/i18n/zh-CN.json +28 -28
- package/lib/bmad-cache/cis/website/src/lib/locales.mjs +27 -27
- package/lib/bmad-cache/cis/website/src/lib/site-url.mjs +25 -25
- package/lib/bmad-cache/cis/website/src/rehype-base-paths.js +88 -88
- package/lib/bmad-cache/cis/website/src/rehype-markdown-links.js +117 -117
- package/lib/bmad-cache/cis/website/src/styles/custom.css +503 -503
- package/lib/bmad-cache/gds/.claude-plugin/marketplace.json +59 -59
- package/lib/bmad-cache/gds/.markdownlint-cli2.yaml +35 -35
- package/lib/bmad-cache/gds/.prettierignore +9 -9
- package/lib/bmad-cache/gds/CNAME +1 -1
- package/lib/bmad-cache/gds/LICENSE +26 -26
- package/lib/bmad-cache/gds/README.md +132 -132
- package/lib/bmad-cache/gds/_git_preserved/HEAD +1 -1
- package/lib/bmad-cache/gds/_git_preserved/config +13 -13
- package/lib/bmad-cache/gds/_git_preserved/description +1 -1
- package/lib/bmad-cache/gds/_git_preserved/hooks/applypatch-msg.sample +15 -15
- package/lib/bmad-cache/gds/_git_preserved/hooks/commit-msg.sample +24 -24
- package/lib/bmad-cache/gds/_git_preserved/hooks/fsmonitor-watchman.sample +174 -174
- package/lib/bmad-cache/gds/_git_preserved/hooks/post-update.sample +8 -8
- package/lib/bmad-cache/gds/_git_preserved/hooks/pre-applypatch.sample +14 -14
- package/lib/bmad-cache/gds/_git_preserved/hooks/pre-commit.sample +49 -49
- package/lib/bmad-cache/gds/_git_preserved/hooks/pre-merge-commit.sample +13 -13
- package/lib/bmad-cache/gds/_git_preserved/hooks/pre-push.sample +53 -53
- package/lib/bmad-cache/gds/_git_preserved/hooks/pre-rebase.sample +169 -169
- package/lib/bmad-cache/gds/_git_preserved/hooks/pre-receive.sample +24 -24
- package/lib/bmad-cache/gds/_git_preserved/hooks/prepare-commit-msg.sample +42 -42
- package/lib/bmad-cache/gds/_git_preserved/hooks/push-to-checkout.sample +78 -78
- package/lib/bmad-cache/gds/_git_preserved/hooks/sendemail-validate.sample +77 -77
- package/lib/bmad-cache/gds/_git_preserved/hooks/update.sample +128 -128
- package/lib/bmad-cache/gds/_git_preserved/info/exclude +6 -6
- package/lib/bmad-cache/gds/_git_preserved/packed-refs +2 -2
- package/lib/bmad-cache/gds/_git_preserved/refs/heads/main +1 -1
- package/lib/bmad-cache/gds/_git_preserved/refs/remotes/origin/HEAD +1 -1
- package/lib/bmad-cache/gds/_git_preserved/shallow +1 -1
- package/lib/bmad-cache/gds/eslint.config.mjs +141 -141
- package/lib/bmad-cache/gds/package.json +91 -91
- package/lib/bmad-cache/gds/prettier.config.mjs +32 -32
- package/lib/bmad-cache/gds/src/agents/gds-agent-game-architect/customize.toml +57 -57
- package/lib/bmad-cache/gds/src/agents/gds-agent-game-designer/customize.toml +59 -59
- package/lib/bmad-cache/gds/src/agents/gds-agent-game-dev/customize.toml +129 -129
- package/lib/bmad-cache/gds/src/agents/gds-agent-game-dev/gametest/qa-index.csv +18 -18
- package/lib/bmad-cache/gds/src/agents/gds-agent-game-solo-dev/customize.toml +60 -60
- package/lib/bmad-cache/gds/src/agents/gds-agent-tech-writer/customize.toml +65 -65
- package/lib/bmad-cache/gds/src/module-help.csv +36 -36
- package/lib/bmad-cache/gds/src/module.yaml +113 -113
- package/lib/bmad-cache/gds/src/workflows/1-preproduction/gds-brainstorm-game/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/1-preproduction/gds-brainstorm-game/game-brain-methods.csv +25 -25
- package/lib/bmad-cache/gds/src/workflows/1-preproduction/gds-create-game-brief/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/1-preproduction/research/gds-domain-research/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-create-gdd/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-create-gdd/game-types.csv +24 -24
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-create-narrative/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-create-prd/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-create-prd/data/domain-complexity.csv +14 -14
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-create-prd/data/project-types.csv +10 -10
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-create-ux-design/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-edit-gdd/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-edit-prd/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-validate-gdd/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-validate-gdd/data/genre-complexity.csv +26 -26
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-validate-prd/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-validate-prd/data/domain-complexity.csv +14 -14
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-validate-prd/data/project-types.csv +10 -10
- package/lib/bmad-cache/gds/src/workflows/3-technical/gds-check-implementation-readiness/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/3-technical/gds-create-epics-and-stories/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/3-technical/gds-game-architecture/architecture-patterns.yaml +507 -507
- package/lib/bmad-cache/gds/src/workflows/3-technical/gds-game-architecture/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/3-technical/gds-game-architecture/decision-catalog.yaml +340 -340
- package/lib/bmad-cache/gds/src/workflows/3-technical/gds-game-architecture/engine-mcps.yaml +270 -270
- package/lib/bmad-cache/gds/src/workflows/3-technical/gds-game-architecture/pattern-categories.csv +12 -12
- package/lib/bmad-cache/gds/src/workflows/3-technical/gds-generate-project-context/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/4-production/gds-code-review/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/4-production/gds-correct-course/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/4-production/gds-create-story/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/4-production/gds-dev-story/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/4-production/gds-retrospective/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/4-production/gds-sprint-planning/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/4-production/gds-sprint-planning/sprint-status-template.yaml +55 -55
- package/lib/bmad-cache/gds/src/workflows/4-production/gds-sprint-status/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/gametest/gds-e2e-scaffold/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/gametest/gds-performance-test/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/gametest/gds-playtest-plan/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/gametest/gds-test-automate/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/gametest/gds-test-design/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/gametest/gds-test-framework/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/gametest/gds-test-review/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/gds-document-project/customize.toml +41 -41
- package/lib/bmad-cache/gds/src/workflows/gds-document-project/documentation-requirements.csv +12 -12
- package/lib/bmad-cache/gds/src/workflows/gds-document-project/templates/project-scan-report-schema.json +160 -160
- package/lib/bmad-cache/gds/src/workflows/gds-quick-flow/gds-quick-dev/customize.toml +41 -41
- package/lib/bmad-cache/gds/tools/build-docs.mjs +450 -450
- package/lib/bmad-cache/gds/website/astro.config.mjs +142 -142
- package/lib/bmad-cache/gds/website/src/components/Banner.astro +71 -71
- package/lib/bmad-cache/gds/website/src/components/Header.astro +94 -94
- package/lib/bmad-cache/gds/website/src/components/MobileMenuFooter.astro +33 -33
- package/lib/bmad-cache/gds/website/src/content/config.ts +6 -6
- package/lib/bmad-cache/gds/website/src/lib/site-url.mjs +25 -25
- package/lib/bmad-cache/gds/website/src/rehype-base-paths.js +88 -88
- package/lib/bmad-cache/gds/website/src/rehype-markdown-links.js +117 -117
- package/lib/bmad-cache/gds/website/src/styles/custom.css +503 -503
- package/lib/bmad-cache/tea/.claude-plugin/marketplace.json +33 -33
- package/lib/bmad-cache/tea/.coderabbit.yaml +40 -40
- package/lib/bmad-cache/tea/.github/CODE_OF_CONDUCT.md +128 -128
- package/lib/bmad-cache/tea/.github/FUNDING.yaml +15 -15
- package/lib/bmad-cache/tea/.github/ISSUE_TEMPLATE/config.yaml +11 -11
- package/lib/bmad-cache/tea/.github/ISSUE_TEMPLATE/feature_request.md +70 -70
- package/lib/bmad-cache/tea/.github/ISSUE_TEMPLATE/issue.md +61 -61
- package/lib/bmad-cache/tea/.github/workflows/docs.yaml +66 -66
- package/lib/bmad-cache/tea/.github/workflows/quality.yaml +117 -117
- package/lib/bmad-cache/tea/.husky/pre-commit +20 -20
- package/lib/bmad-cache/tea/.markdownlint-cli2.yaml +36 -36
- package/lib/bmad-cache/tea/.prettierignore +9 -9
- package/lib/bmad-cache/tea/CHANGELOG.md +241 -241
- package/lib/bmad-cache/tea/CONTRIBUTING.md +268 -268
- package/lib/bmad-cache/tea/LICENSE +26 -26
- package/lib/bmad-cache/tea/README.md +416 -416
- package/lib/bmad-cache/tea/SECURITY.md +85 -85
- package/lib/bmad-cache/tea/_git_preserved/HEAD +1 -1
- package/lib/bmad-cache/tea/_git_preserved/config +13 -13
- package/lib/bmad-cache/tea/_git_preserved/description +1 -1
- package/lib/bmad-cache/tea/_git_preserved/hooks/applypatch-msg.sample +15 -15
- package/lib/bmad-cache/tea/_git_preserved/hooks/commit-msg.sample +24 -24
- package/lib/bmad-cache/tea/_git_preserved/hooks/fsmonitor-watchman.sample +174 -174
- package/lib/bmad-cache/tea/_git_preserved/hooks/post-update.sample +8 -8
- package/lib/bmad-cache/tea/_git_preserved/hooks/pre-applypatch.sample +14 -14
- package/lib/bmad-cache/tea/_git_preserved/hooks/pre-commit.sample +49 -49
- package/lib/bmad-cache/tea/_git_preserved/hooks/pre-merge-commit.sample +13 -13
- package/lib/bmad-cache/tea/_git_preserved/hooks/pre-push.sample +53 -53
- package/lib/bmad-cache/tea/_git_preserved/hooks/pre-rebase.sample +169 -169
- package/lib/bmad-cache/tea/_git_preserved/hooks/pre-receive.sample +24 -24
- package/lib/bmad-cache/tea/_git_preserved/hooks/prepare-commit-msg.sample +42 -42
- package/lib/bmad-cache/tea/_git_preserved/hooks/push-to-checkout.sample +78 -78
- package/lib/bmad-cache/tea/_git_preserved/hooks/sendemail-validate.sample +77 -77
- package/lib/bmad-cache/tea/_git_preserved/hooks/update.sample +128 -128
- package/lib/bmad-cache/tea/_git_preserved/info/exclude +6 -6
- package/lib/bmad-cache/tea/_git_preserved/packed-refs +2 -2
- package/lib/bmad-cache/tea/_git_preserved/refs/heads/main +1 -1
- package/lib/bmad-cache/tea/_git_preserved/refs/remotes/origin/HEAD +1 -1
- package/lib/bmad-cache/tea/_git_preserved/shallow +1 -1
- package/lib/bmad-cache/tea/docs/404.md +20 -20
- package/lib/bmad-cache/tea/docs/explanation/engagement-models.md +767 -767
- package/lib/bmad-cache/tea/docs/explanation/fixture-architecture.md +484 -484
- package/lib/bmad-cache/tea/docs/explanation/knowledge-base-system.md +601 -601
- package/lib/bmad-cache/tea/docs/explanation/network-first-patterns.md +884 -884
- package/lib/bmad-cache/tea/docs/explanation/risk-based-testing.md +628 -628
- package/lib/bmad-cache/tea/docs/explanation/step-file-architecture.md +599 -599
- package/lib/bmad-cache/tea/docs/explanation/subagent-architecture.md +189 -189
- package/lib/bmad-cache/tea/docs/explanation/tea-overview.md +474 -474
- package/lib/bmad-cache/tea/docs/explanation/test-quality-standards.md +965 -965
- package/lib/bmad-cache/tea/docs/explanation/testing-as-engineering.md +115 -115
- package/lib/bmad-cache/tea/docs/glossary/index.md +160 -160
- package/lib/bmad-cache/tea/docs/how-to/brownfield/use-tea-for-enterprise.md +571 -571
- package/lib/bmad-cache/tea/docs/how-to/brownfield/use-tea-with-existing-tests.md +631 -631
- package/lib/bmad-cache/tea/docs/how-to/customization/configure-browser-automation.md +243 -243
- package/lib/bmad-cache/tea/docs/how-to/customization/extend-tea-with-custom-workflows.md +102 -102
- package/lib/bmad-cache/tea/docs/how-to/customization/integrate-playwright-utils.md +846 -846
- package/lib/bmad-cache/tea/docs/how-to/workflows/run-atdd.md +462 -462
- package/lib/bmad-cache/tea/docs/how-to/workflows/run-automate.md +693 -693
- package/lib/bmad-cache/tea/docs/how-to/workflows/run-nfr-assess.md +731 -731
- package/lib/bmad-cache/tea/docs/how-to/workflows/run-test-design.md +144 -144
- package/lib/bmad-cache/tea/docs/how-to/workflows/run-test-review.md +634 -634
- package/lib/bmad-cache/tea/docs/how-to/workflows/run-trace.md +966 -966
- package/lib/bmad-cache/tea/docs/how-to/workflows/setup-ci.md +763 -763
- package/lib/bmad-cache/tea/docs/how-to/workflows/setup-test-framework.md +122 -122
- package/lib/bmad-cache/tea/docs/how-to/workflows/teach-me-testing.md +302 -302
- package/lib/bmad-cache/tea/docs/index.md +65 -65
- package/lib/bmad-cache/tea/docs/reference/commands.md +356 -356
- package/lib/bmad-cache/tea/docs/reference/configuration.md +1144 -1144
- package/lib/bmad-cache/tea/docs/reference/knowledge-base.md +406 -406
- package/lib/bmad-cache/tea/docs/reference/troubleshooting.md +837 -837
- package/lib/bmad-cache/tea/docs/tutorials/learn-testing-tea-academy.md +266 -266
- package/lib/bmad-cache/tea/docs/tutorials/tea-lite-quickstart.md +465 -465
- package/lib/bmad-cache/tea/eslint.config.mjs +141 -141
- package/lib/bmad-cache/tea/package-lock.json +16046 -16046
- package/lib/bmad-cache/tea/package.json +118 -118
- package/lib/bmad-cache/tea/prettier.config.mjs +32 -32
- package/lib/bmad-cache/tea/src/agents/bmad-tea/SKILL.md +80 -80
- package/lib/bmad-cache/tea/src/agents/bmad-tea/customize.toml +104 -104
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/adr-quality-readiness-checklist.md +377 -377
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/api-request.md +563 -563
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/api-testing-patterns.md +915 -915
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/auth-session.md +548 -548
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/burn-in.md +273 -273
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/ci-burn-in.md +717 -717
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/component-tdd.md +486 -486
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/contract-testing.md +1066 -1066
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/data-factories.md +500 -500
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/email-auth.md +721 -721
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/error-handling.md +725 -725
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/feature-flags.md +750 -750
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/file-utils.md +456 -456
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/fixture-architecture.md +401 -401
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/fixtures-composition.md +382 -382
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/intercept-network-call.md +426 -426
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/log.md +426 -426
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/network-error-monitor.md +401 -401
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/network-first.md +486 -486
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/network-recorder.md +527 -527
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/nfr-criteria.md +670 -670
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/overview.md +286 -286
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/pact-broker-webhooks.md +237 -237
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/pact-consumer-di.md +310 -310
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/pact-consumer-framework-setup.md +704 -704
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/pact-mcp.md +205 -205
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/pactjs-utils-consumer-helpers.md +379 -379
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/pactjs-utils-overview.md +219 -219
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/pactjs-utils-provider-verifier.md +397 -397
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/pactjs-utils-request-filter.md +224 -224
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/pactjs-utils-zod-to-pact.md +262 -262
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/playwright-cli.md +280 -280
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/playwright-config.md +734 -734
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/probability-impact.md +601 -601
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/recurse.md +421 -421
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/risk-governance.md +615 -615
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/selective-testing.md +732 -732
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/selector-resilience.md +527 -527
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/test-healing-patterns.md +644 -644
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/test-levels-framework.md +473 -473
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/test-priorities-matrix.md +373 -373
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/test-quality.md +664 -664
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/timing-debugging.md +372 -372
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/visual-debugging.md +527 -527
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/webhook-module-setup.md +122 -122
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/webhook-providers.md +155 -155
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/webhook-risk-guidance.md +114 -114
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/webhook-template-matchers.md +160 -160
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/webhook-testing-fundamentals.md +42 -42
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/webhook-timeout-error.md +130 -130
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/webhook-waiting-querying.md +167 -167
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/tea-index.csv +52 -52
- package/lib/bmad-cache/tea/src/module-help.csv +11 -11
- package/lib/bmad-cache/tea/src/module.yaml +307 -307
- package/lib/bmad-cache/tea/src/workflows/testarch/README.md +76 -76
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/SKILL.md +129 -129
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/checklist.md +198 -198
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/customize.toml +40 -40
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/data/curriculum.yaml +129 -129
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/data/quiz-questions.yaml +206 -206
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/data/role-paths.yaml +136 -136
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/data/session-content-map.yaml +219 -219
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/data/tea-resources-index.yaml +394 -394
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/instructions.md +137 -137
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-01-init.md +235 -235
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-01b-continue.md +147 -147
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-02-assess.md +258 -258
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-03-session-menu.md +219 -219
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-04-session-01.md +460 -460
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-04-session-02.md +465 -465
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-04-session-03.md +301 -301
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-04-session-04.md +234 -234
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-04-session-05.md +234 -234
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-04-session-06.md +209 -209
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-04-session-07.md +220 -220
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-05-completion.md +347 -347
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-e/step-e-01-assess-workflow.md +141 -141
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-e/step-e-02-apply-edits.md +130 -130
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-v/step-v-01-validate.md +272 -272
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/templates/certificate-template.md +86 -86
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/templates/progress-template.yaml +95 -95
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/templates/session-notes-template.md +83 -83
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/workflow-plan-teach-me-testing.md +950 -950
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/SKILL.md +85 -85
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/atdd-checklist-template.md +394 -394
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/checklist.md +375 -375
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/customize.toml +40 -40
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/instructions.md +44 -44
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/adr-quality-readiness-checklist.md +377 -377
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/api-request.md +563 -563
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/api-testing-patterns.md +915 -915
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/auth-session.md +548 -548
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/burn-in.md +273 -273
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/ci-burn-in.md +717 -717
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/component-tdd.md +486 -486
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/contract-testing.md +1067 -1067
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/data-factories.md +500 -500
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/email-auth.md +721 -721
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/error-handling.md +725 -725
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/feature-flags.md +750 -750
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/file-utils.md +456 -456
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/fixture-architecture.md +401 -401
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/fixtures-composition.md +382 -382
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/intercept-network-call.md +426 -426
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/log.md +426 -426
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/network-error-monitor.md +401 -401
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/network-first.md +486 -486
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/network-recorder.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/nfr-criteria.md +670 -670
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/overview.md +286 -286
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/pact-broker-webhooks.md +237 -237
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/pact-consumer-di.md +310 -310
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/pact-consumer-framework-setup.md +757 -757
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/pact-mcp.md +205 -205
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/pactjs-utils-consumer-helpers.md +380 -380
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/pactjs-utils-overview.md +219 -219
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/pactjs-utils-provider-verifier.md +397 -397
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/pactjs-utils-request-filter.md +224 -224
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/pactjs-utils-zod-to-pact.md +262 -262
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/playwright-cli.md +280 -280
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/playwright-config.md +734 -734
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/probability-impact.md +601 -601
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/recurse.md +421 -421
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/risk-governance.md +615 -615
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/selective-testing.md +732 -732
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/selector-resilience.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/test-healing-patterns.md +644 -644
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/test-levels-framework.md +473 -473
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/test-priorities-matrix.md +373 -373
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/test-quality.md +664 -664
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/timing-debugging.md +372 -372
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/visual-debugging.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/webhook-module-setup.md +122 -122
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/webhook-providers.md +155 -155
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/webhook-risk-guidance.md +114 -114
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/webhook-template-matchers.md +160 -160
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/webhook-testing-fundamentals.md +42 -42
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/webhook-timeout-error.md +130 -130
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/knowledge/webhook-waiting-querying.md +167 -167
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/tea-index.csv +52 -52
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-01-preflight-and-context.md +244 -244
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-01b-resume.md +96 -96
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-02-generation-mode.md +125 -125
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-03-test-strategy.md +110 -110
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-04-generate-tests.md +335 -335
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-04a-subagent-api-failing.md +294 -294
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-04b-subagent-e2e-failing.md +244 -244
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-04c-aggregate.md +394 -394
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-05-validate-and-complete.md +123 -123
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-e/step-01-assess.md +65 -65
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-e/step-02-apply-edit.md +68 -68
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-v/step-01-validate.md +75 -75
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/validation-report-20260127-095021.md +73 -73
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/validation-report-20260127-102401.md +116 -116
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/workflow-plan.md +21 -21
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/workflow.yaml +46 -46
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/SKILL.md +85 -85
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/checklist.md +611 -611
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/customize.toml +40 -40
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/instructions.md +49 -49
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/adr-quality-readiness-checklist.md +377 -377
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/api-request.md +563 -563
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/api-testing-patterns.md +915 -915
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/auth-session.md +548 -548
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/burn-in.md +273 -273
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/ci-burn-in.md +717 -717
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/component-tdd.md +486 -486
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/contract-testing.md +1066 -1066
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/data-factories.md +500 -500
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/email-auth.md +721 -721
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/error-handling.md +725 -725
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/feature-flags.md +750 -750
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/file-utils.md +456 -456
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/fixture-architecture.md +401 -401
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/fixtures-composition.md +382 -382
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/intercept-network-call.md +426 -426
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/log.md +426 -426
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/network-error-monitor.md +401 -401
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/network-first.md +486 -486
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/network-recorder.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/nfr-criteria.md +670 -670
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/overview.md +286 -286
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/pact-broker-webhooks.md +237 -237
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/pact-consumer-di.md +310 -310
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/pact-consumer-framework-setup.md +757 -757
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/pact-mcp.md +205 -205
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/pactjs-utils-consumer-helpers.md +380 -380
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/pactjs-utils-overview.md +216 -216
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/pactjs-utils-provider-verifier.md +397 -397
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/pactjs-utils-request-filter.md +224 -224
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/playwright-cli.md +280 -280
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/playwright-config.md +734 -734
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/probability-impact.md +601 -601
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/recurse.md +421 -421
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/risk-governance.md +615 -615
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/selective-testing.md +732 -732
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/selector-resilience.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/test-healing-patterns.md +644 -644
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/test-levels-framework.md +473 -473
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/test-priorities-matrix.md +373 -373
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/test-quality.md +664 -664
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/timing-debugging.md +372 -372
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/visual-debugging.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/webhook-module-setup.md +122 -122
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/webhook-providers.md +155 -155
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/webhook-risk-guidance.md +114 -114
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/webhook-template-matchers.md +160 -160
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/webhook-testing-fundamentals.md +42 -42
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/webhook-timeout-error.md +130 -130
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/knowledge/webhook-waiting-querying.md +167 -167
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/tea-index.csv +51 -51
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-c/step-01-preflight-and-context.md +237 -237
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-c/step-01b-resume.md +94 -94
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-c/step-02-identify-targets.md +169 -169
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-c/step-03-generate-tests.md +394 -394
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-c/step-03a-subagent-api.md +271 -271
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-c/step-03b-subagent-backend.md +246 -246
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-c/step-03b-subagent-e2e.md +213 -213
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-c/step-03c-aggregate.md +398 -398
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-c/step-04-validate-and-summarize.md +114 -114
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-e/step-01-assess.md +65 -65
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-e/step-02-apply-edit.md +68 -68
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/steps-v/step-01-validate.md +75 -75
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/validation-report-20260127-095021.md +72 -72
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/validation-report-20260127-102401.md +114 -114
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/workflow-plan.md +20 -20
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/workflow.yaml +53 -53
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/SKILL.md +85 -85
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/azure-pipelines-template.yaml +155 -155
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/checklist.md +289 -289
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/customize.toml +40 -40
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/github-actions-template.yaml +328 -328
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/gitlab-ci-template.yaml +158 -158
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/harness-pipeline-template.yaml +160 -160
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/instructions.md +44 -44
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/jenkins-pipeline-template.groovy +129 -129
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/adr-quality-readiness-checklist.md +377 -377
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/api-request.md +563 -563
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/api-testing-patterns.md +915 -915
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/auth-session.md +548 -548
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/burn-in.md +273 -273
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/ci-burn-in.md +717 -717
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/component-tdd.md +486 -486
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/contract-testing.md +1066 -1066
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/data-factories.md +500 -500
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/email-auth.md +721 -721
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/error-handling.md +725 -725
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/feature-flags.md +750 -750
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/file-utils.md +456 -456
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/fixture-architecture.md +401 -401
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/fixtures-composition.md +382 -382
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/intercept-network-call.md +426 -426
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/log.md +426 -426
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/network-error-monitor.md +401 -401
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/network-first.md +486 -486
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/network-recorder.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/nfr-criteria.md +670 -670
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/overview.md +286 -286
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/pact-broker-webhooks.md +237 -237
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/pact-consumer-di.md +310 -310
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/pact-consumer-framework-setup.md +757 -757
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/pact-mcp.md +205 -205
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/pactjs-utils-consumer-helpers.md +380 -380
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/pactjs-utils-overview.md +216 -216
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/pactjs-utils-provider-verifier.md +397 -397
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/pactjs-utils-request-filter.md +224 -224
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/playwright-cli.md +280 -280
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/playwright-config.md +734 -734
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/probability-impact.md +601 -601
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/recurse.md +421 -421
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/risk-governance.md +615 -615
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/selective-testing.md +732 -732
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/selector-resilience.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/test-healing-patterns.md +644 -644
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/test-levels-framework.md +473 -473
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/test-priorities-matrix.md +373 -373
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/test-quality.md +664 -664
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/timing-debugging.md +372 -372
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/visual-debugging.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/webhook-module-setup.md +122 -122
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/webhook-providers.md +155 -155
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/webhook-risk-guidance.md +114 -114
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/webhook-template-matchers.md +160 -160
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/webhook-testing-fundamentals.md +42 -42
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/webhook-timeout-error.md +130 -130
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/knowledge/webhook-waiting-querying.md +167 -167
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/tea-index.csv +51 -51
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/steps-c/step-01-preflight.md +158 -158
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/steps-c/step-01b-resume.md +110 -110
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/steps-c/step-02-generate-pipeline.md +293 -293
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/steps-c/step-03-configure-quality-gates.md +145 -145
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/steps-c/step-04-validate-and-summary.md +100 -100
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/steps-e/step-01-assess.md +65 -65
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/steps-e/step-02-apply-edit.md +68 -68
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/steps-v/step-01-validate.md +89 -89
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/validation-report-20260127-095021.md +72 -72
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/validation-report-20260127-102401.md +114 -114
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/workflow-plan.md +20 -20
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/workflow.yaml +48 -48
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/SKILL.md +85 -85
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/checklist.md +345 -345
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/customize.toml +40 -40
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/instructions.md +44 -44
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/adr-quality-readiness-checklist.md +377 -377
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/api-request.md +563 -563
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/api-testing-patterns.md +915 -915
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/auth-session.md +548 -548
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/burn-in.md +273 -273
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/ci-burn-in.md +717 -717
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/component-tdd.md +486 -486
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/contract-testing.md +1066 -1066
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/data-factories.md +500 -500
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/email-auth.md +721 -721
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/error-handling.md +725 -725
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/feature-flags.md +750 -750
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/file-utils.md +456 -456
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/fixture-architecture.md +401 -401
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/fixtures-composition.md +382 -382
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/intercept-network-call.md +426 -426
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/log.md +426 -426
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/network-error-monitor.md +401 -401
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/network-first.md +486 -486
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/network-recorder.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/nfr-criteria.md +670 -670
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/overview.md +286 -286
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/pact-broker-webhooks.md +237 -237
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/pact-consumer-di.md +310 -310
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/pact-consumer-framework-setup.md +757 -757
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/pact-mcp.md +205 -205
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/pactjs-utils-consumer-helpers.md +380 -380
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/pactjs-utils-overview.md +216 -216
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/pactjs-utils-provider-verifier.md +397 -397
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/pactjs-utils-request-filter.md +224 -224
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/playwright-cli.md +280 -280
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/playwright-config.md +734 -734
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/probability-impact.md +601 -601
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/recurse.md +421 -421
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/risk-governance.md +615 -615
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/selective-testing.md +732 -732
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/selector-resilience.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/test-healing-patterns.md +644 -644
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/test-levels-framework.md +473 -473
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/test-priorities-matrix.md +373 -373
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/test-quality.md +664 -664
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/timing-debugging.md +372 -372
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/visual-debugging.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/webhook-module-setup.md +122 -122
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/webhook-providers.md +155 -155
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/webhook-risk-guidance.md +114 -114
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/webhook-template-matchers.md +160 -160
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/webhook-testing-fundamentals.md +42 -42
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/webhook-timeout-error.md +130 -130
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/knowledge/webhook-waiting-querying.md +167 -167
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/tea-index.csv +51 -51
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/steps-c/step-01-preflight.md +132 -132
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/steps-c/step-01b-resume.md +116 -116
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/steps-c/step-02-select-framework.md +117 -117
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/steps-c/step-03-scaffold-framework.md +328 -328
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/steps-c/step-04-docs-and-scripts.md +105 -105
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/steps-c/step-05-validate-and-summary.md +101 -101
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/steps-e/step-01-assess.md +65 -65
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/steps-e/step-02-apply-edit.md +68 -68
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/steps-v/step-01-validate.md +75 -75
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/validation-report-20260127-095021.md +73 -73
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/validation-report-20260127-102401.md +116 -116
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/workflow-plan.md +22 -22
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/workflow.yaml +48 -48
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/SKILL.md +85 -85
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/checklist.md +407 -407
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/customize.toml +40 -40
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/instructions.md +43 -43
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/nfr-report-template.md +470 -470
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/adr-quality-readiness-checklist.md +377 -377
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/api-request.md +563 -563
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/api-testing-patterns.md +915 -915
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/auth-session.md +548 -548
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/burn-in.md +273 -273
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/ci-burn-in.md +717 -717
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/component-tdd.md +486 -486
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/contract-testing.md +1066 -1066
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/data-factories.md +500 -500
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/email-auth.md +721 -721
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/error-handling.md +725 -725
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/feature-flags.md +750 -750
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/file-utils.md +456 -456
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/fixture-architecture.md +401 -401
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/fixtures-composition.md +382 -382
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/intercept-network-call.md +426 -426
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/log.md +426 -426
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/network-error-monitor.md +401 -401
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/network-first.md +486 -486
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/network-recorder.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/nfr-criteria.md +670 -670
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/overview.md +286 -286
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/pact-broker-webhooks.md +237 -237
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/pact-consumer-di.md +310 -310
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/pact-consumer-framework-setup.md +757 -757
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/pact-mcp.md +205 -205
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/pactjs-utils-consumer-helpers.md +380 -380
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/pactjs-utils-overview.md +216 -216
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/pactjs-utils-provider-verifier.md +397 -397
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/pactjs-utils-request-filter.md +224 -224
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/playwright-cli.md +280 -280
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/playwright-config.md +734 -734
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/probability-impact.md +601 -601
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/recurse.md +421 -421
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/risk-governance.md +615 -615
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/selective-testing.md +732 -732
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/selector-resilience.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/test-healing-patterns.md +644 -644
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/test-levels-framework.md +473 -473
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/test-priorities-matrix.md +373 -373
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/test-quality.md +664 -664
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/timing-debugging.md +372 -372
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/visual-debugging.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/webhook-module-setup.md +122 -122
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/webhook-providers.md +155 -155
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/webhook-risk-guidance.md +114 -114
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/webhook-template-matchers.md +160 -160
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/webhook-testing-fundamentals.md +42 -42
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/webhook-timeout-error.md +130 -130
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/knowledge/webhook-waiting-querying.md +167 -167
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/tea-index.csv +51 -51
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-01-load-context.md +138 -138
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-01b-resume.md +106 -106
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-02-define-thresholds.md +107 -107
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-03-gather-evidence.md +108 -108
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-04-evaluate-and-score.md +254 -254
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-04a-subagent-security.md +138 -138
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-04b-subagent-performance.md +84 -84
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-04c-subagent-reliability.md +85 -85
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-04d-subagent-scalability.md +88 -88
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-04e-aggregate-nfr.md +264 -264
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-05-generate-report.md +116 -116
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-e/step-01-assess.md +65 -65
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-e/step-02-apply-edit.md +68 -68
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-v/step-01-validate.md +75 -75
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/validation-report-20260127-095021.md +73 -73
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/validation-report-20260127-102401.md +116 -116
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/workflow-plan.md +19 -19
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/workflow.yaml +48 -48
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/SKILL.md +87 -87
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/checklist.md +464 -464
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/customize.toml +40 -40
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/instructions.md +104 -104
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/adr-quality-readiness-checklist.md +377 -377
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/api-request.md +563 -563
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/api-testing-patterns.md +915 -915
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/auth-session.md +548 -548
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/burn-in.md +273 -273
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/ci-burn-in.md +717 -717
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/component-tdd.md +486 -486
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/contract-testing.md +1066 -1066
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/data-factories.md +500 -500
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/email-auth.md +721 -721
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/error-handling.md +725 -725
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/feature-flags.md +750 -750
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/file-utils.md +456 -456
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/fixture-architecture.md +401 -401
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/fixtures-composition.md +382 -382
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/intercept-network-call.md +426 -426
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/log.md +426 -426
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/network-error-monitor.md +401 -401
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/network-first.md +486 -486
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/network-recorder.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/nfr-criteria.md +670 -670
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/overview.md +286 -286
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/pact-broker-webhooks.md +237 -237
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/pact-consumer-di.md +310 -310
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/pact-consumer-framework-setup.md +757 -757
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/pact-mcp.md +205 -205
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/pactjs-utils-consumer-helpers.md +380 -380
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/pactjs-utils-overview.md +216 -216
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/pactjs-utils-provider-verifier.md +397 -397
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/pactjs-utils-request-filter.md +224 -224
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/playwright-cli.md +280 -280
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/playwright-config.md +734 -734
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/probability-impact.md +601 -601
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/recurse.md +421 -421
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/risk-governance.md +615 -615
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/selective-testing.md +732 -732
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/selector-resilience.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/test-healing-patterns.md +644 -644
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/test-levels-framework.md +473 -473
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/test-priorities-matrix.md +373 -373
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/test-quality.md +664 -664
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/timing-debugging.md +372 -372
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/visual-debugging.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/webhook-module-setup.md +122 -122
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/webhook-providers.md +155 -155
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/webhook-risk-guidance.md +114 -114
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/webhook-template-matchers.md +160 -160
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/webhook-testing-fundamentals.md +42 -42
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/webhook-timeout-error.md +130 -130
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/knowledge/webhook-waiting-querying.md +167 -167
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/tea-index.csv +51 -51
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-c/step-01-detect-mode.md +140 -140
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-c/step-01b-resume.md +116 -116
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-c/step-02-load-context.md +248 -248
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-c/step-03-risk-and-testability.md +116 -116
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-c/step-04-coverage-plan.md +129 -129
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-c/step-05-generate-output.md +236 -236
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-e/step-01-assess.md +65 -65
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-e/step-02-apply-edit.md +68 -68
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-v/step-01-validate.md +75 -75
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/test-design-architecture-template.md +233 -233
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/test-design-handoff-template.md +70 -70
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/test-design-qa-template.md +399 -399
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/test-design-template.md +347 -347
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/validation-report-20260127-095021.md +73 -73
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/validation-report-20260127-102401.md +116 -116
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/workflow-plan.md +22 -22
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/workflow.yaml +77 -77
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/SKILL.md +85 -85
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/checklist.md +475 -475
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/customize.toml +40 -40
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/instructions.md +45 -45
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/adr-quality-readiness-checklist.md +377 -377
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/api-request.md +563 -563
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/api-testing-patterns.md +915 -915
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/auth-session.md +548 -548
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/burn-in.md +273 -273
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/ci-burn-in.md +717 -717
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/component-tdd.md +486 -486
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/contract-testing.md +1066 -1066
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/data-factories.md +500 -500
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/email-auth.md +721 -721
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/error-handling.md +725 -725
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/feature-flags.md +750 -750
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/file-utils.md +456 -456
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/fixture-architecture.md +401 -401
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/fixtures-composition.md +382 -382
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/intercept-network-call.md +426 -426
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/log.md +426 -426
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/network-error-monitor.md +401 -401
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/network-first.md +486 -486
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/network-recorder.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/nfr-criteria.md +670 -670
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/overview.md +286 -286
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/pact-broker-webhooks.md +237 -237
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/pact-consumer-di.md +310 -310
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/pact-consumer-framework-setup.md +757 -757
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/pact-mcp.md +205 -205
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/pactjs-utils-consumer-helpers.md +380 -380
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/pactjs-utils-overview.md +216 -216
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/pactjs-utils-provider-verifier.md +397 -397
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/pactjs-utils-request-filter.md +224 -224
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/playwright-cli.md +280 -280
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/playwright-config.md +734 -734
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/probability-impact.md +601 -601
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/recurse.md +421 -421
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/risk-governance.md +615 -615
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/selective-testing.md +732 -732
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/selector-resilience.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/test-healing-patterns.md +644 -644
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/test-levels-framework.md +473 -473
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/test-priorities-matrix.md +373 -373
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/test-quality.md +664 -664
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/timing-debugging.md +372 -372
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/visual-debugging.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/webhook-module-setup.md +122 -122
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/webhook-providers.md +155 -155
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/webhook-risk-guidance.md +114 -114
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/webhook-template-matchers.md +160 -160
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/webhook-testing-fundamentals.md +42 -42
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/webhook-timeout-error.md +130 -130
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/knowledge/webhook-waiting-querying.md +167 -167
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/tea-index.csv +51 -51
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-c/step-01-load-context.md +197 -197
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-c/step-01b-resume.md +104 -104
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-c/step-02-discover-tests.md +120 -120
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-c/step-03-quality-evaluation.md +274 -274
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-c/step-03a-subagent-determinism.md +257 -257
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-c/step-03b-subagent-isolation.md +125 -125
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-c/step-03c-subagent-maintainability.md +102 -102
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-c/step-03e-subagent-performance.md +117 -117
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-c/step-03f-aggregate-scores.md +277 -277
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-c/step-04-generate-report.md +119 -119
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-e/step-01-assess.md +65 -65
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-e/step-02-apply-edit.md +68 -68
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/steps-v/step-01-validate.md +75 -75
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/test-review-template.md +387 -387
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/validation-report-20260127-095021.md +72 -72
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/validation-report-20260127-102401.md +114 -114
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/workflow-plan.md +18 -18
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/workflow.yaml +48 -48
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/SKILL.md +87 -87
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/checklist.md +671 -671
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/customize.toml +40 -40
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/instructions.md +45 -45
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/adr-quality-readiness-checklist.md +377 -377
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/api-request.md +563 -563
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/api-testing-patterns.md +915 -915
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/auth-session.md +548 -548
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/burn-in.md +273 -273
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/ci-burn-in.md +717 -717
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/component-tdd.md +486 -486
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/contract-testing.md +1066 -1066
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/data-factories.md +500 -500
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/email-auth.md +721 -721
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/error-handling.md +725 -725
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/feature-flags.md +750 -750
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/file-utils.md +456 -456
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/fixture-architecture.md +401 -401
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/fixtures-composition.md +382 -382
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/intercept-network-call.md +426 -426
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/log.md +426 -426
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/network-error-monitor.md +401 -401
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/network-first.md +486 -486
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/network-recorder.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/nfr-criteria.md +670 -670
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/overview.md +286 -286
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/pact-broker-webhooks.md +237 -237
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/pact-consumer-di.md +310 -310
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/pact-consumer-framework-setup.md +757 -757
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/pact-mcp.md +205 -205
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/pactjs-utils-consumer-helpers.md +380 -380
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/pactjs-utils-overview.md +216 -216
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/pactjs-utils-provider-verifier.md +397 -397
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/pactjs-utils-request-filter.md +224 -224
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/playwright-cli.md +280 -280
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/playwright-config.md +734 -734
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/probability-impact.md +601 -601
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/recurse.md +421 -421
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/risk-governance.md +615 -615
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/selective-testing.md +732 -732
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/selector-resilience.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/test-healing-patterns.md +644 -644
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/test-levels-framework.md +473 -473
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/test-priorities-matrix.md +373 -373
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/test-quality.md +664 -664
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/timing-debugging.md +372 -372
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/visual-debugging.md +527 -527
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/webhook-module-setup.md +122 -122
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/webhook-providers.md +155 -155
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/webhook-risk-guidance.md +114 -114
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/webhook-template-matchers.md +160 -160
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/webhook-testing-fundamentals.md +42 -42
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/webhook-timeout-error.md +130 -130
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/knowledge/webhook-waiting-querying.md +167 -167
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/tea-index.csv +51 -51
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/steps-c/step-01-load-context.md +166 -166
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/steps-c/step-01b-resume.md +102 -102
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/steps-c/step-02-discover-tests.md +132 -132
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/steps-c/step-03-map-criteria.md +101 -101
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/steps-c/step-04-analyze-gaps.md +628 -628
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/steps-c/step-05-gate-decision.md +681 -681
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/steps-e/step-01-assess.md +65 -65
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/steps-e/step-02-apply-edit.md +68 -68
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/steps-v/step-01-validate.md +75 -75
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/trace-template.md +716 -716
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/validation-report-20260127-095021.md +73 -73
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/validation-report-20260127-102401.md +116 -116
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/workflow-plan.md +24 -24
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/workflow.yaml +80 -80
- package/lib/bmad-cache/tea/test/README.md +23 -23
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/critical-actions/actions-as-string.agent.yaml +27 -27
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/critical-actions/empty-string-in-actions.agent.yaml +30 -30
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu/empty-menu.agent.yaml +22 -22
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu/missing-menu.agent.yaml +20 -20
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu-commands/empty-command-target.agent.yaml +25 -25
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu-commands/no-command-target.agent.yaml +24 -24
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu-triggers/camel-case.agent.yaml +25 -25
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu-triggers/compound-invalid-format.agent.yaml +25 -25
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu-triggers/compound-mismatched-kebab.agent.yaml +25 -25
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu-triggers/duplicate-triggers.agent.yaml +31 -31
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu-triggers/empty-trigger.agent.yaml +25 -25
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu-triggers/leading-asterisk.agent.yaml +25 -25
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu-triggers/snake-case.agent.yaml +25 -25
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/menu-triggers/trigger-with-spaces.agent.yaml +25 -25
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/metadata/empty-module-string.agent.yaml +26 -26
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/metadata/empty-name.agent.yaml +24 -24
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/metadata/extra-metadata-fields.agent.yaml +27 -27
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/metadata/missing-id.agent.yaml +23 -23
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/persona/empty-principles-array.agent.yaml +24 -24
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/persona/empty-string-in-principles.agent.yaml +27 -27
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/persona/extra-persona-fields.agent.yaml +27 -27
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/persona/missing-role.agent.yaml +24 -24
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/prompts/empty-content.agent.yaml +29 -29
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/prompts/extra-prompt-fields.agent.yaml +31 -31
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/prompts/missing-content.agent.yaml +28 -28
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/prompts/missing-id.agent.yaml +28 -28
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/top-level/empty-file.agent.yaml +5 -5
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/top-level/extra-top-level-keys.agent.yaml +28 -28
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/top-level/missing-agent-key.agent.yaml +11 -11
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/yaml-errors/invalid-indentation.agent.yaml +19 -19
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/invalid/yaml-errors/malformed-yaml.agent.yaml +18 -18
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/critical-actions/empty-critical-actions.agent.yaml +24 -24
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/critical-actions/no-critical-actions.agent.yaml +22 -22
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/critical-actions/valid-critical-actions.agent.yaml +27 -27
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/menu/multiple-menu-items.agent.yaml +31 -31
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/menu/single-menu-item.agent.yaml +22 -22
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/menu-commands/all-command-types.agent.yaml +37 -37
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/menu-commands/multiple-commands.agent.yaml +24 -24
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/menu-triggers/compound-triggers.agent.yaml +31 -31
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/menu-triggers/kebab-case-triggers.agent.yaml +34 -34
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/metadata/core-agent-with-module.agent.yaml +24 -24
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/metadata/empty-module-name-in-path.agent.yaml +24 -24
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/metadata/malformed-path-treated-as-core.agent.yaml +24 -24
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/metadata/module-agent-correct.agent.yaml +24 -24
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/metadata/module-agent-missing-module.agent.yaml +23 -23
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/metadata/wrong-module-value.agent.yaml +24 -24
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/persona/complete-persona.agent.yaml +24 -24
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/prompts/empty-prompts.agent.yaml +24 -24
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/prompts/no-prompts.agent.yaml +22 -22
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/prompts/valid-prompts-minimal.agent.yaml +28 -28
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/prompts/valid-prompts-with-description.agent.yaml +30 -30
- package/lib/bmad-cache/tea/test/fixtures/agent-schema/valid/top-level/minimal-core-agent.agent.yaml +24 -24
- package/lib/bmad-cache/tea/test/schema/agent.js +491 -491
- package/lib/bmad-cache/tea/test/test-agent-schema.js +387 -387
- package/lib/bmad-cache/tea/test/test-installation-components.js +422 -422
- package/lib/bmad-cache/tea/test/test-knowledge-base.js +213 -213
- package/lib/bmad-cache/tea/test/test-release-metadata.js +71 -71
- package/lib/bmad-cache/tea/test/unit-test-schema.js +133 -133
- package/lib/bmad-cache/tea/test/validate-agent-schema.js +110 -110
- package/lib/bmad-cache/tea/tools/build-docs.js +575 -575
- package/lib/bmad-cache/tea/tools/fix-doc-links.js +288 -288
- package/lib/bmad-cache/tea/tools/schema/agent.js +491 -491
- package/lib/bmad-cache/tea/tools/validate-agent-schema.js +284 -284
- package/lib/bmad-cache/tea/tools/validate-doc-links.js +371 -371
- package/lib/bmad-cache/tea/tools/validate-tea-workflow-descriptions.js +122 -122
- package/lib/bmad-cache/tea/tools/verify-paths.js +100 -100
- package/lib/bmad-cache/tea/website/README.md +137 -137
- package/lib/bmad-cache/tea/website/astro.config.mjs +183 -183
- package/lib/bmad-cache/tea/website/package-lock.json +6856 -6856
- package/lib/bmad-cache/tea/website/package.json +24 -24
- package/lib/bmad-cache/tea/website/public/img/tea-logo.svg +7 -7
- package/lib/bmad-cache/tea/website/public/robots.txt +37 -37
- package/lib/bmad-cache/tea/website/src/components/Banner.astro +74 -74
- package/lib/bmad-cache/tea/website/src/components/Header.astro +121 -121
- package/lib/bmad-cache/tea/website/src/components/MobileMenuFooter.astro +53 -53
- package/lib/bmad-cache/tea/website/src/content/config.ts +6 -6
- package/lib/bmad-cache/tea/website/src/lib/site-url.js +25 -25
- package/lib/bmad-cache/tea/website/src/pages/404.astro +11 -11
- package/lib/bmad-cache/tea/website/src/rehype-base-paths.js +89 -89
- package/lib/bmad-cache/tea/website/src/rehype-markdown-links.js +117 -117
- package/lib/bmad-cache/tea/website/src/styles/custom.css +518 -518
- package/lib/bmad-cache/tea/website/tsconfig.json +9 -9
- package/lib/bmad-cache/wds/.claude-plugin/marketplace.json +35 -35
- package/lib/bmad-cache/wds/.markdownlint-cli2.yaml +38 -38
- package/lib/bmad-cache/wds/.prettierignore +9 -9
- package/lib/bmad-cache/wds/LICENSE +27 -27
- package/lib/bmad-cache/wds/README.md +139 -139
- package/lib/bmad-cache/wds/_git_preserved/HEAD +1 -1
- package/lib/bmad-cache/wds/_git_preserved/config +13 -13
- package/lib/bmad-cache/wds/_git_preserved/description +1 -1
- package/lib/bmad-cache/wds/_git_preserved/hooks/applypatch-msg.sample +15 -15
- package/lib/bmad-cache/wds/_git_preserved/hooks/commit-msg.sample +24 -24
- package/lib/bmad-cache/wds/_git_preserved/hooks/fsmonitor-watchman.sample +174 -174
- package/lib/bmad-cache/wds/_git_preserved/hooks/post-update.sample +8 -8
- package/lib/bmad-cache/wds/_git_preserved/hooks/pre-applypatch.sample +14 -14
- package/lib/bmad-cache/wds/_git_preserved/hooks/pre-commit.sample +49 -49
- package/lib/bmad-cache/wds/_git_preserved/hooks/pre-merge-commit.sample +13 -13
- package/lib/bmad-cache/wds/_git_preserved/hooks/pre-push.sample +53 -53
- package/lib/bmad-cache/wds/_git_preserved/hooks/pre-rebase.sample +169 -169
- package/lib/bmad-cache/wds/_git_preserved/hooks/pre-receive.sample +24 -24
- package/lib/bmad-cache/wds/_git_preserved/hooks/prepare-commit-msg.sample +42 -42
- package/lib/bmad-cache/wds/_git_preserved/hooks/push-to-checkout.sample +78 -78
- package/lib/bmad-cache/wds/_git_preserved/hooks/sendemail-validate.sample +77 -77
- package/lib/bmad-cache/wds/_git_preserved/hooks/update.sample +128 -128
- package/lib/bmad-cache/wds/_git_preserved/info/exclude +6 -6
- package/lib/bmad-cache/wds/_git_preserved/packed-refs +2 -2
- package/lib/bmad-cache/wds/_git_preserved/refs/heads/main +1 -1
- package/lib/bmad-cache/wds/_git_preserved/refs/remotes/origin/HEAD +1 -1
- package/lib/bmad-cache/wds/_git_preserved/shallow +1 -1
- package/lib/bmad-cache/wds/eslint.config.mjs +152 -152
- package/lib/bmad-cache/wds/package.json +82 -82
- package/lib/bmad-cache/wds/prettier.config.mjs +32 -32
- package/lib/bmad-cache/wds/src/agents/wds-agent-freya-ux/bmad-skill-manifest.yaml +12 -12
- package/lib/bmad-cache/wds/src/agents/wds-agent-saga-analyst/bmad-skill-manifest.yaml +12 -12
- package/lib/bmad-cache/wds/src/module-help.csv +19 -19
- package/lib/bmad-cache/wds/src/module.yaml +145 -145
- package/lib/bmad-cache/wds/src/workflows/wds-0-alignment-signoff/bmad-skill-manifest.yaml +1 -1
- package/lib/bmad-cache/wds/src/workflows/wds-0-project-setup/bmad-skill-manifest.yaml +1 -1
- package/lib/bmad-cache/wds/src/workflows/wds-0-project-setup/resources/wds-1-project-brief/templates/platform-requirements.template.yaml +69 -69
- package/lib/bmad-cache/wds/src/workflows/wds-0-project-setup/resources/wds-7-design-system/templates/catalog.template.html +363 -363
- package/lib/bmad-cache/wds/src/workflows/wds-1-project-brief/bmad-skill-manifest.yaml +1 -1
- package/lib/bmad-cache/wds/src/workflows/wds-1-project-brief/templates/platform-requirements.template.yaml +69 -69
- package/lib/bmad-cache/wds/src/workflows/wds-2-trigger-mapping/bmad-skill-manifest.yaml +1 -1
- package/lib/bmad-cache/wds/src/workflows/wds-3-scenarios/bmad-skill-manifest.yaml +1 -1
- package/lib/bmad-cache/wds/src/workflows/wds-4-ux-design/bmad-skill-manifest.yaml +1 -1
- package/lib/bmad-cache/wds/src/workflows/wds-4-ux-design/templates/design-delivery.template.yaml +104 -104
- package/lib/bmad-cache/wds/src/workflows/wds-4-ux-design/templates/test-scenario.template.yaml +192 -192
- package/lib/bmad-cache/wds/src/workflows/wds-5-agentic-development/bmad-skill-manifest.yaml +1 -1
- package/lib/bmad-cache/wds/src/workflows/wds-5-agentic-development/templates/components/dev-mode.css +164 -164
- package/lib/bmad-cache/wds/src/workflows/wds-5-agentic-development/templates/components/dev-mode.html +18 -18
- package/lib/bmad-cache/wds/src/workflows/wds-5-agentic-development/templates/components/dev-mode.js +430 -430
- package/lib/bmad-cache/wds/src/workflows/wds-5-agentic-development/templates/demo-data-template.json +63 -63
- package/lib/bmad-cache/wds/src/workflows/wds-5-agentic-development/templates/page-template.html +465 -465
- package/lib/bmad-cache/wds/src/workflows/wds-5-agentic-development/templates/work-file-template.yaml +264 -264
- package/lib/bmad-cache/wds/src/workflows/wds-6-asset-generation/bmad-skill-manifest.yaml +1 -1
- package/lib/bmad-cache/wds/src/workflows/wds-7-design-system/bmad-skill-manifest.yaml +1 -1
- package/lib/bmad-cache/wds/src/workflows/wds-7-design-system/templates/catalog.template.html +363 -363
- package/lib/bmad-cache/wds/src/workflows/wds-8-product-evolution/bmad-skill-manifest.yaml +1 -1
- package/lib/bmad-customize/bmm-analyst.customize.yaml +8 -8
- package/lib/bmad-customize/bmm-architect.customize.yaml +8 -8
- package/lib/bmad-customize/bmm-bmad-master.customize.yaml +6 -6
- package/lib/bmad-customize/bmm-dev.customize.yaml +8 -8
- package/lib/bmad-customize/bmm-pm.customize.yaml +8 -8
- package/lib/bmad-customize/bmm-qa.customize.yaml +26 -26
- package/lib/bmad-customize/bmm-sm.customize.yaml +8 -8
- package/lib/bmad-customize/bmm-tech-writer.customize.yaml +8 -8
- package/lib/bmad-customize/bmm-ux-designer.customize.yaml +8 -8
- package/lib/bmad-extension-plugin/.claude-plugin/marketplace.json +1 -1
- package/lib/bmad.js +191 -1
- package/lib/installer.js +2024 -2024
- package/lib/merge/roomodes.js +125 -125
- package/lib/methodology/version.json +7 -7
- package/lib/mil498-templates/OCD.md +169 -169
- package/lib/mil498-templates/README.md +4 -4
- package/lib/mil498-templates/SDD.md +163 -163
- package/lib/mil498-templates/SDP.md +307 -307
- package/lib/mil498-templates/SRS.md +219 -219
- package/lib/mil498-templates/SSDD.md +154 -154
- package/lib/mil498-templates/SSS.md +225 -225
- package/lib/mil498-templates/STD.md +188 -188
- package/lib/profile.js +130 -130
- package/lib/reconfigure.js +334 -334
- package/lib/skill-authoring.js +732 -732
- package/lib/templates/agents-md.template.md +67 -67
- package/lib/templates/clinerules.template.md +13 -13
- package/lib/templates/instruction-block-onprem.template.md +86 -86
- package/lib/templates/instruction-block-universal.template.md +29 -29
- package/lib/templates/project-context.template.md +47 -47
- package/lib/templates/roomodes.template.yaml +96 -96
- package/lib/uninstall.js +314 -314
- package/lib/warning-filter.js +245 -245
- package/mil498/OCD.md +169 -169
- package/mil498/README.md +4 -4
- package/mil498/SDP.md +307 -307
- package/mil498/SRS.md +219 -219
- package/mil498/SSDD.md +154 -154
- package/mil498/SSS.md +225 -225
- package/mil498/STD.md +188 -188
- package/package.json +57 -57
- package/scripts/build-bmad-cache.js +494 -494
- package/skills/README.md +473 -473
- package/skills/add-sprint/SKILL.md +204 -204
- package/skills/add-sprint/skill.json +7 -7
- package/skills/add-to-sprint/SKILL.md +270 -270
- package/skills/add-to-sprint/skill.json +7 -7
- package/skills/ai-audit-trail/SKILL.md +19 -19
- package/skills/ai-audit-trail/skill.json +20 -20
- package/skills/auto-bug-detection/SKILL.md +165 -165
- package/skills/auto-bug-detection/skill.json +8 -8
- package/skills/bmad-sprint-planning/SKILL.md +362 -362
- package/skills/bmad-sprint-planning/skill.json +7 -7
- package/skills/bmad-sprint-status/SKILL.md +312 -312
- package/skills/bmad-sprint-status/skill.json +7 -7
- package/skills/cleanup-done/SKILL.md +242 -242
- package/skills/cleanup-done/skill.json +7 -7
- package/skills/close-sprint/SKILL.md +409 -409
- package/skills/close-sprint/skill.json +7 -7
- package/skills/code-review/SKILL.md +79 -79
- package/skills/code-review/claude-code.md +64 -64
- package/skills/code-review/cline.md +55 -55
- package/skills/code-review/generic.md +39 -39
- package/skills/code-review/skill.json +7 -7
- package/skills/commit-message/SKILL.md +75 -75
- package/skills/commit-message/generic.md +75 -75
- package/skills/commit-message/skill.json +7 -7
- package/skills/cpp-best-practices/SKILL.md +230 -230
- package/skills/cpp-best-practices/examples/modern-idioms.md +189 -189
- package/skills/cpp-best-practices/examples/naming-and-organization.md +102 -102
- package/skills/cpp-best-practices/skill.json +25 -25
- package/skills/create-hardened-docker-skill/README.md +85 -85
- package/skills/create-hardened-docker-skill/SKILL.md +633 -633
- package/skills/create-hardened-docker-skill/scripts/create-all.sh +489 -489
- package/skills/create-hardened-docker-skill/skill.json +7 -7
- package/skills/csharp-best-practices/SKILL.md +274 -274
- package/skills/csharp-best-practices/skill.json +23 -23
- package/skills/docker-hardening-verification/scripts/verify-hardening.sh +39 -39
- package/skills/docker-image-signing/scripts/sign-image.sh +33 -33
- package/skills/document-revision-history/SKILL.md +100 -100
- package/skills/document-revision-history/skill.json +18 -18
- package/skills/generate-backlog/SKILL.md +219 -219
- package/skills/generate-backlog/skill.json +7 -7
- package/skills/git-workflow-skill/README.md +135 -135
- package/skills/git-workflow-skill/SKILL.md +190 -190
- package/skills/git-workflow-skill/hooks/commit-msg +61 -61
- package/skills/git-workflow-skill/hooks/pre-commit +38 -38
- package/skills/git-workflow-skill/hooks/prepare-commit-msg +56 -56
- package/skills/git-workflow-skill/scripts/finish-feature.sh +192 -192
- package/skills/git-workflow-skill/scripts/install-hooks.sh +55 -55
- package/skills/git-workflow-skill/scripts/start-feature.sh +110 -110
- package/skills/git-workflow-skill/scripts/validate-workflow.sh +229 -229
- package/skills/git-workflow-skill/skill.json +21 -21
- package/skills/js-ts-security-skill/scripts/verify-security.sh +136 -136
- package/skills/js-ts-security-skill/skill.json +17 -17
- package/skills/modify-sprint/SKILL.md +341 -341
- package/skills/modify-sprint/skill.json +7 -7
- package/skills/open-presentation/SKILL.md +31 -31
- package/skills/open-presentation/skill.json +11 -11
- package/skills/prioritize-backlog/SKILL.md +242 -242
- package/skills/prioritize-backlog/skill.json +7 -7
- package/skills/python-best-practices/SKILL.md +381 -381
- package/skills/python-best-practices/skill.json +26 -26
- package/skills/remove-from-sprint/SKILL.md +213 -213
- package/skills/remove-from-sprint/skill.json +7 -7
- package/skills/self-signed-cert/scripts/generate-cert.sh +43 -43
- package/skills/skill-creator/SKILL.md +211 -211
- package/skills/skill-creator/claude-code.md +64 -64
- package/skills/skill-creator/generic.md +192 -192
- package/skills/skill-creator/references/output-patterns.md +82 -82
- package/skills/skill-creator/references/workflows.md +28 -28
- package/skills/skill-creator/scripts/init_skill.py +208 -208
- package/skills/skill-creator/scripts/package_skill.py +99 -99
- package/skills/skill-creator/scripts/quick_validate.py +113 -113
- package/skills/skill-creator/skill.json +8 -8
- package/skills/sprint-status-view/SKILL.md +212 -212
- package/skills/sprint-status-view/skill.json +7 -7
- package/skills/story-status-lookup/SKILL.md +106 -106
- package/skills/story-status-lookup/skill.json +8 -8
- package/skills/test-generator/SKILL.md +74 -74
- package/skills/test-generator/claude-code.md +103 -103
- package/skills/test-generator/cline.md +69 -69
- package/skills/test-generator/generic.md +61 -61
- package/skills/test-generator/skill.json +18 -18
- package/skills/vercel-react-best-practices/SKILL.md +105 -105
- package/skills/vercel-react-best-practices/claude-code.md +80 -80
- package/skills/vercel-react-best-practices/generic.md +105 -105
- package/skills/vercel-react-best-practices/skill.json +19 -19
- package/skills/verify-hardened-docker-skill/README.md +85 -85
- package/skills/verify-hardened-docker-skill/SKILL.md +438 -438
- package/skills/verify-hardened-docker-skill/scripts/verify-docker-hardening.sh +439 -439
- package/skills/verify-hardened-docker-skill/skill.json +7 -7
- package/lib/.bmad-extension-plugin.build-1264-1777348888201/.claude-plugin/marketplace.json +0 -109
- package/lib/.bmad-extension-plugin.build-1264-1777348888201/skills/module-help.csv +0 -62
- package/lib/.bmad-extension-plugin.build-1264-1777348888201/skills/module.yaml +0 -20
- package/lib/.bmad-extension-plugin.build-24696-1777348768444/.claude-plugin/marketplace.json +0 -109
- package/lib/.bmad-extension-plugin.build-24696-1777348768444/skills/module-help.csv +0 -62
- package/lib/.bmad-extension-plugin.build-24696-1777348768444/skills/module.yaml +0 -20
- package/lib/.bmad-extension-plugin.build-25428-1777348694953/.claude-plugin/marketplace.json +0 -109
- package/lib/.bmad-extension-plugin.build-25428-1777348694953/skills/module-help.csv +0 -62
- package/lib/.bmad-extension-plugin.build-25428-1777348694953/skills/module.yaml +0 -20
- package/lib/bmad-cache/bmb/_git_preserved/logs/HEAD +0 -1
- package/lib/bmad-cache/bmb/_git_preserved/logs/refs/heads/main +0 -1
- package/lib/bmad-cache/bmb/_git_preserved/logs/refs/remotes/origin/HEAD +0 -1
- package/lib/bmad-cache/cis/_git_preserved/logs/HEAD +0 -1
- package/lib/bmad-cache/cis/_git_preserved/logs/refs/heads/main +0 -1
- package/lib/bmad-cache/cis/_git_preserved/logs/refs/remotes/origin/HEAD +0 -1
- package/lib/bmad-cache/gds/_git_preserved/logs/HEAD +0 -1
- package/lib/bmad-cache/gds/_git_preserved/logs/refs/heads/main +0 -1
- package/lib/bmad-cache/gds/_git_preserved/logs/refs/remotes/origin/HEAD +0 -1
- package/lib/bmad-cache/tea/.github/workflows/publish.yaml +0 -168
- package/lib/bmad-cache/tea/.vscode/settings.json +0 -47
- package/lib/bmad-cache/wds/_git_preserved/logs/HEAD +0 -1
- package/lib/bmad-cache/wds/_git_preserved/logs/refs/heads/main +0 -1
- 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
|
+
};
|