@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,15 +1,4 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * state-sync.js — PostToolUse hook
4
- *
5
- * Keeps .morph/state.json in sync with the agent's actions, without requiring
6
- * the agent to think about state. Fail-open: any error returns {} silently.
7
- *
8
- * Triggers:
9
- * - Write/Edit of .morph/features/{feature}/2-plan/plan.md → register feature
10
- * - Write/Edit of .morph/features/{feature}/{phase}/{output} → mark output created
11
- * - AskUserQuestion answered with option starting "aprovado" → advance gate
12
- */
13
2
 
14
3
  import { existsSync } from 'fs';
15
4
  import { join } from 'path';
@@ -27,7 +16,7 @@ import {
27
16
 
28
17
  const PHASE_FOLDERS = {
29
18
  '1-design': 'design',
30
- '1-ui': 'uiux', // legacy alias — still recognized during folder transition
19
+ '1-ui': 'uiux', // legacy alias
31
20
  '2-plan': 'plan',
32
21
  '3-implement': 'implement',
33
22
  '4-review': 'review',
@@ -62,8 +51,6 @@ function registerFeatureIfMissing(state, featureName) {
62
51
  if (!state.features) state.features = {};
63
52
  if (state.features[featureName]) return false;
64
53
 
65
- // Canonical object-form gates (incl. optional uiux) + stamped identity
66
- // (name=slug, workType, createdAt) — no anonymous shell, no boolean drift.
67
54
  state.features[featureName] = createDefaultFeature(featureName);
68
55
  return true;
69
56
  }
@@ -94,9 +81,7 @@ function isApprovalAnswer(answer) {
94
81
  );
95
82
  }
96
83
 
97
- // Gate monotonicity — mirror of src/core/state/gate-order.js (hooks are
98
- // installed standalone and cannot import from src/). Keep in sync:
99
- // plan first; uiux and review require plan.
84
+ // Mirror of src/core/state/gate-order.js: hooks are installed standalone and cannot import src/.
100
85
  const GATE_PREREQ = { plan: [], uiux: ['plan'], review: ['plan'] };
101
86
  function unmetGatePrereqs(gate, gates) {
102
87
  const isApproved = (g) => gates?.[g]?.approved === true || gates?.[g] === true;
@@ -108,9 +93,7 @@ function handleWriteEdit(input, sessionRoot) {
108
93
  const featureName = extractFeatureName(filePath);
109
94
  if (!featureName) return false;
110
95
 
111
- // Sync the tree that OWNS the written file. Syncing the session's tree
112
- // instead registered the feature (and its outputs) in the wrong `.morph/`
113
- // whenever a root-rooted session wrote into a secondary worktree.
96
+ // Sync the tree that OWNS the written file: a root session also writes into worktrees.
114
97
  const cwd = resolveMorphRoot(filePath, sessionRoot);
115
98
  if (!stateExists(cwd)) return false;
116
99
 
@@ -125,8 +108,6 @@ function handleWriteEdit(input, sessionRoot) {
125
108
  }
126
109
 
127
110
  if (changed) {
128
- // Persist the touched feature to its authoritative feature.json and refresh
129
- // the thin root index. The feature's state thus travels in its own folder.
130
111
  persistFeature(featureName, state.features[featureName], cwd);
131
112
  }
132
113
  return true;
@@ -142,22 +123,11 @@ function handleAskUserQuestion(input, sessionRoot) {
142
123
  const gate = gateFromQuestion(question);
143
124
  if (!gate) continue;
144
125
 
145
- // Attribute the approval to the feature the QUESTION names, in the tree
146
- // that owns it — never to "the first in_progress feature of whatever tree
147
- // the session sits in", which is how a Gate 2 for one feature was stamped
148
- // onto `export-hub`, leaving it with G2 approved and G1 never asked.
126
+ // The feature the QUESTION names, in the tree that owns it — never the session tree's first in-progress feature.
149
127
  const located = locateFeatureForQuestion(question, sessionRoot);
150
128
  if (!located) {
151
- // Refuse to guess. A gate on the wrong feature inverts gate order and
152
- // invites a second stamp on the real owner; writing nothing costs one
153
- // explicit remediation step.
154
- //
155
- // The remediation differs by WHY it refused. When the question names a
156
- // specific slug that no known tree has registered (a hotfix gate asked
157
- // before `create` ran — the case that used to fall through to "the
158
- // tree's one feature"), `morph-spec approve <that-name> <gate>` is a
159
- // dead end: approve.js does `getFeature()` and hard-refuses "Feature
160
- // not found". Name the real fix instead of the wrong command.
129
+ // Refuse to guess: a gate on the wrong feature inverts gate order. For an unregistered slug
130
+ // `approve` is a dead end ("Feature not found"), so the remediation names `create`.
161
131
  const allNames = allWorkTrees(sessionRoot).flatMap((r) => featureNamesIn(r));
162
132
  const candidate = unrecognizedSlugLike(question, allNames);
163
133
  const remediation = candidate
@@ -176,20 +146,14 @@ function handleAskUserQuestion(input, sessionRoot) {
176
146
  const { name: featureName, root } = located;
177
147
  if (!stateExists(root)) continue;
178
148
 
179
- // Prefer the authoritative feature.json; fall back to loadState's merged
180
- // view so a feature carried only by the thin index still reports its real
181
- // gates (otherwise every predecessor reads as unapproved and the
182
- // out-of-order warning below fires on a perfectly ordered approval).
149
+ // Fallback to the merged view: a feature only in the thin index would read every predecessor as unapproved.
183
150
  const feature = readFeatureFile(featureName, root) || loadState(root)?.features?.[featureName];
184
151
  const gates = feature?.approvalGates;
185
152
  const existing = gates?.[gate];
186
153
  const alreadyApproved = existing === true || existing?.approved === true;
187
154
  if (alreadyApproved) continue;
188
155
 
189
- // Gate monotonicity: fail-open. The human said "aprovado", so we DO approve
190
- // (never block a human in the loop), but if a predecessor gate is still
191
- // pending we surface a non-blocking warning — the CLI `morph-spec approve`
192
- // hard-refuses the same shape; here we alert so the operator can reconcile.
156
+ // Out of order still approves (never block a human in the loop); `morph-spec approve` refuses the same shape.
193
157
  const unmet = unmetGatePrereqs(gate, gates);
194
158
  if (unmet.length) {
195
159
  warnings.push(
@@ -199,15 +163,9 @@ function handleAskUserQuestion(input, sessionRoot) {
199
163
  );
200
164
  }
201
165
 
202
- // An "aprovado" answer to AskUserQuestion is a human approving in the loop.
203
- // Route through the shared canonical gate writer so the hook and the CLI
204
- // (`morph-spec approve`) produce one consistent shape
205
- // ({ approved, timestamp, approvedBy }) in the authoritative feature.json —
206
- // and a manual approval stays distinguishable from a policy auto-approval.
207
- // Same contract as `approve`'s --self-assessed: a human may ship Gate 3 without the
208
- // independent review reports, but the degradation is stamped, never silent.
166
+ // Same contract as `approve --self-assessed`: approving review without its reports is stamped, never silent.
209
167
  const selfAssessed = gate === 'review' && feature?.workType !== 'hotfix'
210
- && ['verifier.md', 'reviewer.md'].some((f) => !existsSync(join(root, '.morph', 'features', featureName, '4-review', f)));
168
+ && (feature?.workType === 'chore' ? ['reviewer.md'] : ['verifier.md', 'reviewer.md']).some((f) => !existsSync(join(root, '.morph', 'features', featureName, '4-review', f)));
211
169
  if (selfAssessed) {
212
170
  warnings.push(
213
171
  `⚠️ [morph-spec] Gate 3 de "${featureName}" aprovado SEM os relatórios de review independentes (verifier.md, reviewer.md) — ` +
@@ -219,19 +177,9 @@ function handleAskUserQuestion(input, sessionRoot) {
219
177
  return warnings.length ? warnings.join('\n') : null;
220
178
  }
221
179
 
222
- /**
223
- * @param {object} payload - parsed PostToolUse stdin payload (Write|Edit|AskUserQuestion)
224
- * @returns {Promise<object|null>} a hookSpecificOutput warning on a gate
225
- * order/attribution violation, or null (pass/silent).
226
- */
227
180
  export async function run(payload) {
228
181
  try {
229
- // The SESSION's root. It is only a starting point: which `.morph/` actually
230
- // gets read and written is decided per event — by the target path for
231
- // Write/Edit, by the feature the question names for AskUserQuestion. The
232
- // `stateExists` gate therefore moved into each handler, against the tree that
233
- // handler resolved; gating on the session's tree up here would skip work that
234
- // belongs to a worktree (and, worse, silently pass it to the wrong one).
182
+ // Only a starting point: each handler resolves the tree it reads and writes, so the stateExists gate lives there.
235
183
  const sessionRoot = projectDir();
236
184
 
237
185
  const input = payload || {};
@@ -244,12 +192,8 @@ export async function run(payload) {
244
192
  } else if (toolName === 'AskUserQuestion') {
245
193
  warning = handleAskUserQuestion(input, sessionRoot);
246
194
  }
247
- } catch {
248
- // fail-open
249
- }
195
+ } catch {}
250
196
 
251
- // A non-blocking order-violation warning is surfaced as injected context
252
- // (hookSpecificOutput.additionalContext); everything else stays silent.
253
197
  return warning ? contextResult(warning, 'PostToolUse') : null;
254
198
  } catch {
255
199
  return null;
@@ -1,41 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- /**
4
- * Telemetry Hook: harness execution-path capture
5
- *
6
- * Events: PostToolUse + PostToolUseFailure (all tools), PostToolBatch,
7
- * SubagentStop, UserPromptSubmit
8
- *
9
- * morph-spec is a Natural Language Harness: there is no central runtime to
10
- * instrument — Claude Code executes the skills and sub-agents. The only
11
- * vantage points are hooks. This one is the generic capture point: every tool
12
- * call and every user prompt is appended (one JSON line) to
13
- * .morph/logs/events.jsonl, from which `morph-spec telemetry` and
14
- * `morph-spec cost` reconstruct the path and measure it.
15
- *
16
- * Correlation is by an EXPLICIT key (feature + phase) written into each event,
17
- * not by ambient span context — hooks fire as separate node processes with no
18
- * shared trace context. `sessionId` and `agentId` (present only inside a
19
- * sub-agent) separate the orchestrator from the sub-agents it dispatched.
20
- *
21
- * PostToolUse fires only when a tool SUCCEEDS; a Bash command that exits
22
- * non-zero fires PostToolUseFailure instead. Only the exit code parsed from
23
- * its `error` first line is kept — the rest of `error` is the command's
24
- * output, where a secret value can appear with no name beside it.
25
- *
26
- * SINK RESOLUTION: the event is appended to the `.morph/` tree that OWNS the
27
- * tool's target file — the tree of its `.morph/features/` segment
28
- * (`resolveMorphRoot`), else the nearest morph tree above the file; tools with
29
- * no file path sink on the nearest morph tree above the payload's `cwd`, so a
30
- * sub-agent working in a worktree logs its Bash calls next to its Reads and
31
- * Writes. The feature is the tree's BINDING (`morph/<f>` branch, then
32
- * `MORPH_FEATURE` in `.morph/worktree.env`) before its most recent active
33
- * feature; a bound feature with no `feature.json` in that tree keeps phase null.
34
- *
35
- * SILENT: never blocks, never injects context — only appends. Fail-open on any
36
- * error (exit 0). Only active inside a morph project (stateExists gate).
37
- */
38
-
39
3
  import { statSync, readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
40
4
  import { dirname, join } from 'path';
41
5
  import { stateExists, getActiveFeature, derivePhaseForFeature } from '../../shared/state-reader.js';
@@ -51,10 +15,7 @@ function normalize(p) {
51
15
  return (p || '').replace(/\\/g, '/');
52
16
  }
53
17
 
54
- // A multi-KB prompt or command written verbatim on every call dominates the
55
- // per-line cost. The ORIGINAL length travels in a sibling `<field>Chars` key,
56
- // which `morph-spec cost` prefers over the truncated string's own length —
57
- // a contract with the telemetry reader: do not rename either half.
18
+ // The original length travels in a sibling `<field>Chars` key that `morph-spec cost` prefers: do not rename either half.
58
19
  const MAX_FIELD_CHARS = 2000;
59
20
  const TRUNCATE_SUFFIX = '…[truncado]';
60
21
 
@@ -64,13 +25,7 @@ const DEFAULT_READ_LINES = 2000;
64
25
  const MAX_SLICE_BYTES = 10 * 1024 * 1024;
65
26
  const BINARY_READ = /\.(png|jpe?g|gif|webp|bmp|ico|pdf)$/i;
66
27
 
67
- /**
68
- * Redact, then cap a free-text field, reporting its original length. Redacting
69
- * first keeps a token cut at the cap from surviving as a recognizable prefix.
70
- *
71
- * @param {string} value
72
- * @returns {{ value: string, chars: number }}
73
- */
28
+ // Redact before capping: a token cut at the cap would survive as a recognizable prefix.
74
29
  function capField(value) {
75
30
  const str = typeof value === 'string' ? value : '';
76
31
  const clean = redactSecrets(str);
@@ -78,15 +33,11 @@ function capField(value) {
78
33
  return { value: clean.slice(0, MAX_FIELD_CHARS) + TRUNCATE_SUFFIX, chars: str.length };
79
34
  }
80
35
 
81
- /**
82
- * Classify a Read target into the telemetry `kind` taxonomy — a contract with
83
- * src/lib/telemetry/telemetry-reader.js, so these values are pinned. Specific
84
- * categories first, 'code' last.
85
- */
36
+ // The `kind` values are a contract with src/lib/telemetry/telemetry-reader.js.
86
37
  function classifyReadKind(target) {
87
38
  const t = (target || '').toLowerCase();
88
39
 
89
- if (t.includes('evals/review-checklist')) return 'rubric';
40
+ if (t.includes('evals/review-checklist')) return 'checklist';
90
41
  if (t.includes('standards/')) return 'standard';
91
42
  if (t.endsWith('skill.md') || t.includes('/skills/')) return 'skill';
92
43
  if (t.split('/').pop() === 'agents.json') return 'agents';
@@ -107,7 +58,6 @@ function classifyReadKind(target) {
107
58
  return 'code';
108
59
  }
109
60
 
110
- /** Nearest directory at or above `dir` that is a morph project; `fallback` when none. */
111
61
  function morphRootAbove(dir, fallback) {
112
62
  if (!dir) return fallback;
113
63
  let current = normalize(dir);
@@ -122,12 +72,7 @@ function morphRootAbove(dir, fallback) {
122
72
 
123
73
  const FEATURE_NAME = /^[A-Za-z0-9._-]+$/;
124
74
 
125
- /**
126
- * The feature the TREE is bound to, before any guess from its state: the
127
- * branch `morph/<f>` checked out there, then `MORPH_FEATURE` in its
128
- * `.morph/worktree.env` (a task worktree sits on `morph-task/<n>`). Without
129
- * it, a sub-agent's worktree reported the root's most recent active feature.
130
- */
75
+ // Branch binding before state: otherwise a sub-agent's worktree reports the root's most recent active feature.
131
76
  async function boundFeature(root) {
132
77
  const fromBranch = morphBranchFeature(root);
133
78
  if (fromBranch) return fromBranch;
@@ -143,10 +88,6 @@ function featureFromPath(target) {
143
88
  return m ? m[1] : null;
144
89
  }
145
90
 
146
- /**
147
- * What a Read actually put in context: the requested line window, not the
148
- * whole file. Binary reads (images, PDFs) go in whole.
149
- */
150
91
  function readSliceBytes(absPath, input, size) {
151
92
  if (BINARY_READ.test(absPath)) return size;
152
93
  if (size > MAX_SLICE_BYTES) return undefined;
@@ -157,7 +98,7 @@ function readSliceBytes(absPath, input, size) {
157
98
  return Buffer.byteLength(lines.slice(offset - 1, offset - 1 + limit).join('\n'), 'utf-8');
158
99
  }
159
100
 
160
- /** Exit code and outcome of a finished tool call, without keeping its output. */
101
+ // Only the exit code is kept: the rest of `error` is command output, where a secret can appear with no name beside it.
161
102
  function outcomeOf(payload, input) {
162
103
  if (payload.hook_event_name === 'PostToolUseFailure') {
163
104
  const firstLine = String(payload.error ?? '').split('\n', 1)[0];
@@ -181,11 +122,7 @@ function countByName(calls) {
181
122
  return out;
182
123
  }
183
124
 
184
- /**
185
- * Sub-agent tokens live in their own transcript; each stop emits the delta.
186
- * One file per agent: two sub-agents stopping together never share a
187
- * read-modify-write, and one agent never stops twice at once.
188
- */
125
+ // One store file per agent, so two sub-agents stopping together never share a read-modify-write.
189
126
  function agentUsageEvent(payload, cwd) {
190
127
  const usage = readTranscriptUsage(payload.agent_transcript_path);
191
128
  if (!usage || !payload.agent_id) return null;
@@ -204,11 +141,6 @@ function agentUsageEvent(payload, cwd) {
204
141
  return { event: 'AgentUsage', ...usage, delta };
205
142
  }
206
143
 
207
- /**
208
- * @param {object} payload - parsed hook payload (PostToolUse, PostToolUseFailure,
209
- * PostToolBatch, SubagentStop or UserPromptSubmit)
210
- * @returns {Promise<null>} always null — pure side effect (append + claim heartbeat)
211
- */
212
144
  export async function run(payload) {
213
145
  try {
214
146
  if (!payload) return null;
@@ -222,8 +154,6 @@ export async function run(payload) {
222
154
 
223
155
  if (!stateExists(cwd)) return null;
224
156
 
225
- // getActiveFeature → loadState hydrates every feature.json: O(active
226
- // features) per call, fine while archive keeps that set small.
227
157
  let feature = null;
228
158
  let phase = null;
229
159
  try {
@@ -237,14 +167,9 @@ export async function run(payload) {
237
167
  feature = active?.name ?? featureFromPath(targetFilePath);
238
168
  if (feature) phase = active?.feature?.phase || derivePhaseForFeature(feature, cwd);
239
169
  }
240
- } catch {
241
- // enrichment is best-effort — a missing feature never blocks capture
242
- }
170
+ } catch {}
243
171
 
244
- // Claim heartbeat: this hook fires on every tool call, so it is the proof
245
- // of life for the advisory feature claim. shouldHeartbeat() is a local,
246
- // git-free pre-check that says no on ~99% of calls; only then is the git
247
- // common dir resolved (pure filesystem, no subprocess).
172
+ // Claim heartbeat: shouldHeartbeat is a git-free pre-check that says no on ~99% of calls.
248
173
  if (feature && payload.session_id) {
249
174
  try {
250
175
  const { shouldHeartbeat, heartbeatClaim } = await import('../../shared/claim-registry.js');
@@ -253,7 +178,7 @@ export async function run(payload) {
253
178
  const common = resolveGitCommonDir(cwd);
254
179
  if (common) heartbeatClaim(feature, { gitCommonDir: common, sessionId: payload.session_id });
255
180
  }
256
- } catch { /* fail-open — a missed heartbeat only shortens a claim's life */ }
181
+ } catch {}
257
182
  }
258
183
 
259
184
  const hookEvent = payload.hook_event_name;
@@ -290,17 +215,14 @@ export async function run(payload) {
290
215
  event.kind = classifyReadKind(event.target);
291
216
  if (Number.isInteger(input.offset)) event.offset = input.offset;
292
217
  if (Number.isInteger(input.limit)) event.limit = input.limit;
293
- // A failed Read ingested nothing. A stat/read race with a file deleted
294
- // between the Read and this hook omits the numbers rather than fail.
218
+ // A failed Read ingested nothing; a file deleted before this hook just omits the numbers.
295
219
  if (hookEvent !== 'PostToolUseFailure') {
296
220
  try {
297
221
  const abs = targetAbs || resolveTargetPath(event.target, projectDir);
298
222
  event.bytes = statSync(abs).size;
299
223
  const read = readSliceBytes(abs, input, event.bytes);
300
224
  if (read !== undefined) event.bytesRead = read;
301
- } catch {
302
- // omit bytes
303
- }
225
+ } catch {}
304
226
  }
305
227
  } else if (tool === 'Bash' || tool === 'PowerShell') {
306
228
  const command = capField(input.command || '');
@@ -311,8 +233,6 @@ export async function run(payload) {
311
233
  } else if (tool === 'Write' || tool === 'Edit') {
312
234
  event.target = normalize(input.file_path || input.path);
313
235
  } else {
314
- // Every other tool still counts toward "the whole path" — a compact
315
- // target keeps Grep/Glob/WebFetch bursts distinguishable.
316
236
  const target = input.file_path || input.path || input.pattern || input.query || input.url || '';
317
237
  if (target) event.target = normalize(String(target));
318
238
  }
@@ -333,7 +253,6 @@ export async function run(payload) {
333
253
  appendTelemetryEvent(event, cwd);
334
254
  return null;
335
255
  } catch {
336
- // Fail-open
337
256
  return null;
338
257
  }
339
258
  }
@@ -1,14 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- /**
4
- * Stop Hook: phase-transition alert, slice report audit, session usage.
5
- *
6
- * Phase transition tracking: stores last-seen phase per feature in
7
- * .morph/memory/phase-watch.json so the alert fires exactly once.
8
- *
9
- * Fail-open: exits 0 on any error.
10
- */
11
-
12
3
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
13
4
  import { join } from 'path';
14
5
  import { createHash } from 'crypto';
@@ -21,11 +12,7 @@ import { appendTelemetryEvent } from '../../shared/telemetry-logger.js';
21
12
  import { readTranscriptUsage, usageDelta } from '../../shared/transcript-usage.js';
22
13
  import { sliceProgress, sliceReportPath, inspectReport } from '../../shared/slice-progress.js';
23
14
 
24
- /**
25
- * Slices whose report is final but lacks what the report contract asks for:
26
- * prose, the files touched, every scenario the slice covers. Advisory: the
27
- * same rule `report-check` applies when the orchestrator resumes control.
28
- */
15
+ // Advisory: the same rule `report-check` applies when the orchestrator resumes control.
29
16
  function auditSliceReports(featurePath) {
30
17
  const progress = sliceProgress(featurePath);
31
18
  if (!progress) return [];
@@ -47,30 +34,16 @@ const MISSING_HINT = {
47
34
  partial: '1ª linha PARCIAL: a fatia não terminou',
48
35
  };
49
36
 
50
- // Phases where compaction is strongly recommended before starting
51
37
  const COMPACT_TRIGGER_PHASES = new Set(['implement']);
52
38
 
53
- // Phases where compaction is suggested when arriving (heavy spec phases)
54
39
  const HEAVY_SOURCE_PHASES = new Set(['plan', 'uiux']);
55
40
 
56
- /**
57
- * @param {object} payload - parsed Stop stdin payload. Most of this hook's
58
- * logic reads its own state from disk rather than the payload, EXCEPT the
59
- * session-usage capture below, which needs `payload.transcript_path` (and
60
- * `payload.session_id` for the throttle key) — the one thing only the
61
- * payload carries.
62
- * @returns {Promise<object|null>} a hookSpecificOutput advisory (phase
63
- * transition / build failure / incomplete slice report), or null (pass/silent).
64
- */
65
41
  export async function run(payload) {
66
42
  try {
67
- // Prevent infinite loop
68
43
  if (process.env.MORPH_STOP_HOOK_ACTIVE === '1') return null;
69
44
 
70
45
  if (!stateExists()) return null;
71
46
 
72
- // getActiveFeature() only returns in_progress/draft features.
73
- // Fall back to the most recently updated feature when all features are 'done'.
74
47
  let active = getActiveFeature() || getMostRecentFeature();
75
48
  if (!active) return null;
76
49
 
@@ -79,9 +52,8 @@ export async function run(payload) {
79
52
  const warnings = [];
80
53
 
81
54
  const memDir = join(cwd, '.morph', 'memory');
82
- try { mkdirSync(memDir, { recursive: true }); } catch { /* non-blocking */ }
55
+ try { mkdirSync(memDir, { recursive: true }); } catch {}
83
56
 
84
- // ── Phase transition detection ────────────────────────────────────────────
85
57
  const currentPhase = feature.phase || derivePhaseForFeature(name, cwd);
86
58
 
87
59
  const watchPath = join(memDir, 'phase-watch.json');
@@ -92,13 +64,12 @@ export async function run(payload) {
92
64
 
93
65
  const lastPhase = phaseWatch[name];
94
66
 
95
- // Update stored phase (always, so the alert only fires once per transition)
67
+ // Always stored, so the alert fires once per transition.
96
68
  try {
97
69
  phaseWatch[name] = currentPhase;
98
70
  writeFileSync(watchPath, JSON.stringify(phaseWatch, null, 2), 'utf-8');
99
- } catch { /* non-blocking */ }
71
+ } catch {}
100
72
 
101
- // Alert when entering implement from a heavy spec phase
102
73
  const isPhaseTransition = lastPhase && lastPhase !== currentPhase;
103
74
  const enteringImplement = COMPACT_TRIGGER_PHASES.has(currentPhase);
104
75
  const comingFromHeavyPhase = HEAVY_SOURCE_PHASES.has(lastPhase);
@@ -116,14 +87,7 @@ export async function run(payload) {
116
87
  );
117
88
  }
118
89
 
119
- // ── Slice report audit ────────────────────────────────────────────────────
120
- // Advisory warnings only — Stop hook never blocks.
121
- //
122
- // The same audit re-runs every Stop; without dedup it re-injects the same
123
- // ~500-token block turn after turn even when nothing changed. Hash-gate it:
124
- // only inject when the warning SET differs from the last one actually
125
- // surfaced for this feature. Resolved (no more warnings) clears the stored
126
- // hash, so a later recurrence — even an identical one — is reported fresh.
90
+ // Hash-gated: the audit re-runs every Stop, so only a changed warning set is injected; resolving clears the hash.
127
91
  try {
128
92
  const featurePath = join(cwd, '.morph', 'features', name);
129
93
  const reportWarnings = auditSliceReports(featurePath);
@@ -140,22 +104,15 @@ export async function run(payload) {
140
104
  warnings.push('📋 Relatórios de fatia:');
141
105
  for (const w of reportWarnings) warnings.push(` • ${w}`);
142
106
  warnedHashes[name] = { ...warnedHashes[name], reports: hash };
143
- try { writeFileSync(warnedHashesPath, JSON.stringify(warnedHashes, null, 2), 'utf-8'); } catch { /* non-blocking */ }
107
+ try { writeFileSync(warnedHashesPath, JSON.stringify(warnedHashes, null, 2), 'utf-8'); } catch {}
144
108
  }
145
- // same hash as last time it fired → already surfaced, stay silent
146
109
  } else if (warnedHashes[name]?.reports) {
147
110
  delete warnedHashes[name].reports;
148
- try { writeFileSync(warnedHashesPath, JSON.stringify(warnedHashes, null, 2), 'utf-8'); } catch { /* non-blocking */ }
111
+ try { writeFileSync(warnedHashesPath, JSON.stringify(warnedHashes, null, 2), 'utf-8'); } catch {}
149
112
  }
150
- } catch {
151
- // Fail-open — audit must never block Stop
152
- }
153
- // ────────────────────────────────────────────────────────────────────────
113
+ } catch {}
154
114
 
155
- // ── Session usage (real tokens, from the transcript) ──────────────────────
156
- // Silent side effect. The transcript total is cumulative, so each event
157
- // also carries the `delta` since the previous sample of the same session:
158
- // summing deltas per sessionId is the only sum that does not double-count.
115
+ // The transcript total is cumulative: summing the per-session `delta` is the only sum that does not double-count.
159
116
  try {
160
117
  const usage = readTranscriptUsage(payload?.transcript_path);
161
118
  if (usage) {
@@ -177,13 +134,10 @@ export async function run(payload) {
177
134
  delta,
178
135
  }, cwd);
179
136
  stored[sessionKey] = usage;
180
- try { writeFileSync(usagePath, JSON.stringify(stored, null, 2), 'utf-8'); } catch { /* non-blocking */ }
137
+ try { writeFileSync(usagePath, JSON.stringify(stored, null, 2), 'utf-8'); } catch {}
181
138
  }
182
139
  }
183
- } catch {
184
- // Fail-open — usage capture must never block Stop
185
- }
186
- // ────────────────────────────────────────────────────────────────────────
140
+ } catch {}
187
141
 
188
142
  if (warnings.length === 0) return null;
189
143
 
@@ -1,18 +1,10 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * warn-bash-source-write — PreToolUse Bash, warn only: a project source file written
4
- * by the shell (`writeTargets`) skips every Write/Edit guard (phase folders,
5
- * protected files, comment-guard). The command still runs.
6
- */
2
+ // Warn only: a source file written by the shell skips every Write/Edit guard. The command still runs.
7
3
  import { contextResult, runStandalone } from '../../shared/hook-response.js';
8
4
  import { stateExists } from '../../shared/state-reader.js';
9
5
  import { writeTargets } from '../../shared/command-tokens.js';
10
6
  import { isProjectSource } from '../../shared/source-files.js';
11
7
 
12
- /**
13
- * @param {object} payload - parsed PreToolUse stdin payload (Bash matcher)
14
- * @returns {Promise<object|null>} additionalContext naming the files, or null
15
- */
16
8
  export async function run(payload) {
17
9
  try {
18
10
  if (!stateExists()) return null;