@polymorphism-tech/morph-spec 9.0.0 → 9.2.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 (345) hide show
  1. package/CLAUDE.md +8 -9
  2. package/README.md +38 -36
  3. package/bin/morph-spec.js +11 -63
  4. package/bin/validate.js +1 -39
  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 +2 -1
  9. package/framework/MORPH.md +3 -3
  10. package/framework/agents.json +19 -11
  11. package/framework/commands/morph-apply.md +1 -1
  12. package/framework/commands/morph-status.md +1 -1
  13. package/framework/docs/CLI.md +22 -31
  14. package/framework/evals/review-checklist.md +12 -2
  15. package/framework/hooks/README.md +8 -44
  16. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
  17. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
  18. package/framework/hooks/claude-code/core/chain.js +4 -65
  19. package/framework/hooks/claude-code/core/comment-guard.js +2 -16
  20. package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
  21. package/framework/hooks/claude-code/core/gate-guard.js +7 -67
  22. package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
  23. package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
  24. package/framework/hooks/claude-code/core/heavy-release.js +2 -13
  25. package/framework/hooks/claude-code/core/loop-detect.js +2 -31
  26. package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
  27. package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
  28. package/framework/hooks/claude-code/core/state-sync.js +12 -68
  29. package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
  30. package/framework/hooks/claude-code/core/validate-completion.js +11 -57
  31. package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
  32. package/framework/hooks/claude-code/core/worktree-context.js +10 -110
  33. package/framework/hooks/claude-code/core/worktree-create.js +6 -70
  34. package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
  35. package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
  36. package/framework/hooks/claude-code/statusline.sh +11 -11
  37. package/framework/hooks/shared/activity-logger.js +9 -89
  38. package/framework/hooks/shared/agent-reap.js +2 -17
  39. package/framework/hooks/shared/build-check.js +6 -75
  40. package/framework/hooks/shared/claim-registry.js +9 -119
  41. package/framework/hooks/shared/command-tokens.js +6 -51
  42. package/framework/hooks/shared/comment-rules.js +5 -20
  43. package/framework/hooks/shared/comment-scanner.js +6 -25
  44. package/framework/hooks/shared/comment-stripper.js +11 -85
  45. package/framework/hooks/shared/e2e-check.js +0 -0
  46. package/framework/hooks/shared/errata.js +2 -15
  47. package/framework/hooks/shared/feature-locator.js +11 -95
  48. package/framework/hooks/shared/frontend-locations.js +1 -3
  49. package/framework/hooks/shared/gate-question.js +1 -7
  50. package/framework/hooks/shared/git-exclude.js +3 -20
  51. package/framework/hooks/shared/git-paths.js +8 -104
  52. package/framework/hooks/shared/graph-freshness.js +22 -166
  53. package/framework/hooks/shared/graph-refresh.js +21 -149
  54. package/framework/hooks/shared/heavy-command.js +4 -12
  55. package/framework/hooks/shared/hook-response.js +8 -83
  56. package/framework/hooks/shared/morph-cli.js +8 -52
  57. package/framework/hooks/shared/payload-utils.js +10 -132
  58. package/framework/hooks/shared/phase-utils.js +1 -42
  59. package/framework/hooks/shared/redact-secrets.js +4 -15
  60. package/framework/hooks/shared/reparse-scan.js +6 -66
  61. package/framework/hooks/shared/slice-progress.js +4 -23
  62. package/framework/hooks/shared/source-files.js +4 -17
  63. package/framework/hooks/shared/state-reader.js +13 -183
  64. package/framework/hooks/shared/state-writer.js +8 -52
  65. package/framework/hooks/shared/stdin-reader.js +1 -11
  66. package/framework/hooks/shared/telemetry-logger.js +7 -67
  67. package/framework/hooks/shared/transcript-usage.js +7 -29
  68. package/framework/hooks/shared/worktree-root.js +7 -29
  69. package/framework/rules/csharp-standards.md +1 -1
  70. package/framework/rules/frontend-standards.md +1 -0
  71. package/framework/rules/hangfire-standards.md +1 -1
  72. package/framework/rules/neon-standards.md +1 -1
  73. package/framework/rules/nextjs-standards.md +1 -1
  74. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  75. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  76. package/framework/skills/core/morph-apply/SKILL.md +4 -4
  77. package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
  78. package/framework/skills/core/morph-machine/SKILL.md +1 -1
  79. package/framework/skills/core/morph-media/SKILL.md +1 -1
  80. package/framework/skills/core/morph-plan/SKILL.md +9 -7
  81. package/framework/skills/core/morph-review/SKILL.md +9 -6
  82. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  83. package/framework/skills/core/morph-uiux/SKILL.md +29 -12
  84. package/framework/standards/STANDARDS.json +36 -14
  85. package/framework/standards/STANDARDS.meta.json +16 -2
  86. package/framework/standards/ai-agents/agent-archetypes.md +2 -2
  87. package/framework/standards/ai-agents/agent-session.md +1 -1
  88. package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
  89. package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
  90. package/framework/standards/ai-agents/guardrails.md +1 -1
  91. package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
  92. package/framework/standards/ai-agents/observability-patterns.md +1 -1
  93. package/framework/standards/ai-agents/production.md +1 -1
  94. package/framework/standards/ai-agents/prompt-sources.md +2 -2
  95. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  96. package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
  97. package/framework/standards/ai-agents/setup.md +2 -2
  98. package/framework/standards/ai-agents/sweet-spot.md +2 -2
  99. package/framework/standards/ai-agents/tool-adapters.md +0 -2
  100. package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
  101. package/framework/standards/ai-agents/workflows.md +1 -1
  102. package/framework/standards/architecture/code-comments.md +1 -1
  103. package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
  104. package/framework/standards/backend/api/validation.md +1 -1
  105. package/framework/standards/backend/dotnet/testing.md +10 -4
  106. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
  107. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
  108. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
  109. package/framework/standards/data/nosql/cache/redis.md +1 -1
  110. package/framework/standards/data/nosql/cosmos-db.md +3 -3
  111. package/framework/standards/frontend/admin-app/forms.md +1 -1
  112. package/framework/standards/frontend/design-system/art-direction.md +88 -0
  113. package/framework/standards/frontend/design-system/premium-finish.md +77 -2
  114. package/framework/standards/frontend/nextjs/testing.md +10 -6
  115. package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
  116. package/framework/templates/.idea/morph-templates.xml +92 -0
  117. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  118. package/framework/templates/CLAUDE.md.core.template +3 -3
  119. package/framework/templates/IDE-SNIPPETS.md +262 -262
  120. package/framework/templates/REGISTRY.json +9 -9
  121. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  122. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  123. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  124. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  125. package/framework/templates/docs/plan.md +3 -1
  126. package/framework/templates/docs/ui-mockups.md +4 -0
  127. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  128. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  129. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  130. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  131. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  132. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  133. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  134. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  135. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  136. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  137. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  138. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  139. package/framework/templates/state.template.json +17 -17
  140. package/framework/templates/ui/design-system.css +226 -226
  141. package/package.json +1 -1
  142. package/src/commands/context/context-refresh.js +0 -16
  143. package/src/commands/dashboard/dashboard.js +2 -8
  144. package/src/commands/e2e/e2e.js +17 -104
  145. package/src/commands/graph/affected.js +0 -14
  146. package/src/commands/graph/build.js +11 -50
  147. package/src/commands/graph/explain.js +0 -16
  148. package/src/commands/graph/status.js +1 -12
  149. package/src/commands/mcp/mcp-setup.js +4 -62
  150. package/src/commands/observability/cost.js +5 -57
  151. package/src/commands/observability/telemetry.js +1 -14
  152. package/src/commands/project/branch.js +4 -45
  153. package/src/commands/project/claim.js +4 -48
  154. package/src/commands/project/doctor.js +48 -302
  155. package/src/commands/project/env.js +1 -27
  156. package/src/commands/project/fleet.js +3 -32
  157. package/src/commands/project/init.js +4 -68
  158. package/src/commands/project/install-plugin-cmd.js +0 -1
  159. package/src/commands/project/standards.js +1 -62
  160. package/src/commands/project/status.js +0 -53
  161. package/src/commands/project/update.js +31 -239
  162. package/src/commands/project/worktree.js +108 -882
  163. package/src/commands/scaffold/scaffold.js +0 -25
  164. package/src/commands/state/advance.js +14 -85
  165. package/src/commands/state/approve.js +15 -97
  166. package/src/commands/state/archive.js +12 -64
  167. package/src/commands/state/auto-approval.js +2 -5
  168. package/src/commands/state/create.js +22 -109
  169. package/src/commands/state/delete.js +11 -63
  170. package/src/commands/state/finish.js +98 -475
  171. package/src/commands/state/gate-check.js +22 -132
  172. package/src/commands/state/gate-decision.js +3 -39
  173. package/src/commands/state/handoff.js +3 -30
  174. package/src/commands/state/list.js +14 -84
  175. package/src/commands/state/report-check.js +8 -58
  176. package/src/commands/state/retype.js +4 -26
  177. package/src/commands/validation/index.js +0 -3
  178. package/src/commands/validation/mutate.js +11 -11
  179. package/src/commands/validation/plan-check.js +4 -2
  180. package/src/commands/validation/tests-audit.js +7 -0
  181. package/src/commands/validation/validate-feature.js +10 -97
  182. package/src/commands/validation/verify.js +30 -113
  183. package/src/core/paths/output-schema.js +2 -101
  184. package/src/core/state/feature-identity.js +1 -25
  185. package/src/core/state/gate-order.js +0 -27
  186. package/src/core/state/lifecycle-audit.js +8 -100
  187. package/src/core/state/state-manager.js +22 -304
  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 +0 -1
  191. package/src/core/work-types.js +1 -36
  192. package/src/dashboard/app/App.jsx +1 -8
  193. package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
  194. package/src/dashboard/app/components/CommandPalette.jsx +0 -7
  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 +1 -5
  207. package/src/dashboard/app/lib/project.js +0 -12
  208. package/src/dashboard/app/main.jsx +13 -13
  209. package/src/dashboard/app/pages/AgentMap.jsx +1 -8
  210. package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
  211. package/src/dashboard/app/pages/Features.jsx +1 -8
  212. package/src/dashboard/app/pages/Guide.jsx +0 -1
  213. package/src/dashboard/app/pages/HookInspector.jsx +2 -23
  214. package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
  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 +10 -69
  220. package/src/dashboard/scanner/agents-scanner.js +1 -23
  221. package/src/dashboard/scanner/docs-scanner.js +0 -6
  222. package/src/dashboard/scanner/feature-status.js +2 -5
  223. package/src/dashboard/scanner/features-scanner.js +10 -66
  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 +2 -6
  231. package/src/dashboard/vite-plugin-morph-data.js +5 -19
  232. package/src/dashboard/vite.config.js +24 -24
  233. package/src/lib/ai/ai-checks.js +7 -52
  234. package/src/lib/ai/ai-project-scan.js +14 -108
  235. package/src/lib/ai/code-audit.js +7 -63
  236. package/src/lib/ai/pin-drift.js +8 -51
  237. package/src/lib/ai/registry-audit.js +11 -90
  238. package/src/lib/ci/ci-verify.js +4 -19
  239. package/src/lib/ci/github-ci.js +9 -37
  240. package/src/lib/ci/workflow-scaffold.js +14 -10
  241. package/src/lib/detectors/claude-config-detector.js +13 -74
  242. package/src/lib/detectors/index.js +4 -50
  243. package/src/lib/e2e/applicability.js +37 -0
  244. package/src/lib/e2e/compose-env.js +5 -66
  245. package/src/lib/e2e/stack-teardown.js +6 -40
  246. package/src/lib/generators/architecture-scanner.js +18 -156
  247. package/src/lib/generators/handoff-generator.js +9 -53
  248. package/src/lib/generators/scaffold-generator.js +5 -101
  249. package/src/lib/generators/settings-generator.js +4 -37
  250. package/src/lib/git/branch-classifier.js +11 -84
  251. package/src/lib/git/default-branch.js +14 -169
  252. package/src/lib/git/evidence-staleness.js +1 -0
  253. package/src/lib/git/gh.js +2 -17
  254. package/src/lib/git/pending-prs.js +6 -37
  255. package/src/lib/git/pr-files.js +3 -28
  256. package/src/lib/git/pr-index.js +3 -34
  257. package/src/lib/git/tree-fingerprint.js +3 -15
  258. package/src/lib/git/tree-state.js +6 -33
  259. package/src/lib/graph/god-nodes.js +2 -22
  260. package/src/lib/graph/graph-health.js +3 -24
  261. package/src/lib/graph/graph-loader.js +5 -56
  262. package/src/lib/graph/graphify-runtime.js +7 -37
  263. package/src/lib/graph/graphifyignore.js +8 -74
  264. package/src/lib/graph/sanitize.js +16 -136
  265. package/src/lib/heavy/cleanup.js +1 -1
  266. package/src/lib/installers/effects.js +8 -93
  267. package/src/lib/installers/mcp-installer.js +9 -169
  268. package/src/lib/installers/memory-installer.js +4 -27
  269. package/src/lib/installers/templates-reconcile.js +5 -60
  270. package/src/lib/mutation/adapters.js +6 -17
  271. package/src/lib/mutation/dotnet-introspection.js +20 -106
  272. package/src/lib/mutation/runner.js +23 -75
  273. package/src/lib/mutation/subset.js +1 -4
  274. package/src/lib/mutation/trx.js +0 -1
  275. package/src/lib/plan/artifact-vocabulary.js +5 -25
  276. package/src/lib/plan/checks.js +34 -33
  277. package/src/lib/plan/plan-parser.js +7 -8
  278. package/src/lib/projects/registry.js +7 -44
  279. package/src/lib/review-reports.js +11 -26
  280. package/src/lib/risk-detector.js +41 -173
  281. package/src/lib/specs/adr-index.js +6 -34
  282. package/src/lib/specs/frontmatter.js +2 -15
  283. package/src/lib/specs/gfm-table.js +1 -7
  284. package/src/lib/specs/revocations.js +1 -14
  285. package/src/lib/specs/screen-checklist.js +47 -124
  286. package/src/lib/stack/dotnet-build-ignores.js +2 -6
  287. package/src/lib/stack/stack-profile.js +1 -28
  288. package/src/lib/stack/stack-scan.js +1 -8
  289. package/src/lib/stack-filter.js +4 -27
  290. package/src/lib/standards/registry.js +1 -7
  291. package/src/lib/standards/sections.js +2 -13
  292. package/src/lib/standards/verified-against.js +19 -191
  293. package/src/lib/tasks/eval-node.js +21 -169
  294. package/src/lib/tasks/linux-runner.js +18 -142
  295. package/src/lib/tasks/test-nodes.js +43 -240
  296. package/src/lib/tasks/test-runner.js +43 -341
  297. package/src/lib/telemetry/metrics.js +7 -36
  298. package/src/lib/telemetry/telemetry-reader.js +13 -125
  299. package/src/lib/telemetry/verify-event.js +2 -30
  300. package/src/lib/tests-audit/audit.js +96 -0
  301. package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
  302. package/src/lib/validators/code-budget/index.js +0 -7
  303. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
  304. package/src/lib/validators/contracts/index.js +0 -6
  305. package/src/lib/validators/nextjs/index.js +0 -5
  306. package/src/lib/validators/nextjs/next-component-validator.js +4 -81
  307. package/src/lib/validators/packages/index.js +0 -6
  308. package/src/lib/validators/packages/package-validator.js +6 -56
  309. package/src/lib/validators/reuse/index.js +0 -8
  310. package/src/lib/validators/reuse/reuse-validator.js +11 -101
  311. package/src/lib/validators/reuse/symbol-scanner.js +6 -62
  312. package/src/lib/validators/traceability-check.js +5 -63
  313. package/src/lib/validators/validation-runner.js +21 -158
  314. package/src/lib/validators/verify-runner.js +85 -376
  315. package/src/lib/work-type-detector.js +5 -67
  316. package/src/lib/worktree/docker-audit.js +17 -136
  317. package/src/lib/worktree/env-allocator.js +10 -98
  318. package/src/lib/worktree/feature-truth.js +12 -115
  319. package/src/lib/worktree/finish-backup.js +32 -242
  320. package/src/lib/worktree/fleet-scanner.js +13 -95
  321. package/src/lib/worktree/ignored-triage.js +15 -91
  322. package/src/lib/worktree/vscode-settings.js +2 -3
  323. package/src/lib/worktree/worktree-audit.js +21 -213
  324. package/src/lib/worktree/worktree-include.js +3 -5
  325. package/src/scripts/global-install.js +3 -9
  326. package/src/scripts/install-plugin.js +1 -13
  327. package/src/scripts/setup-infra.js +17 -147
  328. package/src/utils/agents-installer.js +29 -276
  329. package/src/utils/banner.js +4 -25
  330. package/src/utils/claude-md-injector.js +1 -37
  331. package/src/utils/claude-settings-manager.js +1 -52
  332. package/src/utils/file-copier.js +11 -95
  333. package/src/utils/hooks-installer.js +21 -298
  334. package/src/utils/skills-installer.js +4 -101
  335. package/src/utils/version-checker.js +4 -83
  336. package/framework/commands/morph-proposal.md +0 -9
  337. package/framework/hooks/git/commit-msg/conventional-commits.sh +0 -33
  338. package/framework/hooks/git/pre-commit/agents.sh +0 -25
  339. package/framework/hooks/git/pre-commit/orchestrator.sh +0 -64
  340. package/framework/hooks/git/pre-push/run-tests.sh +0 -44
  341. package/src/commands/utils/index.js +0 -3
  342. package/src/lib/detectors/design-system-detector.js +0 -188
  343. package/src/lib/generators/design-system-generator.js +0 -508
  344. package/src/lib/specs/old-format.js +0 -68
  345. 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,130 +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
