@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,13 +1,3 @@
1
- /**
2
- * Claude Code Hooks Installer (v2)
3
- *
4
- * Registry-based installer for all MORPH-SPEC Claude Code hooks.
5
- * Declarative MORPH_HOOKS array defines all hooks.
6
- * Idempotent merge into .claude/settings.local.json.
7
- *
8
- * Called by `morph-spec init` and `morph-spec update`.
9
- */
10
-
11
1
  import { join, dirname } from 'path';
12
2
  import { readFile } from 'fs/promises';
13
3
  import { existsSync, chmodSync, readdirSync } from 'fs';
@@ -15,124 +5,37 @@ import { homedir } from 'os';
15
5
  import { execSync } from 'child_process';
16
6
  import { realEffects } from '../lib/installers/effects.js';
17
7
 
18
- /**
19
- * Current hooks schema version — bump when hook definitions change.
20
- *
21
- * 3.6.0 (R2 of the performance plan) consolidates spawns: every core hook now
22
- * exports `run(payload)` (framework/hooks/claude-code/core/*.js) and the
23
- * registry below routes multi-hook event+matcher groups through the new
24
- * `core/chain.js`, which imports and runs several hooks' `run()` in ONE node
25
- * process instead of one process per hook. Same events, same matchers, same
26
- * observable behavior (block/warn/silent) — only the process count per
27
- * Write/Edit drops (9 → 4). The hook files are re-copied unconditionally
28
- * (`copyDirectory` defaults to `overwrite: true`), so the bump does not gate
29
- * delivery — it makes `morph-spec update` report the change honestly instead
30
- * of "already current".
31
- *
32
- * 3.8.0 (worktree governance) registers Claude Code's WorktreeCreate /
33
- * WorktreeRemove events — the native `claude -w` / EnterWorktree / Agent
34
- * isolation worktree is replaced by `morph-spec worktree setup|provision`
35
- * (junctions, port block, primary-root landing) and its removal routed
36
- * through the safe `worktree remove --if-clean` — and appends
37
- * block-raw-worktree-add to the PreToolUse:Bash chain.
38
- *
39
- * 3.9.0 binds telemetry-log to PostToolUseFailure (a Bash command that exits
40
- * non-zero fires only that event, so red runs used to vanish from the log),
41
- * PostToolBatch (once per model turn that used tools) and SubagentStop (the
42
- * sub-agent's own token usage). The three only record, so they run with
43
- * `async: true`: a node process per model turn never holds the turn up.
44
- *
45
- * 3.10.0 appends graph-refresh-kick to the all-tools PostToolUse chain: after a
46
- * Bash or PowerShell command that runs git/gh/morph-spec it launches the
47
- * detached refresh of the ROOT's codebase graph
48
- * (framework/hooks/shared/graph-refresh.js). `update` re-runs
49
- * this installer, so installed projects get the refresh without re-running
50
- * `setup-infra` — the step that never reached them.
51
- *
52
- * 3.11.0 writes every `timeout` in seconds, the unit Claude Code reads: the
53
- * values were milliseconds, so a hung hook held the session for hours (120000
54
- * on WorktreeCreate is 33 h) instead of the seconds they meant.
55
- *
56
- * 3.12.0 adds heavy-gate to the Bash PreToolUse chain and heavy-release to the
57
- * PostToolUse and PostToolUseFailure chains: the opt-in `heavy.gateBash`.
58
- */
59
- const HOOKS_VERSION = '3.12.0';
8
+ const HOOKS_VERSION = '3.13.0';
60
9
 
61
- /** Marker for old dispatch.js (v1) */
62
10
  const OLD_DISPATCH_COMMAND = 'node framework/hooks/agent-teams/dispatch.js';
63
11
 
