@polymorphism-tech/morph-spec 8.47.0 → 9.1.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 (377) hide show
  1. package/CLAUDE.md +51 -43
  2. package/README.md +39 -38
  3. package/bin/morph-spec.js +41 -113
  4. package/bin/validate.js +1 -147
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +1 -1
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/CLAUDE.md +41 -101
  9. package/framework/MORPH.md +95 -252
  10. package/framework/agents.json +86 -21
  11. package/framework/commands/morph-apply.md +5 -28
  12. package/framework/commands/morph-archive.md +1 -1
  13. package/framework/commands/morph-hotfix.md +8 -8
  14. package/framework/commands/morph-infra.md +1 -1
  15. package/framework/commands/morph-preflight.md +2 -3
  16. package/framework/commands/morph-status.md +4 -4
  17. package/framework/docs/CLI.md +574 -286
  18. package/framework/evals/review-checklist.md +82 -41
  19. package/framework/hooks/README.md +10 -11
  20. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
  21. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
  22. package/framework/hooks/claude-code/core/chain.js +4 -65
  23. package/framework/hooks/claude-code/core/comment-guard.js +3 -17
  24. package/framework/hooks/claude-code/core/denylist.json +33 -3
  25. package/framework/hooks/claude-code/core/enforce-phase-writes.js +15 -63
  26. package/framework/hooks/claude-code/core/gate-guard.js +9 -78
  27. package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
  28. package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
  29. package/framework/hooks/claude-code/core/heavy-release.js +2 -13
  30. package/framework/hooks/claude-code/core/loop-detect.js +2 -31
  31. package/framework/hooks/claude-code/core/pre-bash-denylist.js +36 -2
  32. package/framework/hooks/claude-code/core/protect-spec-files.js +25 -297
  33. package/framework/hooks/claude-code/core/state-sync.js +15 -85
  34. package/framework/hooks/claude-code/core/telemetry-log.js +15 -96
  35. package/framework/hooks/claude-code/core/validate-completion.js +37 -147
  36. package/framework/hooks/claude-code/core/warn-bash-source-write.js +23 -0
  37. package/framework/hooks/claude-code/core/worktree-context.js +15 -114
  38. package/framework/hooks/claude-code/core/worktree-create.js +6 -70
  39. package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
  40. package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
  41. package/framework/hooks/claude-code/statusline.py +55 -41
  42. package/framework/hooks/package.json +3 -0
  43. package/framework/hooks/shared/activity-logger.js +9 -89
  44. package/framework/hooks/shared/agent-reap.js +2 -17
  45. package/framework/hooks/shared/build-check.js +11 -76
  46. package/framework/hooks/shared/claim-registry.js +9 -119
  47. package/framework/hooks/shared/command-tokens.js +168 -44
  48. package/framework/hooks/shared/comment-rules.js +5 -20
  49. package/framework/hooks/shared/comment-scanner.js +11 -43
  50. package/framework/hooks/shared/comment-stripper.js +11 -85
  51. package/framework/hooks/shared/e2e-check.js +0 -0
  52. package/framework/hooks/shared/errata.js +59 -0
  53. package/framework/hooks/shared/feature-locator.js +11 -95
  54. package/framework/hooks/shared/frontend-locations.js +4 -0
  55. package/framework/hooks/shared/gate-question.js +12 -0
  56. package/framework/hooks/shared/git-exclude.js +3 -20
  57. package/framework/hooks/shared/git-paths.js +8 -104
  58. package/framework/hooks/shared/graph-freshness.js +22 -166
  59. package/framework/hooks/shared/graph-refresh.js +21 -149
  60. package/framework/hooks/shared/heavy-command.js +4 -12
  61. package/framework/hooks/shared/hook-response.js +8 -83
  62. package/framework/hooks/shared/morph-cli.js +8 -52
  63. package/framework/hooks/shared/payload-utils.js +10 -132
  64. package/framework/hooks/shared/phase-utils.js +14 -72
  65. package/framework/hooks/shared/redact-secrets.js +4 -15
  66. package/framework/hooks/shared/reparse-scan.js +6 -66
  67. package/framework/hooks/shared/slice-progress.js +99 -0
  68. package/framework/hooks/shared/source-files.js +4 -17
  69. package/framework/hooks/shared/spawn-command.js +23 -0
  70. package/framework/hooks/shared/state-reader.js +51 -247
  71. package/framework/hooks/shared/state-writer.js +9 -54
  72. package/framework/hooks/shared/stdin-reader.js +1 -11
  73. package/framework/hooks/shared/telemetry-logger.js +7 -67
  74. package/framework/hooks/shared/transcript-usage.js +7 -29
  75. package/framework/hooks/shared/worktree-root.js +7 -29
  76. package/framework/mcp/registry.json +3 -4
  77. package/framework/phases.json +23 -39
  78. package/framework/rules/csharp-standards.md +1 -1
  79. package/framework/rules/frontend-standards.md +1 -0
  80. package/framework/rules/hangfire-standards.md +1 -1
  81. package/framework/rules/neon-standards.md +1 -1
  82. package/framework/schemas/config.schema.json +14 -9
  83. package/framework/schemas/feature.schema.json +15 -50
  84. package/framework/skills/README.md +3 -4
  85. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  86. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  87. package/framework/skills/core/morph-apply/SKILL.md +149 -222
  88. package/framework/skills/core/morph-hotfix/SKILL.md +33 -42
  89. package/framework/skills/core/morph-machine/SKILL.md +2 -2
  90. package/framework/skills/core/morph-media/SKILL.md +1 -1
  91. package/framework/skills/core/morph-plan/SKILL.md +137 -299
  92. package/framework/skills/core/morph-review/SKILL.md +111 -136
  93. package/framework/skills/core/morph-standards/SKILL.md +27 -39
  94. package/framework/skills/core/morph-uiux/SKILL.md +94 -152
  95. package/framework/standards/STANDARDS.json +109 -1219
  96. package/framework/standards/STANDARDS.meta.json +1319 -0
  97. package/framework/standards/ai-agents/agent-archetypes.md +2 -2
  98. package/framework/standards/ai-agents/agent-session.md +1 -1
  99. package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
  100. package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
  101. package/framework/standards/ai-agents/evals-with-cache.md +1 -2
  102. package/framework/standards/ai-agents/guardrails.md +1 -1
  103. package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
  104. package/framework/standards/ai-agents/observability-patterns.md +1 -1
  105. package/framework/standards/ai-agents/production.md +1 -1
  106. package/framework/standards/ai-agents/prompt-sources.md +2 -2
  107. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  108. package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
  109. package/framework/standards/ai-agents/setup.md +3 -3
  110. package/framework/standards/ai-agents/sweet-spot.md +2 -2
  111. package/framework/standards/ai-agents/tool-adapters.md +0 -2
  112. package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
  113. package/framework/standards/ai-agents/workflows.md +1 -1
  114. package/framework/standards/architecture/code-comments.md +7 -7
  115. package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
  116. package/framework/standards/backend/api/validation.md +1 -1
  117. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
  118. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
  119. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
  120. package/framework/standards/data/nosql/cache/redis.md +1 -1
  121. package/framework/standards/data/nosql/cosmos-db.md +3 -3
  122. package/framework/standards/frontend/admin-app/forms.md +1 -1
  123. package/framework/standards/frontend/design-system/art-direction.md +88 -0
  124. package/framework/standards/frontend/design-system/premium-finish.md +77 -2
  125. package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
  126. package/framework/standards/integration/mcp/mcp-tools.md +7 -7
  127. package/framework/templates/CLAUDE.md.core.template +3 -3
  128. package/framework/templates/README.md +2 -3
  129. package/framework/templates/REGISTRY.json +15 -179
  130. package/framework/templates/docs/plan.md +72 -0
  131. package/framework/templates/docs/ui-mockups.md +4 -0
  132. package/framework/templates/dotnet/ai-kit/README.md +1 -9
  133. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
  134. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
  135. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
  136. package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
  137. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
  138. package/package.json +1 -1
  139. package/src/commands/context/context-refresh.js +0 -16
  140. package/src/commands/dashboard/dashboard.js +2 -8
  141. package/src/commands/e2e/e2e.js +18 -105
  142. package/src/commands/graph/affected.js +0 -14
  143. package/src/commands/graph/build.js +11 -50
  144. package/src/commands/graph/explain.js +0 -16
  145. package/src/commands/graph/status.js +1 -12
  146. package/src/commands/mcp/mcp-setup.js +4 -62
  147. package/src/commands/observability/cost.js +17 -111
  148. package/src/commands/observability/telemetry.js +1 -14
  149. package/src/commands/project/branch.js +4 -45
  150. package/src/commands/project/claim.js +4 -48
  151. package/src/commands/project/doctor-procs.js +1 -1
  152. package/src/commands/project/doctor.js +53 -307
  153. package/src/commands/project/env.js +1 -27
  154. package/src/commands/project/fleet.js +6 -35
  155. package/src/commands/project/heavy.js +3 -3
  156. package/src/commands/project/init.js +8 -68
  157. package/src/commands/project/install-plugin-cmd.js +0 -1
  158. package/src/commands/project/setup-infra-cmd.js +2 -1
  159. package/src/commands/project/stack-scan.js +58 -0
  160. package/src/commands/project/standards.js +65 -72
  161. package/src/commands/project/status.js +28 -121
  162. package/src/commands/project/update.js +31 -223
  163. package/src/commands/project/worktree.js +109 -883
  164. package/src/commands/scaffold/scaffold.js +0 -25
  165. package/src/commands/state/advance.js +65 -120
  166. package/src/commands/state/approve.js +87 -229
  167. package/src/commands/state/archive.js +23 -139
  168. package/src/commands/state/auto-approval.js +65 -0
  169. package/src/commands/state/create.js +49 -109
  170. package/src/commands/state/delete.js +11 -63
  171. package/src/commands/state/finish.js +105 -494
  172. package/src/commands/state/gate-check.js +74 -183
  173. package/src/commands/state/gate-decision.js +8 -42
  174. package/src/commands/state/handoff.js +4 -31
  175. package/src/commands/state/list.js +21 -91
  176. package/src/commands/state/report-check.js +134 -338
  177. package/src/commands/state/retype.js +12 -28
  178. package/src/commands/validation/index.js +0 -3
  179. package/src/commands/validation/mutate.js +79 -19
  180. package/src/commands/validation/plan-check.js +81 -0
  181. package/src/commands/validation/validate-feature.js +52 -216
  182. package/src/commands/validation/verify.js +45 -160
  183. package/src/core/paths/output-schema.js +9 -155
  184. package/src/core/state/feature-identity.js +1 -25
  185. package/src/core/state/gate-order.js +2 -30
  186. package/src/core/state/lifecycle-audit.js +8 -100
  187. package/src/core/state/state-manager.js +32 -395
  188. package/src/core/templates/template-registry.js +2 -97
  189. package/src/core/templates/template-renderer.js +13 -93
  190. package/src/core/templates/template-seed.js +21 -0
  191. package/src/core/work-types.js +10 -45
  192. package/src/dashboard/app/App.jsx +1 -8
  193. package/src/dashboard/app/components/ActivityFeed.jsx +2 -8
  194. package/src/dashboard/app/components/CommandPalette.jsx +4 -11
  195. package/src/dashboard/app/components/DetailPanel.jsx +1 -5
  196. package/src/dashboard/app/components/EntityLink.jsx +0 -12
  197. package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
  198. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
  199. package/src/dashboard/app/components/Markdown.jsx +0 -9
  200. package/src/dashboard/app/components/PhaseRail.jsx +0 -11
  201. package/src/dashboard/app/components/SearchFilter.jsx +0 -4
  202. package/src/dashboard/app/components/Sidebar.jsx +0 -5
  203. package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
  204. package/src/dashboard/app/hooks/usePref.js +1 -5
  205. package/src/dashboard/app/lib/format.js +0 -2
  206. package/src/dashboard/app/lib/phases.js +3 -9
  207. package/src/dashboard/app/lib/project.js +0 -12
  208. package/src/dashboard/app/pages/Activity.jsx +1 -1
  209. package/src/dashboard/app/pages/AgentMap.jsx +1 -8
  210. package/src/dashboard/app/pages/FeatureDetail.jsx +139 -133
  211. package/src/dashboard/app/pages/Features.jsx +9 -16
  212. package/src/dashboard/app/pages/Guide.jsx +2 -3
  213. package/src/dashboard/app/pages/HookInspector.jsx +2 -23
  214. package/src/dashboard/app/pages/PhasePipeline.jsx +3 -7
  215. package/src/dashboard/app/pages/Portfolio.jsx +0 -1
  216. package/src/dashboard/app/pages/RulesMap.jsx +1 -16
  217. package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
  218. package/src/dashboard/app/pages/Standards.jsx +2 -17
  219. package/src/dashboard/scanner/activity-scanner.js +14 -83
  220. package/src/dashboard/scanner/agents-scanner.js +1 -23
  221. package/src/dashboard/scanner/docs-scanner.js +1 -7
  222. package/src/dashboard/scanner/feature-status.js +2 -5
  223. package/src/dashboard/scanner/features-scanner.js +92 -113
  224. package/src/dashboard/scanner/hooks-scanner.js +5 -28
  225. package/src/dashboard/scanner/index.js +0 -4
  226. package/src/dashboard/scanner/portfolio.js +5 -29
  227. package/src/dashboard/scanner/project-api.js +8 -54
  228. package/src/dashboard/scanner/rules-scanner.js +1 -2
  229. package/src/dashboard/scanner/skills-scanner.js +2 -12
  230. package/src/dashboard/scanner/standards-scanner.js +4 -7
  231. package/src/dashboard/vite-plugin-morph-data.js +5 -19
  232. package/src/lib/ai/ai-checks.js +7 -52
  233. package/src/lib/ai/ai-project-scan.js +14 -108
  234. package/src/lib/ai/code-audit.js +7 -63
  235. package/src/lib/ai/pin-drift.js +8 -51
  236. package/src/lib/ai/registry-audit.js +11 -90
  237. package/src/lib/ci/ci-verify.js +14 -13
  238. package/src/lib/ci/github-ci.js +9 -37
  239. package/src/lib/ci/workflow-scaffold.js +2 -10
  240. package/src/lib/detectors/claude-config-detector.js +13 -74
  241. package/src/lib/detectors/index.js +8 -59
  242. package/src/lib/e2e/compose-env.js +5 -66
  243. package/src/lib/e2e/stack-teardown.js +6 -40
  244. package/src/lib/generators/architecture-scanner.js +18 -156
  245. package/src/lib/generators/handoff-generator.js +37 -112
  246. package/src/lib/generators/scaffold-generator.js +5 -101
  247. package/src/lib/generators/settings-generator.js +11 -122
  248. package/src/lib/git/branch-classifier.js +11 -84
  249. package/src/lib/git/default-branch.js +14 -169
  250. package/src/lib/git/evidence-staleness.js +2 -1
  251. package/src/lib/git/gh.js +2 -17
  252. package/src/lib/git/pending-prs.js +6 -37
  253. package/src/lib/git/pr-files.js +3 -28
  254. package/src/lib/git/pr-index.js +3 -34
  255. package/src/lib/git/tree-fingerprint.js +17 -17
  256. package/src/lib/git/tree-state.js +6 -33
  257. package/src/lib/graph/god-nodes.js +2 -22
  258. package/src/lib/graph/graph-health.js +3 -24
  259. package/src/lib/graph/graph-loader.js +5 -56
  260. package/src/lib/graph/graphify-runtime.js +7 -37
  261. package/src/lib/graph/graphifyignore.js +8 -74
  262. package/src/lib/graph/sanitize.js +16 -136
  263. package/src/lib/heavy/cleanup.js +2 -6
  264. package/src/lib/installers/effects.js +8 -93
  265. package/src/lib/installers/mcp-installer.js +9 -169
  266. package/src/lib/installers/memory-installer.js +8 -33
  267. package/src/lib/installers/templates-reconcile.js +5 -60
  268. package/src/lib/mutation/commands.js +3 -2
  269. package/src/lib/mutation/dotnet-introspection.js +281 -0
  270. package/src/lib/mutation/plan.js +14 -6
  271. package/src/lib/mutation/runner.js +1 -1
  272. package/src/lib/mutation/subset.js +47 -13
  273. package/src/lib/plan/artifact-vocabulary.js +40 -0
  274. package/src/lib/plan/checks.js +208 -0
  275. package/src/lib/plan/plan-parser.js +204 -0
  276. package/src/lib/plan/plan-template.js +18 -0
  277. package/src/lib/projects/registry.js +7 -44
  278. package/src/lib/review-reports.js +204 -0
  279. package/src/lib/risk-detector.js +131 -274
  280. package/src/lib/specs/adr-index.js +38 -85
  281. package/src/lib/specs/design-seed.js +18 -0
  282. package/src/lib/specs/frontmatter.js +31 -0
  283. package/src/lib/specs/gfm-table.js +11 -0
  284. package/src/lib/specs/revocations.js +31 -102
  285. package/src/lib/specs/screen-checklist.js +56 -132
  286. package/src/lib/stack/dotnet-build-ignores.js +43 -0
  287. package/src/lib/stack/stack-profile.js +1 -28
  288. package/src/lib/stack/stack-scan.js +260 -0
  289. package/src/lib/stack-filter.js +4 -27
  290. package/src/lib/standards/registry.js +37 -0
  291. package/src/lib/standards/sections.js +47 -0
  292. package/src/lib/standards/verified-against.js +19 -191
  293. package/src/lib/tasks/eval-node.js +23 -179
  294. package/src/lib/tasks/linux-runner.js +18 -142
  295. package/src/lib/tasks/test-nodes.js +43 -313
  296. package/src/lib/tasks/test-runner.js +107 -447
  297. package/src/lib/telemetry/metrics.js +13 -103
  298. package/src/lib/telemetry/telemetry-reader.js +13 -238
  299. package/src/lib/telemetry/verify-event.js +4 -34
  300. package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
  301. package/src/lib/validators/code-budget/index.js +0 -7
  302. package/src/lib/validators/contracts/contract-compliance-validator.js +4 -26
  303. package/src/lib/validators/contracts/index.js +0 -6
  304. package/src/lib/validators/nextjs/index.js +0 -5
  305. package/src/lib/validators/nextjs/next-component-validator.js +4 -81
  306. package/src/lib/validators/packages/index.js +0 -6
  307. package/src/lib/validators/packages/package-validator.js +6 -56
  308. package/src/lib/validators/reuse/index.js +0 -8
  309. package/src/lib/validators/reuse/reuse-validator.js +24 -137
  310. package/src/lib/validators/reuse/symbol-scanner.js +6 -62
  311. package/src/lib/validators/traceability-check.js +54 -156
  312. package/src/lib/validators/validation-runner.js +20 -125
  313. package/src/lib/validators/verify-runner.js +152 -575
  314. package/src/lib/work-type-detector.js +5 -67
  315. package/src/lib/worktree/docker-audit.js +17 -137
  316. package/src/lib/worktree/env-allocator.js +10 -98
  317. package/src/lib/worktree/feature-truth.js +12 -115
  318. package/src/lib/worktree/finish-backup.js +32 -242
  319. package/src/lib/worktree/fleet-scanner.js +31 -126
  320. package/src/lib/worktree/ignored-triage.js +15 -91
  321. package/src/lib/worktree/vscode-settings.js +2 -3
  322. package/src/lib/worktree/worktree-audit.js +21 -213
  323. package/src/lib/worktree/worktree-include.js +3 -5
  324. package/src/scripts/global-install.js +3 -9
  325. package/src/scripts/install-plugin.js +1 -13
  326. package/src/scripts/setup-infra.js +17 -138
  327. package/src/utils/agents-installer.js +92 -272
  328. package/src/utils/banner.js +4 -25
  329. package/src/utils/claude-md-injector.js +1 -37
  330. package/src/utils/claude-settings-manager.js +1 -87
  331. package/src/utils/file-copier.js +32 -90
  332. package/src/utils/hooks-installer.js +26 -296
  333. package/src/utils/skills-installer.js +4 -102
  334. package/src/utils/version-checker.js +4 -83
  335. package/framework/commands/morph-proposal.md +0 -57
  336. package/framework/evals/trace-schema.md +0 -67
  337. package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
  338. package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
  339. package/framework/hooks/git/pre-commit/specs.sh +0 -50
  340. package/framework/hooks/shared/tasks-validator.js +0 -111
  341. package/framework/rules/morph-task-tracking.md +0 -54
  342. package/framework/rules/morph-workflow.md +0 -244
  343. package/framework/schemas/tasks.schema.json +0 -79
  344. package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
  345. package/framework/skills/core/morph-implement/SKILL.md +0 -194
  346. package/framework/skills/core/morph-proposal/SKILL.md +0 -152
  347. package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
  348. package/framework/templates/docs/proposal.md +0 -192
  349. package/framework/templates/docs/spec.md +0 -57
  350. package/framework/templates/docs/ui-components.md +0 -52
  351. package/framework/templates/docs/ui-design-system.md +0 -76
  352. package/framework/templates/docs/ui-flows.md +0 -167
  353. package/framework/templates/examples/spec-examples.md +0 -91
  354. package/framework/templates/feature/decisions.md +0 -124
  355. package/framework/templates/feature/recap.md +0 -145
  356. package/framework/templates/tasks.template.json +0 -17
  357. package/src/commands/state/usecase.js +0 -143
  358. package/src/commands/tasks/dag.js +0 -181
  359. package/src/commands/utils/index.js +0 -3
  360. package/src/lib/agents/output-collector.js +0 -76
  361. package/src/lib/detectors/design-system-detector.js +0 -188
  362. package/src/lib/evaluator-report.js +0 -104
  363. package/src/lib/generators/design-system-generator.js +0 -508
  364. package/src/lib/specs/acervo-format.js +0 -70
  365. package/src/lib/specs/acervo-lifecycle.js +0 -264
  366. package/src/lib/specs/done-criteria-check.js +0 -200
  367. package/src/lib/specs/usecase-check.js +0 -301
  368. package/src/lib/specs/usecase-promoter.js +0 -301
  369. package/src/lib/specs/usecase-registry.js +0 -237
  370. package/src/lib/tasks/dispatch-planner.js +0 -319
  371. package/src/lib/tasks/dotnet-test-introspection.js +0 -632
  372. package/src/lib/tasks/mermaid-renderer.js +0 -132
  373. package/src/lib/tasks/task-classifier.js +0 -30
  374. package/src/lib/tasks/task-parser.js +0 -209
  375. package/src/lib/tasks/tasks-json-parser.js +0 -296
  376. package/src/lib/tasks/test-scope.js +0 -597
  377. package/src/utils/agent-id-mapper.js +0 -11