- * 3.13.0 adds warn-bash-source-write to the Bash PreToolUse chain: a warning,
60
- * never a block, when a command writes project source through a redirect.
61
- */
62
8
  const HOOKS_VERSION = '3.13.0';
63
9
 
64
- /** Marker for old dispatch.js (v1) */
65
10
  const OLD_DISPATCH_COMMAND = 'node framework/hooks/agent-teams/dispatch.js';
66
11
 
67
- /**
68
- * Native permissions.deny entries managed by MORPH-SPEC.
69
- *
70
- * state.json is NOT in deny because the agent edits it directly as a
71
- * fallback to the state-sync hook (per v7 hybrid state model). The
72
- * framework/ tree remains read-only — only the installer touches it.
73
- *
74
- * `Edit(path)` ONLY — never a matching `Write(path)`. Claude Code matches file
75
- * permissions on `Edit(path)` rules alone; an `Edit` rule covers every
76
- * file-editing tool, Write included, while a `Write(path)` rule matches nothing
77
- * and is reported as a warning on every session start. Verified empirically, not
78
- * assumed: with `Edit(.morph/framework/**)` as the ONLY deny entry, the Write
79
- * tool was refused and the target file was never created.
80
- */
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.
81
14
  const MORPH_DENY = [
82
- // Framework files — installer-managed, immutable at runtime
83
15
  'Edit(.morph/framework/**)',
84
- // .claude/CLAUDE.md (managed copy — source is framework/CLAUDE.md)
85
16
  'Edit(.claude/CLAUDE.md)',
86
- // .claude/rules/ (copied from framework/rules/)
87
17
  'Edit(.claude/rules/**)',
88
- // .claude/agents/morph-* (generated by agents-installer)
89
18
  'Edit(.claude/agents/morph-*)',
90
- // .claude/skills/ (copied from framework/skills/)
91
19
  'Edit(.claude/skills/**)',
92
20
  ];