64
- /**
65
- * Native permissions.deny entries managed by MORPH-SPEC.
66
- *
67
- * state.json is NOT in deny because the agent edits it directly as a
68
- * fallback to the state-sync hook (per v7 hybrid state model). The
69
- * framework/ tree remains read-only — only the installer touches it.
70
- *
71
- * `Edit(path)` ONLY — never a matching `Write(path)`. Claude Code matches file
72
- * permissions on `Edit(path)` rules alone; an `Edit` rule covers every
73
- * file-editing tool, Write included, while a `Write(path)` rule matches nothing
74
- * and is reported as a warning on every session start. Verified empirically, not
75
- * assumed: with `Edit(.morph/framework/**)` as the ONLY deny entry, the Write
76
- * tool was refused and the target file was never created.
77
- */
12
+ // `Edit(path)` only: an Edit rule also covers Write, while a `Write(path)` rule
13
+ // matches nothing and makes Claude Code warn on every session start.
78
14
  const MORPH_DENY = [
79
- // Framework files — installer-managed, immutable at runtime
80
15
  'Edit(.morph/framework/**)',
81
- // .claude/CLAUDE.md (managed copy — source is framework/CLAUDE.md)
82
16
  'Edit(.claude/CLAUDE.md)',
83
- // .claude/rules/ (copied from framework/rules/)
84
17
  'Edit(.claude/rules/**)',
85
- // .claude/agents/morph-* (generated by agents-installer)
86
18
  'Edit(.claude/agents/morph-*)',
87
- // .claude/skills/ (copied from framework/skills/)
88
19
  'Edit(.claude/skills/**)',
89
20
  ];
90
21
 
91
- /**
92
- * Native permissions.allow entries managed by MORPH-SPEC.
93
- *
94
- * Pre-authorizes operations the agent legitimately performs during the
95
- * morph-spec flow, so the user does not get permission prompts on every
96
- * proposal.md / spec.md / tasks.json write.
97
- *
98
- * `Edit(path)` ONLY, for the same reason as MORPH_DENY: the `Edit` rule already
99
- * covers Write, and a `Write(path)` rule is inert noise.
100
- */
101
22
  const MORPH_ALLOW = [
102
- // Feature artifacts — the agent's working directory during proposal/plan/implement/review
103
23
  'Edit(.morph/features/**)',
104
- // state.json — fallback path when state-sync hook can't cover a case
105
24
  'Edit(.morph/state.json)',
106
- // Long-lived architectural context — agent may reconcile during brainstorming
107
25
  'Edit(.morph/context/**)',
108
- // Folder creation under .morph
109
26
  'Bash(mkdir:.morph/**)',
110
27
  'Bash(ls:.morph/**)',
28
+ // An auto-mode deny here pauses the review mutation round.
29
+ 'Bash(morph-spec mutate:*)',
30
+ 'Bash(npx morph-spec mutate:*)',
111
31
  ];
112
32
 
113
- // Backwards-compat alias used by older tests
114
33
  const MORPH_PERMISSIONS = MORPH_DENY;
115
34
 
116
- /**
117
- * All MORPH-SPEC hook definitions.
118
- * Each entry maps to a Claude Code hook event.
119
- *
120
- * R2 consolidation: an entry whose group has more than one hook now declares
121
- * ONE `core/chain.js` command with an `args` array (the hook base names, in
122
- * the ORDER they run) instead of one command per hook — chain.js imports and
123
- * calls each hook's `run(payload)` in that same process (see its own doc
124
- * comment for the block-first-wins / context-concatenation rules). A group
125
- * with exactly one hook stays a direct command — chaining a single link buys
126
- * nothing. `args` must stay a SEPARATE field from `command`: `getHookSubpath()`
127
- * below extracts the installed path from `command` alone via a regex anchored
128
- * on `claude-code/`, and inlining args into that string would get captured
129
- * into the quoted path.
130
- */
35
+ // A multi-hook group is ONE `core/chain.js` command whose `args` list the hooks in
36
+ // run order (block-first-wins). `args` stays separate from `command`: getHookSubpath()
37
+ // would capture inlined args into the quoted path. `timeout` is in SECONDS.
131
38
  const MORPH_HOOKS = [
132
- // === SessionStart — worktree feature identity (secondary worktree) or
133
- // active-feature summary (primary root). Fulfills the SessionStart
134
- // injection role long documented but never wired (inject-morph-context.js).
135
- // Solo (only hook for this event) — nothing to chain. ===
136
39
  {
137
40
  event: 'SessionStart',
138
41
  matcher: null,
@@ -143,13 +46,6 @@ const MORPH_HOOKS = [
143
46
  }]
144
47
  },
