drt-method 1.0.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/AGENTS.md +45 -0
- package/GETTING-STARTED.md +269 -0
- package/README.md +233 -0
- package/drt-method-1.0.0.tgz +0 -0
- package/drt-modules.yaml +1 -0
- package/package.json +51 -0
- package/scripts/install-from-github.ps1 +22 -0
- package/scripts/install-from-github.sh +17 -0
- package/src/core-skills/drt-advanced-elicitation/SKILL.md +142 -0
- package/src/core-skills/drt-advanced-elicitation/methods.csv +72 -0
- package/src/core-skills/drt-brainstorming/SKILL.md +80 -0
- package/src/core-skills/drt-brainstorming/assets/brain-icons.json +166 -0
- package/src/core-skills/drt-brainstorming/assets/brain-methods.csv +109 -0
- package/src/core-skills/drt-brainstorming/assets/brain-selector.html +326 -0
- package/src/core-skills/drt-brainstorming/customize.toml +84 -0
- package/src/core-skills/drt-brainstorming/references/converge.md +24 -0
- package/src/core-skills/drt-brainstorming/references/finalize.md +26 -0
- package/src/core-skills/drt-brainstorming/references/headless.md +54 -0
- package/src/core-skills/drt-brainstorming/references/in-chat-techniques.md +18 -0
- package/src/core-skills/drt-brainstorming/references/mode-autonomous.md +10 -0
- package/src/core-skills/drt-brainstorming/references/mode-facilitator.md +11 -0
- package/src/core-skills/drt-brainstorming/references/mode-partner.md +16 -0
- package/src/core-skills/drt-brainstorming/references/resume.md +5 -0
- package/src/core-skills/drt-brainstorming/scripts/brain.py +740 -0
- package/src/core-skills/drt-customize/SKILL.md +111 -0
- package/src/core-skills/drt-customize/scripts/list_customizable_skills.py +231 -0
- package/src/core-skills/drt-editorial-review-prose/SKILL.md +86 -0
- package/src/core-skills/drt-editorial-review-structure/SKILL.md +179 -0
- package/src/core-skills/drt-forge-idea/SKILL.md +107 -0
- package/src/core-skills/drt-forge-idea/customize.toml +42 -0
- package/src/core-skills/drt-forge-idea/scripts/resolve_personas.py +270 -0
- package/src/core-skills/drt-help/SKILL.md +75 -0
- package/src/core-skills/drt-index-docs/SKILL.md +66 -0
- package/src/core-skills/drt-party-mode/SKILL.md +60 -0
- package/src/core-skills/drt-party-mode/customize.toml +211 -0
- package/src/core-skills/drt-party-mode/references/create-party.md +70 -0
- package/src/core-skills/drt-party-mode/references/mode-agent-team.md +13 -0
- package/src/core-skills/drt-party-mode/references/mode-auto.md +13 -0
- package/src/core-skills/drt-party-mode/references/mode-subagent.md +31 -0
- package/src/core-skills/drt-party-mode/references/party-memory.md +51 -0
- package/src/core-skills/drt-party-mode/scripts/resolve_party.py +272 -0
- package/src/core-skills/drt-review-adversarial-general/SKILL.md +37 -0
- package/src/core-skills/drt-review-edge-case-hunter/SKILL.md +73 -0
- package/src/core-skills/drt-review-edge-case-hunter/references/deletion-check.md +14 -0
- package/src/core-skills/drt-review-verification-gap/SKILL.md +106 -0
- package/src/core-skills/drt-shard-doc/SKILL.md +105 -0
- package/src/core-skills/drt-spec/SKILL.md +160 -0
- package/src/core-skills/drt-spec/assets/headless-schemas.md +33 -0
- package/src/core-skills/drt-spec/assets/spec-template.md +49 -0
- package/src/core-skills/drt-spec/assets/stories-schema.md +44 -0
- package/src/core-skills/drt-spec/customize.toml +53 -0
- package/src/core-skills/module-help.csv +14 -0
- package/src/core-skills/module.yaml +33 -0
- package/src/drt-skills/1-analysis/drt-agent-analyst/SKILL.md +76 -0
- package/src/drt-skills/1-analysis/drt-agent-analyst/customize.toml +90 -0
- package/src/drt-skills/1-analysis/drt-agent-tech-writer/SKILL.md +76 -0
- package/src/drt-skills/1-analysis/drt-agent-tech-writer/customize.toml +81 -0
- package/src/drt-skills/1-analysis/drt-agent-tech-writer/explain-concept.md +20 -0
- package/src/drt-skills/1-analysis/drt-agent-tech-writer/mermaid-gen.md +20 -0
- package/src/drt-skills/1-analysis/drt-agent-tech-writer/validate-doc.md +19 -0
- package/src/drt-skills/1-analysis/drt-agent-tech-writer/write-document.md +20 -0
- package/src/drt-skills/1-analysis/drt-document-project/SKILL.md +62 -0
- package/src/drt-skills/1-analysis/drt-document-project/checklist.md +245 -0
- package/src/drt-skills/1-analysis/drt-document-project/customize.toml +41 -0
- package/src/drt-skills/1-analysis/drt-document-project/documentation-requirements.csv +12 -0
- package/src/drt-skills/1-analysis/drt-document-project/instructions.md +128 -0
- package/src/drt-skills/1-analysis/drt-document-project/templates/deep-dive-template.md +345 -0
- package/src/drt-skills/1-analysis/drt-document-project/templates/index-template.md +169 -0
- package/src/drt-skills/1-analysis/drt-document-project/templates/project-overview-template.md +103 -0
- package/src/drt-skills/1-analysis/drt-document-project/templates/project-scan-report-schema.json +160 -0
- package/src/drt-skills/1-analysis/drt-document-project/templates/source-tree-template.md +135 -0
- package/src/drt-skills/1-analysis/drt-document-project/workflows/deep-dive-instructions.md +300 -0
- package/src/drt-skills/1-analysis/drt-document-project/workflows/deep-dive-workflow.md +34 -0
- package/src/drt-skills/1-analysis/drt-document-project/workflows/full-scan-instructions.md +1111 -0
- package/src/drt-skills/1-analysis/drt-document-project/workflows/full-scan-workflow.md +34 -0
- package/src/drt-skills/1-analysis/drt-prfaq/SKILL.md +135 -0
- package/src/drt-skills/1-analysis/drt-prfaq/agents/artifact-analyzer.md +60 -0
- package/src/drt-skills/1-analysis/drt-prfaq/agents/web-researcher.md +49 -0
- package/src/drt-skills/1-analysis/drt-prfaq/assets/prfaq-template.md +62 -0
- package/src/drt-skills/1-analysis/drt-prfaq/bmad-manifest.json +16 -0
- package/src/drt-skills/1-analysis/drt-prfaq/customize.toml +41 -0
- package/src/drt-skills/1-analysis/drt-prfaq/references/customer-faq.md +55 -0
- package/src/drt-skills/1-analysis/drt-prfaq/references/internal-faq.md +51 -0
- package/src/drt-skills/1-analysis/drt-prfaq/references/press-release.md +60 -0
- package/src/drt-skills/1-analysis/drt-prfaq/references/verdict.md +83 -0
- package/src/drt-skills/1-analysis/drt-product-brief/SKILL.md +91 -0
- package/src/drt-skills/1-analysis/drt-product-brief/assets/brief-template.md +41 -0
- package/src/drt-skills/1-analysis/drt-product-brief/customize.toml +99 -0
- package/src/drt-skills/1-analysis/research/drt-domain-research/SKILL.md +96 -0
- package/src/drt-skills/1-analysis/research/drt-domain-research/customize.toml +41 -0
- package/src/drt-skills/1-analysis/research/drt-domain-research/domain-steps/step-01-init.md +137 -0
- package/src/drt-skills/1-analysis/research/drt-domain-research/domain-steps/step-02-domain-analysis.md +229 -0
- package/src/drt-skills/1-analysis/research/drt-domain-research/domain-steps/step-03-competitive-landscape.md +238 -0
- package/src/drt-skills/1-analysis/research/drt-domain-research/domain-steps/step-04-regulatory-focus.md +206 -0
- package/src/drt-skills/1-analysis/research/drt-domain-research/domain-steps/step-05-technical-trends.md +234 -0
- package/src/drt-skills/1-analysis/research/drt-domain-research/domain-steps/step-06-research-synthesis.md +450 -0
- package/src/drt-skills/1-analysis/research/drt-domain-research/research.template.md +29 -0
- package/src/drt-skills/1-analysis/research/drt-market-research/SKILL.md +96 -0
- package/src/drt-skills/1-analysis/research/drt-market-research/customize.toml +41 -0
- package/src/drt-skills/1-analysis/research/drt-market-research/research.template.md +29 -0
- package/src/drt-skills/1-analysis/research/drt-market-research/steps/step-01-init.md +184 -0
- package/src/drt-skills/1-analysis/research/drt-market-research/steps/step-02-customer-behavior.md +239 -0
- package/src/drt-skills/1-analysis/research/drt-market-research/steps/step-03-customer-pain-points.md +251 -0
- package/src/drt-skills/1-analysis/research/drt-market-research/steps/step-04-customer-decisions.md +261 -0
- package/src/drt-skills/1-analysis/research/drt-market-research/steps/step-05-competitive-analysis.md +173 -0
- package/src/drt-skills/1-analysis/research/drt-market-research/steps/step-06-research-completion.md +484 -0
- package/src/drt-skills/1-analysis/research/drt-technical-research/SKILL.md +96 -0
- package/src/drt-skills/1-analysis/research/drt-technical-research/customize.toml +41 -0
- package/src/drt-skills/1-analysis/research/drt-technical-research/research.template.md +29 -0
- package/src/drt-skills/1-analysis/research/drt-technical-research/technical-steps/step-01-init.md +137 -0
- package/src/drt-skills/1-analysis/research/drt-technical-research/technical-steps/step-02-technical-overview.md +239 -0
- package/src/drt-skills/1-analysis/research/drt-technical-research/technical-steps/step-03-integration-patterns.md +248 -0
- package/src/drt-skills/1-analysis/research/drt-technical-research/technical-steps/step-04-architectural-patterns.md +202 -0
- package/src/drt-skills/1-analysis/research/drt-technical-research/technical-steps/step-05-implementation-research.md +233 -0
- package/src/drt-skills/1-analysis/research/drt-technical-research/technical-steps/step-06-research-synthesis.md +493 -0
- package/src/drt-skills/2-plan-workflows/drt-agent-pm/SKILL.md +76 -0
- package/src/drt-skills/2-plan-workflows/drt-agent-pm/customize.toml +75 -0
- package/src/drt-skills/2-plan-workflows/drt-agent-ux-designer/SKILL.md +76 -0
- package/src/drt-skills/2-plan-workflows/drt-agent-ux-designer/customize.toml +60 -0
- package/src/drt-skills/2-plan-workflows/drt-create-prd/SKILL.md +30 -0
- package/src/drt-skills/2-plan-workflows/drt-create-prd/customize.toml +41 -0
- package/src/drt-skills/2-plan-workflows/drt-edit-prd/SKILL.md +30 -0
- package/src/drt-skills/2-plan-workflows/drt-edit-prd/customize.toml +42 -0
- package/src/drt-skills/2-plan-workflows/drt-prd/SKILL.md +92 -0
- package/src/drt-skills/2-plan-workflows/drt-prd/assets/headless-schemas.md +76 -0
- package/src/drt-skills/2-plan-workflows/drt-prd/assets/prd-template.md +165 -0
- package/src/drt-skills/2-plan-workflows/drt-prd/assets/prd-validation-checklist.md +135 -0
- package/src/drt-skills/2-plan-workflows/drt-prd/assets/validation-report-template.html +325 -0
- package/src/drt-skills/2-plan-workflows/drt-prd/customize.toml +147 -0
- package/src/drt-skills/2-plan-workflows/drt-prd/references/headless.md +39 -0
- package/src/drt-skills/2-plan-workflows/drt-prd/references/validate.md +97 -0
- package/src/drt-skills/2-plan-workflows/drt-ux/SKILL.md +90 -0
- package/src/drt-skills/2-plan-workflows/drt-ux/assets/color-themes.md +9 -0
- package/src/drt-skills/2-plan-workflows/drt-ux/assets/design-directions.md +9 -0
- package/src/drt-skills/2-plan-workflows/drt-ux/assets/design-example-editorial.md +158 -0
- package/src/drt-skills/2-plan-workflows/drt-ux/assets/design-example-mobile.md +93 -0
- package/src/drt-skills/2-plan-workflows/drt-ux/assets/design-example-shadcn.md +109 -0
- package/src/drt-skills/2-plan-workflows/drt-ux/assets/excalidraw-wireframe.md +19 -0
- package/src/drt-skills/2-plan-workflows/drt-ux/assets/experience-example-mobile.md +112 -0
- package/src/drt-skills/2-plan-workflows/drt-ux/assets/experience-example-shadcn.md +133 -0
- package/src/drt-skills/2-plan-workflows/drt-ux/assets/headless-schemas.md +84 -0
- package/src/drt-skills/2-plan-workflows/drt-ux/assets/key-screens.md +29 -0
- package/src/drt-skills/2-plan-workflows/drt-ux/assets/validation-report-template.html +319 -0
- package/src/drt-skills/2-plan-workflows/drt-ux/customize.toml +100 -0
- package/src/drt-skills/2-plan-workflows/drt-ux/references/creative-tools.md +19 -0
- package/src/drt-skills/2-plan-workflows/drt-ux/references/design-md-spec.md +50 -0
- package/src/drt-skills/2-plan-workflows/drt-ux/references/headless.md +37 -0
- package/src/drt-skills/2-plan-workflows/drt-ux/references/validate.md +115 -0
- package/src/drt-skills/2-plan-workflows/drt-validate-prd/SKILL.md +30 -0
- package/src/drt-skills/2-plan-workflows/drt-validate-prd/customize.toml +42 -0
- package/src/drt-skills/3-solutioning/drt-agent-architect/SKILL.md +76 -0
- package/src/drt-skills/3-solutioning/drt-agent-architect/customize.toml +65 -0
- package/src/drt-skills/3-solutioning/drt-architecture/SKILL.md +85 -0
- package/src/drt-skills/3-solutioning/drt-architecture/assets/spine-template.md +79 -0
- package/src/drt-skills/3-solutioning/drt-architecture/customize.toml +100 -0
- package/src/drt-skills/3-solutioning/drt-architecture/references/headless.md +26 -0
- package/src/drt-skills/3-solutioning/drt-architecture/references/reviewer-gate.md +13 -0
- package/src/drt-skills/3-solutioning/drt-architecture/scripts/lint_spine.py +257 -0
- package/src/drt-skills/3-solutioning/drt-check-implementation-readiness/SKILL.md +91 -0
- package/src/drt-skills/3-solutioning/drt-check-implementation-readiness/customize.toml +41 -0
- package/src/drt-skills/3-solutioning/drt-check-implementation-readiness/steps/step-01-document-discovery.md +179 -0
- package/src/drt-skills/3-solutioning/drt-check-implementation-readiness/steps/step-02-prd-analysis.md +168 -0
- package/src/drt-skills/3-solutioning/drt-check-implementation-readiness/steps/step-03-epic-coverage-validation.md +169 -0
- package/src/drt-skills/3-solutioning/drt-check-implementation-readiness/steps/step-04-ux-alignment.md +129 -0
- package/src/drt-skills/3-solutioning/drt-check-implementation-readiness/steps/step-05-epic-quality-review.md +241 -0
- package/src/drt-skills/3-solutioning/drt-check-implementation-readiness/steps/step-06-final-assessment.md +132 -0
- package/src/drt-skills/3-solutioning/drt-check-implementation-readiness/templates/readiness-report-template.md +4 -0
- package/src/drt-skills/3-solutioning/drt-create-architecture/SKILL.md +30 -0
- package/src/drt-skills/3-solutioning/drt-create-architecture/customize.toml +41 -0
- package/src/drt-skills/3-solutioning/drt-create-epics-and-stories/SKILL.md +93 -0
- package/src/drt-skills/3-solutioning/drt-create-epics-and-stories/customize.toml +41 -0
- package/src/drt-skills/3-solutioning/drt-create-epics-and-stories/steps/step-01-validate-prerequisites.md +263 -0
- package/src/drt-skills/3-solutioning/drt-create-epics-and-stories/steps/step-02-design-epics.md +242 -0
- package/src/drt-skills/3-solutioning/drt-create-epics-and-stories/steps/step-03-create-stories.md +255 -0
- package/src/drt-skills/3-solutioning/drt-create-epics-and-stories/steps/step-04-final-validation.md +143 -0
- package/src/drt-skills/3-solutioning/drt-create-epics-and-stories/templates/epics-template.md +61 -0
- package/src/drt-skills/3-solutioning/drt-generate-project-context/SKILL.md +81 -0
- package/src/drt-skills/3-solutioning/drt-generate-project-context/customize.toml +41 -0
- package/src/drt-skills/3-solutioning/drt-generate-project-context/project-context-template.md +21 -0
- package/src/drt-skills/3-solutioning/drt-generate-project-context/steps/step-01-discover.md +186 -0
- package/src/drt-skills/3-solutioning/drt-generate-project-context/steps/step-02-generate.md +321 -0
- package/src/drt-skills/3-solutioning/drt-generate-project-context/steps/step-03-complete.md +284 -0
- package/src/drt-skills/4-implementation/drt-agent-dev/SKILL.md +76 -0
- package/src/drt-skills/4-implementation/drt-agent-dev/customize.toml +95 -0
- package/src/drt-skills/4-implementation/drt-checkpoint-preview/SKILL.md +68 -0
- package/src/drt-skills/4-implementation/drt-checkpoint-preview/customize.toml +41 -0
- package/src/drt-skills/4-implementation/drt-checkpoint-preview/generate-trail.md +38 -0
- package/src/drt-skills/4-implementation/drt-checkpoint-preview/step-01-orientation.md +105 -0
- package/src/drt-skills/4-implementation/drt-checkpoint-preview/step-02-walkthrough.md +89 -0
- package/src/drt-skills/4-implementation/drt-checkpoint-preview/step-03-detail-pass.md +106 -0
- package/src/drt-skills/4-implementation/drt-checkpoint-preview/step-04-testing.md +74 -0
- package/src/drt-skills/4-implementation/drt-checkpoint-preview/step-05-wrapup.md +30 -0
- package/src/drt-skills/4-implementation/drt-code-review/SKILL.md +90 -0
- package/src/drt-skills/4-implementation/drt-code-review/customize.toml +93 -0
- package/src/drt-skills/4-implementation/drt-code-review/steps/step-01-gather-context.md +84 -0
- package/src/drt-skills/4-implementation/drt-code-review/steps/step-02-review.md +32 -0
- package/src/drt-skills/4-implementation/drt-code-review/steps/step-03-triage.md +50 -0
- package/src/drt-skills/4-implementation/drt-code-review/steps/step-04-present.md +132 -0
- package/src/drt-skills/4-implementation/drt-correct-course/SKILL.md +301 -0
- package/src/drt-skills/4-implementation/drt-correct-course/checklist.md +288 -0
- package/src/drt-skills/4-implementation/drt-correct-course/customize.toml +41 -0
- package/src/drt-skills/4-implementation/drt-create-story/SKILL.md +432 -0
- package/src/drt-skills/4-implementation/drt-create-story/checklist.md +357 -0
- package/src/drt-skills/4-implementation/drt-create-story/customize.toml +41 -0
- package/src/drt-skills/4-implementation/drt-create-story/discover-inputs.md +88 -0
- package/src/drt-skills/4-implementation/drt-create-story/template.md +49 -0
- package/src/drt-skills/4-implementation/drt-dev-auto/SKILL.md +123 -0
- package/src/drt-skills/4-implementation/drt-dev-auto/compile-epic-context.md +62 -0
- package/src/drt-skills/4-implementation/drt-dev-auto/customize.toml +108 -0
- package/src/drt-skills/4-implementation/drt-dev-auto/spec-template.md +98 -0
- package/src/drt-skills/4-implementation/drt-dev-auto/step-01-clarify-and-route.md +84 -0
- package/src/drt-skills/4-implementation/drt-dev-auto/step-02-plan.md +31 -0
- package/src/drt-skills/4-implementation/drt-dev-auto/step-03-implement.md +42 -0
- package/src/drt-skills/4-implementation/drt-dev-auto/step-04-review.md +95 -0
- package/src/drt-skills/4-implementation/drt-dev-story/SKILL.md +500 -0
- package/src/drt-skills/4-implementation/drt-dev-story/checklist.md +80 -0
- package/src/drt-skills/4-implementation/drt-dev-story/customize.toml +41 -0
- package/src/drt-skills/4-implementation/drt-implement-task/SKILL.md +101 -0
- package/src/drt-skills/4-implementation/drt-implement-task/assets/task-spec-template.md +60 -0
- package/src/drt-skills/4-implementation/drt-implement-task/customize.toml +17 -0
- package/src/drt-skills/4-implementation/drt-implement-task/steps/step-01-gather-intent.md +42 -0
- package/src/drt-skills/4-implementation/drt-implement-task/steps/step-02-plan-and-approve.md +43 -0
- package/src/drt-skills/4-implementation/drt-implement-task/steps/step-03-confirm-dependencies.md +38 -0
- package/src/drt-skills/4-implementation/drt-implement-task/steps/step-04-implement.md +42 -0
- package/src/drt-skills/4-implementation/drt-implement-task/steps/step-05-qc-test.md +31 -0
- package/src/drt-skills/4-implementation/drt-implement-task/steps/step-06-quality-review.md +30 -0
- package/src/drt-skills/4-implementation/drt-implement-task/steps/step-07-fix-findings.md +33 -0
- package/src/drt-skills/4-implementation/drt-implement-task/steps/step-08-complete.md +35 -0
- package/src/drt-skills/4-implementation/drt-qa-generate-e2e-tests/SKILL.md +176 -0
- package/src/drt-skills/4-implementation/drt-qa-generate-e2e-tests/checklist.md +33 -0
- package/src/drt-skills/4-implementation/drt-qa-generate-e2e-tests/customize.toml +41 -0
- package/src/drt-skills/4-implementation/drt-quick-dev/SKILL.md +13 -0
- package/src/drt-skills/4-implementation/drt-quick-dev/compile-epic-context.md +62 -0
- package/src/drt-skills/4-implementation/drt-quick-dev/customize.toml +83 -0
- package/src/drt-skills/4-implementation/drt-quick-dev/render.js +206 -0
- package/src/drt-skills/4-implementation/drt-quick-dev/spec-template.md +89 -0
- package/src/drt-skills/4-implementation/drt-quick-dev/step-01-clarify-and-route.md +103 -0
- package/src/drt-skills/4-implementation/drt-quick-dev/step-02-plan.md +47 -0
- package/src/drt-skills/4-implementation/drt-quick-dev/step-03-implement.md +45 -0
- package/src/drt-skills/4-implementation/drt-quick-dev/step-04-review.md +55 -0
- package/src/drt-skills/4-implementation/drt-quick-dev/step-05-present.md +78 -0
- package/src/drt-skills/4-implementation/drt-quick-dev/step-oneshot.md +78 -0
- package/src/drt-skills/4-implementation/drt-quick-dev/sync-sprint-status.md +19 -0
- package/src/drt-skills/4-implementation/drt-quick-dev/workflow.md +85 -0
- package/src/drt-skills/4-implementation/drt-retrospective/SKILL.md +1527 -0
- package/src/drt-skills/4-implementation/drt-retrospective/customize.toml +41 -0
- package/src/drt-skills/4-implementation/drt-sprint-planning/SKILL.md +319 -0
- package/src/drt-skills/4-implementation/drt-sprint-planning/checklist.md +34 -0
- package/src/drt-skills/4-implementation/drt-sprint-planning/customize.toml +41 -0
- package/src/drt-skills/4-implementation/drt-sprint-planning/sprint-status-template.yaml +69 -0
- package/src/drt-skills/4-implementation/drt-sprint-status/SKILL.md +311 -0
- package/src/drt-skills/4-implementation/drt-sprint-status/customize.toml +41 -0
- package/src/drt-skills/module-help.csv +32 -0
- package/src/drt-skills/module.yaml +95 -0
- package/src/scripts/lib/load-toml.js +43 -0
- package/src/scripts/lib/merge-utils.js +89 -0
- package/src/scripts/memlog.js +184 -0
- package/src/scripts/package.json +7 -0
- package/src/scripts/resolve-config.js +56 -0
- package/src/scripts/resolve-customization.js +77 -0
- package/tools/installer/cli-utils.js +53 -0
- package/tools/installer/commands/install.js +149 -0
- package/tools/installer/commands/status.js +65 -0
- package/tools/installer/commands/uninstall.js +167 -0
- package/tools/installer/core/config.js +73 -0
- package/tools/installer/core/existing-install.js +121 -0
- package/tools/installer/core/install-paths.js +132 -0
- package/tools/installer/core/installer.js +1762 -0
- package/tools/installer/core/legacy-warnings.js +151 -0
- package/tools/installer/core/manifest-generator.js +859 -0
- package/tools/installer/core/manifest.js +433 -0
- package/tools/installer/core/uv-check.js +97 -0
- package/tools/installer/core/wsl-node-check.js +109 -0
- package/tools/installer/drt-cli.js +104 -0
- package/tools/installer/file-ops.js +204 -0
- package/tools/installer/fs-native.js +116 -0
- package/tools/installer/ide/_config-driven.js +972 -0
- package/tools/installer/ide/manager.js +324 -0
- package/tools/installer/ide/platform-codes.js +80 -0
- package/tools/installer/ide/platform-codes.yaml +343 -0
- package/tools/installer/ide/shared/installed-skills.js +50 -0
- package/tools/installer/ide/shared/path-utils.js +217 -0
- package/tools/installer/ide/shared/skill-manifest.js +57 -0
- package/tools/installer/install-messages.yaml +29 -0
- package/tools/installer/list-options.js +210 -0
- package/tools/installer/message-loader.js +83 -0
- package/tools/installer/modules/channel-plan.js +203 -0
- package/tools/installer/modules/channel-resolver.js +241 -0
- package/tools/installer/modules/custom-module-manager.js +918 -0
- package/tools/installer/modules/external-manager.js +667 -0
- package/tools/installer/modules/module-help-schema.js +13 -0
- package/tools/installer/modules/official-modules.js +2257 -0
- package/tools/installer/modules/plugin-resolver.js +398 -0
- package/tools/installer/modules/version-resolver.js +336 -0
- package/tools/installer/project-root.js +224 -0
- package/tools/installer/prompts.js +852 -0
- package/tools/installer/set-overrides.js +330 -0
- package/tools/installer/ui.js +2090 -0
- package/tools/installer/yaml-format.js +245 -0
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
const fs = require('../fs-native');
|
|
2
|
+
const os = require('node:os');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { execSync } = require('node:child_process');
|
|
5
|
+
const yaml = require('yaml');
|
|
6
|
+
const prompts = require('../prompts');
|
|
7
|
+
const { resolveChannel, tagExists, parseGitHubRepo } = require('./channel-resolver');
|
|
8
|
+
const { decideChannelForModule } = require('./channel-plan');
|
|
9
|
+
const { getProjectRoot } = require('../project-root');
|
|
10
|
+
|
|
11
|
+
const VALID_CHANNELS = new Set(['stable', 'next', 'pinned']);
|
|
12
|
+
|
|
13
|
+
function normalizeChannelName(raw) {
|
|
14
|
+
if (typeof raw !== 'string') return null;
|
|
15
|
+
const lower = raw.trim().toLowerCase();
|
|
16
|
+
return VALID_CHANNELS.has(lower) ? lower : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Conservative quoting for tag names passed to git commands. Tags are
|
|
21
|
+
* user-typed (--pin) or come from the GitHub API. Only allow the semver
|
|
22
|
+
* character class we use to tag DRT releases; anything else throws.
|
|
23
|
+
*/
|
|
24
|
+
function quoteShell(ref) {
|
|
25
|
+
if (typeof ref !== 'string' || !/^[\w.\-+/]+$/.test(ref)) {
|
|
26
|
+
throw new Error(`Unsafe ref name: ${JSON.stringify(ref)}`);
|
|
27
|
+
}
|
|
28
|
+
return `"${ref}"`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function readChannelMarker(markerPath) {
|
|
32
|
+
try {
|
|
33
|
+
if (!(await fs.pathExists(markerPath))) return null;
|
|
34
|
+
const content = await fs.readFile(markerPath, 'utf8');
|
|
35
|
+
return JSON.parse(content);
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function writeChannelMarker(markerPath, data) {
|
|
42
|
+
try {
|
|
43
|
+
await fs.writeFile(markerPath, JSON.stringify({ ...data, writtenAt: new Date().toISOString() }, null, 2));
|
|
44
|
+
} catch {
|
|
45
|
+
// Best-effort: marker is an optimization, not a correctness requirement.
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const REGISTRY_CONFIG_PATH = path.join(getProjectRoot(), 'drt-modules.yaml');
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Manages official modules from the bundled registry file. The remote
|
|
53
|
+
* marketplace fetch has been retired; this repo is the single source of truth
|
|
54
|
+
* for which official modules exist and how they are displayed.
|
|
55
|
+
*
|
|
56
|
+
* @class ExternalModuleManager
|
|
57
|
+
*/
|
|
58
|
+
class ExternalModuleManager {
|
|
59
|
+
// moduleCode → { channel, version, ref, sha, repoUrl, resolvedFallback }
|
|
60
|
+
// Populated when cloneExternalModule resolves a channel. Shared across all
|
|
61
|
+
// instances so the manifest writer (which often instantiates a fresh
|
|
62
|
+
// ExternalModuleManager) sees resolutions made during install.
|
|
63
|
+
static _resolutions = new Map();
|
|
64
|
+
|
|
65
|
+
// moduleCode → ResolvedModule (from PluginResolver). Populated for registry
|
|
66
|
+
// modules flagged `marketplace-plugin: true`, whose installable skills live
|
|
67
|
+
// outside a single module.yaml directory and must be resolved from
|
|
68
|
+
// .claude-plugin/marketplace.json. Shared across instances so install() can
|
|
69
|
+
// pick up the resolution computed during findExternalModuleSource.
|
|
70
|
+
static _pluginResolutions = new Map();
|
|
71
|
+
|
|
72
|
+
constructor() {}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get the most recent channel resolution for a module (if any).
|
|
76
|
+
* @param {string} moduleCode
|
|
77
|
+
* @returns {Object|null}
|
|
78
|
+
*/
|
|
79
|
+
getResolution(moduleCode) {
|
|
80
|
+
return ExternalModuleManager._resolutions.get(moduleCode) || null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get the cached marketplace-plugin resolution for a module (if any).
|
|
85
|
+
* @param {string} moduleCode
|
|
86
|
+
* @returns {Object|null} ResolvedModule from PluginResolver, or null
|
|
87
|
+
*/
|
|
88
|
+
getPluginResolution(moduleCode) {
|
|
89
|
+
return ExternalModuleManager._pluginResolutions.get(moduleCode) || null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Load the official modules registry from the bundled YAML file.
|
|
94
|
+
* @returns {Object} Parsed YAML content with modules array
|
|
95
|
+
*/
|
|
96
|
+
async loadExternalModulesConfig() {
|
|
97
|
+
if (this.cachedModules) {
|
|
98
|
+
return this.cachedModules;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const content = await fs.readFile(REGISTRY_CONFIG_PATH, 'utf8');
|
|
103
|
+
const config = yaml.parse(content);
|
|
104
|
+
this.cachedModules = config;
|
|
105
|
+
return config;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
await prompts.log.warn(`Failed to load modules config: ${error.message}`);
|
|
108
|
+
return { modules: [] };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Normalize a module entry from either the remote registry format
|
|
114
|
+
* (snake_case, array) or the legacy bundled format (kebab-case, object map).
|
|
115
|
+
* @param {Object} mod - Raw module config from YAML
|
|
116
|
+
* @param {string} [key] - Key name (only for legacy map format)
|
|
117
|
+
* @returns {Object} Normalized module info
|
|
118
|
+
*/
|
|
119
|
+
_normalizeModule(mod, key) {
|
|
120
|
+
return {
|
|
121
|
+
key: key || mod.name,
|
|
122
|
+
url: mod.repository || mod.url,
|
|
123
|
+
moduleDefinition: mod.module_definition || mod['module-definition'],
|
|
124
|
+
code: mod.code,
|
|
125
|
+
name: mod.display_name || mod.name,
|
|
126
|
+
description: mod.description || '',
|
|
127
|
+
defaultSelected: mod.default_selected === true || mod.defaultSelected === true,
|
|
128
|
+
type: mod.type || 'drt-org',
|
|
129
|
+
npmPackage: mod.npm_package || mod.npmPackage || null,
|
|
130
|
+
pluginName: mod.plugin_name || mod.pluginName || null,
|
|
131
|
+
defaultChannel: normalizeChannelName(mod.default_channel || mod.defaultChannel) || 'stable',
|
|
132
|
+
deprecated: mod.deprecated === true,
|
|
133
|
+
deprecationMessage: mod.deprecation_message || mod['deprecation-message'] || mod.deprecationMessage || null,
|
|
134
|
+
marketplacePlugin: mod.marketplace_plugin === true || mod['marketplace-plugin'] === true || mod.marketplacePlugin === true,
|
|
135
|
+
postInstallMessage: mod.post_install_message || mod['post-install-message'] || mod.postInstallMessage || null,
|
|
136
|
+
builtIn: mod.built_in === true,
|
|
137
|
+
isExternal: mod.built_in !== true,
|
|
138
|
+
// Prior codes this module was registered under (e.g. `drt-loop` was
|
|
139
|
+
// `bauto`). Lets a renamed module keep resolving existing installs
|
|
140
|
+
// instead of orphaning them — see getModuleByCode().
|
|
141
|
+
aliases: Array.isArray(mod.aliases) ? mod.aliases : [],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get list of available modules from the registry
|
|
147
|
+
* @returns {Array<Object>} Array of module info objects
|
|
148
|
+
*/
|
|
149
|
+
async listAvailable() {
|
|
150
|
+
const config = await this.loadExternalModulesConfig();
|
|
151
|
+
|
|
152
|
+
// Remote format: modules is an array
|
|
153
|
+
if (Array.isArray(config.modules)) {
|
|
154
|
+
return config.modules.map((mod) => this._normalizeModule(mod));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Legacy bundled format: modules is an object map
|
|
158
|
+
const modules = [];
|
|
159
|
+
for (const [key, mod] of Object.entries(config.modules || {})) {
|
|
160
|
+
modules.push(this._normalizeModule(mod, key));
|
|
161
|
+
}
|
|
162
|
+
return modules;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get module info by code. Falls back to matching a registry entry's
|
|
167
|
+
* `aliases` list, so a module that was renamed (its `code` changed) still
|
|
168
|
+
* resolves for installs recorded under the prior code.
|
|
169
|
+
* @param {string} code - The module code (e.g., 'cis'), current or aliased
|
|
170
|
+
* @returns {Object|null} Module info or null if not found
|
|
171
|
+
*/
|
|
172
|
+
async getModuleByCode(code) {
|
|
173
|
+
const modules = await this.listAvailable();
|
|
174
|
+
return modules.find((m) => m.code === code) || modules.find((m) => m.aliases.includes(code)) || null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Resolve a possibly-legacy module code to its current canonical code.
|
|
179
|
+
* Returns the input unchanged if it doesn't match any registry entry or
|
|
180
|
+
* alias (e.g. a custom/unknown module).
|
|
181
|
+
* @param {string} code
|
|
182
|
+
* @returns {Promise<string>}
|
|
183
|
+
*/
|
|
184
|
+
async resolveCanonicalCode(code) {
|
|
185
|
+
const info = await this.getModuleByCode(code);
|
|
186
|
+
return info ? info.code : code;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get the cache directory for external modules
|
|
191
|
+
* @returns {string} Path to the external modules cache directory
|
|
192
|
+
*/
|
|
193
|
+
getExternalCacheDir() {
|
|
194
|
+
const cacheDir = path.join(os.homedir(), '.drt', 'cache', 'external-modules');
|
|
195
|
+
return cacheDir;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Clone an external module repository to cache, resolving the requested
|
|
200
|
+
* channel (stable / next / pinned) to a concrete git ref.
|
|
201
|
+
*
|
|
202
|
+
* @param {string} moduleCode - Code of the external module
|
|
203
|
+
* @param {Object} options - Clone options
|
|
204
|
+
* @param {boolean} [options.silent] - Suppress spinner output
|
|
205
|
+
* @param {Object} [options.channelOptions] - Parsed channel flags. See
|
|
206
|
+
* modules/channel-plan.js. When absent, the module installs on its
|
|
207
|
+
* registry-declared default channel (typically 'stable').
|
|
208
|
+
* @returns {string} Path to the cloned repository
|
|
209
|
+
*/
|
|
210
|
+
async cloneExternalModule(moduleCode, options = {}) {
|
|
211
|
+
const moduleInfo = await this.getModuleByCode(moduleCode);
|
|
212
|
+
|
|
213
|
+
if (!moduleInfo) {
|
|
214
|
+
throw new Error(`External module '${moduleCode}' not found in the DRT registry`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Normalize to the canonical code so cache dir, in-memory resolutions,
|
|
218
|
+
// and log/error text stay consistent even when called with a renamed
|
|
219
|
+
// module's prior alias (getModuleByCode resolves aliases above).
|
|
220
|
+
moduleCode = moduleInfo.code;
|
|
221
|
+
|
|
222
|
+
const cacheDir = this.getExternalCacheDir();
|
|
223
|
+
const moduleCacheDir = path.join(cacheDir, moduleCode);
|
|
224
|
+
const silent = options.silent || false;
|
|
225
|
+
|
|
226
|
+
// Create cache directory if it doesn't exist
|
|
227
|
+
await fs.ensureDir(cacheDir);
|
|
228
|
+
|
|
229
|
+
// Helper to create a spinner or a no-op when silent
|
|
230
|
+
const createSpinner = async () => {
|
|
231
|
+
if (silent) {
|
|
232
|
+
return {
|
|
233
|
+
start() {},
|
|
234
|
+
stop() {},
|
|
235
|
+
error() {},
|
|
236
|
+
message() {},
|
|
237
|
+
cancel() {},
|
|
238
|
+
clear() {},
|
|
239
|
+
get isSpinning() {
|
|
240
|
+
return false;
|
|
241
|
+
},
|
|
242
|
+
get isCancelled() {
|
|
243
|
+
return false;
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
return await prompts.spinner();
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// ─── Resolve channel plan ─────────────────────────────────────────────
|
|
251
|
+
// Post-install callers (config generation, directory setup, help catalog
|
|
252
|
+
// rebuild) invoke findModuleSource/cloneExternalModule without
|
|
253
|
+
// channelOptions just to locate the module's files. Those calls must not
|
|
254
|
+
// redecide the channel — the install step already chose one, cloned the
|
|
255
|
+
// right ref, and recorded a resolution. If we re-resolve without flags,
|
|
256
|
+
// we'd snap back to stable and overwrite a pinned install.
|
|
257
|
+
const hasExplicitChannelInput =
|
|
258
|
+
options.channelOptions &&
|
|
259
|
+
(options.channelOptions.global ||
|
|
260
|
+
(options.channelOptions.nextSet && options.channelOptions.nextSet.size > 0) ||
|
|
261
|
+
(options.channelOptions.pins && options.channelOptions.pins.size > 0));
|
|
262
|
+
const existingResolution = ExternalModuleManager._resolutions.get(moduleCode);
|
|
263
|
+
const haveUsableCache = await fs.pathExists(moduleCacheDir);
|
|
264
|
+
|
|
265
|
+
if (!hasExplicitChannelInput && existingResolution && haveUsableCache) {
|
|
266
|
+
// This is a look-up only; the module is already installed at its chosen
|
|
267
|
+
// ref. Skip cloning and return the cached path unchanged.
|
|
268
|
+
return moduleCacheDir;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const planEntry = decideChannelForModule({
|
|
272
|
+
code: moduleCode,
|
|
273
|
+
channelOptions: options.channelOptions,
|
|
274
|
+
registryDefault: moduleInfo.defaultChannel,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Same-plan short-circuit: a single install calls cloneExternalModule
|
|
278
|
+
// several times (config collection, directory setup, help-catalog rebuild)
|
|
279
|
+
// with the same channelOptions. The first call resolves + clones; later
|
|
280
|
+
// calls with an identical plan and a valid cache should return immediately
|
|
281
|
+
// instead of re-running resolveChannel() and `git fetch` (slow; can fail
|
|
282
|
+
// on flaky networks even though the tagCache dedupes the GitHub API hit).
|
|
283
|
+
if (existingResolution && haveUsableCache && existingResolution.channel === planEntry.channel) {
|
|
284
|
+
const samePin = planEntry.channel !== 'pinned' || existingResolution.version === planEntry.pin;
|
|
285
|
+
if (samePin) return moduleCacheDir;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
let resolved;
|
|
289
|
+
try {
|
|
290
|
+
resolved = await resolveChannel({
|
|
291
|
+
channel: planEntry.channel,
|
|
292
|
+
pin: planEntry.pin,
|
|
293
|
+
repoUrl: moduleInfo.url,
|
|
294
|
+
});
|
|
295
|
+
} catch (error) {
|
|
296
|
+
// Tag-API failure (rate limit, transient network). If we already have
|
|
297
|
+
// a usable cache at a recorded ref, treat this as "couldn't check for
|
|
298
|
+
// updates" and re-use the cached version silently — that's the right
|
|
299
|
+
// call for an update/quick-update, since the semantics don't change
|
|
300
|
+
// and the user isn't worse off than before they ran this command.
|
|
301
|
+
const cachedMarker = await readChannelMarker(path.join(moduleCacheDir, '.drt-channel.json'));
|
|
302
|
+
if (cachedMarker?.channel && (await fs.pathExists(moduleCacheDir))) {
|
|
303
|
+
if (!silent) {
|
|
304
|
+
await prompts.log.warn(
|
|
305
|
+
`Could not check for updates to ${moduleInfo.name} (${error.message}); using cached ${cachedMarker.version || cachedMarker.channel}.`,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
ExternalModuleManager._resolutions.set(moduleCode, {
|
|
309
|
+
channel: cachedMarker.channel,
|
|
310
|
+
version: cachedMarker.version || 'main',
|
|
311
|
+
ref: cachedMarker.version && cachedMarker.version !== 'main' ? cachedMarker.version : null,
|
|
312
|
+
sha: cachedMarker.sha,
|
|
313
|
+
repoUrl: moduleInfo.url,
|
|
314
|
+
resolvedFallback: false,
|
|
315
|
+
planSource: 'cached',
|
|
316
|
+
});
|
|
317
|
+
return moduleCacheDir;
|
|
318
|
+
}
|
|
319
|
+
// No cache to fall back on — this is effectively a fresh install with
|
|
320
|
+
// no offline safety net. Surface a clear error with actionable guidance.
|
|
321
|
+
const isRateLimited = /rate limit/i.test(error.message);
|
|
322
|
+
const hint = isRateLimited
|
|
323
|
+
? process.env.GITHUB_TOKEN
|
|
324
|
+
? 'Your GITHUB_TOKEN may have expired or been rate-limited on its own budget. Try a different token or wait for the reset.'
|
|
325
|
+
: 'Set a GITHUB_TOKEN env var (any personal access token with public-repo read) to raise the 60-req/hour anonymous limit.'
|
|
326
|
+
: `Check your network connection, or rerun with \`--next=${moduleCode}\` / \`--pin ${moduleCode}=<tag>\` to skip the tag lookup.`;
|
|
327
|
+
throw new Error(`Could not resolve stable tag for '${moduleCode}' (${error.message}). ${hint}`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (resolved.resolvedFallback && !silent) {
|
|
331
|
+
if (resolved.reason === 'no-stable-tags') {
|
|
332
|
+
await prompts.log.warn(`No stable releases found for ${moduleInfo.name}; installing from main.`);
|
|
333
|
+
} else if (resolved.reason === 'not-a-github-url') {
|
|
334
|
+
await prompts.log.warn(`Cannot determine stable tags for ${moduleInfo.name} (non-GitHub URL); installing from main.`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Validate pin before we burn time cloning. Best-effort: skip on non-GitHub URLs.
|
|
339
|
+
if (planEntry.channel === 'pinned') {
|
|
340
|
+
const parsed = parseGitHubRepo(moduleInfo.url);
|
|
341
|
+
if (parsed) {
|
|
342
|
+
try {
|
|
343
|
+
const exists = await tagExists(parsed.owner, parsed.repo, planEntry.pin);
|
|
344
|
+
if (!exists) {
|
|
345
|
+
throw new Error(`Tag '${planEntry.pin}' not found in ${parsed.owner}/${parsed.repo}.`);
|
|
346
|
+
}
|
|
347
|
+
} catch (error) {
|
|
348
|
+
if (error.message?.includes('not found')) throw error;
|
|
349
|
+
// Network hiccup on tag verification — let the clone attempt fail clearly.
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ─── Clone or update cache by resolved channel ────────────────────────
|
|
355
|
+
const markerPath = path.join(moduleCacheDir, '.drt-channel.json');
|
|
356
|
+
const currentMarker = await readChannelMarker(markerPath);
|
|
357
|
+
const needsChannelReset = currentMarker && currentMarker.channel !== resolved.channel;
|
|
358
|
+
|
|
359
|
+
let needsDependencyInstall = false;
|
|
360
|
+
let wasNewClone = false;
|
|
361
|
+
|
|
362
|
+
if (needsChannelReset && (await fs.pathExists(moduleCacheDir))) {
|
|
363
|
+
// Channel changed (e.g. user switched stable→next). Blow away and re-clone
|
|
364
|
+
// to avoid tangling shallow clones of different refs.
|
|
365
|
+
await fs.remove(moduleCacheDir);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (await fs.pathExists(moduleCacheDir)) {
|
|
369
|
+
// Cache exists on the right channel. Refresh the ref.
|
|
370
|
+
const fetchSpinner = await createSpinner();
|
|
371
|
+
fetchSpinner.start(`Fetching ${moduleInfo.name}...`);
|
|
372
|
+
try {
|
|
373
|
+
const currentSha = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
|
|
374
|
+
|
|
375
|
+
if (resolved.channel === 'next') {
|
|
376
|
+
execSync('git fetch origin --depth 1', {
|
|
377
|
+
cwd: moduleCacheDir,
|
|
378
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
379
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
380
|
+
});
|
|
381
|
+
execSync('git reset --hard origin/HEAD', {
|
|
382
|
+
cwd: moduleCacheDir,
|
|
383
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
384
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
385
|
+
});
|
|
386
|
+
} else {
|
|
387
|
+
// stable or pinned — fetch the specific tag and check it out.
|
|
388
|
+
execSync(`git fetch --depth 1 origin tag ${quoteShell(resolved.ref)} --no-tags`, {
|
|
389
|
+
cwd: moduleCacheDir,
|
|
390
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
391
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
392
|
+
});
|
|
393
|
+
execSync(`git checkout --quiet FETCH_HEAD`, {
|
|
394
|
+
cwd: moduleCacheDir,
|
|
395
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const newSha = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
|
|
400
|
+
fetchSpinner.stop(`Fetched ${moduleInfo.name}`);
|
|
401
|
+
if (currentSha !== newSha) needsDependencyInstall = true;
|
|
402
|
+
} catch {
|
|
403
|
+
fetchSpinner.error(`Fetch failed, re-downloading ${moduleInfo.name}`);
|
|
404
|
+
await fs.remove(moduleCacheDir);
|
|
405
|
+
wasNewClone = true;
|
|
406
|
+
}
|
|
407
|
+
} else {
|
|
408
|
+
wasNewClone = true;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (wasNewClone) {
|
|
412
|
+
const fetchSpinner = await createSpinner();
|
|
413
|
+
fetchSpinner.start(`Fetching ${moduleInfo.name}...`);
|
|
414
|
+
try {
|
|
415
|
+
if (resolved.channel === 'next') {
|
|
416
|
+
execSync(`git clone --depth 1 "${moduleInfo.url}" "${moduleCacheDir}"`, {
|
|
417
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
418
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
419
|
+
});
|
|
420
|
+
} else {
|
|
421
|
+
execSync(`git clone --depth 1 --branch ${quoteShell(resolved.ref)} "${moduleInfo.url}" "${moduleCacheDir}"`, {
|
|
422
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
423
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
fetchSpinner.stop(`Fetched ${moduleInfo.name}`);
|
|
427
|
+
} catch (error) {
|
|
428
|
+
fetchSpinner.error(`Failed to fetch ${moduleInfo.name}`);
|
|
429
|
+
throw new Error(`Failed to clone external module '${moduleCode}' at ${resolved.version}: ${error.message}`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Record resolution (channel + tag + SHA) for the manifest writer to pick up.
|
|
434
|
+
const sha = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
|
|
435
|
+
ExternalModuleManager._resolutions.set(moduleCode, {
|
|
436
|
+
channel: resolved.channel,
|
|
437
|
+
version: resolved.version,
|
|
438
|
+
ref: resolved.ref,
|
|
439
|
+
sha,
|
|
440
|
+
repoUrl: moduleInfo.url,
|
|
441
|
+
resolvedFallback: !!resolved.resolvedFallback,
|
|
442
|
+
planSource: planEntry.source,
|
|
443
|
+
});
|
|
444
|
+
await writeChannelMarker(markerPath, { channel: resolved.channel, version: resolved.version, sha });
|
|
445
|
+
|
|
446
|
+
// Install dependencies if package.json exists
|
|
447
|
+
const packageJsonPath = path.join(moduleCacheDir, 'package.json');
|
|
448
|
+
const nodeModulesPath = path.join(moduleCacheDir, 'node_modules');
|
|
449
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
450
|
+
// Install if node_modules doesn't exist, or if package.json is newer (dependencies changed)
|
|
451
|
+
const nodeModulesMissing = !(await fs.pathExists(nodeModulesPath));
|
|
452
|
+
|
|
453
|
+
// Force install if we updated or cloned new
|
|
454
|
+
if (needsDependencyInstall || wasNewClone || nodeModulesMissing) {
|
|
455
|
+
const installSpinner = await createSpinner();
|
|
456
|
+
installSpinner.start(`Installing dependencies for ${moduleInfo.name}...`);
|
|
457
|
+
try {
|
|
458
|
+
execSync('npm install --omit=dev --no-audit --no-fund --no-progress --legacy-peer-deps', {
|
|
459
|
+
cwd: moduleCacheDir,
|
|
460
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
461
|
+
timeout: 120_000, // 2 minute timeout
|
|
462
|
+
});
|
|
463
|
+
installSpinner.stop(`Installed dependencies for ${moduleInfo.name}`);
|
|
464
|
+
} catch (error) {
|
|
465
|
+
installSpinner.error(`Failed to install dependencies for ${moduleInfo.name}`);
|
|
466
|
+
if (!silent) await prompts.log.warn(` ${error.message}`);
|
|
467
|
+
}
|
|
468
|
+
} else {
|
|
469
|
+
// Check if package.json is newer than node_modules
|
|
470
|
+
let packageJsonNewer = false;
|
|
471
|
+
try {
|
|
472
|
+
const packageStats = await fs.stat(packageJsonPath);
|
|
473
|
+
const nodeModulesStats = await fs.stat(nodeModulesPath);
|
|
474
|
+
packageJsonNewer = packageStats.mtime > nodeModulesStats.mtime;
|
|
475
|
+
} catch {
|
|
476
|
+
// If stat fails, assume we need to install
|
|
477
|
+
packageJsonNewer = true;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (packageJsonNewer) {
|
|
481
|
+
const installSpinner = await createSpinner();
|
|
482
|
+
installSpinner.start(`Installing dependencies for ${moduleInfo.name}...`);
|
|
483
|
+
try {
|
|
484
|
+
execSync('npm install --omit=dev --no-audit --no-fund --no-progress --legacy-peer-deps', {
|
|
485
|
+
cwd: moduleCacheDir,
|
|
486
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
487
|
+
timeout: 120_000, // 2 minute timeout
|
|
488
|
+
});
|
|
489
|
+
installSpinner.stop(`Installed dependencies for ${moduleInfo.name}`);
|
|
490
|
+
} catch (error) {
|
|
491
|
+
installSpinner.error(`Failed to install dependencies for ${moduleInfo.name}`);
|
|
492
|
+
if (!silent) await prompts.log.warn(` ${error.message}`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return moduleCacheDir;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Find the source path for an external module
|
|
503
|
+
* @param {string} moduleCode - Code of the external module
|
|
504
|
+
* @param {Object} options - Options passed to cloneExternalModule
|
|
505
|
+
* @returns {string|null} Path to the module source or null if not found
|
|
506
|
+
*/
|
|
507
|
+
async findExternalModuleSource(moduleCode, options = {}) {
|
|
508
|
+
const moduleInfo = await this.getModuleByCode(moduleCode);
|
|
509
|
+
|
|
510
|
+
if (!moduleInfo || moduleInfo.builtIn) {
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Normalize to the canonical code — see cloneExternalModule for why.
|
|
515
|
+
moduleCode = moduleInfo.code;
|
|
516
|
+
|
|
517
|
+
// Marketplace-plugin modules (registry entries flagged `marketplace-plugin`)
|
|
518
|
+
// ship a .claude-plugin/marketplace.json and keep their module.yaml inside a
|
|
519
|
+
// skill's assets/ rather than in one directory alongside the skills. Resolve
|
|
520
|
+
// them through the PluginResolver (the same machinery custom-URL installs
|
|
521
|
+
// use) and return the directory that holds module.yaml so config/version/
|
|
522
|
+
// directory callers work; install() copies the resolved skill dirs.
|
|
523
|
+
if (moduleInfo.marketplacePlugin) {
|
|
524
|
+
const pluginMod = await this.resolvePluginModule(moduleCode, options);
|
|
525
|
+
if (pluginMod && pluginMod.moduleYamlPath) {
|
|
526
|
+
return path.dirname(pluginMod.moduleYamlPath);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Clone the external module repo
|
|
531
|
+
const cloneDir = await this.cloneExternalModule(moduleCode, options);
|
|
532
|
+
|
|
533
|
+
// The module-definition specifies the path to module.yaml relative to repo root
|
|
534
|
+
// We need to return the directory containing module.yaml
|
|
535
|
+
const moduleDefinitionPath = moduleInfo.moduleDefinition; // e.g., 'skills/module.yaml'
|
|
536
|
+
const configuredPath = path.join(cloneDir, moduleDefinitionPath);
|
|
537
|
+
|
|
538
|
+
if (await fs.pathExists(configuredPath)) {
|
|
539
|
+
return path.dirname(configuredPath);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Fallback: search skills/ and src/ (root level and one level deep for subfolders)
|
|
543
|
+
for (const dir of ['skills', 'src']) {
|
|
544
|
+
const rootCandidate = path.join(cloneDir, dir, 'module.yaml');
|
|
545
|
+
if (await fs.pathExists(rootCandidate)) {
|
|
546
|
+
return path.dirname(rootCandidate);
|
|
547
|
+
}
|
|
548
|
+
const dirPath = path.join(cloneDir, dir);
|
|
549
|
+
if (await fs.pathExists(dirPath)) {
|
|
550
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
551
|
+
for (const entry of entries) {
|
|
552
|
+
if (entry.isDirectory()) {
|
|
553
|
+
const subCandidate = path.join(dirPath, entry.name, 'module.yaml');
|
|
554
|
+
if (await fs.pathExists(subCandidate)) {
|
|
555
|
+
return path.dirname(subCandidate);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Check repo root as last fallback
|
|
563
|
+
const rootCandidate = path.join(cloneDir, 'module.yaml');
|
|
564
|
+
if (await fs.pathExists(rootCandidate)) {
|
|
565
|
+
return path.dirname(rootCandidate);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Nothing found: the cloned ref does not contain a recognizable module structure.
|
|
569
|
+
// This happens when a stable tag predates a module restructure (e.g. the repo
|
|
570
|
+
// moved files from payload/ to skills/ after the tag was cut). Returning a
|
|
571
|
+
// non-existent path silently causes a confusing ENOENT deep inside copyModuleWithFiltering;
|
|
572
|
+
// throw a descriptive error here instead so the user knows what happened and how to recover.
|
|
573
|
+
const resolution = ExternalModuleManager._resolutions.get(moduleCode);
|
|
574
|
+
const versionHint = resolution?.version ? `version ${resolution.version}` : 'the cloned version';
|
|
575
|
+
const channelHint =
|
|
576
|
+
resolution?.channel === 'stable' ? ` Try reinstalling with \`--next=${moduleCode}\` to use the latest main branch instead.` : '';
|
|
577
|
+
throw new Error(
|
|
578
|
+
`Module '${moduleCode}' was downloaded but its module definition was not found. ` +
|
|
579
|
+
`Expected '${moduleDefinitionPath}' to exist in ${versionHint}, but it is missing. ` +
|
|
580
|
+
`The repository may have been restructured after this release was tagged.${channelHint}`,
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Resolve a marketplace-plugin registry module to an installable plugin
|
|
586
|
+
* definition. Clones the repo (respecting the channel plan), reads its
|
|
587
|
+
* .claude-plugin/marketplace.json, and runs the PluginResolver against the
|
|
588
|
+
* plugin matching this module. The result (skillPaths + module.yaml +
|
|
589
|
+
* module-help.csv) is cached so install() can copy the resolved skill dirs.
|
|
590
|
+
*
|
|
591
|
+
* @param {string} moduleCode - Code of the external module
|
|
592
|
+
* @param {Object} options - Options passed to cloneExternalModule
|
|
593
|
+
* @returns {Promise<Object|null>} ResolvedModule from PluginResolver, or null
|
|
594
|
+
* when the module is not a marketplace plugin or cannot be resolved.
|
|
595
|
+
*/
|
|
596
|
+
async resolvePluginModule(moduleCode, options = {}) {
|
|
597
|
+
const moduleInfo = await this.getModuleByCode(moduleCode);
|
|
598
|
+
if (!moduleInfo || moduleInfo.builtIn || !moduleInfo.marketplacePlugin) {
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Normalize to the canonical code — see cloneExternalModule for why.
|
|
603
|
+
moduleCode = moduleInfo.code;
|
|
604
|
+
|
|
605
|
+
const cloneDir = await this.cloneExternalModule(moduleCode, options);
|
|
606
|
+
|
|
607
|
+
const marketplacePath = path.join(cloneDir, '.claude-plugin', 'marketplace.json');
|
|
608
|
+
if (!(await fs.pathExists(marketplacePath))) {
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
let marketplace;
|
|
613
|
+
try {
|
|
614
|
+
marketplace = JSON.parse(await fs.readFile(marketplacePath, 'utf8'));
|
|
615
|
+
} catch {
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const plugins = Array.isArray(marketplace.plugins) ? marketplace.plugins : [];
|
|
620
|
+
if (plugins.length === 0) {
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Prefer the plugin whose name matches the registry's plugin_name (or code),
|
|
625
|
+
// falling back to any plugin that declares skills.
|
|
626
|
+
const preferredName = moduleInfo.pluginName || moduleInfo.code;
|
|
627
|
+
const ordered = [...plugins].sort((a, b) => {
|
|
628
|
+
const am = a?.name === preferredName ? 0 : 1;
|
|
629
|
+
const bm = b?.name === preferredName ? 0 : 1;
|
|
630
|
+
return am - bm;
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
const { PluginResolver } = require('./plugin-resolver');
|
|
634
|
+
const resolver = new PluginResolver();
|
|
635
|
+
|
|
636
|
+
for (const plugin of ordered) {
|
|
637
|
+
if (!plugin || !Array.isArray(plugin.skills) || plugin.skills.length === 0) {
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
let resolvedMods;
|
|
641
|
+
try {
|
|
642
|
+
resolvedMods = await resolver.resolve(cloneDir, plugin);
|
|
643
|
+
} catch {
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
if (!resolvedMods || resolvedMods.length === 0) {
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
// Match the registry code, then the preferred plugin name, then accept a
|
|
650
|
+
// lone resolved module.
|
|
651
|
+
const match =
|
|
652
|
+
resolvedMods.find((mod) => mod.code === moduleCode) ||
|
|
653
|
+
resolvedMods.find((mod) => mod.code === preferredName) ||
|
|
654
|
+
(resolvedMods.length === 1 ? resolvedMods[0] : null);
|
|
655
|
+
if (match) {
|
|
656
|
+
match.repoUrl = moduleInfo.url;
|
|
657
|
+
ExternalModuleManager._pluginResolutions.set(moduleCode, match);
|
|
658
|
+
return match;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
return null;
|
|
663
|
+
}
|
|
664
|
+
cachedModules = null;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
module.exports = { ExternalModuleManager };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical schema for per-module `module-help.csv` files.
|
|
3
|
+
*
|
|
4
|
+
* Both the merger (`Installer.mergeModuleHelpCatalogs`) and the synthesizer
|
|
5
|
+
* (`PluginResolver._buildSynthesizedHelpCsv`) emit this exact header. The
|
|
6
|
+
* merger compares each per-module file's header against this string and
|
|
7
|
+
* warns on drift, so any rename here must be matched in external module
|
|
8
|
+
* authors' CSVs (or accepted as a positional fall-through with a warning).
|
|
9
|
+
*/
|
|
10
|
+
const MODULE_HELP_CSV_HEADER =
|
|
11
|
+
'module,skill,display-name,menu-code,description,action,args,phase,preceded-by,followed-by,required,output-location,outputs';
|
|
12
|
+
|
|
13
|
+
module.exports = { MODULE_HELP_CSV_HEADER };
|