93
21
 
94
- /**
95
- * Native permissions.allow entries managed by MORPH-SPEC.
96
- *
97
- * Pre-authorizes operations the agent legitimately performs during the
98
- * morph-spec flow, so the user does not get permission prompts on every
99
- * plan.md / slice report write.
100
- *
101
- * `Edit(path)` ONLY, for the same reason as MORPH_DENY: the `Edit` rule already
102
- * covers Write, and a `Write(path)` rule is inert noise.
103
- */
104
22
  const MORPH_ALLOW = [
105
- // Feature artifacts — the agent's working directory during plan/uiux/implement/review
106
23
  'Edit(.morph/features/**)',
107
- // state.json — fallback path when state-sync hook can't cover a case
108
24
  'Edit(.morph/state.json)',
109
- // Long-lived architectural context — agent may reconcile during planning
110
25
  'Edit(.morph/context/**)',
111
- // Folder creation under .morph
112
26
  'Bash(mkdir:.morph/**)',
113
27
  'Bash(ls:.morph/**)',
114
- // Gate 3 mutation round: restores bytes itself and has --restore; an auto-mode deny pauses the gate
28
+ // An auto-mode deny here pauses the review mutation round.
115
29
  'Bash(morph-spec mutate:*)',
116
30
  'Bash(npx morph-spec mutate:*)',
117
31
  ];