145
48
 
146
- // === PreToolUse: Write|Edit — phase + spec protection (silent on success).
147
- // Chained in the SAME order the previous two-entry registry ran them:
148
- // protect-spec-files (locked-file check) before enforce-phase-writes (phase
149
- // folder check) — a block from either one wins and skips the other. Neither
150
- // hook declared its own timeout (both are pure sync fs checks with no
151
- // subprocess calls), so the chain declares none either — same platform
152
- // default both relied on individually. ===
153
49
  {
154
50
  event: 'PreToolUse',
155
51
  matcher: 'Write|Edit',
@@ -160,35 +56,18 @@ const MORPH_HOOKS = [
160
56
  }]
161
57
  },
162
58
 
163
- // === PreToolUse: Bash — irreversible-command guards, chained (block-first-
164
- // wins, one process). pre-bash-denylist (static pattern list) runs before
165
- // block-destructive-worktree-removal (dynamic: scans the target for reparse
166
- // points), so the cheap check short-circuits the filesystem walk. The second
167
- // one is the only defense against the raw-removal vector that does not depend
168
- // on someone having read MORPH.md. block-raw-worktree-add is its way-IN
169
- // counterpart (a raw `git worktree add` builds a tree with no junctions, no
170
- // ports, no identity) — static, so it costs nothing after the first two.
171
- // heavy-gate comes after every guard — it takes a slot, and a command a guard
172
- // blocks must not hold one — and before the rewriter.
173
- // worktree-env-inject is LAST: it rewrites launcher commands (updatedInput)
174
- // with the worktree's port block, and a command a previous link blocked must
175
- // never be rewritten. Block-first-wins guarantees it never sees one.
59
+ // heavy-gate after every guard (a blocked command must not hold a slot);
60
+ // worktree-env-inject LAST so a command a previous link blocked is never rewritten.
176
61
  {
177
62
  event: 'PreToolUse',
178
63
  matcher: 'Bash',
179
64
  hooks: [{
180
65
  type: 'command',
181
66
  command: 'node framework/hooks/claude-code/core/chain.js',
182
- args: ['pre-bash-denylist', 'block-destructive-worktree-removal', 'block-raw-worktree-add', 'heavy-gate', 'worktree-env-inject']
67
+ args: ['pre-bash-denylist', 'block-destructive-worktree-removal', 'block-raw-worktree-add', 'warn-bash-source-write', 'heavy-gate', 'worktree-env-inject']
183
68
  }]
184
69
  },
185
70
 
186
- // === WorktreeCreate — REPLACES Claude Code's native `git worktree add` for
187
- // `claude -w`, the EnterWorktree tool and `Agent isolation:"worktree"`
188
- // (measured: all three fire it). The hook prints the created path as its
189
- // last stdout line; a non-zero exit fails the creation. No matcher exists
190
- // for this event. 120s: it spawns the morph-spec CLI (setup or provision),
191
- // which runs git, links infra, allocates ports and materializes state. ===
192
71
  {
193
72
  event: 'WorktreeCreate',
194
73
  matcher: null,
@@ -199,11 +78,7 @@ const MORPH_HOOKS = [
199
78
  }]
200
79
  },
201
80
 
202
- // === WorktreeRemove — routes Claude Code's own cleanup through
203
- // `morph-spec worktree remove --target --if-clean` (junctions dropped BEFORE
204
- // git touches the tree; a dirty tree is left alone). Output ignored, always
205
- // exit 0. Secondary net: in headless runs the event was observed not to
206
- // fire — block-destructive-worktree-removal remains the primary defense. ===
81
+ // Not fired in headless runs: block-destructive-worktree-removal stays the primary defense.
207
82
  {
208
83
  event: 'WorktreeRemove',
209
84
  matcher: null,
@@ -214,28 +89,17 @@ const MORPH_HOOKS = [
214
89
  }]