@@ -1,28 +1,3 @@
1
- /**
2
- * worktree-audit.js — reconcile the three views of "which worktrees exist".
3
- *
4
- * Orphan worktrees accumulate silently. In the observed session,
5
- * `.worktrees/verify-tables-ui` and `.worktrees/tmp-cleanup` were registered in
6
- * `git worktree list` yet ABSENT from `morph-spec fleet` — fleet only reports
7
- * worktrees whose branch matches `refs/heads/morph/*`, so anything else is
8
- * invisible by construction and therefore never cleaned up (C2).
9
- *
10
- * Nothing compared the three sources that must agree:
11
- *
12
- * 1. git's registry — `git worktree list --porcelain`
13
- * 2. the disk — directories under `worktrees/` (and legacy `.worktrees/`)
14
- * 3. morph's features — `.morph/features/{f}/feature.json`
15
- *
16
- * Read-only and fail-open: anything unresolvable yields no issue rather than a
17
- * throw. A diagnostic that invents problems is worse than one that misses some.
18
- * The mutations all live in `pruneWorktrees`, and each is deliberately the
19
- * safest possible: `git worktree prune` drops ADMIN RECORDS for directories
20
- * that are already gone, and the empty-residue sweep removes only directories
21
- * with zero entries inside — nothing that can hold work. Deleting a directory
22
- * that still HOLDS something stays a human decision, the same PROVE-or-DECLINE
23
- * discipline `teardownWorktree` follows.
24
- */
25
-
26
1
  import { execFileSync } from 'child_process';
