bmad-method 6.0.3 → 6.0.5-next.0
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/.augment/code_review_guidelines.yaml +2 -42
- package/.claude/skills/bmad-os-findings-triage/SKILL.md +6 -0
- package/.claude/skills/bmad-os-findings-triage/prompts/agent-prompt.md +104 -0
- package/.claude/skills/bmad-os-findings-triage/prompts/instructions.md +286 -0
- package/.claude/skills/bmad-os-review-pr/SKILL.md +1 -1
- package/.claude/skills/bmad-os-review-pr/prompts/instructions.md +63 -6
- package/.claude/skills/bmad-os-review-prompt/SKILL.md +177 -0
- package/.claude/skills/bmad-os-root-cause-analysis/SKILL.md +12 -0
- package/.claude/skills/bmad-os-root-cause-analysis/prompts/instructions.md +74 -0
- package/.github/ISSUE_TEMPLATE/config.yaml +1 -1
- package/.github/ISSUE_TEMPLATE/documentation.yaml +1 -1
- package/.github/workflows/publish.yaml +243 -0
- package/CHANGELOG.md +32 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +8 -8
- package/README_CN.md +121 -0
- package/docs/_STYLE_GUIDE.md +10 -10
- package/docs/explanation/brainstorming.md +1 -1
- package/docs/explanation/party-mode.md +1 -1
- package/docs/explanation/preventing-agent-conflicts.md +1 -1
- package/docs/explanation/project-context.md +15 -15
- package/docs/explanation/quick-flow.md +9 -9
- package/docs/how-to/established-projects.md +7 -7
- package/docs/how-to/get-answers-about-bmad.md +2 -2
- package/docs/how-to/install-bmad.md +16 -6
- package/docs/how-to/project-context.md +2 -2
- package/docs/how-to/quick-fixes.md +5 -5
- package/docs/how-to/shard-large-documents.md +1 -1
- package/docs/how-to/upgrade-to-v6.md +8 -5
- package/docs/index.md +2 -2
- package/docs/reference/agents.md +14 -14
- package/docs/reference/commands.md +64 -70
- package/docs/reference/testing.md +1 -1
- package/docs/reference/workflow-map.md +19 -19
- package/docs/tutorials/getting-started.md +34 -34
- package/docs/zh-cn/404.md +9 -0
- package/docs/zh-cn/_STYLE_GUIDE.md +370 -0
- package/docs/zh-cn/explanation/advanced-elicitation.md +62 -0
- package/docs/zh-cn/explanation/adversarial-review.md +71 -0
- package/docs/zh-cn/explanation/brainstorming.md +43 -0
- package/docs/zh-cn/explanation/established-projects-faq.md +60 -0
- package/docs/zh-cn/explanation/party-mode.md +79 -0
- package/docs/zh-cn/explanation/preventing-agent-conflicts.md +137 -0
- package/docs/zh-cn/explanation/project-context.md +176 -0
- package/docs/zh-cn/explanation/quick-flow.md +93 -0
- package/docs/zh-cn/explanation/why-solutioning-matters.md +90 -0
- package/docs/zh-cn/how-to/customize-bmad.md +182 -0
- package/docs/zh-cn/how-to/established-projects.md +134 -0
- package/docs/zh-cn/how-to/get-answers-about-bmad.md +144 -0
- package/docs/zh-cn/how-to/install-bmad.md +105 -0
- package/docs/zh-cn/how-to/non-interactive-installation.md +181 -0
- package/docs/zh-cn/how-to/project-context.md +152 -0
- package/docs/zh-cn/how-to/quick-fixes.md +140 -0
- package/docs/zh-cn/how-to/shard-large-documents.md +86 -0
- package/docs/zh-cn/how-to/upgrade-to-v6.md +120 -0
- package/docs/zh-cn/index.md +69 -0
- package/docs/zh-cn/reference/agents.md +41 -0
- package/docs/zh-cn/reference/commands.md +166 -0
- package/docs/zh-cn/reference/modules.md +94 -0
- package/docs/zh-cn/reference/testing.md +122 -0
- package/docs/zh-cn/reference/workflow-map.md +104 -0
- package/docs/zh-cn/roadmap.mdx +152 -0
- package/docs/zh-cn/tutorials/getting-started.md +300 -0
- package/package.json +1 -1
- package/src/bmm/agents/analyst.agent.yaml +1 -1
- package/src/bmm/agents/bmad-skill-manifest.yaml +39 -0
- package/src/bmm/agents/dev.agent.yaml +2 -2
- package/src/bmm/agents/pm.agent.yaml +1 -1
- package/src/bmm/agents/qa.agent.yaml +1 -1
- package/src/bmm/agents/quick-flow-solo-dev.agent.yaml +6 -2
- package/src/bmm/agents/sm.agent.yaml +4 -4
- package/src/bmm/agents/tech-writer/bmad-skill-manifest.yaml +3 -0
- package/src/bmm/agents/tech-writer/tech-writer.agent.yaml +1 -1
- package/src/bmm/module-help.csv +11 -10
- package/src/bmm/workflows/1-analysis/create-product-brief/bmad-skill-manifest.yaml +3 -0
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-02-vision.md +1 -1
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-03-users.md +1 -1
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-04-metrics.md +1 -1
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-05-scope.md +1 -1
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-06-complete.md +1 -1
- package/src/bmm/workflows/1-analysis/research/bmad-skill-manifest.yaml +14 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/bmad-skill-manifest.yaml +14 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02-discovery.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02b-vision.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02c-executive-summary.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-03-success.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-04-journeys.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-05-domain.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-06-innovation.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-07-project-type.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-08-scoping.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-09-functional.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-10-nonfunctional.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-11-polish.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-12-complete.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-01-discovery.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-02-review.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-01-discovery.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-10-smart-validation.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-11-holistic-quality-validation.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-13-report-complete.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/bmad-skill-manifest.yaml +3 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-02-discovery.md +1 -1
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-03-core-experience.md +2 -2
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-04-emotional-response.md +2 -2
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-05-inspiration.md +2 -2
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-06-design-system.md +2 -2
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-07-defining-experience.md +2 -2
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-08-visual-foundation.md +2 -2
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-09-design-directions.md +2 -2
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-10-user-journeys.md +2 -2
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-11-component-strategy.md +2 -2
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-12-ux-patterns.md +2 -2
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-13-responsive-accessibility.md +2 -2
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-14-complete.md +1 -1
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/bmad-skill-manifest.yaml +3 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-06-final-assessment.md +1 -1
- package/src/bmm/workflows/3-solutioning/create-architecture/bmad-skill-manifest.yaml +3 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-02-context.md +2 -2
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-03-starter.md +2 -2
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-04-decisions.md +2 -2
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-05-patterns.md +2 -2
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-06-structure.md +2 -2
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-07-validation.md +2 -2
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-08-complete.md +1 -1
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/bmad-skill-manifest.yaml +3 -0
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-01-validate-prerequisites.md +1 -1
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-02-design-epics.md +1 -1
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-03-create-stories.md +1 -1
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-04-final-validation.md +2 -2
- package/src/bmm/workflows/4-implementation/code-review/bmad-skill-manifest.yaml +3 -0
- package/src/bmm/workflows/4-implementation/code-review/discover-inputs.md +88 -0
- package/src/bmm/workflows/4-implementation/code-review/workflow.md +271 -0
- package/src/bmm/workflows/4-implementation/correct-course/bmad-skill-manifest.yaml +3 -0
- package/src/bmm/workflows/4-implementation/correct-course/checklist.md +1 -1
- package/src/bmm/workflows/4-implementation/correct-course/{instructions.md → workflow.md} +79 -12
- package/src/bmm/workflows/4-implementation/create-story/bmad-skill-manifest.yaml +3 -0
- package/src/bmm/workflows/4-implementation/create-story/checklist.md +9 -10
- package/src/bmm/workflows/4-implementation/create-story/discover-inputs.md +88 -0
- package/src/bmm/workflows/4-implementation/create-story/workflow.md +388 -0
- package/src/bmm/workflows/4-implementation/dev-story/bmad-skill-manifest.yaml +3 -0
- package/src/bmm/workflows/4-implementation/dev-story/{instructions.xml → workflow.md} +49 -2
- package/src/bmm/workflows/4-implementation/retrospective/bmad-skill-manifest.yaml +3 -0
- package/src/bmm/workflows/4-implementation/retrospective/{instructions.md → workflow.md} +64 -23
- package/src/bmm/workflows/4-implementation/sprint-planning/bmad-skill-manifest.yaml +3 -0
- package/src/bmm/workflows/4-implementation/sprint-planning/sprint-status-template.yaml +1 -0
- package/src/bmm/workflows/4-implementation/sprint-planning/{instructions.md → workflow.md} +55 -10
- package/src/bmm/workflows/4-implementation/sprint-status/bmad-skill-manifest.yaml +3 -0
- package/src/bmm/workflows/4-implementation/sprint-status/{instructions.md → workflow.md} +45 -8
- package/src/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/SKILL.md +6 -0
- package/src/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/bmad-skill-manifest.yaml +1 -0
- package/src/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/steps/step-01-clarify-and-route.md +54 -0
- package/src/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/steps/step-02-plan.md +39 -0
- package/src/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/steps/step-03-implement.md +35 -0
- package/src/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/steps/step-04-review.md +55 -0
- package/src/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/steps/step-05-present.md +19 -0
- package/src/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/tech-spec-template.md +90 -0
- package/src/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/workflow.md +84 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/bmad-skill-manifest.yaml +3 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-05-adversarial-review.md +8 -14
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/workflow.md +1 -1
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/bmad-skill-manifest.yaml +3 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-04-review.md +4 -6
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md +1 -1
- package/src/bmm/workflows/document-project/bmad-skill-manifest.yaml +3 -0
- package/src/bmm/workflows/document-project/instructions.md +5 -7
- package/src/bmm/workflows/document-project/workflow.md +39 -0
- package/src/bmm/workflows/document-project/workflows/deep-dive-instructions.md +0 -1
- package/src/bmm/workflows/document-project/workflows/deep-dive-workflow.md +42 -0
- package/src/bmm/workflows/document-project/workflows/full-scan-instructions.md +0 -1
- package/src/bmm/workflows/document-project/workflows/full-scan-workflow.md +42 -0
- package/src/bmm/workflows/generate-project-context/bmad-skill-manifest.yaml +3 -0
- package/src/bmm/workflows/generate-project-context/steps/step-02-generate.md +2 -2
- package/src/bmm/workflows/qa-generate-e2e-tests/bmad-skill-manifest.yaml +3 -0
- package/src/bmm/workflows/qa-generate-e2e-tests/checklist.md +1 -1
- package/src/bmm/workflows/qa-generate-e2e-tests/{instructions.md → workflow.md} +40 -7
- package/src/core/agents/bmad-master.agent.yaml +1 -1
- package/src/core/agents/bmad-skill-manifest.yaml +3 -0
- package/src/core/module-help.csv +3 -2
- package/src/core/module.yaml +1 -1
- package/src/core/tasks/bmad-help/SKILL.md +6 -0
- package/src/core/tasks/bmad-help/bmad-skill-manifest.yaml +1 -0
- package/src/core/tasks/{help.md → bmad-help/workflow.md} +6 -4
- package/src/core/tasks/bmad-review-adversarial-general/SKILL.md +6 -0
- package/src/core/tasks/bmad-review-adversarial-general/bmad-skill-manifest.yaml +1 -0
- package/src/core/tasks/bmad-review-adversarial-general/workflow.md +32 -0
- package/src/core/tasks/bmad-review-edge-case-hunter/SKILL.md +6 -0
- package/src/core/tasks/bmad-review-edge-case-hunter/bmad-skill-manifest.yaml +1 -0
- package/src/core/tasks/bmad-review-edge-case-hunter/workflow.md +62 -0
- package/src/core/tasks/bmad-skill-manifest.yaml +19 -0
- package/src/core/workflows/advanced-elicitation/bmad-skill-manifest.yaml +3 -0
- package/src/core/workflows/advanced-elicitation/workflow.md +138 -0
- package/src/core/workflows/brainstorming/bmad-skill-manifest.yaml +3 -0
- package/src/core/workflows/brainstorming/steps/step-01-session-setup.md +31 -18
- package/src/core/workflows/brainstorming/steps/step-01b-continue.md +1 -1
- package/src/core/workflows/brainstorming/steps/step-03-technique-execution.md +3 -3
- package/src/core/workflows/brainstorming/steps/step-04-idea-organization.md +2 -2
- package/src/core/workflows/brainstorming/workflow.md +5 -3
- package/src/core/workflows/party-mode/bmad-skill-manifest.yaml +3 -0
- package/src/core/workflows/party-mode/workflow.md +1 -1
- package/src/utility/agent-components/activation-steps.txt +2 -2
- package/src/utility/agent-components/handler-multi.txt +1 -2
- package/test/adversarial-review-tests/README.md +3 -3
- package/test/adversarial-review-tests/test-cases.yaml +2 -2
- package/test/fixtures/agent-schema/valid/menu/multiple-menu-items.agent.yaml +1 -1
- package/test/fixtures/agent-schema/valid/menu-commands/all-command-types.agent.yaml +1 -1
- package/test/fixtures/agent-schema/valid/menu-commands/multiple-commands.agent.yaml +1 -2
- package/test/fixtures/file-refs-csv/valid/bmm-style.csv +1 -1
- package/test/test-file-refs-csv.js +1 -1
- package/test/test-install-to-bmad.js +154 -0
- package/test/test-installation-components.js +1586 -2
- package/test/test-workflow-path-regex.js +88 -0
- package/tools/cli/installers/install-messages.yaml +1 -1
- package/tools/cli/installers/lib/core/installer.js +34 -1
- package/tools/cli/installers/lib/core/manifest-generator.js +332 -41
- package/tools/cli/installers/lib/ide/_base-ide.js +24 -15
- package/tools/cli/installers/lib/ide/_config-driven.js +547 -53
- package/tools/cli/installers/lib/ide/manager.js +26 -62
- package/tools/cli/installers/lib/ide/platform-codes.yaml +116 -29
- package/tools/cli/installers/lib/ide/shared/agent-command-generator.js +1 -0
- package/tools/cli/installers/lib/ide/shared/bmad-artifacts.js +7 -0
- package/tools/cli/installers/lib/ide/shared/path-utils.js +68 -3
- package/tools/cli/installers/lib/ide/shared/skill-manifest.js +90 -0
- package/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js +2 -0
- package/tools/cli/installers/lib/ide/shared/workflow-command-generator.js +6 -145
- package/tools/cli/installers/lib/ide/templates/agent-command-template.md +1 -1
- package/tools/cli/installers/lib/ide/templates/combined/default-workflow.md +1 -1
- package/tools/cli/installers/lib/ide/templates/combined/gemini-workflow-yaml.toml +1 -1
- package/tools/cli/installers/lib/ide/templates/combined/gemini-workflow.toml +1 -1
- package/tools/cli/installers/lib/ide/templates/combined/opencode-agent.md +1 -1
- package/tools/cli/installers/lib/ide/templates/combined/opencode-task.md +0 -1
- package/tools/cli/installers/lib/ide/templates/combined/opencode-tool.md +0 -1
- package/tools/cli/installers/lib/ide/templates/combined/opencode-workflow-yaml.md +0 -1
- package/tools/cli/installers/lib/ide/templates/combined/opencode-workflow.md +0 -1
- package/tools/cli/installers/lib/modules/manager.js +9 -132
- package/tools/cli/lib/agent/compiler.js +1 -10
- package/tools/cli/lib/agent-analyzer.js +2 -14
- package/tools/cli/lib/yaml-xml-builder.js +1 -18
- package/tools/docs/native-skills-migration-checklist.md +281 -0
- package/tools/platform-codes.yaml +1 -1
- package/tools/schema/agent.js +1 -3
- package/tools/validate-file-refs.js +2 -0
- package/website/astro.config.mjs +24 -3
- package/website/src/content/config.ts +2 -1
- package/website/src/content/i18n/zh-CN.json +28 -0
- package/src/bmm/workflows/4-implementation/code-review/instructions.xml +0 -227
- package/src/bmm/workflows/4-implementation/code-review/workflow.yaml +0 -43
- package/src/bmm/workflows/4-implementation/correct-course/workflow.yaml +0 -53
- package/src/bmm/workflows/4-implementation/create-story/instructions.xml +0 -346
- package/src/bmm/workflows/4-implementation/create-story/workflow.yaml +0 -52
- package/src/bmm/workflows/4-implementation/dev-story/workflow.yaml +0 -20
- package/src/bmm/workflows/4-implementation/retrospective/workflow.yaml +0 -52
- package/src/bmm/workflows/4-implementation/sprint-planning/workflow.yaml +0 -47
- package/src/bmm/workflows/4-implementation/sprint-status/workflow.yaml +0 -25
- package/src/bmm/workflows/document-project/workflow.yaml +0 -22
- package/src/bmm/workflows/document-project/workflows/deep-dive.yaml +0 -31
- package/src/bmm/workflows/document-project/workflows/full-scan.yaml +0 -31
- package/src/bmm/workflows/qa-generate-e2e-tests/workflow.yaml +0 -42
- package/src/core/tasks/review-adversarial-general.xml +0 -49
- package/src/core/tasks/workflow.xml +0 -235
- package/src/core/workflows/advanced-elicitation/workflow.xml +0 -118
- package/src/utility/agent-components/handler-validate-workflow.txt +0 -7
- package/src/utility/agent-components/handler-workflow.txt +0 -10
- package/tools/cli/installers/lib/ide/codex.js +0 -440
- package/tools/cli/installers/lib/ide/github-copilot.js +0 -699
- package/tools/cli/installers/lib/ide/kilo.js +0 -269
- package/tools/cli/installers/lib/ide/rovodev.js +0 -257
- package/tools/cli/installers/lib/ide/templates/combined/default-workflow-yaml.md +0 -14
- package/tools/cli/installers/lib/ide/templates/combined/kiro-workflow-yaml.md +0 -15
- package/tools/cli/installers/lib/ide/templates/workflow-command-template.md +0 -13
- package/tools/cli/installers/lib/ide/templates/workflow-commander.md +0 -5
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
const os = require('node:os');
|
|
1
2
|
const path = require('node:path');
|
|
2
3
|
const fs = require('fs-extra');
|
|
4
|
+
const yaml = require('yaml');
|
|
3
5
|
const { BaseIdeSetup } = require('./_base-ide');
|
|
4
6
|
const prompts = require('../../../lib/prompts');
|
|
5
7
|
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
|
6
8
|
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
|
|
7
9
|
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
|
|
10
|
+
const csv = require('csv-parse/sync');
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* Config-driven IDE setup handler
|
|
@@ -24,6 +27,34 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
|
|
24
27
|
super(platformCode, platformConfig.name, platformConfig.preferred);
|
|
25
28
|
this.platformConfig = platformConfig;
|
|
26
29
|
this.installerConfig = platformConfig.installer || null;
|
|
30
|
+
|
|
31
|
+
// Set configDir from target_dir so base-class detect() works
|
|
32
|
+
if (this.installerConfig?.target_dir) {
|
|
33
|
+
this.configDir = this.installerConfig.target_dir;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Detect whether this IDE already has configuration in the project.
|
|
39
|
+
* For skill_format platforms, checks for bmad-prefixed entries in target_dir
|
|
40
|
+
* (matching old codex.js behavior) instead of just checking directory existence.
|
|
41
|
+
* @param {string} projectDir - Project directory
|
|
42
|
+
* @returns {Promise<boolean>}
|
|
43
|
+
*/
|
|
44
|
+
async detect(projectDir) {
|
|
45
|
+
if (this.installerConfig?.skill_format && this.configDir) {
|
|
46
|
+
const dir = path.join(projectDir || process.cwd(), this.configDir);
|
|
47
|
+
if (await fs.pathExists(dir)) {
|
|
48
|
+
try {
|
|
49
|
+
const entries = await fs.readdir(dir);
|
|
50
|
+
return entries.some((e) => typeof e === 'string' && e.startsWith('bmad'));
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return super.detect(projectDir);
|
|
27
58
|
}
|
|
28
59
|
|
|
29
60
|
/**
|
|
@@ -34,6 +65,25 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
|
|
34
65
|
* @returns {Promise<Object>} Setup result
|
|
35
66
|
*/
|
|
36
67
|
async setup(projectDir, bmadDir, options = {}) {
|
|
68
|
+
// Check for BMAD files in ancestor directories that would cause duplicates
|
|
69
|
+
if (this.installerConfig?.ancestor_conflict_check) {
|
|
70
|
+
const conflict = await this.findAncestorConflict(projectDir);
|
|
71
|
+
if (conflict) {
|
|
72
|
+
await prompts.log.error(
|
|
73
|
+
`Found existing BMAD skills in ancestor installation: ${conflict}\n` +
|
|
74
|
+
` ${this.name} inherits skills from parent directories, so this would cause duplicates.\n` +
|
|
75
|
+
` Please remove the BMAD files from that directory first:\n` +
|
|
76
|
+
` rm -rf "${conflict}"/bmad*`,
|
|
77
|
+
);
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
reason: 'ancestor-conflict',
|
|
81
|
+
error: `Ancestor conflict: ${conflict}`,
|
|
82
|
+
conflictDir: conflict,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
37
87
|
if (!options.silent) await prompts.log.info(`Setting up ${this.name}...`);
|
|
38
88
|
|
|
39
89
|
// Clean up any old BMAD installation first
|
|
@@ -67,39 +117,48 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
|
|
67
117
|
async installToTarget(projectDir, bmadDir, config, options) {
|
|
68
118
|
const { target_dir, template_type, artifact_types } = config;
|
|
69
119
|
|
|
70
|
-
// Skip targets with explicitly empty artifact_types
|
|
120
|
+
// Skip targets with explicitly empty artifact_types and no verbatim skills
|
|
71
121
|
// This prevents creating empty directories when no artifacts will be written
|
|
72
|
-
|
|
73
|
-
|
|
122
|
+
const skipStandardArtifacts = Array.isArray(artifact_types) && artifact_types.length === 0;
|
|
123
|
+
if (skipStandardArtifacts && !config.skill_format) {
|
|
124
|
+
return { success: true, results: { agents: 0, workflows: 0, tasks: 0, tools: 0, skills: 0 } };
|
|
74
125
|
}
|
|
75
126
|
|
|
76
127
|
const targetPath = path.join(projectDir, target_dir);
|
|
77
128
|
await this.ensureDir(targetPath);
|
|
78
129
|
|
|
79
130
|
const selectedModules = options.selectedModules || [];
|
|
80
|
-
const results = { agents: 0, workflows: 0, tasks: 0, tools: 0 };
|
|
131
|
+
const results = { agents: 0, workflows: 0, tasks: 0, tools: 0, skills: 0 };
|
|
132
|
+
|
|
133
|
+
// Install standard artifacts (agents, workflows, tasks, tools)
|
|
134
|
+
if (!skipStandardArtifacts) {
|
|
135
|
+
// Install agents
|
|
136
|
+
if (!artifact_types || artifact_types.includes('agents')) {
|
|
137
|
+
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
|
138
|
+
const { artifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules);
|
|
139
|
+
results.agents = await this.writeAgentArtifacts(targetPath, artifacts, template_type, config);
|
|
140
|
+
}
|
|
81
141
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
142
|
+
// Install workflows
|
|
143
|
+
if (!artifact_types || artifact_types.includes('workflows')) {
|
|
144
|
+
const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
|
|
145
|
+
const { artifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
|
|
146
|
+
results.workflows = await this.writeWorkflowArtifacts(targetPath, artifacts, template_type, config);
|
|
147
|
+
}
|
|
88
148
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
149
|
+
// Install tasks and tools using template system (supports TOML for Gemini, MD for others)
|
|
150
|
+
if (!artifact_types || artifact_types.includes('tasks') || artifact_types.includes('tools')) {
|
|
151
|
+
const taskToolGen = new TaskToolCommandGenerator(this.bmadFolderName);
|
|
152
|
+
const { artifacts } = await taskToolGen.collectTaskToolArtifacts(bmadDir);
|
|
153
|
+
const taskToolResult = await this.writeTaskToolArtifacts(targetPath, artifacts, template_type, config);
|
|
154
|
+
results.tasks = taskToolResult.tasks || 0;
|
|
155
|
+
results.tools = taskToolResult.tools || 0;
|
|
156
|
+
}
|
|
94
157
|
}
|
|
95
158
|
|
|
96
|
-
// Install
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
-
const { artifacts } = await taskToolGen.collectTaskToolArtifacts(bmadDir);
|
|
100
|
-
const taskToolResult = await this.writeTaskToolArtifacts(targetPath, artifacts, template_type, config);
|
|
101
|
-
results.tasks = taskToolResult.tasks || 0;
|
|
102
|
-
results.tools = taskToolResult.tools || 0;
|
|
159
|
+
// Install verbatim skills (type: skill)
|
|
160
|
+
if (config.skill_format) {
|
|
161
|
+
results.skills = await this.installVerbatimSkills(projectDir, bmadDir, targetPath, config);
|
|
103
162
|
}
|
|
104
163
|
|
|
105
164
|
await this.printSummary(results, target_dir, options);
|
|
@@ -115,7 +174,7 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
|
|
115
174
|
* @returns {Promise<Object>} Installation result
|
|
116
175
|
*/
|
|
117
176
|
async installToMultipleTargets(projectDir, bmadDir, targets, options) {
|
|
118
|
-
const allResults = { agents: 0, workflows: 0, tasks: 0, tools: 0 };
|
|
177
|
+
const allResults = { agents: 0, workflows: 0, tasks: 0, tools: 0, skills: 0 };
|
|
119
178
|
|
|
120
179
|
for (const target of targets) {
|
|
121
180
|
const result = await this.installToTarget(projectDir, bmadDir, target, options);
|
|
@@ -124,6 +183,7 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
|
|
124
183
|
allResults.workflows += result.results.workflows || 0;
|
|
125
184
|
allResults.tasks += result.results.tasks || 0;
|
|
126
185
|
allResults.tools += result.results.tools || 0;
|
|
186
|
+
allResults.skills += result.results.skills || 0;
|
|
127
187
|
}
|
|
128
188
|
}
|
|
129
189
|
|
|
@@ -146,8 +206,13 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
|
|
146
206
|
for (const artifact of artifacts) {
|
|
147
207
|
const content = this.renderTemplate(template, artifact);
|
|
148
208
|
const filename = this.generateFilename(artifact, 'agent', extension);
|
|
149
|
-
|
|
150
|
-
|
|
209
|
+
|
|
210
|
+
if (config.skill_format) {
|
|
211
|
+
await this.writeSkillFile(targetPath, artifact, content);
|
|
212
|
+
} else {
|
|
213
|
+
const filePath = path.join(targetPath, filename);
|
|
214
|
+
await this.writeFile(filePath, content);
|
|
215
|
+
}
|
|
151
216
|
count++;
|
|
152
217
|
}
|
|
153
218
|
|
|
@@ -167,20 +232,17 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
|
|
167
232
|
|
|
168
233
|
for (const artifact of artifacts) {
|
|
169
234
|
if (artifact.type === 'workflow-command') {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const workflowTemplateType = artifact.isYamlWorkflow
|
|
173
|
-
? config.yaml_workflow_template || `${templateType}-workflow-yaml`
|
|
174
|
-
: config.md_workflow_template || `${templateType}-workflow`;
|
|
175
|
-
|
|
176
|
-
// Fall back to default templates if specific ones don't exist
|
|
177
|
-
const finalTemplateType = artifact.isYamlWorkflow ? 'default-workflow-yaml' : 'default-workflow';
|
|
178
|
-
// workflowTemplateType already contains full name (e.g., 'gemini-workflow-yaml'), so pass empty artifactType
|
|
179
|
-
const { content: template, extension } = await this.loadTemplate(workflowTemplateType, '', config, finalTemplateType);
|
|
235
|
+
const workflowTemplateType = config.md_workflow_template || `${templateType}-workflow`;
|
|
236
|
+
const { content: template, extension } = await this.loadTemplate(workflowTemplateType, '', config, 'default-workflow');
|
|
180
237
|
const content = this.renderTemplate(template, artifact);
|
|
181
238
|
const filename = this.generateFilename(artifact, 'workflow', extension);
|
|
182
|
-
|
|
183
|
-
|
|
239
|
+
|
|
240
|
+
if (config.skill_format) {
|
|
241
|
+
await this.writeSkillFile(targetPath, artifact, content);
|
|
242
|
+
} else {
|
|
243
|
+
const filePath = path.join(targetPath, filename);
|
|
244
|
+
await this.writeFile(filePath, content);
|
|
245
|
+
}
|
|
184
246
|
count++;
|
|
185
247
|
}
|
|
186
248
|
}
|
|
@@ -222,8 +284,13 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
|
|
222
284
|
|
|
223
285
|
const content = this.renderTemplate(template, artifact);
|
|
224
286
|
const filename = this.generateFilename(artifact, artifact.type, extension);
|
|
225
|
-
|
|
226
|
-
|
|
287
|
+
|
|
288
|
+
if (config.skill_format) {
|
|
289
|
+
await this.writeSkillFile(targetPath, artifact, content);
|
|
290
|
+
} else {
|
|
291
|
+
const filePath = path.join(targetPath, filename);
|
|
292
|
+
await this.writeFile(filePath, content);
|
|
293
|
+
}
|
|
227
294
|
|
|
228
295
|
if (artifact.type === 'task') {
|
|
229
296
|
taskCount++;
|
|
@@ -390,20 +457,144 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
|
|
|
390
457
|
// No default
|
|
391
458
|
}
|
|
392
459
|
|
|
393
|
-
|
|
460
|
+
// Replace _bmad placeholder with actual folder name BEFORE inserting paths,
|
|
461
|
+
// so that paths containing '_bmad' are not corrupted by the blanket replacement.
|
|
462
|
+
let rendered = template.replaceAll('_bmad', this.bmadFolderName);
|
|
463
|
+
|
|
464
|
+
// Replace {{bmadFolderName}} placeholder if present
|
|
465
|
+
rendered = rendered.replaceAll('{{bmadFolderName}}', this.bmadFolderName);
|
|
466
|
+
|
|
467
|
+
rendered = rendered
|
|
394
468
|
.replaceAll('{{name}}', artifact.name || '')
|
|
395
469
|
.replaceAll('{{module}}', artifact.module || 'core')
|
|
396
470
|
.replaceAll('{{path}}', pathToUse)
|
|
397
471
|
.replaceAll('{{description}}', artifact.description || `${artifact.name} ${artifact.type || ''}`)
|
|
398
472
|
.replaceAll('{{workflow_path}}', pathToUse);
|
|
399
473
|
|
|
400
|
-
|
|
401
|
-
|
|
474
|
+
return rendered;
|
|
475
|
+
}
|
|
402
476
|
|
|
403
|
-
|
|
404
|
-
|
|
477
|
+
/**
|
|
478
|
+
* Write artifact as a skill directory with SKILL.md inside.
|
|
479
|
+
* Writes artifact as a skill directory with SKILL.md inside.
|
|
480
|
+
* @param {string} targetPath - Base skills directory
|
|
481
|
+
* @param {Object} artifact - Artifact data
|
|
482
|
+
* @param {string} content - Rendered template content
|
|
483
|
+
*/
|
|
484
|
+
async writeSkillFile(targetPath, artifact, content) {
|
|
485
|
+
const { resolveSkillName } = require('./shared/path-utils');
|
|
405
486
|
|
|
406
|
-
|
|
487
|
+
// Get the skill name (prefers canonicalId, falls back to path-derived) and remove .md
|
|
488
|
+
const flatName = resolveSkillName(artifact);
|
|
489
|
+
const skillName = path.basename(flatName.replace(/\.md$/, ''));
|
|
490
|
+
|
|
491
|
+
if (!skillName) {
|
|
492
|
+
throw new Error(`Cannot derive skill name for artifact: ${artifact.relativePath || JSON.stringify(artifact)}`);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Create skill directory
|
|
496
|
+
const skillDir = path.join(targetPath, skillName);
|
|
497
|
+
await this.ensureDir(skillDir);
|
|
498
|
+
|
|
499
|
+
// Transform content: rewrite frontmatter for skills format
|
|
500
|
+
const skillContent = this.transformToSkillFormat(content, skillName);
|
|
501
|
+
|
|
502
|
+
await this.writeFile(path.join(skillDir, 'SKILL.md'), skillContent);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Transform artifact content to Agent Skills format.
|
|
507
|
+
* Rewrites frontmatter to contain only unquoted name and description.
|
|
508
|
+
* @param {string} content - Original content with YAML frontmatter
|
|
509
|
+
* @param {string} skillName - Skill name (must match directory name)
|
|
510
|
+
* @returns {string} Transformed content
|
|
511
|
+
*/
|
|
512
|
+
transformToSkillFormat(content, skillName) {
|
|
513
|
+
// Normalize line endings
|
|
514
|
+
content = content.replaceAll('\r\n', '\n').replaceAll('\r', '\n');
|
|
515
|
+
|
|
516
|
+
// Parse frontmatter
|
|
517
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
518
|
+
if (!fmMatch) {
|
|
519
|
+
// No frontmatter -- wrap with minimal frontmatter
|
|
520
|
+
const fm = yaml.stringify({ name: skillName, description: skillName }).trimEnd();
|
|
521
|
+
return `---\n${fm}\n---\n\n${content}`;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const frontmatter = fmMatch[1];
|
|
525
|
+
const body = fmMatch[2];
|
|
526
|
+
|
|
527
|
+
// Parse frontmatter with yaml library to extract description
|
|
528
|
+
let description;
|
|
529
|
+
try {
|
|
530
|
+
const parsed = yaml.parse(frontmatter);
|
|
531
|
+
const rawDesc = parsed?.description;
|
|
532
|
+
description = typeof rawDesc === 'string' && rawDesc ? rawDesc : `${skillName} skill`;
|
|
533
|
+
} catch {
|
|
534
|
+
description = `${skillName} skill`;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Build new frontmatter with only name and description, unquoted
|
|
538
|
+
const newFrontmatter = yaml.stringify({ name: skillName, description: String(description) }, { lineWidth: 0 }).trimEnd();
|
|
539
|
+
return `---\n${newFrontmatter}\n---\n${body}`;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Install a custom agent launcher.
|
|
544
|
+
* For skill_format platforms, produces <skillDir>/SKILL.md.
|
|
545
|
+
* For flat platforms, produces a single file in target_dir.
|
|
546
|
+
* @param {string} projectDir - Project directory
|
|
547
|
+
* @param {string} agentName - Agent name (e.g., "fred-commit-poet")
|
|
548
|
+
* @param {string} agentPath - Path to compiled agent (relative to project root)
|
|
549
|
+
* @param {Object} metadata - Agent metadata
|
|
550
|
+
* @returns {Object|null} Info about created file/skill
|
|
551
|
+
*/
|
|
552
|
+
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
|
553
|
+
if (!this.installerConfig?.target_dir) return null;
|
|
554
|
+
|
|
555
|
+
const { customAgentDashName } = require('./shared/path-utils');
|
|
556
|
+
const targetPath = path.join(projectDir, this.installerConfig.target_dir);
|
|
557
|
+
await this.ensureDir(targetPath);
|
|
558
|
+
|
|
559
|
+
// Build artifact to reuse existing template rendering.
|
|
560
|
+
// The default-agent template already includes the _bmad/ prefix before {{path}},
|
|
561
|
+
// but agentPath is relative to project root (e.g. "_bmad/custom/agents/fred.md").
|
|
562
|
+
// Strip the bmadFolderName prefix so the template doesn't produce a double path.
|
|
563
|
+
const bmadPrefix = this.bmadFolderName + '/';
|
|
564
|
+
const normalizedPath = agentPath.startsWith(bmadPrefix) ? agentPath.slice(bmadPrefix.length) : agentPath;
|
|
565
|
+
|
|
566
|
+
const artifact = {
|
|
567
|
+
type: 'agent-launcher',
|
|
568
|
+
name: agentName,
|
|
569
|
+
description: metadata?.description || `${agentName} agent`,
|
|
570
|
+
agentPath: normalizedPath,
|
|
571
|
+
relativePath: normalizedPath,
|
|
572
|
+
module: 'custom',
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
const { content: template } = await this.loadTemplate(
|
|
576
|
+
this.installerConfig.template_type || 'default',
|
|
577
|
+
'agent',
|
|
578
|
+
this.installerConfig,
|
|
579
|
+
'default-agent',
|
|
580
|
+
);
|
|
581
|
+
const content = this.renderTemplate(template, artifact);
|
|
582
|
+
|
|
583
|
+
if (this.installerConfig.skill_format) {
|
|
584
|
+
const skillName = customAgentDashName(agentName).replace(/\.md$/, '');
|
|
585
|
+
const skillDir = path.join(targetPath, skillName);
|
|
586
|
+
await this.ensureDir(skillDir);
|
|
587
|
+
const skillContent = this.transformToSkillFormat(content, skillName);
|
|
588
|
+
const skillPath = path.join(skillDir, 'SKILL.md');
|
|
589
|
+
await this.writeFile(skillPath, skillContent);
|
|
590
|
+
return { path: path.relative(projectDir, skillPath), command: `$${skillName}` };
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Flat file output
|
|
594
|
+
const filename = customAgentDashName(agentName);
|
|
595
|
+
const filePath = path.join(targetPath, filename);
|
|
596
|
+
await this.writeFile(filePath, content);
|
|
597
|
+
return { path: path.relative(projectDir, filePath), command: agentName };
|
|
407
598
|
}
|
|
408
599
|
|
|
409
600
|
/**
|
|
@@ -414,10 +605,11 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
|
|
|
414
605
|
* @returns {string} Generated filename
|
|
415
606
|
*/
|
|
416
607
|
generateFilename(artifact, artifactType, extension = '.md') {
|
|
417
|
-
const {
|
|
608
|
+
const { resolveSkillName } = require('./shared/path-utils');
|
|
418
609
|
|
|
419
610
|
// Reuse central logic to ensure consistent naming conventions
|
|
420
|
-
|
|
611
|
+
// Prefers canonicalId from manifest when available, falls back to path-derived name
|
|
612
|
+
const standardName = resolveSkillName(artifact);
|
|
421
613
|
|
|
422
614
|
// Clean up potential double extensions from source files (e.g. .yaml.md, .xml.md -> .md)
|
|
423
615
|
// This handles any extensions that might slip through toDashPath()
|
|
@@ -433,6 +625,80 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
|
|
|
433
625
|
return baseName.replace(/\.md$/, extension);
|
|
434
626
|
}
|
|
435
627
|
|
|
628
|
+
/**
|
|
629
|
+
* Install verbatim skill directories (type: skill entries from skill-manifest.csv).
|
|
630
|
+
* Copies the entire source directory as-is into the IDE skill directory.
|
|
631
|
+
* The source SKILL.md is used directly — no frontmatter transformation or file generation.
|
|
632
|
+
* @param {string} projectDir - Project directory
|
|
633
|
+
* @param {string} bmadDir - BMAD installation directory
|
|
634
|
+
* @param {string} targetPath - Target skills directory
|
|
635
|
+
* @param {Object} config - Installation configuration
|
|
636
|
+
* @returns {Promise<number>} Count of skills installed
|
|
637
|
+
*/
|
|
638
|
+
async installVerbatimSkills(projectDir, bmadDir, targetPath, config) {
|
|
639
|
+
const bmadFolderName = path.basename(bmadDir);
|
|
640
|
+
const bmadPrefix = bmadFolderName + '/';
|
|
641
|
+
const csvPath = path.join(bmadDir, '_config', 'skill-manifest.csv');
|
|
642
|
+
|
|
643
|
+
if (!(await fs.pathExists(csvPath))) return 0;
|
|
644
|
+
|
|
645
|
+
const csvContent = await fs.readFile(csvPath, 'utf8');
|
|
646
|
+
const records = csv.parse(csvContent, {
|
|
647
|
+
columns: true,
|
|
648
|
+
skip_empty_lines: true,
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
let count = 0;
|
|
652
|
+
|
|
653
|
+
for (const record of records) {
|
|
654
|
+
const canonicalId = record.canonicalId;
|
|
655
|
+
if (!canonicalId) continue;
|
|
656
|
+
|
|
657
|
+
// Derive source directory from path column
|
|
658
|
+
// path is like "_bmad/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/SKILL.md"
|
|
659
|
+
// Strip bmadFolderName prefix and join with bmadDir, then get dirname
|
|
660
|
+
const relativePath = record.path.startsWith(bmadPrefix) ? record.path.slice(bmadPrefix.length) : record.path;
|
|
661
|
+
const sourceFile = path.join(bmadDir, relativePath);
|
|
662
|
+
const sourceDir = path.dirname(sourceFile);
|
|
663
|
+
|
|
664
|
+
if (!(await fs.pathExists(sourceDir))) continue;
|
|
665
|
+
|
|
666
|
+
// Clean target before copy to prevent stale files
|
|
667
|
+
const skillDir = path.join(targetPath, canonicalId);
|
|
668
|
+
await fs.remove(skillDir);
|
|
669
|
+
await fs.ensureDir(skillDir);
|
|
670
|
+
|
|
671
|
+
// Copy all skill files, filtering OS/editor artifacts recursively
|
|
672
|
+
const skipPatterns = new Set(['.DS_Store', 'Thumbs.db', 'desktop.ini']);
|
|
673
|
+
const skipSuffixes = ['~', '.swp', '.swo', '.bak'];
|
|
674
|
+
const filter = (src) => {
|
|
675
|
+
const name = path.basename(src);
|
|
676
|
+
if (src === sourceDir) return true;
|
|
677
|
+
if (skipPatterns.has(name)) return false;
|
|
678
|
+
if (name.startsWith('.') && name !== '.gitkeep') return false;
|
|
679
|
+
if (skipSuffixes.some((s) => name.endsWith(s))) return false;
|
|
680
|
+
return true;
|
|
681
|
+
};
|
|
682
|
+
await fs.copy(sourceDir, skillDir, { filter });
|
|
683
|
+
|
|
684
|
+
count++;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Post-install cleanup: remove _bmad/ directories for skills with install_to_bmad === "false"
|
|
688
|
+
for (const record of records) {
|
|
689
|
+
if (record.install_to_bmad === 'false') {
|
|
690
|
+
const relativePath = record.path.startsWith(bmadPrefix) ? record.path.slice(bmadPrefix.length) : record.path;
|
|
691
|
+
const sourceFile = path.join(bmadDir, relativePath);
|
|
692
|
+
const sourceDir = path.dirname(sourceFile);
|
|
693
|
+
if (await fs.pathExists(sourceDir)) {
|
|
694
|
+
await fs.remove(sourceDir);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
return count;
|
|
700
|
+
}
|
|
701
|
+
|
|
436
702
|
/**
|
|
437
703
|
* Print installation summary
|
|
438
704
|
* @param {Object} results - Installation results
|
|
@@ -445,6 +711,7 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
|
|
|
445
711
|
if (results.workflows > 0) parts.push(`${results.workflows} workflows`);
|
|
446
712
|
if (results.tasks > 0) parts.push(`${results.tasks} tasks`);
|
|
447
713
|
if (results.tools > 0) parts.push(`${results.tools} tools`);
|
|
714
|
+
if (results.skills > 0) parts.push(`${results.skills} skills`);
|
|
448
715
|
await prompts.log.success(`${this.name} configured: ${parts.join(', ')} → ${targetDir}`);
|
|
449
716
|
}
|
|
450
717
|
|
|
@@ -453,6 +720,34 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
|
|
|
453
720
|
* @param {string} projectDir - Project directory
|
|
454
721
|
*/
|
|
455
722
|
async cleanup(projectDir, options = {}) {
|
|
723
|
+
// Migrate legacy target directories (e.g. .opencode/agent → .opencode/agents)
|
|
724
|
+
if (this.installerConfig?.legacy_targets) {
|
|
725
|
+
if (!options.silent) await prompts.log.message(' Migrating legacy directories...');
|
|
726
|
+
for (const legacyDir of this.installerConfig.legacy_targets) {
|
|
727
|
+
if (this.isGlobalPath(legacyDir)) {
|
|
728
|
+
await this.warnGlobalLegacy(legacyDir, options);
|
|
729
|
+
} else {
|
|
730
|
+
await this.cleanupTarget(projectDir, legacyDir, options);
|
|
731
|
+
await this.removeEmptyParents(projectDir, legacyDir);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Strip BMAD markers from copilot-instructions.md if present
|
|
737
|
+
if (this.name === 'github-copilot') {
|
|
738
|
+
await this.cleanupCopilotInstructions(projectDir, options);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Strip BMAD modes from .kilocodemodes if present
|
|
742
|
+
if (this.name === 'kilo') {
|
|
743
|
+
await this.cleanupKiloModes(projectDir, options);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Strip BMAD entries from .rovodev/prompts.yml if present
|
|
747
|
+
if (this.name === 'rovo-dev') {
|
|
748
|
+
await this.cleanupRovoDevPrompts(projectDir, options);
|
|
749
|
+
}
|
|
750
|
+
|
|
456
751
|
// Clean all target directories
|
|
457
752
|
if (this.installerConfig?.targets) {
|
|
458
753
|
const parentDirs = new Set();
|
|
@@ -473,6 +768,41 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
|
|
|
473
768
|
}
|
|
474
769
|
}
|
|
475
770
|
|
|
771
|
+
/**
|
|
772
|
+
* Check if a path is global (starts with ~ or is absolute)
|
|
773
|
+
* @param {string} p - Path to check
|
|
774
|
+
* @returns {boolean}
|
|
775
|
+
*/
|
|
776
|
+
isGlobalPath(p) {
|
|
777
|
+
return p.startsWith('~') || path.isAbsolute(p);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Warn about stale BMAD files in a global legacy directory (never auto-deletes)
|
|
782
|
+
* @param {string} legacyDir - Legacy directory path (may start with ~)
|
|
783
|
+
* @param {Object} options - Options (silent, etc.)
|
|
784
|
+
*/
|
|
785
|
+
async warnGlobalLegacy(legacyDir, options = {}) {
|
|
786
|
+
try {
|
|
787
|
+
const expanded = legacyDir.startsWith('~/')
|
|
788
|
+
? path.join(os.homedir(), legacyDir.slice(2))
|
|
789
|
+
: legacyDir === '~'
|
|
790
|
+
? os.homedir()
|
|
791
|
+
: legacyDir;
|
|
792
|
+
|
|
793
|
+
if (!(await fs.pathExists(expanded))) return;
|
|
794
|
+
|
|
795
|
+
const entries = await fs.readdir(expanded);
|
|
796
|
+
const bmadFiles = entries.filter((e) => typeof e === 'string' && e.startsWith('bmad'));
|
|
797
|
+
|
|
798
|
+
if (bmadFiles.length > 0 && !options.silent) {
|
|
799
|
+
await prompts.log.warn(`Found ${bmadFiles.length} stale BMAD file(s) in ${expanded}. Remove manually: rm ${expanded}/bmad-*`);
|
|
800
|
+
}
|
|
801
|
+
} catch {
|
|
802
|
+
// Errors reading global paths are silently ignored
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
476
806
|
/**
|
|
477
807
|
* Cleanup a specific target directory
|
|
478
808
|
* @param {string} projectDir - Project directory
|
|
@@ -504,7 +834,7 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
|
|
|
504
834
|
if (!entry || typeof entry !== 'string') {
|
|
505
835
|
continue;
|
|
506
836
|
}
|
|
507
|
-
if (entry.startsWith('bmad')) {
|
|
837
|
+
if (entry.startsWith('bmad') && !entry.startsWith('bmad-os-')) {
|
|
508
838
|
const entryPath = path.join(targetPath, entry);
|
|
509
839
|
try {
|
|
510
840
|
await fs.remove(entryPath);
|
|
@@ -532,24 +862,188 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
|
|
|
532
862
|
}
|
|
533
863
|
}
|
|
534
864
|
/**
|
|
535
|
-
*
|
|
865
|
+
* Strip BMAD-owned content from .github/copilot-instructions.md.
|
|
866
|
+
* The old custom installer injected content between <!-- BMAD:START --> and <!-- BMAD:END --> markers.
|
|
867
|
+
* Deletes the file if nothing remains. Restores .bak backup if one exists.
|
|
868
|
+
*/
|
|
869
|
+
async cleanupCopilotInstructions(projectDir, options = {}) {
|
|
870
|
+
const filePath = path.join(projectDir, '.github', 'copilot-instructions.md');
|
|
871
|
+
|
|
872
|
+
if (!(await fs.pathExists(filePath))) return;
|
|
873
|
+
|
|
874
|
+
try {
|
|
875
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
876
|
+
const startIdx = content.indexOf('<!-- BMAD:START -->');
|
|
877
|
+
const endIdx = content.indexOf('<!-- BMAD:END -->');
|
|
878
|
+
|
|
879
|
+
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) return;
|
|
880
|
+
|
|
881
|
+
const cleaned = content.slice(0, startIdx) + content.slice(endIdx + '<!-- BMAD:END -->'.length);
|
|
882
|
+
|
|
883
|
+
if (cleaned.trim().length === 0) {
|
|
884
|
+
await fs.remove(filePath);
|
|
885
|
+
const backupPath = `${filePath}.bak`;
|
|
886
|
+
if (await fs.pathExists(backupPath)) {
|
|
887
|
+
await fs.rename(backupPath, filePath);
|
|
888
|
+
if (!options.silent) await prompts.log.message(' Restored copilot-instructions.md from backup');
|
|
889
|
+
}
|
|
890
|
+
} else {
|
|
891
|
+
await fs.writeFile(filePath, cleaned, 'utf8');
|
|
892
|
+
const backupPath = `${filePath}.bak`;
|
|
893
|
+
if (await fs.pathExists(backupPath)) await fs.remove(backupPath);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
if (!options.silent) await prompts.log.message(' Cleaned BMAD markers from copilot-instructions.md');
|
|
897
|
+
} catch {
|
|
898
|
+
if (!options.silent) await prompts.log.warn(' Warning: Could not clean BMAD markers from copilot-instructions.md');
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
/**
|
|
903
|
+
* Strip BMAD-owned modes from .kilocodemodes.
|
|
904
|
+
* The old custom kilo.js installer added modes with slug starting with 'bmad-'.
|
|
905
|
+
* Parses YAML, filters out BMAD modes, rewrites. Leaves file as-is on parse failure.
|
|
906
|
+
*/
|
|
907
|
+
async cleanupKiloModes(projectDir, options = {}) {
|
|
908
|
+
const kiloModesPath = path.join(projectDir, '.kilocodemodes');
|
|
909
|
+
|
|
910
|
+
if (!(await fs.pathExists(kiloModesPath))) return;
|
|
911
|
+
|
|
912
|
+
const content = await fs.readFile(kiloModesPath, 'utf8');
|
|
913
|
+
|
|
914
|
+
let config;
|
|
915
|
+
try {
|
|
916
|
+
config = yaml.parse(content) || {};
|
|
917
|
+
} catch {
|
|
918
|
+
if (!options.silent) await prompts.log.warn(' Warning: Could not parse .kilocodemodes for cleanup');
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
if (!Array.isArray(config.customModes)) return;
|
|
923
|
+
|
|
924
|
+
const originalCount = config.customModes.length;
|
|
925
|
+
config.customModes = config.customModes.filter((mode) => mode && (!mode.slug || !mode.slug.startsWith('bmad-')));
|
|
926
|
+
const removedCount = originalCount - config.customModes.length;
|
|
927
|
+
|
|
928
|
+
if (removedCount > 0) {
|
|
929
|
+
try {
|
|
930
|
+
await fs.writeFile(kiloModesPath, yaml.stringify(config, { lineWidth: 0 }));
|
|
931
|
+
if (!options.silent) await prompts.log.message(` Removed ${removedCount} BMAD modes from .kilocodemodes`);
|
|
932
|
+
} catch {
|
|
933
|
+
if (!options.silent) await prompts.log.warn(' Warning: Could not write .kilocodemodes during cleanup');
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Strip BMAD-owned entries from .rovodev/prompts.yml.
|
|
940
|
+
* The old custom rovodev.js installer registered workflows in prompts.yml.
|
|
941
|
+
* Parses YAML, filters out entries with name starting with 'bmad-', rewrites.
|
|
942
|
+
* Removes the file if no entries remain.
|
|
943
|
+
*/
|
|
944
|
+
async cleanupRovoDevPrompts(projectDir, options = {}) {
|
|
945
|
+
const promptsPath = path.join(projectDir, '.rovodev', 'prompts.yml');
|
|
946
|
+
|
|
947
|
+
if (!(await fs.pathExists(promptsPath))) return;
|
|
948
|
+
|
|
949
|
+
const content = await fs.readFile(promptsPath, 'utf8');
|
|
950
|
+
|
|
951
|
+
let config;
|
|
952
|
+
try {
|
|
953
|
+
config = yaml.parse(content) || {};
|
|
954
|
+
} catch {
|
|
955
|
+
if (!options.silent) await prompts.log.warn(' Warning: Could not parse prompts.yml for cleanup');
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
if (!Array.isArray(config.prompts)) return;
|
|
960
|
+
|
|
961
|
+
const originalCount = config.prompts.length;
|
|
962
|
+
config.prompts = config.prompts.filter((entry) => entry && (!entry.name || !entry.name.startsWith('bmad-')));
|
|
963
|
+
const removedCount = originalCount - config.prompts.length;
|
|
964
|
+
|
|
965
|
+
if (removedCount > 0) {
|
|
966
|
+
try {
|
|
967
|
+
if (config.prompts.length === 0) {
|
|
968
|
+
await fs.remove(promptsPath);
|
|
969
|
+
} else {
|
|
970
|
+
await fs.writeFile(promptsPath, yaml.stringify(config, { lineWidth: 0 }));
|
|
971
|
+
}
|
|
972
|
+
if (!options.silent) await prompts.log.message(` Removed ${removedCount} BMAD entries from prompts.yml`);
|
|
973
|
+
} catch {
|
|
974
|
+
if (!options.silent) await prompts.log.warn(' Warning: Could not write prompts.yml during cleanup');
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* Check ancestor directories for existing BMAD files in the same target_dir.
|
|
981
|
+
* IDEs like Claude Code inherit commands from parent directories, so an existing
|
|
982
|
+
* installation in an ancestor would cause duplicate commands.
|
|
983
|
+
* @param {string} projectDir - Project directory being installed to
|
|
984
|
+
* @returns {Promise<string|null>} Path to conflicting directory, or null if clean
|
|
985
|
+
*/
|
|
986
|
+
async findAncestorConflict(projectDir) {
|
|
987
|
+
const targetDir = this.installerConfig?.target_dir;
|
|
988
|
+
if (!targetDir) return null;
|
|
989
|
+
|
|
990
|
+
const resolvedProject = await fs.realpath(path.resolve(projectDir));
|
|
991
|
+
let current = path.dirname(resolvedProject);
|
|
992
|
+
const root = path.parse(current).root;
|
|
993
|
+
|
|
994
|
+
while (current !== root && current.length > root.length) {
|
|
995
|
+
const candidatePath = path.join(current, targetDir);
|
|
996
|
+
try {
|
|
997
|
+
if (await fs.pathExists(candidatePath)) {
|
|
998
|
+
const entries = await fs.readdir(candidatePath);
|
|
999
|
+
const hasBmad = entries.some(
|
|
1000
|
+
(e) => typeof e === 'string' && e.toLowerCase().startsWith('bmad') && !e.toLowerCase().startsWith('bmad-os-'),
|
|
1001
|
+
);
|
|
1002
|
+
if (hasBmad) {
|
|
1003
|
+
return candidatePath;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
} catch {
|
|
1007
|
+
// Can't read directory — skip
|
|
1008
|
+
}
|
|
1009
|
+
current = path.dirname(current);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
return null;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/**
|
|
1016
|
+
* Walk up ancestor directories from relativeDir toward projectDir, removing each if empty
|
|
536
1017
|
* Stops at projectDir boundary — never removes projectDir itself
|
|
537
1018
|
* @param {string} projectDir - Project root (boundary)
|
|
538
1019
|
* @param {string} relativeDir - Relative directory to start from
|
|
539
1020
|
*/
|
|
540
1021
|
async removeEmptyParents(projectDir, relativeDir) {
|
|
1022
|
+
const resolvedProject = path.resolve(projectDir);
|
|
541
1023
|
let current = relativeDir;
|
|
542
1024
|
let last = null;
|
|
543
1025
|
while (current && current !== '.' && current !== last) {
|
|
544
1026
|
last = current;
|
|
545
|
-
const fullPath = path.
|
|
1027
|
+
const fullPath = path.resolve(projectDir, current);
|
|
1028
|
+
// Boundary guard: never traverse outside projectDir
|
|
1029
|
+
if (!fullPath.startsWith(resolvedProject + path.sep) && fullPath !== resolvedProject) break;
|
|
546
1030
|
try {
|
|
547
|
-
if (!(await fs.pathExists(fullPath)))
|
|
1031
|
+
if (!(await fs.pathExists(fullPath))) {
|
|
1032
|
+
// Dir already gone — advance current; last is reset at top of next iteration
|
|
1033
|
+
current = path.dirname(current);
|
|
1034
|
+
continue;
|
|
1035
|
+
}
|
|
548
1036
|
const remaining = await fs.readdir(fullPath);
|
|
549
1037
|
if (remaining.length > 0) break;
|
|
550
1038
|
await fs.rmdir(fullPath);
|
|
551
|
-
} catch {
|
|
552
|
-
|
|
1039
|
+
} catch (error) {
|
|
1040
|
+
// ENOTEMPTY: TOCTOU race (file added between readdir and rmdir) — skip level, continue upward
|
|
1041
|
+
// ENOENT: dir removed by another process between pathExists and rmdir — skip level, continue upward
|
|
1042
|
+
if (error.code === 'ENOTEMPTY' || error.code === 'ENOENT') {
|
|
1043
|
+
current = path.dirname(current);
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
1046
|
+
break; // fatal error (e.g. EACCES) — stop upward walk
|
|
553
1047
|
}
|
|
554
1048
|
current = path.dirname(current);
|
|
555
1049
|
}
|