215
90
  },
216
91
 
217
- // === PostToolUse: Write|Edit — tasks.json schema guard, trace auto-generation,
218
- // comment-guard LAST: it blocks, and a block ends the chain. ===
92
+ // comment-guard must be the last Write|Edit link: a block ends the chain.
219
93
  {
220
94
  event: 'PostToolUse',
221
95
  matcher: 'Write|Edit',
222
96
  hooks: [{
223
97
  type: 'command',
224
- command: 'node framework/hooks/claude-code/core/chain.js',
225
- args: ['tasks-json-guard', 'trace-autogen', 'comment-guard']
98
+ command: 'node framework/hooks/claude-code/core/comment-guard.js'
226
99
  }]
227
100
  },
228
101
 
229
- // === PostToolUse: Write|Edit|AskUserQuestion — state-sync (autonomous
230
- // state.json updates) then gate-guard. gate-guard runs AFTER state-sync so
231
- // it sees the gate that state-sync just recorded — now true within a single
232
- // process too, because state-writer.js's persistFeature() invalidates
233
- // state-reader.js's per-process loadState() memo on every write (R1.1 added
234
- // that memo; without the invalidation, a chained gate-guard could read the
235
- // pre-write snapshot). Both are silent fail-open guardrails; gate-guard only
236
- // warns (never blocks) when an observed gate action disagrees with the
237
- // persisted gateDecisions (issue #63). Both links declared 3000ms; sequential
238
- // execution means the worst case is additive, so the chain declares the sum. ===
102
+ // gate-guard after state-sync so it sees the gate state-sync just recorded.
239
103
  {
240
104
  event: 'PostToolUse',
241
105
  matcher: 'Write|Edit|AskUserQuestion',
@@ -247,15 +111,6 @@ const MORPH_HOOKS = [
247
111
  }]
248
112
  },
249
113
 
250
- // === PostToolUse: All — telemetry capture (silent) + loop detection +
251
- // graph refresh kick. telemetry-log is the single generic vantage point of an
252
- // NLH: it appends every tool call (skill, sub-agent dispatch, knowledge-base
253
- // read, …) to .morph/logs/events.jsonl. Silent and fail-open — never blocks,
254
- // never injects. `morph-spec telemetry` reads the stream back.
255
- // graph-refresh-kick only spawns a DETACHED worker (never waits on graphify),
256
- // so it rides this chain instead of costing a process of its own per Bash
257
- // call. Timeout stays the sum of the first two links (sequential worst case);
258
- // the kick returns in milliseconds. ===
259
114
  {
260
115
  event: 'PostToolUse',
261
116
  matcher: null,
@@ -267,9 +122,7 @@ const MORPH_HOOKS = [
267
122
  }]
268
123
  },
269
124
 
270
- // === PostToolUseFailure: All — telemetry of the failed call (exit code,
271
- // duration). loop-detect stays out: its warning is shaped for PostToolUse.
272
- // heavy-release gives back the slot of a heavy Bash call that failed.
125
+ // Recording-only bindings are `async: true`; a hook that decides is never async.
273
126
  {
274
127
  event: 'PostToolUseFailure',
275
128
  matcher: null,
@@ -282,8 +135,6 @@ const MORPH_HOOKS = [
282
135
  }]
283
136
  },
284
137
 
285
- // === PostToolBatch — one event per model turn that used tools. The event
286
- // takes no matcher. ===
287
138
  {
288
139
  event: 'PostToolBatch',
289
140
  matcher: null,
@@ -295,8 +146,6 @@ const MORPH_HOOKS = [
295
146
  }]
296
147
  },
297
148
 