118
32
 
119
- // Backwards-compat alias used by older tests
120
33
  const MORPH_PERMISSIONS = MORPH_DENY;
121
34
 
122
- /**
123
- * All MORPH-SPEC hook definitions.
124
- * Each entry maps to a Claude Code hook event.
125
- *
126
- * R2 consolidation: an entry whose group has more than one hook now declares
127
- * ONE `core/chain.js` command with an `args` array (the hook base names, in
128
- * the ORDER they run) instead of one command per hook — chain.js imports and
129
- * calls each hook's `run(payload)` in that same process (see its own doc
130
- * comment for the block-first-wins / context-concatenation rules). A group
131
- * with exactly one hook stays a direct command — chaining a single link buys
132
- * nothing. `args` must stay a SEPARATE field from `command`: `getHookSubpath()`
133
- * below extracts the installed path from `command` alone via a regex anchored
134
- * on `claude-code/`, and inlining args into that string would get captured
135
- * into the quoted path.
136
- */
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.
137
38
  const MORPH_HOOKS = [
138
- // === SessionStart — worktree feature identity (secondary worktree) or
139
- // active-feature summary (primary root). Fulfills the SessionStart
140
- // injection role long documented but never wired (inject-morph-context.js).
141
- // Solo (only hook for this event) — nothing to chain. ===
142
39
  {
143
40
  event: 'SessionStart',
144
41
  matcher: null,
@@ -149,13 +46,6 @@ const MORPH_HOOKS = [
149
46
  }]
150
47
  },