27
2
  import { existsSync, readdirSync, readFileSync, rmdirSync, statSync } from 'fs';
28
3
  import { join, resolve } from 'path';
@@ -73,7 +48,6 @@ function agentLeftoverIssue(item) {
73
48
  };
74
49
  }
75
50
 
76
- /** Feature names morph knows about in the primary root. */
77
51
  function knownFeatures(root) {
78
52
  const names = new Set();
79
53
  try {
@@ -86,14 +60,7 @@ function knownFeatures(root) {
86
60
  return names;
87
61
  }
88
62
 
89
- /**
90
- * Is the feature's authoritative feature.json COMMITTED on its own branch?
91
- *
92
- * Not "does the branch exist": `git worktree add -b morph/{f}` creates the
93
- * branch as a side effect, so branch-existence is true for every registered
94
- * worktree and proves nothing. The blob is the real evidence — it is where the
95
- * per-feature state model says the truth lives.
96
- */
63
+ // Branch existence proves nothing (`worktree add -b` creates it); the committed blob does.
97
64
  function featureCommittedOnBranch(root, feature) {
98
65
  try {
99
66
  execFileSync('git', ['cat-file', '-e', `morph/${feature}:.morph/features/${feature}/feature.json`], {
@@ -105,29 +72,8 @@ function featureCommittedOnBranch(root, feature) {
105
72
  }
106
73
  }
107
74
 
108
- /**
109
- * Does this worktree's branch still hold work that is NOT in the default
110
- * branch, or an OPEN PR?
111
- *
112
- * This is the predicate that separates "an orphan directory to clean up" from
113
- * "the only copy of somebody's work". Two independent pieces of evidence, and
114
- * ANY of them is enough to protect the worktree:
115
- *
116
- * 1. commits the default branch does not have (`git rev-list base..branch`)
117
- * 2. an OPEN pull request on that branch
118
- *
119
- * Fail-CLOSED, and that asymmetry is the whole design: whenever the answer
120
- * cannot be established — no default branch, no origin, git errored — the
121
- * worktree counts as UNINTEGRATED. The cost of being wrong that way is a
122
- * diagnostic that stays quiet; the cost of the opposite is a suggestion to
123
- * delete work that exists nowhere else.
124
- *
125
- * A squash-merged branch legitimately reports commits ahead, so the tree
126
- * comparison (`branch-classifier.js`) is consulted for the same reason
127
- * `branch prune` needs it — the two must not disagree about "is this in".
128
- *
129
- * @returns {{ unintegrated: boolean, evidence: string, pr: number|null }}
130
- */
75
+ // Fail-CLOSED: when integration cannot be established the worktree counts as
76
+ // unintegrated — a quiet diagnostic beats suggesting to delete the only copy.
131
77
  function unintegratedWork(root, branchRef, prIndex) {
132
78
  const branch = (branchRef || '').replace(/^refs\/heads\//, '');
133
79
  if (!branch) return { unintegrated: true, evidence: 'não foi possível resolver a branch do worktree', pr: null };
@@ -143,9 +89,7 @@ function unintegratedWork(root, branchRef, prIndex) {
143
89
  if (ahead === null) return { unintegrated: true, evidence: `não foi possível comparar com ${baseRef}`, pr: null };
144
90
  if (ahead === 0) return { unintegrated: false, evidence: `nenhum commit fora de ${baseRef}`, pr: pr?.number ?? null };
145
91
 
146
- // Commits ahead, but the work may still be IN: a squash-merge leaves exactly
147
- // that shape. Reuse the classifier so `doctor` and `branch prune` cannot
148
- // reach opposite conclusions about the same branch.
92
+ // A squash-merge also leaves commits ahead; the classifier keeps `doctor` and `branch prune` agreeing.
149
93
  const verdict = classifyBranch({ name: branch, sha: '', current: false, worktreePath: null }, { baseRef, cwd: root, pr });
150
94
  if (verdict.kind === 'merged-ancestral' || verdict.kind === 'merged-squash') {
151
95
  return { unintegrated: false, evidence: verdict.reason, pr: pr?.number ?? null };
@@ -153,19 +97,6 @@ function unintegratedWork(root, branchRef, prIndex) {
153
97
  return { unintegrated: true, evidence: `${ahead} commit(s) fora de ${baseRef}`, pr: pr?.number ?? null };
154
98
  }
155
99
 
156
- /**
157
- * Every feature's `worktree` registration in the primary root — the OTHER
158
- * direction from `knownFeatures`/`dirsOnDisk`: not "does git/disk know about
159
- * this worktree", but "does the ROOT's feature.json still point somewhere
160
- * real". `create --worktree` writes this pointer once at birth
161
- * (`{path, branch, createdAt}`) and nothing updates it afterwards except
162
- * `finish`'s own teardown — so a directory removed any other way (by hand,
163
- * `git worktree prune` run directly, a failed teardown that still cleared the
164
- * field, an unrelated script) leaves it dangling with nothing to notice.
165
- *
166
- * @param {string} root
167
- * @returns {Array<{ feature: string, path: string, branch: string|null }>}
168
- */
169
100
  function featureRegistrations(root) {
170
101
  const out = [];
171
102
  try {
@@ -183,23 +114,15 @@ function featureRegistrations(root) {
183
114
  return out;
184
115
  }
185
116
 
186
- /**
187
- * Is this directory literally empty — zero entries, dotfiles included?
188
- *
189
- * The whole point of the distinction: an empty residue holds nothing that can
190
- * be lost, so it can be removed without a human looking at it. Anything else —
191
- * including a directory we cannot read — is reported as NON-empty, because the
192
- * safe answer to "might there be work in here?" is yes.
193
- */
117
+ // Unreadable counts as occupied: emptiness is what licenses automatic removal.
194
118
  function isEmptyDir(path) {
195
119
  try {
196
120
  return readdirSync(path).length === 0;
197
121
  } catch {
198
- return false; // unreadable → treat as occupied
122
+ return false;
199
123
  }
200
124
  }
201
125
 
202
- /** Directories physically present under either worktree root. */
203
126
  function dirsOnDisk(root) {
204
127
  const found = [];
205
128
  const rootNorm = norm(root);
@@ -221,53 +144,8 @@ function dirsOnDisk(root) {
221
144
  }
222
145
 
223
146
  /**
224
- * Reconcile git's registry, the disk and morph's features.
225
- *
226
- * Issue types:
227
- * • registered-missing — git has a worktree whose directory is gone
228
- * • non-morph-branch — a registered worktree under worktrees/ on a
229
- * branch that is not `morph/*`, so `fleet`
230
- * cannot see it
231
- * • unregistered-dir — a directory under worktrees/ git never
232
- * registered
233
- * • no-feature — a `morph/{f}` worktree for a feature morph
234
- * does not know AND whose branch carries
235
- * nothing that is not already integrated
236
- * • unintegrated-work (info) — the same shape, except the branch still holds
237
- * commits the default does not, or an OPEN PR.
238
- * Reported so the reader knows why no removal
239
- * is suggested — this worktree may be the ONLY
240
- * copy of the work
241
- * • dangling-registration — a feature's ROOT feature.json still points
242
- * at a `worktree.path` that no longer exists
243
- * (the symmetric OTHER direction from
244
- * `no-feature`: the worktree is gone, but the
245
- * root's pointer to it survived)
246
- * • unregistered-registration — a feature's `worktree.path` names a
247
- * directory that exists but git's own
248
- * registry does not know as a worktree
249
- *
250
- * • agent-worktree-leftover (info) — a sub-agent's `agent-<hex>` tree or
251
- * `morph-task/agent-<hex>` branch left behind:
252
- * `reapable` when the agent stopped and nothing
253
- * is lost, otherwise the reason it is kept
254
- *
255
- * • docker-stack-orphan (warn) — a `morph-*` docker stack of this repository
256
- * still running for a feature this tree does
257
- * not know, and
258
- * • docker-port-held (warn) — a port from a feature's block published by
259
- * another compose project. Both come from
260
- * `docker-audit.js`: the three sources above
261
- * cannot see a CONTAINER, and a stack outlives
262
- * every one of them
263
- *
264
- * @param {string} [cwd]
265
- * @param {object} [opts]
266
- * @param {Function} [opts.exec] - injectable executor handed to the docker
267
- * audit, so the docker paths are testable with no daemon (the CI runners
268
- * have none). Absent, the real `docker` CLI is used — and answers `[]` when
269
- * it is not installed.
270
- * @returns {Array<{ type: string, detail: string, path?: string, feature?: string, fix: string }>}
147
+ * Reconciles git's worktree registry, the disk, morph's features and docker.
148
+ * Read-only and fail-open; `exec` is the docker executor seam.
271
149
  */
272
150
  export function auditWorktrees(cwd = process.cwd(), { exec } = {}) {
273
151
  if (!isGitRepo(cwd)) return [];
@@ -275,9 +153,7 @@ export function auditWorktrees(cwd = process.cwd(), { exec } = {}) {
275
153
  const rootNorm = norm(root);
276
154
  const issues = [];
277
155
 
278
- // ONE `gh pr list` for the whole audit — null means UNKNOWN (no gh, not
279
- // GitHub, not authenticated), never "no PRs"; `unintegratedWork` treats the
280
- // two differently on purpose.
156
+ // null = UNKNOWN (no gh / not GitHub / unauthenticated), never "no PRs".
281
157
  const prIndex = buildPrIndex(root);
282
158
 
283
159
  const containers = containerDirs(root);
@@ -288,7 +164,7 @@ export function auditWorktrees(cwd = process.cwd(), { exec } = {}) {
288
164
  for (const entry of registered) {
289
165
  const path = norm(entry.path);
290
166
  registeredPaths.add(path);
291
- if (path === rootNorm) continue; // the primary root is not a worktree orphan
167
+ if (path === rootNorm) continue;
292
168
 
293
169
  if (!existsSync(path)) {
294
170
  issues.push({
@@ -306,12 +182,7 @@ export function auditWorktrees(cwd = process.cwd(), { exec } = {}) {
306
182
  const task = entry.branch?.match(/^refs\/heads\/morph-task\/(.+)$/)?.[1] || null;
307
183
 
308
184
  if (!inContainer) {
309
- // A worktree OUTSIDE morph's container was created behind morph's back
310
- // (a native Claude Code run before the WorktreeCreate hook, a raw
311
- // `git worktree add ../x`, another skill). It has no junctions, no port
312
- // block, and nothing morph runs there — the exact shape that let a
313
- // root's `.claude/` be emptied. Reported as `warn` (never a failure,
314
- // NEVER pruned): the work inside may be the only copy.
185
+ // Never pruned: the work inside a tree created outside morph may be the only copy.
315
186
  const work = unintegratedWork(root, entry.branch, prIndex);
316
187
  issues.push({
317
188
  type: 'out-of-container',
@@ -327,10 +198,6 @@ export function auditWorktrees(cwd = process.cwd(), { exec } = {}) {
327
198
 
328
199
  if (task) {
329
200
  if (AGENT_TASK.test(task)) continue; // reported once, by the agent plan below
330
- // `morph-task/*`: a task-isolation tree provisioned by the WorktreeCreate
331
- // hook. Clean → nothing to say (the orchestrator removes it after the
332
- // merge). With commits the default does not have → report, never
333
- // suggest removal.
334
201
  const work = unintegratedWork(root, entry.branch, prIndex);
335
202
  if (work.unintegrated) {
336
203
  issues.push({
@@ -345,11 +212,6 @@ export function auditWorktrees(cwd = process.cwd(), { exec } = {}) {
345
212
  }
346
213
 
347
214
  if (inContainer) {
348
- // Same hazard as `no-feature` below, and it needs the same guard:
349
- // "invisible to fleet" says nothing about whether the WORK is safe
350
- // elsewhere. A worktree on a `feat/*` branch with an open PR is the
351
- // most normal thing in the world, and suggesting its removal is a
352
- // suggestion to destroy the only copy.
353
215
  const work = unintegratedWork(root, entry.branch, prIndex);
354
216
  issues.push(work.unintegrated
355
217
  ? {
@@ -365,10 +227,8 @@ export function auditWorktrees(cwd = process.cwd(), { exec } = {}) {
365
227
  type: 'non-morph-branch',
366
228
  path,
367
229
  detail: `${path} está em ${entry.branch || 'HEAD destacado'}, não numa branch morph/* — o \`fleet\` não o enxerga (e não há trabalho pendente: ${work.evidence})`,
368
- // NEVER raw `git worktree remove` here: it follows the infra junctions
369
- // out and empties the primary root's .claude/ and .morph/framework/.
370
- // `worktree remove` drops them first (PROVE-or-DECLINE) and takes
371
- // --target precisely for worktrees like this one, with no feature.
230
+ // Never raw `git worktree remove`: it follows the infra junctions and
231
+ // empties the root's .claude/ and .morph/framework/.
372
232
  fix: `morph-spec worktree remove --target "${path}" (se já não serve)`,
373
233
  });
374
234
  }
@@ -376,13 +236,8 @@ export function auditWorktrees(cwd = process.cwd(), { exec } = {}) {
376
236
  }
377
237
 
378
238
  if (!features.has(feature) && !featureCommittedOnBranch(root, feature)) {
379
- // "sem feature conhecida" is a bookkeeping fact, NOT a verdict on the
380
- // work: a `finish` that archived the feature leaves exactly this shape,
381
- // and so does a worktree whose PR is still open. In the observed session
382
- // this branch recommended `worktree remove --target` for a worktree
383
- // holding the ONLY copy of an open PR with 12 tasks and a 9.5 at Gate 3.
384
- // So the diagnosis now depends on whether the WORK is integrated, and the
385
- // suggestion to destroy appears only when it provably is.
239
+ // An archived feature or an open PR leaves this same shape: suggest removal
240
+ // only when the work is provably integrated.
386
241
  const work = unintegratedWork(root, entry.branch, prIndex);
387
242
  if (work.unintegrated) {
388
243
  issues.push({
@@ -420,19 +275,12 @@ export function auditWorktrees(cwd = process.cwd(), { exec } = {}) {
420
275
  detail: d.empty
421
276
  ? `${d.container}/${d.name} existe no disco, está VAZIO e o git não o registra — resíduo de um finish anterior`
422
277
  : `${d.container}/${d.name} existe no disco mas o git não o registra — resíduo de um finish anterior`,
423
- // An empty folder has nothing to lose, so it gets the automatic repair.
424
- // One with content keeps the manual chore: there may be work in there,
425
- // and the whole PROVE-or-DECLINE discipline exists for exactly that.
426
278
  fix: d.empty
427
279
  ? `morph-spec doctor --worktrees --prune`
428
280
  : `apague o diretório à mão depois de conferir que nada importante ficou nele`,
429
281
  });
430
282
  }
431
283
 
432
- // The other direction: does the ROOT's own bookkeeping still point at a
433
- // live worktree? `git worktree list`/disk only see what's PHYSICALLY
434
- // there; a registration surviving past its worktree's removal is invisible
435
- // to every check above.
436
284
  for (const reg of featureRegistrations(root)) {
437
285
  const path = norm(reg.path);
438
286
  if (!existsSync(reg.path)) {
@@ -454,13 +302,7 @@ export function auditWorktrees(cwd = process.cwd(), { exec } = {}) {
454
302
  }
455
303
  }
456
304
 
457
- // The fourth source, and the only one that is not a file: the docker stacks.
458
- // A container survives the removal of the worktree, of the branch and of the
459
- // feature — none of the three reconciliations above can see it, and it keeps
460
- // holding the feature's port block. The feature names come from HERE (the
461
- // ones morph knows plus the ones the registered `morph/*` worktrees carry)
462
- // rather than from a second scan inside docker-audit: two readings of the
463
- // same fact are two chances to disagree about which stack is orphaned.
305
+ // Feature names are passed in so docker-audit never rescans and disagrees.
464
306
  const treeFeatures = new Set(features);
465
307
  for (const entry of registered) {
466
308
  const f = entry.branch?.match(/^refs\/heads\/morph\/(.+)$/)?.[1];
@@ -468,31 +310,13 @@ export function auditWorktrees(cwd = process.cwd(), { exec } = {}) {
468
310
  }
469
311
  issues.push(...auditDockerStacks(root, { exec, features: treeFeatures }));
470
312
 
471
- // What a worktree removal leaves behind in `.git/morph-spec/finish-backup/`
472
- // past its retention — `warn`, pruned by `--prune` and by the next finish.
473
313
  issues.push(...auditFinishBackups(root));
474
314
 
475
315
  return issues;
476
316
  }
477
317
 
478
- /**
479
- * Remove the EMPTY directories under `worktrees/`/`.worktrees/` that git does
480
- * not register — the residue a finish leaves behind.
481
- *
482
- * "Empty" is the whole licence to act: a folder with zero entries cannot be
483
- * hiding uncommitted work, so asking a human about it is pure ceremony, and
484
- * the audit that only warns leaves it there forever. A directory with anything
485
- * in it keeps the manual path.
486
- *
487
- * Two guards beyond the emptiness check:
488
- * • only directories directly inside morph's own worktree containers, never
489
- * the containers themselves and never a path git registers;
490
- * • `rmdirSync`, not `rm -rf` — it REFUSES a non-empty directory, so a file
491
- * that appears between the check and the call is a failure, not a loss.
492
- *
493
- * @param {string} root
494
- * @returns {string[]} paths actually removed
495
- */
318
+ // `rmdirSync`, never `rm -rf`: a file that appears after the emptiness check
319
+ // makes it fail instead of being lost.
496
320
  function removeEmptyUnregisteredDirs(root) {
497
321
  const registeredPaths = new Set(parseWorktreeList(root).map((e) => norm(e.path)));
498
322
  const removed = [];
@@ -507,25 +331,9 @@ function removeEmptyUnregisteredDirs(root) {
507
331
  }
508
332
 
509
333
  /**
510
- * Drop git's admin records for worktrees whose directory is already gone,
511
- * remove the EMPTY unregistered residue directories, AND clear any feature's
512
- * `worktree` registration that names a directory that is now gone.
513
- *
514
- * These three are the ONLY prune offered, on purpose: none can destroy
515
- * anything, because everything they touch is either already absent from disk
516
- * or provably empty. Removing a directory that still HOLDS something is
517
- * exactly the operation that needs a human looking at it.
518
- *
519
- * Order is load-bearing: the empty directories go first, so a root pointer
520
- * naming one of them becomes dangling in time for the registration sweep below
521
- * to clear it — one command, not two runs.
522
- *
523
- * `clearDanglingWorktreeRegistration` re-checks the dangling predicate itself
524
- * (path unset, or still exists → no-op) rather than trusting a pre-computed
525
- * list, so this stays correct even called against a stale audit snapshot.
526
- *
527
- * @param {string} [cwd]
528
- * @returns {Promise<{ pruned: boolean, error?: string, removedEmptyDirs: string[], clearedRegistrations: Array<{feature: string, cleared: boolean}> }>}
334
+ * Touches only what is already gone from disk or provably empty. Order matters:
335
+ * empty dirs go first so a root pointer to one becomes dangling and is cleared
336
+ * in the same run.
529
337
  */
530
338
  export async function pruneWorktrees(cwd = process.cwd()) {
531
339
  if (!isGitRepo(cwd)) return { pruned: false, error: 'not-a-git-repo', removedEmptyDirs: [], clearedRegistrations: [] };
@@ -9,11 +9,9 @@ const NESTED_GIT = /(^|\/)\.git(\/|$)/;
9
9
  const gitOut = (cwd, args, input) => execFileSync('git', args, { cwd, stdio: ['pipe', 'pipe', 'ignore'], encoding: 'utf8', input });
10
10
 
11
11
  /**
12
- * Claude Code's `.worktreeinclude` for a worktree morph-spec creates (a WorktreeCreate hook replaces
13
- * the native creation, so the native copy never runs): files matching a pattern AND gitignored in the
14
- * tree are copied when the tree lacks them. Real files only; build artifacts and `node_modules` never.
15
- *
16
- * @returns {string[]} relative paths copied
12
+ * `.worktreeinclude` parity: a WorktreeCreate hook replaces the native creation, so the native copy
13
+ * never runs. Copies files matching a pattern AND gitignored in the tree, when the tree lacks them;
14
+ * never build artifacts or `node_modules`. Returns the relative paths copied.
17
15
  */
18
16
  export function copyWorktreeIncludes(worktreePath, root) {
19
17
  const file = join(root, INCLUDE_FILE);
@@ -1,4 +1,3 @@
1
- // src/scripts/global-install.js
2
1
  import { join, dirname } from 'path';
3
2
  import { fileURLToPath } from 'url';
4
3
  import { homedir, platform } from 'os';
@@ -10,10 +9,7 @@ import { installGlobalStatusline } from '../utils/hooks-installer.js';
10
9
  const __dirname = dirname(fileURLToPath(import.meta.url));
11
10
  const FRAMEWORK_DIR = join(__dirname, '..', '..', 'framework');
12
11
 
13
- /**
14
- * Ensure the npm global bin directory is in the user's PATH on Windows.
15
- * Adds it to the PowerShell $PROFILE so new terminals find `morph-spec`.
16
- */
12
+ /** Windows only: adds the npm global bin to the PowerShell $PROFILE so new terminals find `morph-spec`. */
17
13
  export async function ensureNpmGlobalInPath() {
18
14
  if (platform() !== 'win32') return;
19
15
 
@@ -21,12 +17,11 @@ export async function ensureNpmGlobalInPath() {
21
17
  try {
22
18
  npmGlobalDir = execSync('npm prefix -g', { encoding: 'utf8' }).trim();
23
19
  } catch {
24
- return; // can't determine npm global dir
20
+ return;
25
21
  }
26
22
 
27
23
  if (!npmGlobalDir || !existsSync(npmGlobalDir)) return;
28
24
 
29
- // --- PowerShell $PROFILE ---
30
25
  const psProfile = join(homedir(), 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1');
31
26
  const psProfileLegacy = join(homedir(), 'Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1');
32
27
 
@@ -44,7 +39,7 @@ export async function ensureNpmGlobalInPath() {
44
39
  let content = '';
45
40
  if (existsSync(profilePath)) {
46
41
  content = await readFile(profilePath, 'utf8');
47
- if (content.includes(marker)) continue; // already patched
42
+ if (content.includes(marker)) continue;
48
43
  } else {
49
44
  await mkdir(dirname(profilePath), { recursive: true });
50
45
  }
@@ -56,7 +51,6 @@ export async function ensureNpmGlobalInPath() {
56
51
  }
57
52
  }
58
53
 
59
- // Run as postinstall script
60
54
  if (process.argv[1] === fileURLToPath(import.meta.url)) {
61
55
  const HOOKS_SRC = join(FRAMEWORK_DIR, 'hooks', 'claude-code');
62
56
  const { readFileSync } = await import('fs');
@@ -1,4 +1,3 @@
1
- // src/scripts/install-plugin.js
2
1
  import { join } from 'path';
3
2
  import { homedir } from 'os';
4
3
  import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
@@ -10,7 +9,6 @@ const CACHE_DIR = join(PLUGINS_DIR, 'cache', 'claude-plugins-official');
10
9
  const INSTALLED_FILE = join(PLUGINS_DIR, 'installed_plugins.json');
11
10
  const GH_REPO = 'anthropics/claude-plugins-official';
12
11
 
13
- /** Fetch JSON from GitHub API (no auth — public repo) */
14
12
  async function fetchGitHub(path) {
15
13
  return new Promise((resolve, reject) => {
16
14
  const opts = {
@@ -29,7 +27,6 @@ async function fetchGitHub(path) {
29
27
  });
30
28
  }
31
29
 
32
- /** Download raw file content from a URL, following one redirect */
33
30
  async function downloadFile(rawUrl) {
34
31
  return new Promise((resolve, reject) => {
35
32
  const doGet = (url) => {
@@ -51,25 +48,21 @@ async function downloadFile(rawUrl) {
51
48
  });
52
49
  }
53
50
 
54
- /** Read installed_plugins.json safely */
55
51
  export function readInstalledPlugins() {
56
52
  if (!existsSync(INSTALLED_FILE)) return { version: 2, plugins: {} };
57
53
  try { return JSON.parse(readFileSync(INSTALLED_FILE, 'utf8')); }
58
54
  catch { return { version: 2, plugins: {} }; }
59
55
  }
60
56
 
61
- /** Check if plugin key exists in the plugins map */
62
57
  export function isPluginInstalled(plugins, name) {
63
58
  return `${name}@claude-plugins-official` in plugins;
64
59
  }
65
60
 
66
- /** Build an installed_plugins.json entry */
67
61
  export function buildInstallEntry(pluginName, installPath, gitCommitSha, version) {
68
62
  const now = new Date().toISOString();
69
63
  return { scope: 'user', installPath, version, installedAt: now, lastUpdated: now, gitCommitSha };
70
64
  }
71
65
 
72
- /** Resolve latest commit SHA for a plugin path in the repo */
73
66
  export async function resolvePluginVersion(pluginName) {
74
67
  const commits = await fetchGitHub(`/repos/${GH_REPO}/commits?path=${pluginName}&per_page=1`);
75
68
  if (!Array.isArray(commits) || commits.length === 0) {
@@ -79,7 +72,6 @@ export async function resolvePluginVersion(pluginName) {
79
72
  return { sha, shortSha: sha.slice(0, 12) };
80
73
  }
81
74
 
82
- /** Download all files for a plugin from the repo tree */
83
75
  async function downloadPluginFiles(pluginName, sha, destDir) {
84
76
  const tree = await fetchGitHub(`/repos/${GH_REPO}/git/trees/${sha}?recursive=1`);
85
77
  if (!tree.tree) throw new Error('GitHub tree API returned unexpected shape');
@@ -98,11 +90,7 @@ async function downloadPluginFiles(pluginName, sha, destDir) {
98
90
  }
99
91
  }
100
92
 
101
- /**
102
- * Install a Claude Code plugin from GitHub.
103
- * @param {string} pluginName - e.g. "context7"
104
- * @returns {{ installed: boolean, version: string, alreadyInstalled: boolean }}
105
- */
93
+ /** @returns {{ installed: boolean, version: string, alreadyInstalled: boolean }} */
106
94
  export async function installPlugin(pluginName) {
107
95
  const data = readInstalledPlugins();
108
96
  if (isPluginInstalled(data.plugins, pluginName)) {