298
- // === SubagentStop — the sub-agent's token usage, read from its own
299
- // transcript (the session transcript does not include it). ===
300
149
  {
301
150
  event: 'SubagentStop',
302
151
  matcher: null,
@@ -308,10 +157,6 @@ const MORPH_HOOKS = [
308
157
  }]
309
158
  },
310
159
 
311
- // === UserPromptSubmit — telemetry capture of the user's prompt (silent).
312
- // The only vantage point on the user's own prompt; completes the execution
313
- // path alongside sub-agent prompts captured on PostToolUse. Solo (the only
314
- // hook registered on this event) — nothing to chain. ===
315
160
  {
316
161
  event: 'UserPromptSubmit',
317
162
  matcher: null,
@@ -322,7 +167,6 @@ const MORPH_HOOKS = [
322
167
  }]
323
168
  },
324
169
 
325
- // === Stop — build check + session snapshot. Solo. ===
326
170
  {
327
171
  event: 'Stop',
328
172
  matcher: null,
@@ -334,25 +178,13 @@ const MORPH_HOOKS = [
334
178
  ];
335
179
 
336
180
  /**
337
- * Install or update all MORPH-SPEC hooks in .claude/settings.local.json.
338
- *
339
- * - Idempotent: safe to call multiple times
340
- * - Removes old v1 dispatch.js entry if present
341
- * - Tracks installed version via _morph_managed.hooks_version
342
- *
343
- * @param {string} targetPath - Project root directory
344
- * @param {object} [options]
345
- * @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
346
- * Coletor de efeitos. Esta função é o seam mais barato do caminho de update:
347
- * o objeto de settings INTEIRO já é montado em memória e há UMA escrita, no
348
- * fim. Previsão e execução percorrem a mesma montagem.
181
+ * Idempotent merge of the MORPH-SPEC hooks into `.claude/settings.local.json`.
349
182
  * @returns {Promise<{ installed: number, updated: boolean }>}
350
183
  */
351
184
  export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}) {
352
185
  const claudeDir = join(targetPath, '.claude');
353
186
  const settingsPath = join(claudeDir, 'settings.local.json');
354
187
 
355
- // Read existing settings or start empty
356
188
  let settings = {};
357
189
  if (existsSync(settingsPath)) {
358
190
  try {
@@ -364,17 +196,12 @@ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}
364
196
 
365
197
  settings.hooks = settings.hooks || {};
366
198
 
367
- // Check if already at current version
368
199
  const managed = settings._morph_managed || {};
369
200
  const alreadyCurrent = managed.hooks_version === HOOKS_VERSION;
370
201
 
371
- // Remove old v1 dispatch.js entry
372
202
  removeOldDispatch(settings);
373
-
374
- // Remove all morph-managed hooks (clean slate for idempotent update)
375
203
  removeMorphHooks(settings);
376
204
 
377
- // Install all hooks from the registry
378
205
  let installed = 0;
379
206
  for (const hookDef of MORPH_HOOKS) {
380
207
  const { event, matcher, hooks } = hookDef;
@@ -388,11 +215,6 @@ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}
388
215
  entry.matcher = matcher;
389
216
  }
390
217
  entry.hooks = hooks.map(h => {
391
- // Command hooks: transform path to use $CLAUDE_PROJECT_DIR.
392
- // Hooks are copied to .morph/framework/hooks/ during `morph-spec init/update`.
393
- // `args` (chain.js's hook-name list) is appended AFTER the closing quote —
394
- // getHookSubpath() below only ever sees the bare `h.command` path, never
395
- // the args, so it can't accidentally capture them into the quoted command.
396
218
  const quotedPath = `node "$CLAUDE_PROJECT_DIR/.morph/framework/hooks/claude-code/${getHookSubpath(h.command)}"`;
397
219
  const command = h.args?.length ? `${quotedPath} ${h.args.join(' ')}` : quotedPath;
398
220
  return {
@@ -407,18 +229,12 @@ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}
407
229
  installed++;
408
230
  }
409
231
 
410
- // Install native permissions (idempotent — preserve user-added entries)
411
232
  settings.permissions = settings.permissions || {};
412
233
  settings.permissions.deny = settings.permissions.deny || [];
413
234
  settings.permissions.allow = settings.permissions.allow || [];
414
235
 
415
- // Stale-entry cleanup: remove morph-managed entries we no longer install.
416
- // The filters below drop `MORPH_*.includes(p)` before re-adding, so an entry
417
- // that LEFT those lists would otherwise survive forever in an existing
418
- // install — the upgrade path has to name it explicitly to reclaim it.
419
- // - state.json: was in deny pre-v7, now lives in allow.
420
- // - Write(...): matched nothing and made Claude Code warn on every session
421
- // start; the paired Edit(...) rule is what actually enforced them.
236
+ // An entry that left MORPH_* would survive forever in an existing install
237
+ // unless named here.
422
238
  const STALE_DENY = [
423
239
  'Write(.morph/state.json)',
424
240
  'Edit(.morph/state.json)',
@@ -446,17 +262,14 @@ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}
446
262
  settings.permissions.allow.push(perm);
447
263
  }
448
264
 
449
- // JSON Schema for IDE auto-complete and validation
450
265
  settings['$schema'] = 'https://json.schemastore.org/claude-code-settings.json';
451
266
 
452
- // Environment variables — always set MORPH_SPEC_ACTIVE + Agent Teams flag, preserve user additions
453
267
  settings.env = {
454
268
  ...settings.env,
455
269
  MORPH_SPEC_ACTIVE: 'true',
456
270
  CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1',
457
271
  };
458
272
 
459
- // Attribution — only set if not already customized by user
460
273
  if (!settings.attribution) {
461
274
  settings.attribution = {
462
275
  commit: '🤖 Co-authored by MORPH-SPEC',
@@ -464,29 +277,21 @@ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}
464
277
  };
465
278
  }