151
48
 
152
- // === PreToolUse: Write|Edit — phase + spec protection (silent on success).
153
- // Chained in the SAME order the previous two-entry registry ran them:
154
- // protect-spec-files (locked-file check) before enforce-phase-writes (phase
155
- // folder check) — a block from either one wins and skips the other. Neither
156
- // hook declared its own timeout (both are pure sync fs checks with no
157
- // subprocess calls), so the chain declares none either — same platform
158
- // default both relied on individually. ===
159
49
  {
160
50
  event: 'PreToolUse',
161
51
  matcher: 'Write|Edit',
@@ -166,21 +56,8 @@ const MORPH_HOOKS = [
166
56
  }]
167
57
  },
168
58
 
169
- // === PreToolUse: Bash — irreversible-command guards, chained (block-first-
170
- // wins, one process). pre-bash-denylist (static pattern list) runs before
171
- // block-destructive-worktree-removal (dynamic: scans the target for reparse
172
- // points), so the cheap check short-circuits the filesystem walk. The second
173
- // one is the only defense against the raw-removal vector that does not depend
174
- // on someone having read MORPH.md. block-raw-worktree-add is its way-IN
175
- // counterpart (a raw `git worktree add` builds a tree with no junctions, no
176
- // ports, no identity) — static, so it costs nothing after the first two.
177
- // warn-bash-source-write only adds context (source written by redirect skips
178
- // the Write/Edit guards); after the guards, so a blocked command is not warned.
179
- // heavy-gate comes after every guard — it takes a slot, and a command a guard
180
- // blocks must not hold one — and before the rewriter.
181
- // worktree-env-inject is LAST: it rewrites launcher commands (updatedInput)
182
- // with the worktree's port block, and a command a previous link blocked must
183
- // 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.
184
61
  {
185
62
  event: 'PreToolUse',
186
63
  matcher: 'Bash',
@@ -191,12 +68,6 @@ const MORPH_HOOKS = [
191
68
  }]
192
69
  },
193
70
 
194
- // === WorktreeCreate — REPLACES Claude Code's native `git worktree add` for
195
- // `claude -w`, the EnterWorktree tool and `Agent isolation:"worktree"`
196
- // (measured: all three fire it). The hook prints the created path as its
197
- // last stdout line; a non-zero exit fails the creation. No matcher exists
198
- // for this event. 120s: it spawns the morph-spec CLI (setup or provision),
199
- // which runs git, links infra, allocates ports and materializes state. ===
200
71
  {
201
72
  event: 'WorktreeCreate',
202
73
  matcher: null,
@@ -207,11 +78,7 @@ const MORPH_HOOKS = [
207
78
  }]
208
79
  },
209
80
 
210
- // === WorktreeRemove — routes Claude Code's own cleanup through
211
- // `morph-spec worktree remove --target --if-clean` (junctions dropped BEFORE
212
- // git touches the tree; a dirty tree is left alone). Output ignored, always
213
- // exit 0. Secondary net: in headless runs the event was observed not to
214
- // fire — block-destructive-worktree-removal remains the primary defense. ===
81
+ // Not fired in headless runs: block-destructive-worktree-removal stays the primary defense.
215
82
  {
216
83
  event: 'WorktreeRemove',
217
84
  matcher: null,
@@ -222,8 +89,7 @@ const MORPH_HOOKS = [
222
89
  }]
223
90
  },
224
91
 
225
- // === PostToolUse: Write|Edit — comment-guard, solo: it blocks the edit that
226
- // adds a forbidden comment. ===
92
+ // comment-guard must be the last Write|Edit link: a block ends the chain.
227
93
  {
228
94
  event: 'PostToolUse',
229
95
  matcher: 'Write|Edit',
@@ -233,16 +99,7 @@ const MORPH_HOOKS = [
233
99
  }]
234
100
  },
235
101
 
236
- // === PostToolUse: Write|Edit|AskUserQuestion — state-sync (autonomous
237
- // state.json updates) then gate-guard. gate-guard runs AFTER state-sync so
238
- // it sees the gate that state-sync just recorded — now true within a single
239
- // process too, because state-writer.js's persistFeature() invalidates
240
- // state-reader.js's per-process loadState() memo on every write (R1.1 added
241
- // that memo; without the invalidation, a chained gate-guard could read the
242
- // pre-write snapshot). Both are silent fail-open guardrails; gate-guard only
243
- // warns (never blocks) when an observed gate action disagrees with the
244
- // persisted gateDecisions (issue #63). Both links declared 3000ms; sequential
245
- // 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.
246
103
  {
247
104
  event: 'PostToolUse',
248
105
  matcher: 'Write|Edit|AskUserQuestion',
@@ -254,15 +111,6 @@ const MORPH_HOOKS = [
254
111
  }]
255
112
  },
256
113
 
