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,918 @@
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 prompts = require('../prompts');
6
+
7
+ function quoteCustomRef(ref) {
8
+ if (typeof ref !== 'string' || !/^[\w.\-+/]+$/.test(ref)) {
9
+ throw new Error(`Unsafe ref name: ${JSON.stringify(ref)}`);
10
+ }
11
+ return `"${ref}"`;
12
+ }
13
+
14
+ function isLocalSourcePath(input) {
15
+ return (
16
+ input.startsWith('/') ||
17
+ input.startsWith('./') ||
18
+ input.startsWith('../') ||
19
+ input.startsWith('.\\') ||
20
+ input.startsWith('..\\') ||
21
+ input.startsWith('~') ||
22
+ path.win32.isAbsolute(input)
23
+ );
24
+ }
25
+
26
+ /**
27
+ * Manages custom modules installed from user-provided sources.
28
+ * Supports any Git host (GitHub, GitLab, Bitbucket, self-hosted) and local file paths.
29
+ * Validates input, clones repos, reads .claude-plugin/marketplace.json, resolves plugins.
30
+ */
31
+ class CustomModuleManager {
32
+ /** @type {Map<string, Object>} Shared across all instances: module code -> ResolvedModule */
33
+ static _resolutionCache = new Map();
34
+ /** @type {Set<string>} Repo roots refreshed in the current process (dedupe quick-update fetches). */
35
+ static _refreshedRepoPaths = new Set();
36
+ /** @type {Map<string, Promise<void>>} In-flight refresh operations keyed by repo path. */
37
+ static _refreshInFlight = new Map();
38
+
39
+ // ─── Source Parsing ───────────────────────────────────────────────────────
40
+
41
+ /**
42
+ * Parse a user-provided source input into a structured descriptor.
43
+ * Accepts local file paths, HTTPS Git URLs, HTTP Git URLs, and SSH Git URLs.
44
+ * For HTTPS/HTTP URLs with deep paths (e.g., /tree/main/subdir), extracts the subdir.
45
+ * The original protocol (http or https) is preserved in the returned cloneUrl.
46
+ *
47
+ * @param {string} input - URL or local file path
48
+ * @returns {Object} Parsed source descriptor:
49
+ * { type: 'url'|'local', cloneUrl, subdir, localPath, cacheKey, displayName, isValid, error }
50
+ */
51
+ parseSource(input) {
52
+ if (!input || typeof input !== 'string') {
53
+ return {
54
+ type: null,
55
+ cloneUrl: null,
56
+ subdir: null,
57
+ localPath: null,
58
+ cacheKey: null,
59
+ displayName: null,
60
+ isValid: false,
61
+ error: 'Source is required',
62
+ };
63
+ }
64
+
65
+ const trimmedRaw = input.trim();
66
+ if (!trimmedRaw) {
67
+ return {
68
+ type: null,
69
+ cloneUrl: null,
70
+ subdir: null,
71
+ localPath: null,
72
+ cacheKey: null,
73
+ displayName: null,
74
+ isValid: false,
75
+ error: 'Source is required',
76
+ };
77
+ }
78
+
79
+ // Extract optional @<tag-or-branch> suffix from the end of the input.
80
+ // Semver-valid characters: letters, digits, dot, hyphen, underscore, plus, slash.
81
+ // Raw commit SHAs are NOT supported here — `git clone --branch` can't take
82
+ // them; use --pin at the module level or check out the SHA manually.
83
+ // Only strip when the tail looks like a ref, so we don't disturb
84
+ // URLs without a version spec or the SSH protocol's `git@host:...` prefix.
85
+ let trimmed = trimmedRaw;
86
+ let versionSuffix = null;
87
+ const lastAt = trimmedRaw.lastIndexOf('@');
88
+ // Skip if @ is part of git@github.com:... (first char cannot be stripped as version)
89
+ // and skip if @ appears before the path rather than after a ref-shaped tail.
90
+ if (lastAt > 0) {
91
+ const candidate = trimmedRaw.slice(lastAt + 1);
92
+ const before = trimmedRaw.slice(0, lastAt);
93
+ // candidate must be ref-shaped and must not itself look like a URL / SSH host
94
+ if (/^[\w.\-+/]+$/.test(candidate) && !candidate.includes(':')) {
95
+ // Avoid consuming the @ in `git@host:owner/repo` — `before` wouldn't end with a path separator
96
+ // in that case. Require that the @ comes after the host/path, not inside the auth segment.
97
+ // Rule: the @ is a version suffix only if `before` looks like a complete URL or local path.
98
+ const beforeLooksLikeRepo = isLocalSourcePath(before) || /^https?:\/\//i.test(before) || /^git@[^:]+:.+/.test(before);
99
+ if (beforeLooksLikeRepo) {
100
+ versionSuffix = candidate;
101
+ trimmed = before;
102
+ }
103
+ }
104
+ }
105
+
106
+ // Local path detection: POSIX, Windows, relative, or home-relative.
107
+ if (isLocalSourcePath(trimmed)) {
108
+ if (versionSuffix) {
109
+ return {
110
+ type: 'local',
111
+ cloneUrl: null,
112
+ subdir: null,
113
+ localPath: null,
114
+ cacheKey: null,
115
+ displayName: null,
116
+ isValid: false,
117
+ error: 'Local paths do not support @version suffixes',
118
+ };
119
+ }
120
+ return this._parseLocalPath(trimmed);
121
+ }
122
+
123
+ // SSH URL: git@host:owner/repo.git
124
+ const sshMatch = trimmed.match(/^git@([^:]+):(.+?)\/([^/.]+?)(?:\.git)?$/);
125
+ if (sshMatch) {
126
+ const [, host, owner, repo] = sshMatch;
127
+ return {
128
+ type: 'url',
129
+ cloneUrl: trimmed,
130
+ subdir: null,
131
+ localPath: null,
132
+ version: versionSuffix || null,
133
+ rawInput: trimmedRaw,
134
+ cacheKey: `${host}/${owner}/${repo}`,
135
+ displayName: `${owner}/${repo}`,
136
+ isValid: true,
137
+ error: null,
138
+ };
139
+ }
140
+
141
+ // HTTPS/HTTP URL: generic handling for any Git host.
142
+ // We avoid host-specific parsing — `git clone` will accept whatever URL the
143
+ // user provides. We only need to (a) separate an optional browser-style
144
+ // subdir suffix from the clone URL, (b) extract any embedded ref
145
+ // (branch/tag) from deep-path URLs, and (c) derive a cache key / display
146
+ // name from the path. The original protocol (http or https) is preserved.
147
+ if (/^https?:\/\//i.test(trimmed)) {
148
+ let url;
149
+ try {
150
+ url = new URL(trimmed);
151
+ } catch {
152
+ url = null;
153
+ }
154
+
155
+ if (url && url.host) {
156
+ const host = url.host;
157
+ let repoPath = url.pathname.replace(/^\/+/, '').replace(/\/+$/, '');
158
+ let subdir = null;
159
+ let urlRef = null; // branch/tag/commit extracted from deep-path URLs
160
+
161
+ // Detect browser-style deep-path patterns that embed a ref
162
+ // (branch/tag/commit) and optional subdirectory. These appear
163
+ // across many hosts:
164
+ // GitHub /<repo>/tree|blob/<ref>[/<subdir>]
165
+ // GitLab /<repo>/-/tree|blob/<ref>[/<subdir>]
166
+ // Gitea /<repo>/src/<ref>[/<subdir>]
167
+ // Gitea /<repo>/src/(branch|commit|tag)/<ref>[/<subdir>]
168
+ // Group 1 = repo path prefix, Group 2 = ref, Group 3 = subdir (optional).
169
+ const deepPathPatterns = [
170
+ /^(.+?)\/(?:-\/)?(?:tree|blob)\/([^/]+)(?:\/(.+))?$/,
171
+ /^(.+?)\/src\/(?:branch\/|commit\/|tag\/)?([^/]+)(?:\/(.+))?$/,
172
+ ];
173
+ for (const pattern of deepPathPatterns) {
174
+ const match = repoPath.match(pattern);
175
+ if (match) {
176
+ repoPath = match[1];
177
+ if (match[2]) urlRef = match[2];
178
+ if (match[3]) {
179
+ const cleaned = match[3].replace(/\/+$/, '');
180
+ if (cleaned) subdir = cleaned;
181
+ }
182
+ break;
183
+ }
184
+ }
185
+
186
+ // Some hosts use ?path=/subdir on browse links to point at a file or
187
+ // directory. Honor it when no deep-path marker matched above.
188
+ if (!subdir) {
189
+ const pathParam = url.searchParams.get('path');
190
+ if (pathParam) {
191
+ const cleaned = pathParam.replace(/^\/+/, '').replace(/\/+$/, '');
192
+ if (cleaned) subdir = cleaned;
193
+ }
194
+ }
195
+
196
+ // Strip a single trailing .git for a stable cacheKey/displayName.
197
+ const repoPathClean = repoPath.replace(/\.git$/i, '');
198
+ if (!repoPathClean) {
199
+ return {
200
+ type: null,
201
+ cloneUrl: null,
202
+ subdir: null,
203
+ localPath: null,
204
+ cacheKey: null,
205
+ displayName: null,
206
+ isValid: false,
207
+ error: 'Not a valid Git URL or local path',
208
+ };
209
+ }
210
+
211
+ const cloneUrl = `${url.protocol}//${host}/${repoPathClean}`;
212
+ const cacheKey = `${host}/${repoPathClean}`;
213
+
214
+ // Display name: prefer "<owner>/<repo>" using the last two meaningful
215
+ // path segments.
216
+ const segments = repoPathClean.split('/').filter(Boolean);
217
+ const repoSeg = segments.at(-1);
218
+ const ownerSeg = segments.at(-2);
219
+ const displayName = ownerSeg ? `${ownerSeg}/${repoSeg}` : repoSeg;
220
+
221
+ // Precedence: explicit @version suffix > URL /tree/<ref> path segment.
222
+ const version = versionSuffix || urlRef || null;
223
+
224
+ return {
225
+ type: 'url',
226
+ cloneUrl,
227
+ subdir,
228
+ localPath: null,
229
+ version,
230
+ rawInput: trimmedRaw,
231
+ cacheKey,
232
+ displayName,
233
+ isValid: true,
234
+ error: null,
235
+ };
236
+ }
237
+ }
238
+
239
+ return {
240
+ type: null,
241
+ cloneUrl: null,
242
+ subdir: null,
243
+ localPath: null,
244
+ cacheKey: null,
245
+ displayName: null,
246
+ isValid: false,
247
+ error: 'Not a valid Git URL or local path',
248
+ };
249
+ }
250
+
251
+ /**
252
+ * Parse a local filesystem path.
253
+ * @param {string} rawPath - Path string (may contain ~ for home)
254
+ * @returns {Object} Parsed source descriptor
255
+ */
256
+ _parseLocalPath(rawPath) {
257
+ const expanded = rawPath.startsWith('~') ? path.join(os.homedir(), rawPath.slice(1)) : rawPath;
258
+ const resolved = path.resolve(expanded);
259
+
260
+ if (!fs.pathExistsSync(resolved)) {
261
+ return {
262
+ type: 'local',
263
+ cloneUrl: null,
264
+ subdir: null,
265
+ localPath: resolved,
266
+ cacheKey: null,
267
+ displayName: path.basename(resolved),
268
+ isValid: false,
269
+ error: `Path does not exist: ${resolved}`,
270
+ };
271
+ }
272
+
273
+ return {
274
+ type: 'local',
275
+ cloneUrl: null,
276
+ subdir: null,
277
+ localPath: resolved,
278
+ cacheKey: null,
279
+ displayName: path.basename(resolved),
280
+ isValid: true,
281
+ error: null,
282
+ };
283
+ }
284
+
285
+ // ─── Marketplace JSON ─────────────────────────────────────────────────────
286
+
287
+ /**
288
+ * Read .claude-plugin/marketplace.json from a local directory.
289
+ * @param {string} dirPath - Directory to read from
290
+ * @returns {Object|null} Parsed marketplace.json or null if not found
291
+ */
292
+ async readMarketplaceJsonFromDisk(dirPath) {
293
+ const marketplacePath = path.join(dirPath, '.claude-plugin', 'marketplace.json');
294
+ if (!(await fs.pathExists(marketplacePath))) return null;
295
+ try {
296
+ return JSON.parse(await fs.readFile(marketplacePath, 'utf8'));
297
+ } catch {
298
+ return null;
299
+ }
300
+ }
301
+
302
+ // ─── Discovery ────────────────────────────────────────────────────────────
303
+
304
+ /**
305
+ * Discover modules from pre-read marketplace.json data.
306
+ * @param {Object} marketplaceData - Parsed marketplace.json content
307
+ * @param {string|null} sourceUrl - Source URL for tracking (null for local paths)
308
+ * @returns {Array<Object>} Normalized plugin list
309
+ */
310
+ async discoverModules(marketplaceData, sourceUrl) {
311
+ const plugins = marketplaceData?.plugins;
312
+
313
+ if (!Array.isArray(plugins) || plugins.length === 0) {
314
+ throw new Error('marketplace.json contains no plugins');
315
+ }
316
+
317
+ return plugins.map((plugin) => this._normalizeCustomModule(plugin, sourceUrl, marketplaceData));
318
+ }
319
+
320
+ // ─── Source Resolution ────────────────────────────────────────────────────
321
+
322
+ /**
323
+ * High-level coordinator: parse input, clone if URL, determine discovery vs direct mode.
324
+ * @param {string} input - URL or local path
325
+ * @param {Object} [options] - Options passed to cloneRepo
326
+ * @returns {Object} { parsed, rootDir, repoPath, sourceUrl, marketplace, mode: 'discovery'|'direct' }
327
+ */
328
+ async resolveSource(input, options = {}) {
329
+ const parsed = this.parseSource(input);
330
+ if (!parsed.isValid) throw new Error(parsed.error);
331
+
332
+ let rootDir;
333
+ let repoPath;
334
+ let sourceUrl;
335
+
336
+ if (parsed.type === 'local') {
337
+ rootDir = parsed.localPath;
338
+ repoPath = null;
339
+ sourceUrl = null;
340
+ } else {
341
+ repoPath = await this.cloneRepo(input, options);
342
+ sourceUrl = parsed.cloneUrl;
343
+ rootDir = parsed.subdir ? path.join(repoPath, parsed.subdir) : repoPath;
344
+
345
+ if (parsed.subdir && !(await fs.pathExists(rootDir))) {
346
+ throw new Error(`Subdirectory '${parsed.subdir}' not found in cloned repository`);
347
+ }
348
+ }
349
+
350
+ const marketplace = await this.readMarketplaceJsonFromDisk(rootDir);
351
+ const mode = marketplace ? 'discovery' : 'direct';
352
+
353
+ return { parsed, rootDir, repoPath, sourceUrl, marketplace, mode };
354
+ }
355
+
356
+ // ─── Clone ────────────────────────────────────────────────────────────────
357
+
358
+ /**
359
+ * Get the cache directory for custom modules.
360
+ * @returns {string} Path to the custom modules cache directory
361
+ */
362
+ getCacheDir() {
363
+ return path.join(os.homedir(), '.drt', 'cache', 'custom-modules');
364
+ }
365
+
366
+ /**
367
+ * Clone a custom module repository to cache.
368
+ * Supports any Git host (GitHub, GitLab, Bitbucket, self-hosted, etc.).
369
+ * @param {string} sourceInput - Git URL (HTTPS, HTTP, or SSH)
370
+ * @param {Object} [options] - Clone options
371
+ * @param {boolean} [options.silent] - Suppress spinner output
372
+ * @param {boolean} [options.skipInstall] - Skip npm install (for browsing before user confirms)
373
+ * @returns {string} Path to the cloned repository
374
+ */
375
+ async cloneRepo(sourceInput, options = {}) {
376
+ const parsed = this.parseSource(sourceInput);
377
+ if (!parsed.isValid) throw new Error(parsed.error);
378
+ if (parsed.type === 'local') throw new Error('cloneRepo does not accept local paths');
379
+
380
+ const cacheDir = this.getCacheDir();
381
+ const repoCacheDir = path.join(cacheDir, ...parsed.cacheKey.split('/'));
382
+ const silent = options.silent || false;
383
+ const displayName = parsed.displayName;
384
+
385
+ // Pin override: --pin CODE=TAG resolved at module-selection time overrides
386
+ // any @version suffix present in the URL.
387
+ const effectiveVersion = options.pinOverride || parsed.version || null;
388
+
389
+ await fs.ensureDir(path.dirname(repoCacheDir));
390
+
391
+ const createSpinner = async () => {
392
+ if (silent) {
393
+ return { start() {}, stop() {}, error() {} };
394
+ }
395
+ return await prompts.spinner();
396
+ };
397
+
398
+ // If an existing cache exists but was cloned at a different version, re-clone.
399
+ // Tracked via .drt-source.json's recorded version.
400
+ if (await fs.pathExists(repoCacheDir)) {
401
+ let cachedVersion = null;
402
+ try {
403
+ const existing = await fs.readJson(path.join(repoCacheDir, '.drt-source.json'));
404
+ cachedVersion = existing?.version || null;
405
+ } catch {
406
+ // no metadata; treat as mismatched to be safe if a version was requested
407
+ }
408
+ if ((effectiveVersion || null) !== (cachedVersion || null)) {
409
+ await fs.remove(repoCacheDir);
410
+ }
411
+ }
412
+
413
+ if (await fs.pathExists(repoCacheDir)) {
414
+ // Update existing clone (same version as before)
415
+ const fetchSpinner = await createSpinner();
416
+ fetchSpinner.start(`Updating ${displayName}...`);
417
+ try {
418
+ execSync('git fetch origin --depth 1', {
419
+ cwd: repoCacheDir,
420
+ stdio: ['ignore', 'pipe', 'pipe'],
421
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
422
+ });
423
+ if (effectiveVersion) {
424
+ // Fetch the ref as either a tag or a branch — `origin <ref>` works
425
+ // for both, whereas `origin tag <ref>` fails for branch refs parsed
426
+ // out of /tree/<branch>/... URLs.
427
+ execSync(`git fetch --depth 1 origin ${quoteCustomRef(effectiveVersion)} --no-tags`, {
428
+ cwd: repoCacheDir,
429
+ stdio: ['ignore', 'pipe', 'pipe'],
430
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
431
+ });
432
+ execSync(`git checkout --quiet FETCH_HEAD`, {
433
+ cwd: repoCacheDir,
434
+ stdio: ['ignore', 'pipe', 'pipe'],
435
+ });
436
+ } else {
437
+ // Resolve the default branch (origin/HEAD) and fetch it explicitly.
438
+ // With shallow clones, `origin/HEAD` is stale and `git reset --hard
439
+ // origin/HEAD` never picks up new commits on the default branch.
440
+ let defaultBranch = 'main';
441
+ try {
442
+ defaultBranch = execSync('git symbolic-ref refs/remotes/origin/HEAD --short', {
443
+ cwd: repoCacheDir,
444
+ stdio: 'pipe',
445
+ })
446
+ .toString()
447
+ .trim()
448
+ .replace('origin/', '');
449
+ } catch {
450
+ // Fallback if origin/HEAD is not set
451
+ }
452
+ execSync(`git fetch --depth 1 origin ${quoteCustomRef(defaultBranch)}`, {
453
+ cwd: repoCacheDir,
454
+ stdio: ['ignore', 'pipe', 'pipe'],
455
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
456
+ });
457
+ execSync(`git reset --hard origin/${quoteCustomRef(defaultBranch)}`, {
458
+ cwd: repoCacheDir,
459
+ stdio: ['ignore', 'pipe', 'pipe'],
460
+ });
461
+ }
462
+ fetchSpinner.stop(`Updated ${displayName}`);
463
+ } catch {
464
+ // Fetch failed against an existing cache — most often the remote is
465
+ // unreachable (network down, repo deleted/moved, auth revoked).
466
+ // Preserve the previous clone so re-deploy still works from cached
467
+ // content; surface a warning so the user knows the cache is stale.
468
+ fetchSpinner.error(`Could not refresh ${displayName} — keeping cached copy`);
469
+ await prompts.log.warn(`Custom module ${displayName} was not refreshed (remote unreachable). Using cached copy.`);
470
+ }
471
+ }
472
+
473
+ if (!(await fs.pathExists(repoCacheDir))) {
474
+ const fetchSpinner = await createSpinner();
475
+ fetchSpinner.start(`Cloning ${displayName}${effectiveVersion ? ` @ ${effectiveVersion}` : ''}...`);
476
+ try {
477
+ if (effectiveVersion) {
478
+ execSync(`git clone --depth 1 --branch ${quoteCustomRef(effectiveVersion)} "${parsed.cloneUrl}" "${repoCacheDir}"`, {
479
+ stdio: ['ignore', 'pipe', 'pipe'],
480
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
481
+ });
482
+ } else {
483
+ execSync(`git clone --depth 1 "${parsed.cloneUrl}" "${repoCacheDir}"`, {
484
+ stdio: ['ignore', 'pipe', 'pipe'],
485
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
486
+ });
487
+ }
488
+ fetchSpinner.stop(`Cloned ${displayName}`);
489
+ } catch (error_) {
490
+ fetchSpinner.error(`Failed to clone ${displayName}`);
491
+ const refSuffix = effectiveVersion ? `@${effectiveVersion}` : '';
492
+ throw new Error(`Failed to clone ${parsed.cloneUrl}${refSuffix}: ${error_.message}`);
493
+ }
494
+ }
495
+
496
+ // Record the resolved SHA for the manifest writer.
497
+ let resolvedSha = null;
498
+ try {
499
+ resolvedSha = execSync('git rev-parse HEAD', { cwd: repoCacheDir, stdio: 'pipe' }).toString().trim();
500
+ } catch {
501
+ // swallow — a non-git repo (local path) wouldn't reach here anyway
502
+ }
503
+ // Best-effort: capture the remote default branch name so channel marker
504
+ // metadata for "next" reflects the actual tracked ref (not always "main").
505
+ let defaultRef = 'main';
506
+ if (!effectiveVersion) {
507
+ try {
508
+ const symbolic = execSync('git symbolic-ref --short refs/remotes/origin/HEAD', {
509
+ cwd: repoCacheDir,
510
+ stdio: 'pipe',
511
+ })
512
+ .toString()
513
+ .trim();
514
+ if (symbolic.startsWith('origin/')) {
515
+ defaultRef = symbolic.slice('origin/'.length) || defaultRef;
516
+ }
517
+ } catch {
518
+ // Fallback to previous marker value when symbolic ref is unavailable.
519
+ try {
520
+ const existingMarker = await fs.readJson(path.join(repoCacheDir, '.drt-channel.json'));
521
+ if (existingMarker?.channel === 'next' && typeof existingMarker.version === 'string' && existingMarker.version.trim()) {
522
+ defaultRef = existingMarker.version.trim();
523
+ }
524
+ } catch {
525
+ // Keep default fallback.
526
+ }
527
+ }
528
+ }
529
+
530
+ // Write source metadata for later URL reconstruction
531
+ const metadataPath = path.join(repoCacheDir, '.drt-source.json');
532
+ await fs.writeJson(metadataPath, {
533
+ cloneUrl: parsed.cloneUrl,
534
+ cacheKey: parsed.cacheKey,
535
+ displayName: parsed.displayName,
536
+ version: effectiveVersion || null,
537
+ rawInput: parsed.rawInput || sourceInput,
538
+ sha: resolvedSha,
539
+ clonedAt: new Date().toISOString(),
540
+ });
541
+ // Keep a channel marker in custom cache too so update paths that rely on
542
+ // channel metadata (same as official-module cache) can treat this clone as
543
+ // refreshable. URL + no explicit ref => next, explicit ref => pinned.
544
+ await fs.writeJson(path.join(repoCacheDir, '.drt-channel.json'), {
545
+ channel: effectiveVersion ? 'pinned' : 'next',
546
+ version: effectiveVersion || defaultRef,
547
+ sha: resolvedSha,
548
+ writtenAt: new Date().toISOString(),
549
+ });
550
+
551
+ // Install dependencies if package.json exists (skip during browsing/analysis)
552
+ const packageJsonPath = path.join(repoCacheDir, 'package.json');
553
+ if (!options.skipInstall && (await fs.pathExists(packageJsonPath))) {
554
+ const installSpinner = await createSpinner();
555
+ installSpinner.start(`Installing dependencies for ${displayName}...`);
556
+ try {
557
+ execSync('npm install --omit=dev --no-audit --no-fund --no-progress --legacy-peer-deps', {
558
+ cwd: repoCacheDir,
559
+ stdio: ['ignore', 'pipe', 'pipe'],
560
+ timeout: 120_000,
561
+ });
562
+ installSpinner.stop(`Installed dependencies for ${displayName}`);
563
+ } catch (error_) {
564
+ installSpinner.error(`Failed to install dependencies for ${displayName}`);
565
+ if (!silent) await prompts.log.warn(` ${error_.message}`);
566
+ }
567
+ }
568
+
569
+ return repoCacheDir;
570
+ }
571
+
572
+ // ─── Plugin Resolution ────────────────────────────────────────────────────
573
+
574
+ /**
575
+ * Resolve a plugin to determine installation strategy and module registration files.
576
+ * Results are cached in _resolutionCache keyed by module code.
577
+ * @param {string} repoPath - Absolute path to the cloned repository or local directory
578
+ * @param {Object} plugin - Raw plugin object from marketplace.json
579
+ * @param {string} [sourceUrl] - Original URL for manifest tracking (null for local)
580
+ * @param {string} [localPath] - Local source path for manifest tracking (null for URLs)
581
+ * @returns {Promise<Array<Object>>} Array of ResolvedModule objects
582
+ */
583
+ async resolvePlugin(repoPath, plugin, sourceUrl, localPath) {
584
+ const { PluginResolver } = require('./plugin-resolver');
585
+ const resolver = new PluginResolver();
586
+ const resolved = await resolver.resolve(repoPath, plugin);
587
+
588
+ // Read clone metadata (written by cloneRepo) so we can pick up the
589
+ // resolved git ref + SHA for manifest recording.
590
+ let cloneMetadata = null;
591
+ if (sourceUrl) {
592
+ try {
593
+ cloneMetadata = await fs.readJson(path.join(repoPath, '.drt-source.json'));
594
+ } catch {
595
+ // no metadata — local-source or legacy cache
596
+ }
597
+ }
598
+
599
+ // Stamp source info onto each resolved module for manifest tracking
600
+ for (const mod of resolved) {
601
+ if (sourceUrl) mod.repoUrl = sourceUrl;
602
+ if (localPath) mod.localPath = localPath;
603
+ if (cloneMetadata) {
604
+ mod.cloneRef = cloneMetadata.version || null;
605
+ mod.cloneSha = cloneMetadata.sha || null;
606
+ mod.rawInput = cloneMetadata.rawInput || null;
607
+ }
608
+ CustomModuleManager._resolutionCache.set(mod.code, mod);
609
+ }
610
+
611
+ return resolved;
612
+ }
613
+
614
+ /**
615
+ * Get a cached resolution result by module code.
616
+ * @param {string} moduleCode - Module code to look up
617
+ * @returns {Object|null} ResolvedModule or null if not cached
618
+ */
619
+ getResolution(moduleCode) {
620
+ return CustomModuleManager._resolutionCache.get(moduleCode) || null;
621
+ }
622
+
623
+ // ─── Source Finding ───────────────────────────────────────────────────────
624
+
625
+ /**
626
+ * Find the module source path within a cached or local source directory.
627
+ * @param {string} sourceInput - Git URL or local path (used to locate cached clone)
628
+ * @param {string} [pluginSource] - Plugin source path from marketplace.json
629
+ * @returns {string|null} Path to directory containing module.yaml
630
+ */
631
+ async findModuleSource(sourceInput, pluginSource) {
632
+ const parsed = this.parseSource(sourceInput);
633
+ if (!parsed.isValid) return null;
634
+
635
+ let baseDir;
636
+ if (parsed.type === 'local') {
637
+ baseDir = parsed.localPath;
638
+ } else {
639
+ baseDir = path.join(this.getCacheDir(), ...parsed.cacheKey.split('/'));
640
+ }
641
+
642
+ if (!(await fs.pathExists(baseDir))) return null;
643
+
644
+ // Try plugin source path first (e.g., "./src/pro-skills")
645
+ if (pluginSource) {
646
+ const sourcePath = path.join(baseDir, pluginSource);
647
+ const moduleYaml = path.join(sourcePath, 'module.yaml');
648
+ if (await fs.pathExists(moduleYaml)) {
649
+ return sourcePath;
650
+ }
651
+ }
652
+
653
+ // Fallback: search skills/ and src/ directories
654
+ for (const dir of ['skills', 'src']) {
655
+ const rootCandidate = path.join(baseDir, dir, 'module.yaml');
656
+ if (await fs.pathExists(rootCandidate)) {
657
+ return path.dirname(rootCandidate);
658
+ }
659
+ const dirPath = path.join(baseDir, dir);
660
+ if (await fs.pathExists(dirPath)) {
661
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
662
+ for (const entry of entries) {
663
+ if (entry.isDirectory()) {
664
+ const subCandidate = path.join(dirPath, entry.name, 'module.yaml');
665
+ if (await fs.pathExists(subCandidate)) {
666
+ return path.dirname(subCandidate);
667
+ }
668
+ }
669
+ }
670
+ }
671
+ }
672
+
673
+ // Check base directory root
674
+ const rootCandidate = path.join(baseDir, 'module.yaml');
675
+ if (await fs.pathExists(rootCandidate)) {
676
+ return baseDir;
677
+ }
678
+
679
+ return null;
680
+ }
681
+
682
+ /**
683
+ * Find module source by module code, searching the custom cache.
684
+ * Handles both new 3-level cache structure (host/owner/repo) and
685
+ * legacy 2-level structure (owner/repo).
686
+ * @param {string} moduleCode - Module code to search for
687
+ * @param {Object} [options] - Options
688
+ * @returns {string|null} Path to the module source or null
689
+ */
690
+ async findModuleSourceByCode(moduleCode, options = {}) {
691
+ // Check resolution cache first (populated by resolvePlugin)
692
+ const resolved = CustomModuleManager._resolutionCache.get(moduleCode);
693
+ if (resolved) {
694
+ // For strategies 1-2: the common parent or setup skill's parent has the module files
695
+ if (resolved.moduleYamlPath) {
696
+ return path.dirname(resolved.moduleYamlPath);
697
+ }
698
+ // For strategy 5 (synthesized): return the first skill's parent as a reference path
699
+ if (resolved.skillPaths && resolved.skillPaths.length > 0) {
700
+ return path.dirname(resolved.skillPaths[0]);
701
+ }
702
+ }
703
+
704
+ const cacheDir = this.getCacheDir();
705
+ if (!(await fs.pathExists(cacheDir))) return null;
706
+
707
+ // Search through all cached repo roots
708
+ try {
709
+ const { PluginResolver } = require('./plugin-resolver');
710
+ const resolver = new PluginResolver();
711
+ const repoRoots = await this._findCacheRepoRoots(cacheDir);
712
+
713
+ for (const { repoPath, metadata } of repoRoots) {
714
+ // Quick-update path: refresh URL-backed cached repos before reading
715
+ // files from them so re-deploy uses latest commits for `next` and
716
+ // the pinned ref for `pinned`.
717
+ if (options.drtDir && metadata?.rawInput) {
718
+ await this._refreshRepoCacheOnce(repoPath, metadata);
719
+ }
720
+
721
+ // Check marketplace.json for matching module code
722
+ const marketplacePath = path.join(repoPath, '.claude-plugin', 'marketplace.json');
723
+ if (!(await fs.pathExists(marketplacePath))) continue;
724
+
725
+ try {
726
+ const data = JSON.parse(await fs.readFile(marketplacePath, 'utf8'));
727
+ for (const plugin of data.plugins || []) {
728
+ // Direct name match (legacy behavior)
729
+ if (plugin.name === moduleCode) {
730
+ const sourcePath = plugin.source ? path.join(repoPath, plugin.source) : repoPath;
731
+ const moduleYaml = path.join(sourcePath, 'module.yaml');
732
+ if (await fs.pathExists(moduleYaml)) {
733
+ return sourcePath;
734
+ }
735
+ }
736
+
737
+ // Resolve plugin to check if any module.yaml code matches
738
+ if (plugin.skills && plugin.skills.length > 0) {
739
+ try {
740
+ const resolvedMods = await resolver.resolve(repoPath, plugin);
741
+ for (const mod of resolvedMods) {
742
+ if (mod.code === moduleCode) {
743
+ // Use metadata for URL reconstruction instead of deriving from path
744
+ mod.repoUrl = metadata?.cloneUrl || null;
745
+ CustomModuleManager._resolutionCache.set(mod.code, mod);
746
+ if (mod.moduleYamlPath) {
747
+ return path.dirname(mod.moduleYamlPath);
748
+ }
749
+ if (mod.skillPaths && mod.skillPaths.length > 0) {
750
+ return path.dirname(mod.skillPaths[0]);
751
+ }
752
+ }
753
+ }
754
+ } catch {
755
+ // Skip unresolvable plugins
756
+ }
757
+ }
758
+ }
759
+ } catch {
760
+ // Skip malformed marketplace.json
761
+ }
762
+ }
763
+ } catch {
764
+ // Cache doesn't exist or is inaccessible
765
+ }
766
+
767
+ // Fallback: check manifest for localPath (local-source modules not in cache)
768
+ return this._findLocalSourceFromManifest(moduleCode, options);
769
+ }
770
+
771
+ /**
772
+ * Refresh one cached repo at most once per process with in-flight dedupe.
773
+ * Prevents concurrent quick-update callers from racing the same cache path.
774
+ * @param {string} repoPath - Absolute cache repo path
775
+ * @param {Object} metadata - Parsed .drt-source.json metadata
776
+ */
777
+ async _refreshRepoCacheOnce(repoPath, metadata) {
778
+ if (CustomModuleManager._refreshedRepoPaths.has(repoPath)) return;
779
+
780
+ const existing = CustomModuleManager._refreshInFlight.get(repoPath);
781
+ if (existing) {
782
+ await existing;
783
+ return;
784
+ }
785
+
786
+ const refreshPromise = (async () => {
787
+ try {
788
+ await this.cloneRepo(metadata.rawInput, {
789
+ silent: true,
790
+ pinOverride: metadata.version || undefined,
791
+ });
792
+ CustomModuleManager._refreshedRepoPaths.add(repoPath);
793
+ } catch (error_) {
794
+ // cloneRepo only throws here for unrecoverable cases (no cache present
795
+ // and a fresh clone failed, or an unexpected internal error). The
796
+ // common "remote unreachable but cache exists" case is handled inside
797
+ // cloneRepo, which preserves the clone and returns normally. Reaching
798
+ // this catch means we have no usable cache — surface a warning so the
799
+ // failure isn't silent.
800
+ await prompts.log.warn(`Refresh of cached custom module at ${path.basename(repoPath)} failed: ${error_?.message || error_}`);
801
+ } finally {
802
+ CustomModuleManager._refreshInFlight.delete(repoPath);
803
+ }
804
+ })();
805
+
806
+ CustomModuleManager._refreshInFlight.set(repoPath, refreshPromise);
807
+ await refreshPromise;
808
+ }
809
+
810
+ /**
811
+ * Check the installation manifest for a localPath entry for this module.
812
+ * Used as fallback when the module was installed from a local source (no cache entry).
813
+ * Returns the path only if it still exists on disk; never removes installed files.
814
+ * @param {string} moduleCode - Module code to search for
815
+ * @param {Object} [options] - Options (must include drtDir or will search common locations)
816
+ * @returns {string|null} Path to the local module source or null
817
+ */
818
+ async _findLocalSourceFromManifest(moduleCode, options = {}) {
819
+ try {
820
+ const { Manifest } = require('../core/manifest');
821
+ const manifestObj = new Manifest();
822
+
823
+ // Try to find drtDir from options or common locations
824
+ const drtDir = options.drtDir;
825
+ if (!drtDir) return null;
826
+
827
+ const manifestData = await manifestObj.read(drtDir);
828
+ if (!manifestData?.modulesDetailed) return null;
829
+
830
+ const moduleEntry = manifestData.modulesDetailed.find((m) => m.name === moduleCode);
831
+ if (!moduleEntry?.localPath) return null;
832
+
833
+ // Only return the path if it still exists (source not removed)
834
+ if (await fs.pathExists(moduleEntry.localPath)) {
835
+ return moduleEntry.localPath;
836
+ }
837
+
838
+ return null;
839
+ } catch {
840
+ return null;
841
+ }
842
+ }
843
+
844
+ /**
845
+ * Recursively find repo root directories within the cache.
846
+ * A repo root is identified by containing .drt-source.json (new) or .claude-plugin/ (legacy).
847
+ * Handles both 3-level (host/owner/repo) and legacy 2-level (owner/repo) cache layouts.
848
+ * @param {string} dir - Directory to search
849
+ * @param {number} [depth=0] - Current recursion depth
850
+ * @param {number} [maxDepth=4] - Maximum recursion depth
851
+ * @returns {Promise<Array<{repoPath: string, metadata: Object|null}>>}
852
+ */
853
+ async _findCacheRepoRoots(dir, depth = 0, maxDepth = 4) {
854
+ const results = [];
855
+
856
+ // Check if this directory is a repo root
857
+ const metadataPath = path.join(dir, '.drt-source.json');
858
+ const claudePluginDir = path.join(dir, '.claude-plugin');
859
+
860
+ if (await fs.pathExists(metadataPath)) {
861
+ try {
862
+ const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf8'));
863
+ results.push({ repoPath: dir, metadata });
864
+ } catch {
865
+ results.push({ repoPath: dir, metadata: null });
866
+ }
867
+ return results; // Don't recurse into repo contents
868
+ }
869
+ if (await fs.pathExists(claudePluginDir)) {
870
+ results.push({ repoPath: dir, metadata: null });
871
+ return results;
872
+ }
873
+
874
+ // Recurse into subdirectories
875
+ if (depth >= maxDepth) return results;
876
+ try {
877
+ const entries = await fs.readdir(dir, { withFileTypes: true });
878
+ for (const entry of entries) {
879
+ if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
880
+ const subResults = await this._findCacheRepoRoots(path.join(dir, entry.name), depth + 1, maxDepth);
881
+ results.push(...subResults);
882
+ }
883
+ } catch {
884
+ // Directory not readable
885
+ }
886
+ return results;
887
+ }
888
+
889
+ // ─── Normalization ────────────────────────────────────────────────────────
890
+
891
+ /**
892
+ * Normalize a plugin from marketplace.json to a consistent shape.
893
+ * @param {Object} plugin - Plugin object from marketplace.json
894
+ * @param {string|null} sourceUrl - Source URL (null for local paths)
895
+ * @param {Object} data - Full marketplace.json data
896
+ * @returns {Object} Normalized module info
897
+ */
898
+ _normalizeCustomModule(plugin, sourceUrl, data) {
899
+ return {
900
+ code: plugin.name,
901
+ name: plugin.name,
902
+ displayName: plugin.name,
903
+ description: plugin.description || '',
904
+ version: plugin.version || null,
905
+ author: plugin.author || data.owner || '',
906
+ url: sourceUrl || null,
907
+ source: plugin.source || null,
908
+ skills: plugin.skills || [],
909
+ rawPlugin: plugin,
910
+ type: 'custom',
911
+ trustTier: 'unverified',
912
+ builtIn: false,
913
+ isExternal: true,
914
+ };
915
+ }
916
+ }
917
+
918
+ module.exports = { CustomModuleManager };