466
279
 
467
- // Plans directory for Claude Code plan mode
468
280
  if (!settings.plansDirectory) {
469
281
  settings.plansDirectory = '.morph/plans';
470
282
  }
471
283
 
472
- // Track installed hooks version
473
284
  settings._morph_managed = settings._morph_managed || {};
474
285
  settings._morph_managed.hooks_version = HOOKS_VERSION;
475
286
  settings._morph_managed.hooks_installed = [...new Set(MORPH_HOOKS.map(h => h.event))];
476
287
  settings._morph_managed.generated_at = new Date().toISOString();
477
288
 
478
- // Ensure .claude directory exists
479
289
  fx.ensureDir(claudeDir);
480
290
 
481
291
  fx.writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n');
482
292
  return { installed, updated: !alreadyCurrent };
483
293
  }
484
294
 
485
- /**
486
- * Try to find an existing Python 3 executable.
487
- *
488
- * @returns {string|null} Python command/path if found, null otherwise
489
- */
490
295
  function findPython() {
491
296
  if (process.platform !== 'win32') {
492
297
  for (const cmd of ['python3', 'python']) {
@@ -497,15 +302,12 @@ function findPython() {
497
302
  }
498
303
  return null;
499
304
  }
500
- // Windows: try PATH-visible commands first
501
- // 'py' is the Windows Python Launcher — standard on any Windows Python install
502
305
  for (const cmd of ['python3', 'py', 'python']) {
503
306
  try {
504
307
  const ver = execSync(`${cmd} --version`, { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] });
505
308
  if ((ver + '').includes('Python 3')) return cmd;
506
309
  } catch { /* not on PATH */ }
507
310
  }
508
- // Probe known Windows install locations
509
311
  const base = join(homedir(), 'AppData', 'Local', 'Programs', 'Python');
510
312
  if (existsSync(base)) {
511
313
  let dirs;
@@ -513,7 +315,7 @@ function findPython() {
513
315
  const py3 = dirs
514
316
  .filter(d => /^Python3\d*/i.test(d))
515
317
  .sort()
516
- .reverse(); // prefer highest version
318
+ .reverse();
517
319
  for (const dir of py3) {
518
320
  const exe = join(base, dir, 'python.exe');
519
321
  if (existsSync(exe)) return exe;
@@ -522,15 +324,6 @@ function findPython() {
522
324
  return null;
523
325
  }
524
326
 
525
- /**
526
- * Attempt to auto-install Python 3 using the system package manager.
527
- *
528
- * On Windows: uses winget (built-in on Windows 10+).
529
- * On macOS: uses brew if available.
530
- * On Linux: tries apt-get, then dnf.
531
- *
532
- * @returns {boolean} true if installation succeeded
533
- */
534
327
  function tryInstallPython() {
535
328
  const log = (msg) => process.stdout.write(` ${msg}\n`);
536
329
  log('⚠ Python 3 not found — attempting automatic installation...');
@@ -544,7 +337,6 @@ function tryInstallPython() {
544
337
  } else if (process.platform === 'darwin') {
545
338
  execSync('brew install python3', { stdio: 'inherit', timeout: 120_000 });
546
339
  } else {
547
- // Linux — try apt-get first, then dnf
548
340
  try {
549
341
  execSync('sudo apt-get update -qq && sudo apt-get install -y python3', { stdio: 'inherit', timeout: 120_000 });
550
342
  } catch {
@@ -560,53 +352,26 @@ function tryInstallPython() {
560
352
  }
561
353
  }
562
354
 
563
- /**
564
- * Resolve Python executable suitable for the statusLine command.
565
- *
566
- * Tries to find Python 3 on the system. If not found, attempts automatic
567
- * installation via the system package manager (winget/brew/apt/dnf).
568
- *
569
- * @returns {string} Python executable (full path on Windows if needed)
570
- */
571
355
  function resolvePythonForStatusline() {
572
- // First pass — check existing installation
573
356
  let python = findPython();
574
357
  if (python) return python;
575
358
 
576
- // Not found — try to install
577
359
  if (tryInstallPython()) {
578
- // Re-probe after installation
579
360
  python = findPython();
580
361
  if (python) return python;
581
362
  }
582
363
 
583
- // Last resort: return python3 and let it fail with a clear error
584
364
  return 'python3';
585
365
  }
586
366
 
587
367
  /**
588
- * Install the morph-spec statusline globally into ~/.claude/.
589
- *
590
- * Copies statusline.sh and statusline.py to ~/.claude/ and registers
591
- * the statusLine config in ~/.claude/settings.json so it applies to
592
- * every Claude Code session, not just morph-spec projects.
593
- *
594
- * Python 3 must be available as `python3` on PATH (global prerequisite).
595
- *
596
- * @param {string} hooksSourceDir - Path to framework/hooks/claude-code/
597
- * @param {string} [globalClaudeDirOverride] - Override ~/.claude/ (for testing)
598
- * @param {object} [options]
599
- * @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
600
- * Coletor de efeitos. Estes são os efeitos FORA do projeto — o relatório do
601
- * `--dry-run` é obrigado a nomeá-los separadamente, porque ninguém os
602
- * adivinha da palavra "update".
368
+ * Writes OUTSIDE the project (`~/.claude/`), so `--dry-run` must name these effects separately.
603
369
  * @returns {Promise<{ installed: boolean, globalDir: string }>}
604
370
  */
605
371
  export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOverride = null, { fx = realEffects() } = {}) {
606
372
  const globalDir = globalClaudeDirOverride || join(homedir(), '.claude');
607
373
  fx.ensureDir(globalDir);
608
374
 
609
- // Copy statusline scripts to ~/.claude/
610
375
  const scripts = ['statusline.sh', 'statusline.py'];
611
376
  for (const fname of scripts) {
612
377
  const src = join(hooksSourceDir, fname);
@@ -618,21 +383,15 @@ export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOve
618
383
  }
619
384
  }
620
385
 
621
- // Register statusLine in ~/.claude/settings.json (global user settings)
622
386
  const settingsPath = join(globalDir, 'settings.json');
623
387
 
624
- // On Windows, Claude Code cannot execute .sh files directly — call Python instead.
388
+ // Claude Code on Windows cannot execute .sh directly.
625
389
  const statuslineCommand = process.platform === 'win32'
626
390
  ? `"${resolvePythonForStatusline()}" "${join(globalDir, 'statusline.py').replace(/\\/g, '/')}"`
627
391
  : join(globalDir, 'statusline.sh').replace(/\\/g, '/');
628
392
 
629
- // refreshInterval: without it the status line only re-renders on events
630
- // (new assistant message, /compact, permission or vim mode change), so the
631
- // `↻` countdown to the 5h rate-limit reset — the only widget that moves on
632
- // its own — sits frozen through a long turn. Everything else is event-driven
633
- // and would be fine without this.
634
- // Fusão rasa: só `statusLine` é do morph-spec; tudo o mais no
635
- // `~/.claude/settings.json` é do usuário e sobrevive intocado.
393
+ // Without refreshInterval the status line only re-renders on events, freezing
394
+ // the rate-limit countdown through a long turn. Shallow merge: only `statusLine` is ours.
636
395
  fx.mergeJson(settingsPath, {
637
396
  statusLine: {
638
397
  type: 'command',
@@ -644,12 +403,6 @@ export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOve
644
403
  return { installed: true, globalDir };
645
404
  }
646
405
 
647
-
648
-
649
- /**
650
- * Remove old v1 agent-teams/dispatch.js hook entries.
651
- * @param {Object} settings
652
- */
653
406
  function removeOldDispatch(settings) {
654
407
  if (!settings.hooks?.PostToolUse) return;
655
408
 
@@ -666,12 +419,6 @@ function removeOldDispatch(settings) {
666
419
  }
667
420
  }
668
421
 
669
- /**
670
- * Remove all morph-managed hook entries.
671
- * Uses _morph:true marker (v2.2+) with legacy path-pattern fallback.
672
- * Preserves any user-added hooks.
673
- * @param {Object} settings
674
- */
675
422
  function removeMorphHooks(settings) {
676
423
  const morphPathPattern = /framework\/hooks\/claude-code\//;
677
424
 
@@ -679,14 +426,10 @@ function removeMorphHooks(settings) {
679
426
  if (!Array.isArray(entries)) continue;
680
427
 
681
428
  settings.hooks[event] = entries.filter(entry => {
682
- // Remove if explicitly marked as morph-managed
683
429
  if (entry._morph === true) return false;
684
430
 
685
431
  if (!Array.isArray(entry.hooks)) return true;
686
- // Legacy path-pattern detection: remove if all hooks are morph command hooks
687
- // Note: legacy path only covers command-type hooks (those with a path-matching command).
688
- // Agent/prompt hooks without _morph marker are NOT removed by this fallback.
689
- // This is acceptable since all morph hooks since v2.2.0 carry the _morph marker.
432
+ // Fallback for entries written before the `_morph` marker existed.
690
433
  const nonMorphHooks = entry.hooks.filter(h =>
691
434
  typeof h.command !== 'string' || !morphPathPattern.test(h.command)
692
435
  );
@@ -699,28 +442,15 @@ function removeMorphHooks(settings) {
699
442
  }
700
443
  }
701
444
 
702
- /**
703
- * Extract hook subpath from a full command string.
704
- * @param {string} command - e.g., 'node framework/hooks/claude-code/stop/validate-completion.js'
705
- * @returns {string} e.g., 'stop/validate-completion.js'
706
- */
707
445
  function getHookSubpath(command) {
708
446
  const match = command.match(/claude-code\/(.+)$/);
709
447
  return match ? match[1] : command;
710
448
  }
711
449
 
712
- /**
713
- * Get the current hooks version.
714
- * @returns {string}
715
- */
716
450
  export function getHooksVersion() {
717
451
  return HOOKS_VERSION;
718
452
  }
719
453
 
720
- /**
721
- * Get the list of hook definitions (for testing/inspection).
722
- * @returns {Array}
723
- */
724
454
  export function getHookDefinitions() {
725
455
  return MORPH_HOOKS;
726
456
  }