257
- // === PostToolUse: All — telemetry capture (silent) + loop detection +
258
- // graph refresh kick. telemetry-log is the single generic vantage point of an
259
- // NLH: it appends every tool call (skill, sub-agent dispatch, knowledge-base
260
- // read, …) to .morph/logs/events.jsonl. Silent and fail-open — never blocks,
261
- // never injects. `morph-spec telemetry` reads the stream back.
262
- // graph-refresh-kick only spawns a DETACHED worker (never waits on graphify),
263
- // so it rides this chain instead of costing a process of its own per Bash
264
- // call. Timeout stays the sum of the first two links (sequential worst case);
265
- // the kick returns in milliseconds. ===
266
114
  {
267
115
  event: 'PostToolUse',
268
116
  matcher: null,
@@ -274,9 +122,7 @@ const MORPH_HOOKS = [
274
122
  }]
275
123
  },
276
124
 
277
- // === PostToolUseFailure: All — telemetry of the failed call (exit code,
278
- // duration). loop-detect stays out: its warning is shaped for PostToolUse.
279
- // 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.
280
126
  {
281
127
  event: 'PostToolUseFailure',
282
128
  matcher: null,
@@ -289,8 +135,6 @@ const MORPH_HOOKS = [
289
135
  }]
290
136
  },
291
137
 
292
- // === PostToolBatch — one event per model turn that used tools. The event
293
- // takes no matcher. ===
294
138
  {
295
139
  event: 'PostToolBatch',
296
140
  matcher: null,
@@ -302,8 +146,6 @@ const MORPH_HOOKS = [
302
146
  }]
303
147
  },
304
148
 
305
- // === SubagentStop — the sub-agent's token usage, read from its own
306
- // transcript (the session transcript does not include it). ===
307
149
  {
308
150
  event: 'SubagentStop',
309
151
  matcher: null,
@@ -315,10 +157,6 @@ const MORPH_HOOKS = [
315
157
  }]
316
158
  },
317
159
 
318
- // === UserPromptSubmit — telemetry capture of the user's prompt (silent).
319
- // The only vantage point on the user's own prompt; completes the execution
320
- // path alongside sub-agent prompts captured on PostToolUse. Solo (the only
321
- // hook registered on this event) — nothing to chain. ===
322
160
  {
323
161
  event: 'UserPromptSubmit',
324
162
  matcher: null,
@@ -329,7 +167,6 @@ const MORPH_HOOKS = [
329
167
  }]
330
168
  },
331
169
 
332
- // === Stop — build check + session snapshot. Solo. ===
333
170
  {
334
171
  event: 'Stop',
335
172
  matcher: null,
@@ -341,25 +178,13 @@ const MORPH_HOOKS = [
341
178
  ];
342
179
 
343
180
  /**
344
- * Install or update all MORPH-SPEC hooks in .claude/settings.local.json.
345
- *
346
- * - Idempotent: safe to call multiple times
347
- * - Removes old v1 dispatch.js entry if present
348
- * - Tracks installed version via _morph_managed.hooks_version
349
- *
350
- * @param {string} targetPath - Project root directory
351
- * @param {object} [options]
352
- * @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
353
- * Coletor de efeitos. Esta função é o seam mais barato do caminho de update:
354
- * o objeto de settings INTEIRO já é montado em memória e há UMA escrita, no
355
- * fim. Previsão e execução percorrem a mesma montagem.
181
+ * Idempotent merge of the MORPH-SPEC hooks into `.claude/settings.local.json`.
356
182
  * @returns {Promise<{ installed: number, updated: boolean }>}
357
183
  */
358
184
  export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}) {
359
185
  const claudeDir = join(targetPath, '.claude');
360
186
  const settingsPath = join(claudeDir, 'settings.local.json');
361
187
 
362
- // Read existing settings or start empty
363
188
  let settings = {};
364
189
  if (existsSync(settingsPath)) {
365
190
  try {
@@ -371,17 +196,12 @@ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}
371
196
 
372
197
  settings.hooks = settings.hooks || {};
373
198
 
374
- // Check if already at current version
375
199
  const managed = settings._morph_managed || {};
376
200
  const alreadyCurrent = managed.hooks_version === HOOKS_VERSION;
377
201
 
378
- // Remove old v1 dispatch.js entry
379
202
  removeOldDispatch(settings);
380
-
381
- // Remove all morph-managed hooks (clean slate for idempotent update)
382
203
  removeMorphHooks(settings);
383
204
 
384
- // Install all hooks from the registry
385
205
  let installed = 0;
386
206
  for (const hookDef of MORPH_HOOKS) {
387
207
  const { event, matcher, hooks } = hookDef;
@@ -395,11 +215,6 @@ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}
395
215
  entry.matcher = matcher;
396
216
  }
397
217
  entry.hooks = hooks.map(h => {
398
- // Command hooks: transform path to use $CLAUDE_PROJECT_DIR.
399
- // Hooks are copied to .morph/framework/hooks/ during `morph-spec init/update`.
400
- // `args` (chain.js's hook-name list) is appended AFTER the closing quote —
401
- // getHookSubpath() below only ever sees the bare `h.command` path, never
402
- // the args, so it can't accidentally capture them into the quoted command.
403
218
  const quotedPath = `node "$CLAUDE_PROJECT_DIR/.morph/framework/hooks/claude-code/${getHookSubpath(h.command)}"`;
404
219
  const command = h.args?.length ? `${quotedPath} ${h.args.join(' ')}` : quotedPath;
405
220
  return {
@@ -414,18 +229,12 @@ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}
414
229
  installed++;
415
230
  }
416
231
 
417
- // Install native permissions (idempotent — preserve user-added entries)
418
232
  settings.permissions = settings.permissions || {};
419
233
  settings.permissions.deny = settings.permissions.deny || [];
420
234
  settings.permissions.allow = settings.permissions.allow || [];
421
235
 
422
- // Stale-entry cleanup: remove morph-managed entries we no longer install.
423
- // The filters below drop `MORPH_*.includes(p)` before re-adding, so an entry
424
- // that LEFT those lists would otherwise survive forever in an existing
425
- // install — the upgrade path has to name it explicitly to reclaim it.
426
- // - state.json: was in deny pre-v7, now lives in allow.
427
- // - Write(...): matched nothing and made Claude Code warn on every session
428
- // 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.
429
238
  const STALE_DENY = [
430
239
  'Write(.morph/state.json)',
431
240
  'Edit(.morph/state.json)',
@@ -453,17 +262,14 @@ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}
453
262
  settings.permissions.allow.push(perm);
454
263
  }
455
264
 
456
- // JSON Schema for IDE auto-complete and validation
457
265
  settings['$schema'] = 'https://json.schemastore.org/claude-code-settings.json';
458
266
 
459
- // Environment variables — always set MORPH_SPEC_ACTIVE + Agent Teams flag, preserve user additions
460
267
  settings.env = {
461
268
  ...settings.env,
462
269
  MORPH_SPEC_ACTIVE: 'true',
463
270
  CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1',
464
271
  };
465
272
 
466
- // Attribution — only set if not already customized by user
467
273
  if (!settings.attribution) {
468
274
  settings.attribution = {
469
275
  commit: '🤖 Co-authored by MORPH-SPEC',
@@ -471,29 +277,21 @@ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}
471
277
  };
472
278
  }
