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.
Files changed (299) hide show
  1. package/AGENTS.md +45 -0
  2. package/GETTING-STARTED.md +269 -0
  3. package/README.md +233 -0
  4. package/drt-method-1.0.0.tgz +0 -0
  5. package/drt-modules.yaml +1 -0
  6. package/package.json +51 -0
  7. package/scripts/install-from-github.ps1 +22 -0
  8. package/scripts/install-from-github.sh +17 -0
  9. package/src/core-skills/drt-advanced-elicitation/SKILL.md +142 -0
  10. package/src/core-skills/drt-advanced-elicitation/methods.csv +72 -0
  11. package/src/core-skills/drt-brainstorming/SKILL.md +80 -0
  12. package/src/core-skills/drt-brainstorming/assets/brain-icons.json +166 -0
  13. package/src/core-skills/drt-brainstorming/assets/brain-methods.csv +109 -0
  14. package/src/core-skills/drt-brainstorming/assets/brain-selector.html +326 -0
  15. package/src/core-skills/drt-brainstorming/customize.toml +84 -0
  16. package/src/core-skills/drt-brainstorming/references/converge.md +24 -0
  17. package/src/core-skills/drt-brainstorming/references/finalize.md +26 -0
  18. package/src/core-skills/drt-brainstorming/references/headless.md +54 -0
  19. package/src/core-skills/drt-brainstorming/references/in-chat-techniques.md +18 -0
  20. package/src/core-skills/drt-brainstorming/references/mode-autonomous.md +10 -0
  21. package/src/core-skills/drt-brainstorming/references/mode-facilitator.md +11 -0
  22. package/src/core-skills/drt-brainstorming/references/mode-partner.md +16 -0
  23. package/src/core-skills/drt-brainstorming/references/resume.md +5 -0
  24. package/src/core-skills/drt-brainstorming/scripts/brain.py +740 -0
  25. package/src/core-skills/drt-customize/SKILL.md +111 -0
  26. package/src/core-skills/drt-customize/scripts/list_customizable_skills.py +231 -0
  27. package/src/core-skills/drt-editorial-review-prose/SKILL.md +86 -0
  28. package/src/core-skills/drt-editorial-review-structure/SKILL.md +179 -0
  29. package/src/core-skills/drt-forge-idea/SKILL.md +107 -0
  30. package/src/core-skills/drt-forge-idea/customize.toml +42 -0
  31. package/src/core-skills/drt-forge-idea/scripts/resolve_personas.py +270 -0
  32. package/src/core-skills/drt-help/SKILL.md +75 -0
  33. package/src/core-skills/drt-index-docs/SKILL.md +66 -0
  34. package/src/core-skills/drt-party-mode/SKILL.md +60 -0
  35. package/src/core-skills/drt-party-mode/customize.toml +211 -0
  36. package/src/core-skills/drt-party-mode/references/create-party.md +70 -0
  37. package/src/core-skills/drt-party-mode/references/mode-agent-team.md +13 -0
  38. package/src/core-skills/drt-party-mode/references/mode-auto.md +13 -0
  39. package/src/core-skills/drt-party-mode/references/mode-subagent.md +31 -0
  40. package/src/core-skills/drt-party-mode/references/party-memory.md +51 -0
  41. package/src/core-skills/drt-party-mode/scripts/resolve_party.py +272 -0
  42. package/src/core-skills/drt-review-adversarial-general/SKILL.md +37 -0
  43. package/src/core-skills/drt-review-edge-case-hunter/SKILL.md +73 -0
  44. package/src/core-skills/drt-review-edge-case-hunter/references/deletion-check.md +14 -0
  45. package/src/core-skills/drt-review-verification-gap/SKILL.md +106 -0
  46. package/src/core-skills/drt-shard-doc/SKILL.md +105 -0
  47. package/src/core-skills/drt-spec/SKILL.md +160 -0
  48. package/src/core-skills/drt-spec/assets/headless-schemas.md +33 -0
  49. package/src/core-skills/drt-spec/assets/spec-template.md +49 -0
  50. package/src/core-skills/drt-spec/assets/stories-schema.md +44 -0
  51. package/src/core-skills/drt-spec/customize.toml +53 -0
  52. package/src/core-skills/module-help.csv +14 -0
  53. package/src/core-skills/module.yaml +33 -0
  54. package/src/drt-skills/1-analysis/drt-agent-analyst/SKILL.md +76 -0
  55. package/src/drt-skills/1-analysis/drt-agent-analyst/customize.toml +90 -0
  56. package/src/drt-skills/1-analysis/drt-agent-tech-writer/SKILL.md +76 -0
  57. package/src/drt-skills/1-analysis/drt-agent-tech-writer/customize.toml +81 -0
  58. package/src/drt-skills/1-analysis/drt-agent-tech-writer/explain-concept.md +20 -0
  59. package/src/drt-skills/1-analysis/drt-agent-tech-writer/mermaid-gen.md +20 -0
  60. package/src/drt-skills/1-analysis/drt-agent-tech-writer/validate-doc.md +19 -0
  61. package/src/drt-skills/1-analysis/drt-agent-tech-writer/write-document.md +20 -0
  62. package/src/drt-skills/1-analysis/drt-document-project/SKILL.md +62 -0
  63. package/src/drt-skills/1-analysis/drt-document-project/checklist.md +245 -0
  64. package/src/drt-skills/1-analysis/drt-document-project/customize.toml +41 -0
  65. package/src/drt-skills/1-analysis/drt-document-project/documentation-requirements.csv +12 -0
  66. package/src/drt-skills/1-analysis/drt-document-project/instructions.md +128 -0
  67. package/src/drt-skills/1-analysis/drt-document-project/templates/deep-dive-template.md +345 -0
  68. package/src/drt-skills/1-analysis/drt-document-project/templates/index-template.md +169 -0
  69. package/src/drt-skills/1-analysis/drt-document-project/templates/project-overview-template.md +103 -0
  70. package/src/drt-skills/1-analysis/drt-document-project/templates/project-scan-report-schema.json +160 -0
  71. package/src/drt-skills/1-analysis/drt-document-project/templates/source-tree-template.md +135 -0
  72. package/src/drt-skills/1-analysis/drt-document-project/workflows/deep-dive-instructions.md +300 -0
  73. package/src/drt-skills/1-analysis/drt-document-project/workflows/deep-dive-workflow.md +34 -0
  74. package/src/drt-skills/1-analysis/drt-document-project/workflows/full-scan-instructions.md +1111 -0
  75. package/src/drt-skills/1-analysis/drt-document-project/workflows/full-scan-workflow.md +34 -0
  76. package/src/drt-skills/1-analysis/drt-prfaq/SKILL.md +135 -0
  77. package/src/drt-skills/1-analysis/drt-prfaq/agents/artifact-analyzer.md +60 -0
  78. package/src/drt-skills/1-analysis/drt-prfaq/agents/web-researcher.md +49 -0
  79. package/src/drt-skills/1-analysis/drt-prfaq/assets/prfaq-template.md +62 -0
  80. package/src/drt-skills/1-analysis/drt-prfaq/bmad-manifest.json +16 -0
  81. package/src/drt-skills/1-analysis/drt-prfaq/customize.toml +41 -0
  82. package/src/drt-skills/1-analysis/drt-prfaq/references/customer-faq.md +55 -0
  83. package/src/drt-skills/1-analysis/drt-prfaq/references/internal-faq.md +51 -0
  84. package/src/drt-skills/1-analysis/drt-prfaq/references/press-release.md +60 -0
  85. package/src/drt-skills/1-analysis/drt-prfaq/references/verdict.md +83 -0
  86. package/src/drt-skills/1-analysis/drt-product-brief/SKILL.md +91 -0
  87. package/src/drt-skills/1-analysis/drt-product-brief/assets/brief-template.md +41 -0
  88. package/src/drt-skills/1-analysis/drt-product-brief/customize.toml +99 -0
  89. package/src/drt-skills/1-analysis/research/drt-domain-research/SKILL.md +96 -0
  90. package/src/drt-skills/1-analysis/research/drt-domain-research/customize.toml +41 -0
  91. package/src/drt-skills/1-analysis/research/drt-domain-research/domain-steps/step-01-init.md +137 -0
  92. package/src/drt-skills/1-analysis/research/drt-domain-research/domain-steps/step-02-domain-analysis.md +229 -0
  93. package/src/drt-skills/1-analysis/research/drt-domain-research/domain-steps/step-03-competitive-landscape.md +238 -0
  94. package/src/drt-skills/1-analysis/research/drt-domain-research/domain-steps/step-04-regulatory-focus.md +206 -0
  95. package/src/drt-skills/1-analysis/research/drt-domain-research/domain-steps/step-05-technical-trends.md +234 -0
  96. package/src/drt-skills/1-analysis/research/drt-domain-research/domain-steps/step-06-research-synthesis.md +450 -0
  97. package/src/drt-skills/1-analysis/research/drt-domain-research/research.template.md +29 -0
  98. package/src/drt-skills/1-analysis/research/drt-market-research/SKILL.md +96 -0
  99. package/src/drt-skills/1-analysis/research/drt-market-research/customize.toml +41 -0
  100. package/src/drt-skills/1-analysis/research/drt-market-research/research.template.md +29 -0
  101. package/src/drt-skills/1-analysis/research/drt-market-research/steps/step-01-init.md +184 -0
  102. package/src/drt-skills/1-analysis/research/drt-market-research/steps/step-02-customer-behavior.md +239 -0
  103. package/src/drt-skills/1-analysis/research/drt-market-research/steps/step-03-customer-pain-points.md +251 -0
  104. package/src/drt-skills/1-analysis/research/drt-market-research/steps/step-04-customer-decisions.md +261 -0
  105. package/src/drt-skills/1-analysis/research/drt-market-research/steps/step-05-competitive-analysis.md +173 -0
  106. package/src/drt-skills/1-analysis/research/drt-market-research/steps/step-06-research-completion.md +484 -0
  107. package/src/drt-skills/1-analysis/research/drt-technical-research/SKILL.md +96 -0
  108. package/src/drt-skills/1-analysis/research/drt-technical-research/customize.toml +41 -0
  109. package/src/drt-skills/1-analysis/research/drt-technical-research/research.template.md +29 -0
  110. package/src/drt-skills/1-analysis/research/drt-technical-research/technical-steps/step-01-init.md +137 -0
  111. package/src/drt-skills/1-analysis/research/drt-technical-research/technical-steps/step-02-technical-overview.md +239 -0
  112. package/src/drt-skills/1-analysis/research/drt-technical-research/technical-steps/step-03-integration-patterns.md +248 -0
  113. package/src/drt-skills/1-analysis/research/drt-technical-research/technical-steps/step-04-architectural-patterns.md +202 -0
  114. package/src/drt-skills/1-analysis/research/drt-technical-research/technical-steps/step-05-implementation-research.md +233 -0
  115. package/src/drt-skills/1-analysis/research/drt-technical-research/technical-steps/step-06-research-synthesis.md +493 -0
  116. package/src/drt-skills/2-plan-workflows/drt-agent-pm/SKILL.md +76 -0
  117. package/src/drt-skills/2-plan-workflows/drt-agent-pm/customize.toml +75 -0
  118. package/src/drt-skills/2-plan-workflows/drt-agent-ux-designer/SKILL.md +76 -0
  119. package/src/drt-skills/2-plan-workflows/drt-agent-ux-designer/customize.toml +60 -0
  120. package/src/drt-skills/2-plan-workflows/drt-create-prd/SKILL.md +30 -0
  121. package/src/drt-skills/2-plan-workflows/drt-create-prd/customize.toml +41 -0
  122. package/src/drt-skills/2-plan-workflows/drt-edit-prd/SKILL.md +30 -0
  123. package/src/drt-skills/2-plan-workflows/drt-edit-prd/customize.toml +42 -0
  124. package/src/drt-skills/2-plan-workflows/drt-prd/SKILL.md +92 -0
  125. package/src/drt-skills/2-plan-workflows/drt-prd/assets/headless-schemas.md +76 -0
  126. package/src/drt-skills/2-plan-workflows/drt-prd/assets/prd-template.md +165 -0
  127. package/src/drt-skills/2-plan-workflows/drt-prd/assets/prd-validation-checklist.md +135 -0
  128. package/src/drt-skills/2-plan-workflows/drt-prd/assets/validation-report-template.html +325 -0
  129. package/src/drt-skills/2-plan-workflows/drt-prd/customize.toml +147 -0
  130. package/src/drt-skills/2-plan-workflows/drt-prd/references/headless.md +39 -0
  131. package/src/drt-skills/2-plan-workflows/drt-prd/references/validate.md +97 -0
  132. package/src/drt-skills/2-plan-workflows/drt-ux/SKILL.md +90 -0
  133. package/src/drt-skills/2-plan-workflows/drt-ux/assets/color-themes.md +9 -0
  134. package/src/drt-skills/2-plan-workflows/drt-ux/assets/design-directions.md +9 -0
  135. package/src/drt-skills/2-plan-workflows/drt-ux/assets/design-example-editorial.md +158 -0
  136. package/src/drt-skills/2-plan-workflows/drt-ux/assets/design-example-mobile.md +93 -0
  137. package/src/drt-skills/2-plan-workflows/drt-ux/assets/design-example-shadcn.md +109 -0
  138. package/src/drt-skills/2-plan-workflows/drt-ux/assets/excalidraw-wireframe.md +19 -0
  139. package/src/drt-skills/2-plan-workflows/drt-ux/assets/experience-example-mobile.md +112 -0
  140. package/src/drt-skills/2-plan-workflows/drt-ux/assets/experience-example-shadcn.md +133 -0
  141. package/src/drt-skills/2-plan-workflows/drt-ux/assets/headless-schemas.md +84 -0
  142. package/src/drt-skills/2-plan-workflows/drt-ux/assets/key-screens.md +29 -0
  143. package/src/drt-skills/2-plan-workflows/drt-ux/assets/validation-report-template.html +319 -0
  144. package/src/drt-skills/2-plan-workflows/drt-ux/customize.toml +100 -0
  145. package/src/drt-skills/2-plan-workflows/drt-ux/references/creative-tools.md +19 -0
  146. package/src/drt-skills/2-plan-workflows/drt-ux/references/design-md-spec.md +50 -0
  147. package/src/drt-skills/2-plan-workflows/drt-ux/references/headless.md +37 -0
  148. package/src/drt-skills/2-plan-workflows/drt-ux/references/validate.md +115 -0
  149. package/src/drt-skills/2-plan-workflows/drt-validate-prd/SKILL.md +30 -0
  150. package/src/drt-skills/2-plan-workflows/drt-validate-prd/customize.toml +42 -0
  151. package/src/drt-skills/3-solutioning/drt-agent-architect/SKILL.md +76 -0
  152. package/src/drt-skills/3-solutioning/drt-agent-architect/customize.toml +65 -0
  153. package/src/drt-skills/3-solutioning/drt-architecture/SKILL.md +85 -0
  154. package/src/drt-skills/3-solutioning/drt-architecture/assets/spine-template.md +79 -0
  155. package/src/drt-skills/3-solutioning/drt-architecture/customize.toml +100 -0
  156. package/src/drt-skills/3-solutioning/drt-architecture/references/headless.md +26 -0
  157. package/src/drt-skills/3-solutioning/drt-architecture/references/reviewer-gate.md +13 -0
  158. package/src/drt-skills/3-solutioning/drt-architecture/scripts/lint_spine.py +257 -0
  159. package/src/drt-skills/3-solutioning/drt-check-implementation-readiness/SKILL.md +91 -0
  160. package/src/drt-skills/3-solutioning/drt-check-implementation-readiness/customize.toml +41 -0
  161. package/src/drt-skills/3-solutioning/drt-check-implementation-readiness/steps/step-01-document-discovery.md +179 -0
  162. package/src/drt-skills/3-solutioning/drt-check-implementation-readiness/steps/step-02-prd-analysis.md +168 -0
  163. package/src/drt-skills/3-solutioning/drt-check-implementation-readiness/steps/step-03-epic-coverage-validation.md +169 -0
  164. package/src/drt-skills/3-solutioning/drt-check-implementation-readiness/steps/step-04-ux-alignment.md +129 -0
  165. package/src/drt-skills/3-solutioning/drt-check-implementation-readiness/steps/step-05-epic-quality-review.md +241 -0
  166. package/src/drt-skills/3-solutioning/drt-check-implementation-readiness/steps/step-06-final-assessment.md +132 -0
  167. package/src/drt-skills/3-solutioning/drt-check-implementation-readiness/templates/readiness-report-template.md +4 -0
  168. package/src/drt-skills/3-solutioning/drt-create-architecture/SKILL.md +30 -0
  169. package/src/drt-skills/3-solutioning/drt-create-architecture/customize.toml +41 -0
  170. package/src/drt-skills/3-solutioning/drt-create-epics-and-stories/SKILL.md +93 -0
  171. package/src/drt-skills/3-solutioning/drt-create-epics-and-stories/customize.toml +41 -0
  172. package/src/drt-skills/3-solutioning/drt-create-epics-and-stories/steps/step-01-validate-prerequisites.md +263 -0
  173. package/src/drt-skills/3-solutioning/drt-create-epics-and-stories/steps/step-02-design-epics.md +242 -0
  174. package/src/drt-skills/3-solutioning/drt-create-epics-and-stories/steps/step-03-create-stories.md +255 -0
  175. package/src/drt-skills/3-solutioning/drt-create-epics-and-stories/steps/step-04-final-validation.md +143 -0
  176. package/src/drt-skills/3-solutioning/drt-create-epics-and-stories/templates/epics-template.md +61 -0
  177. package/src/drt-skills/3-solutioning/drt-generate-project-context/SKILL.md +81 -0
  178. package/src/drt-skills/3-solutioning/drt-generate-project-context/customize.toml +41 -0
  179. package/src/drt-skills/3-solutioning/drt-generate-project-context/project-context-template.md +21 -0
  180. package/src/drt-skills/3-solutioning/drt-generate-project-context/steps/step-01-discover.md +186 -0
  181. package/src/drt-skills/3-solutioning/drt-generate-project-context/steps/step-02-generate.md +321 -0
  182. package/src/drt-skills/3-solutioning/drt-generate-project-context/steps/step-03-complete.md +284 -0
  183. package/src/drt-skills/4-implementation/drt-agent-dev/SKILL.md +76 -0
  184. package/src/drt-skills/4-implementation/drt-agent-dev/customize.toml +95 -0
  185. package/src/drt-skills/4-implementation/drt-checkpoint-preview/SKILL.md +68 -0
  186. package/src/drt-skills/4-implementation/drt-checkpoint-preview/customize.toml +41 -0
  187. package/src/drt-skills/4-implementation/drt-checkpoint-preview/generate-trail.md +38 -0
  188. package/src/drt-skills/4-implementation/drt-checkpoint-preview/step-01-orientation.md +105 -0
  189. package/src/drt-skills/4-implementation/drt-checkpoint-preview/step-02-walkthrough.md +89 -0
  190. package/src/drt-skills/4-implementation/drt-checkpoint-preview/step-03-detail-pass.md +106 -0
  191. package/src/drt-skills/4-implementation/drt-checkpoint-preview/step-04-testing.md +74 -0
  192. package/src/drt-skills/4-implementation/drt-checkpoint-preview/step-05-wrapup.md +30 -0
  193. package/src/drt-skills/4-implementation/drt-code-review/SKILL.md +90 -0
  194. package/src/drt-skills/4-implementation/drt-code-review/customize.toml +93 -0
  195. package/src/drt-skills/4-implementation/drt-code-review/steps/step-01-gather-context.md +84 -0
  196. package/src/drt-skills/4-implementation/drt-code-review/steps/step-02-review.md +32 -0
  197. package/src/drt-skills/4-implementation/drt-code-review/steps/step-03-triage.md +50 -0
  198. package/src/drt-skills/4-implementation/drt-code-review/steps/step-04-present.md +132 -0
  199. package/src/drt-skills/4-implementation/drt-correct-course/SKILL.md +301 -0
  200. package/src/drt-skills/4-implementation/drt-correct-course/checklist.md +288 -0
  201. package/src/drt-skills/4-implementation/drt-correct-course/customize.toml +41 -0
  202. package/src/drt-skills/4-implementation/drt-create-story/SKILL.md +432 -0
  203. package/src/drt-skills/4-implementation/drt-create-story/checklist.md +357 -0
  204. package/src/drt-skills/4-implementation/drt-create-story/customize.toml +41 -0
  205. package/src/drt-skills/4-implementation/drt-create-story/discover-inputs.md +88 -0
  206. package/src/drt-skills/4-implementation/drt-create-story/template.md +49 -0
  207. package/src/drt-skills/4-implementation/drt-dev-auto/SKILL.md +123 -0
  208. package/src/drt-skills/4-implementation/drt-dev-auto/compile-epic-context.md +62 -0
  209. package/src/drt-skills/4-implementation/drt-dev-auto/customize.toml +108 -0
  210. package/src/drt-skills/4-implementation/drt-dev-auto/spec-template.md +98 -0
  211. package/src/drt-skills/4-implementation/drt-dev-auto/step-01-clarify-and-route.md +84 -0
  212. package/src/drt-skills/4-implementation/drt-dev-auto/step-02-plan.md +31 -0
  213. package/src/drt-skills/4-implementation/drt-dev-auto/step-03-implement.md +42 -0
  214. package/src/drt-skills/4-implementation/drt-dev-auto/step-04-review.md +95 -0
  215. package/src/drt-skills/4-implementation/drt-dev-story/SKILL.md +500 -0
  216. package/src/drt-skills/4-implementation/drt-dev-story/checklist.md +80 -0
  217. package/src/drt-skills/4-implementation/drt-dev-story/customize.toml +41 -0
  218. package/src/drt-skills/4-implementation/drt-implement-task/SKILL.md +101 -0
  219. package/src/drt-skills/4-implementation/drt-implement-task/assets/task-spec-template.md +60 -0
  220. package/src/drt-skills/4-implementation/drt-implement-task/customize.toml +17 -0
  221. package/src/drt-skills/4-implementation/drt-implement-task/steps/step-01-gather-intent.md +42 -0
  222. package/src/drt-skills/4-implementation/drt-implement-task/steps/step-02-plan-and-approve.md +43 -0
  223. package/src/drt-skills/4-implementation/drt-implement-task/steps/step-03-confirm-dependencies.md +38 -0
  224. package/src/drt-skills/4-implementation/drt-implement-task/steps/step-04-implement.md +42 -0
  225. package/src/drt-skills/4-implementation/drt-implement-task/steps/step-05-qc-test.md +31 -0
  226. package/src/drt-skills/4-implementation/drt-implement-task/steps/step-06-quality-review.md +30 -0
  227. package/src/drt-skills/4-implementation/drt-implement-task/steps/step-07-fix-findings.md +33 -0
  228. package/src/drt-skills/4-implementation/drt-implement-task/steps/step-08-complete.md +35 -0
  229. package/src/drt-skills/4-implementation/drt-qa-generate-e2e-tests/SKILL.md +176 -0
  230. package/src/drt-skills/4-implementation/drt-qa-generate-e2e-tests/checklist.md +33 -0
  231. package/src/drt-skills/4-implementation/drt-qa-generate-e2e-tests/customize.toml +41 -0
  232. package/src/drt-skills/4-implementation/drt-quick-dev/SKILL.md +13 -0
  233. package/src/drt-skills/4-implementation/drt-quick-dev/compile-epic-context.md +62 -0
  234. package/src/drt-skills/4-implementation/drt-quick-dev/customize.toml +83 -0
  235. package/src/drt-skills/4-implementation/drt-quick-dev/render.js +206 -0
  236. package/src/drt-skills/4-implementation/drt-quick-dev/spec-template.md +89 -0
  237. package/src/drt-skills/4-implementation/drt-quick-dev/step-01-clarify-and-route.md +103 -0
  238. package/src/drt-skills/4-implementation/drt-quick-dev/step-02-plan.md +47 -0
  239. package/src/drt-skills/4-implementation/drt-quick-dev/step-03-implement.md +45 -0
  240. package/src/drt-skills/4-implementation/drt-quick-dev/step-04-review.md +55 -0
  241. package/src/drt-skills/4-implementation/drt-quick-dev/step-05-present.md +78 -0
  242. package/src/drt-skills/4-implementation/drt-quick-dev/step-oneshot.md +78 -0
  243. package/src/drt-skills/4-implementation/drt-quick-dev/sync-sprint-status.md +19 -0
  244. package/src/drt-skills/4-implementation/drt-quick-dev/workflow.md +85 -0
  245. package/src/drt-skills/4-implementation/drt-retrospective/SKILL.md +1527 -0
  246. package/src/drt-skills/4-implementation/drt-retrospective/customize.toml +41 -0
  247. package/src/drt-skills/4-implementation/drt-sprint-planning/SKILL.md +319 -0
  248. package/src/drt-skills/4-implementation/drt-sprint-planning/checklist.md +34 -0
  249. package/src/drt-skills/4-implementation/drt-sprint-planning/customize.toml +41 -0
  250. package/src/drt-skills/4-implementation/drt-sprint-planning/sprint-status-template.yaml +69 -0
  251. package/src/drt-skills/4-implementation/drt-sprint-status/SKILL.md +311 -0
  252. package/src/drt-skills/4-implementation/drt-sprint-status/customize.toml +41 -0
  253. package/src/drt-skills/module-help.csv +32 -0
  254. package/src/drt-skills/module.yaml +95 -0
  255. package/src/scripts/lib/load-toml.js +43 -0
  256. package/src/scripts/lib/merge-utils.js +89 -0
  257. package/src/scripts/memlog.js +184 -0
  258. package/src/scripts/package.json +7 -0
  259. package/src/scripts/resolve-config.js +56 -0
  260. package/src/scripts/resolve-customization.js +77 -0
  261. package/tools/installer/cli-utils.js +53 -0
  262. package/tools/installer/commands/install.js +149 -0
  263. package/tools/installer/commands/status.js +65 -0
  264. package/tools/installer/commands/uninstall.js +167 -0
  265. package/tools/installer/core/config.js +73 -0
  266. package/tools/installer/core/existing-install.js +121 -0
  267. package/tools/installer/core/install-paths.js +132 -0
  268. package/tools/installer/core/installer.js +1762 -0
  269. package/tools/installer/core/legacy-warnings.js +151 -0
  270. package/tools/installer/core/manifest-generator.js +859 -0
  271. package/tools/installer/core/manifest.js +433 -0
  272. package/tools/installer/core/uv-check.js +97 -0
  273. package/tools/installer/core/wsl-node-check.js +109 -0
  274. package/tools/installer/drt-cli.js +104 -0
  275. package/tools/installer/file-ops.js +204 -0
  276. package/tools/installer/fs-native.js +116 -0
  277. package/tools/installer/ide/_config-driven.js +972 -0
  278. package/tools/installer/ide/manager.js +324 -0
  279. package/tools/installer/ide/platform-codes.js +80 -0
  280. package/tools/installer/ide/platform-codes.yaml +343 -0
  281. package/tools/installer/ide/shared/installed-skills.js +50 -0
  282. package/tools/installer/ide/shared/path-utils.js +217 -0
  283. package/tools/installer/ide/shared/skill-manifest.js +57 -0
  284. package/tools/installer/install-messages.yaml +29 -0
  285. package/tools/installer/list-options.js +210 -0
  286. package/tools/installer/message-loader.js +83 -0
  287. package/tools/installer/modules/channel-plan.js +203 -0
  288. package/tools/installer/modules/channel-resolver.js +241 -0
  289. package/tools/installer/modules/custom-module-manager.js +918 -0
  290. package/tools/installer/modules/external-manager.js +667 -0
  291. package/tools/installer/modules/module-help-schema.js +13 -0
  292. package/tools/installer/modules/official-modules.js +2257 -0
  293. package/tools/installer/modules/plugin-resolver.js +398 -0
  294. package/tools/installer/modules/version-resolver.js +336 -0
  295. package/tools/installer/project-root.js +224 -0
  296. package/tools/installer/prompts.js +852 -0
  297. package/tools/installer/set-overrides.js +330 -0
  298. package/tools/installer/ui.js +2090 -0
  299. 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 };