473
279
 
474
- // Plans directory for Claude Code plan mode
475
280
  if (!settings.plansDirectory) {
476
281
  settings.plansDirectory = '.morph/plans';
477
282
  }
478
283
 
479
- // Track installed hooks version
480
284
  settings._morph_managed = settings._morph_managed || {};
481
285
  settings._morph_managed.hooks_version = HOOKS_VERSION;
482
286
  settings._morph_managed.hooks_installed = [...new Set(MORPH_HOOKS.map(h => h.event))];
483
287
  settings._morph_managed.generated_at = new Date().toISOString();
484
288
 
485
- // Ensure .claude directory exists
486
289
  fx.ensureDir(claudeDir);
487
290
 
488
291
  fx.writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n');
489
292
  return { installed, updated: !alreadyCurrent };
490
293
  }
491
294
 
492
- /**
493
- * Try to find an existing Python 3 executable.
494
- *
495
- * @returns {string|null} Python command/path if found, null otherwise
496
- */
497
295
  function findPython() {
498
296
  if (process.platform !== 'win32') {
499
297
  for (const cmd of ['python3', 'python']) {
@@ -504,15 +302,12 @@ function findPython() {
504
302
  }
505
303
  return null;
506
304
  }
507
- // Windows: try PATH-visible commands first
508
- // 'py' is the Windows Python Launcher — standard on any Windows Python install
509
305
  for (const cmd of ['python3', 'py', 'python']) {
510
306
  try {
511
307
  const ver = execSync(`${cmd} --version`, { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] });
512
308
  if ((ver + '').includes('Python 3')) return cmd;
513
309
  } catch { /* not on PATH */ }
514
310
  }
515
- // Probe known Windows install locations
516
311
  const base = join(homedir(), 'AppData', 'Local', 'Programs', 'Python');
517
312
  if (existsSync(base)) {
518
313
  let dirs;
@@ -520,7 +315,7 @@ function findPython() {
520
315
  const py3 = dirs
521
316
  .filter(d => /^Python3\d*/i.test(d))
522
317
  .sort()
523
- .reverse(); // prefer highest version
318
+ .reverse();
524
319
  for (const dir of py3) {
525
320
  const exe = join(base, dir, 'python.exe');
526
321
  if (existsSync(exe)) return exe;
@@ -529,15 +324,6 @@ function findPython() {
529
324
  return null;
530
325
  }
531
326
 
532
- /**
533
- * Attempt to auto-install Python 3 using the system package manager.
534
- *
535
- * On Windows: uses winget (built-in on Windows 10+).
536
- * On macOS: uses brew if available.
537
- * On Linux: tries apt-get, then dnf.
538
- *
539
- * @returns {boolean} true if installation succeeded
540
- */
541
327
  function tryInstallPython() {
542
328
  const log = (msg) => process.stdout.write(` ${msg}\n`);
543
329
  log('⚠ Python 3 not found — attempting automatic installation...');
@@ -551,7 +337,6 @@ function tryInstallPython() {
551
337
  } else if (process.platform === 'darwin') {
552
338
  execSync('brew install python3', { stdio: 'inherit', timeout: 120_000 });
553
339
  } else {
554
- // Linux — try apt-get first, then dnf
555
340
  try {
556
341
  execSync('sudo apt-get update -qq && sudo apt-get install -y python3', { stdio: 'inherit', timeout: 120_000 });
557
342
  } catch {
@@ -567,53 +352,26 @@ function tryInstallPython() {
567
352
  }
568
353
  }
569
354
 
570
- /**
571
- * Resolve Python executable suitable for the statusLine command.
572
- *
573
- * Tries to find Python 3 on the system. If not found, attempts automatic
574
- * installation via the system package manager (winget/brew/apt/dnf).
575
- *
576
- * @returns {string} Python executable (full path on Windows if needed)
577
- */
578
355
  function resolvePythonForStatusline() {
579
- // First pass — check existing installation
580
356
  let python = findPython();
581
357
  if (python) return python;
582
358
 
583
- // Not found — try to install
584
359
  if (tryInstallPython()) {
585
- // Re-probe after installation
586
360
  python = findPython();
587
361
  if (python) return python;
588
362
  }
589
363
 
590
- // Last resort: return python3 and let it fail with a clear error
591
364
  return 'python3';
592
365
  }
593
366
 
594
367
  /**
595
- * Install the morph-spec statusline globally into ~/.claude/.
596
- *
597
- * Copies statusline.sh and statusline.py to ~/.claude/ and registers
598
- * the statusLine config in ~/.claude/settings.json so it applies to
599
- * every Claude Code session, not just morph-spec projects.
600
- *
601
- * Python 3 must be available as `python3` on PATH (global prerequisite).
602
- *
603
- * @param {string} hooksSourceDir - Path to framework/hooks/claude-code/
604
- * @param {string} [globalClaudeDirOverride] - Override ~/.claude/ (for testing)
605
- * @param {object} [options]
606
- * @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
607
- * Coletor de efeitos. Estes são os efeitos FORA do projeto — o relatório do
608
- * `--dry-run` é obrigado a nomeá-los separadamente, porque ninguém os
609
- * adivinha da palavra "update".
368
+ * Writes OUTSIDE the project (`~/.claude/`), so `--dry-run` must name these effects separately.
610
369
  * @returns {Promise<{ installed: boolean, globalDir: string }>}
611
370
  */
612
371
  export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOverride = null, { fx = realEffects() } = {}) {
613
372
  const globalDir = globalClaudeDirOverride || join(homedir(), '.claude');
614
373
  fx.ensureDir(globalDir);
615
374
 
616
- // Copy statusline scripts to ~/.claude/
617
375
  const scripts = ['statusline.sh', 'statusline.py'];
618
376
  for (const fname of scripts) {
619
377
  const src = join(hooksSourceDir, fname);
@@ -625,21 +383,15 @@ export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOve
625
383
  }
626
384
  }
627
385
 
628
- // Register statusLine in ~/.claude/settings.json (global user settings)
629
386
  const settingsPath = join(globalDir, 'settings.json');
630
387
 
631
- // On Windows, Claude Code cannot execute .sh files directly — call Python instead.
388
+ // Claude Code on Windows cannot execute .sh directly.
632
389
  const statuslineCommand = process.platform === 'win32'
633
390
  ? `"${resolvePythonForStatusline()}" "${join(globalDir, 'statusline.py').replace(/\\/g, '/')}"`
634
391
  : join(globalDir, 'statusline.sh').replace(/\\/g, '/');
635
392
 
636
- // refreshInterval: without it the status line only re-renders on events
637
- // (new assistant message, /compact, permission or vim mode change), so the
638
- // `↻` countdown to the 5h rate-limit reset — the only widget that moves on
639
- // its own — sits frozen through a long turn. Everything else is event-driven
640
- // and would be fine without this.
641
- // Fusão rasa: só `statusLine` é do morph-spec; tudo o mais no
642
- // `~/.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.
643
395
  fx.mergeJson(settingsPath, {
644
396
  statusLine: {
645
397
  type: 'command',
@@ -651,12 +403,6 @@ export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOve
651
403
  return { installed: true, globalDir };
652
404
  }
653
405
 
654
-
655
-
656
- /**
657
- * Remove old v1 agent-teams/dispatch.js hook entries.
658
- * @param {Object} settings
659
- */
660
406
  function removeOldDispatch(settings) {
661
407
  if (!settings.hooks?.PostToolUse) return;
662
408
 
@@ -673,12 +419,6 @@ function removeOldDispatch(settings) {
673
419
  }
674
420
  }
675
421
 
676
- /**
677
- * Remove all morph-managed hook entries.
678
- * Uses _morph:true marker (v2.2+) with legacy path-pattern fallback.
679
- * Preserves any user-added hooks.
680
- * @param {Object} settings
681
- */
682
422
  function removeMorphHooks(settings) {
683
423
  const morphPathPattern = /framework\/hooks\/claude-code\//;
684
424
 
@@ -686,14 +426,10 @@ function removeMorphHooks(settings) {
686
426
  if (!Array.isArray(entries)) continue;
687
427
 
688
428
  settings.hooks[event] = entries.filter(entry => {
689
- // Remove if explicitly marked as morph-managed
690
429
  if (entry._morph === true) return false;
691
430
 
692
431
  if (!Array.isArray(entry.hooks)) return true;
693
- // Legacy path-pattern detection: remove if all hooks are morph command hooks
694
- // Note: legacy path only covers command-type hooks (those with a path-matching command).
695
- // Agent/prompt hooks without _morph marker are NOT removed by this fallback.
696
- // 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.
697
433
  const nonMorphHooks = entry.hooks.filter(h =>
698
434
  typeof h.command !== 'string' || !morphPathPattern.test(h.command)
699
435
  );
@@ -706,28 +442,15 @@ function removeMorphHooks(settings) {
706
442
  }
707
443
  }
708
444
 
709
- /**
710
- * Extract hook subpath from a full command string.
711
- * @param {string} command - e.g., 'node framework/hooks/claude-code/stop/validate-completion.js'
712
- * @returns {string} e.g., 'stop/validate-completion.js'
713
- */
714
445
  function getHookSubpath(command) {
715
446
  const match = command.match(/claude-code\/(.+)$/);
716
447
  return match ? match[1] : command;
717
448
  }
718
449
 
719
- /**
720
- * Get the current hooks version.
721
- * @returns {string}
722
- */
723
450
  export function getHooksVersion() {
724
451
  return HOOKS_VERSION;
725
452
  }
726
453
 
727
- /**
728
- * Get the list of hook definitions (for testing/inspection).
729
- * @returns {Array}
730
- */
731
454
  export function getHookDefinitions() {
732
455
  return MORPH_HOOKS;
733
456
  }