@polymorphism-tech/morph-spec 8.47.0 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (377) hide show
  1. package/CLAUDE.md +51 -43
  2. package/README.md +39 -38
  3. package/bin/morph-spec.js +41 -113
  4. package/bin/validate.js +1 -147
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +1 -1
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/CLAUDE.md +41 -101
  9. package/framework/MORPH.md +95 -252
  10. package/framework/agents.json +86 -21
  11. package/framework/commands/morph-apply.md +5 -28
  12. package/framework/commands/morph-archive.md +1 -1
  13. package/framework/commands/morph-hotfix.md +8 -8
  14. package/framework/commands/morph-infra.md +1 -1
  15. package/framework/commands/morph-preflight.md +2 -3
  16. package/framework/commands/morph-status.md +4 -4
  17. package/framework/docs/CLI.md +574 -286
  18. package/framework/evals/review-checklist.md +82 -41
  19. package/framework/hooks/README.md +10 -11
  20. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
  21. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
  22. package/framework/hooks/claude-code/core/chain.js +4 -65
  23. package/framework/hooks/claude-code/core/comment-guard.js +3 -17
  24. package/framework/hooks/claude-code/core/denylist.json +33 -3
  25. package/framework/hooks/claude-code/core/enforce-phase-writes.js +15 -63
  26. package/framework/hooks/claude-code/core/gate-guard.js +9 -78
  27. package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
  28. package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
  29. package/framework/hooks/claude-code/core/heavy-release.js +2 -13
  30. package/framework/hooks/claude-code/core/loop-detect.js +2 -31
  31. package/framework/hooks/claude-code/core/pre-bash-denylist.js +36 -2
  32. package/framework/hooks/claude-code/core/protect-spec-files.js +25 -297
  33. package/framework/hooks/claude-code/core/state-sync.js +15 -85
  34. package/framework/hooks/claude-code/core/telemetry-log.js +15 -96
  35. package/framework/hooks/claude-code/core/validate-completion.js +37 -147
  36. package/framework/hooks/claude-code/core/warn-bash-source-write.js +23 -0
  37. package/framework/hooks/claude-code/core/worktree-context.js +15 -114
  38. package/framework/hooks/claude-code/core/worktree-create.js +6 -70
  39. package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
  40. package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
  41. package/framework/hooks/claude-code/statusline.py +55 -41
  42. package/framework/hooks/package.json +3 -0
  43. package/framework/hooks/shared/activity-logger.js +9 -89
  44. package/framework/hooks/shared/agent-reap.js +2 -17
  45. package/framework/hooks/shared/build-check.js +11 -76
  46. package/framework/hooks/shared/claim-registry.js +9 -119
  47. package/framework/hooks/shared/command-tokens.js +168 -44
  48. package/framework/hooks/shared/comment-rules.js +5 -20
  49. package/framework/hooks/shared/comment-scanner.js +11 -43
  50. package/framework/hooks/shared/comment-stripper.js +11 -85
  51. package/framework/hooks/shared/e2e-check.js +0 -0
  52. package/framework/hooks/shared/errata.js +59 -0
  53. package/framework/hooks/shared/feature-locator.js +11 -95
  54. package/framework/hooks/shared/frontend-locations.js +4 -0
  55. package/framework/hooks/shared/gate-question.js +12 -0
  56. package/framework/hooks/shared/git-exclude.js +3 -20
  57. package/framework/hooks/shared/git-paths.js +8 -104
  58. package/framework/hooks/shared/graph-freshness.js +22 -166
  59. package/framework/hooks/shared/graph-refresh.js +21 -149
  60. package/framework/hooks/shared/heavy-command.js +4 -12
  61. package/framework/hooks/shared/hook-response.js +8 -83
  62. package/framework/hooks/shared/morph-cli.js +8 -52
  63. package/framework/hooks/shared/payload-utils.js +10 -132
  64. package/framework/hooks/shared/phase-utils.js +14 -72
  65. package/framework/hooks/shared/redact-secrets.js +4 -15
  66. package/framework/hooks/shared/reparse-scan.js +6 -66
  67. package/framework/hooks/shared/slice-progress.js +99 -0
  68. package/framework/hooks/shared/source-files.js +4 -17
  69. package/framework/hooks/shared/spawn-command.js +23 -0
  70. package/framework/hooks/shared/state-reader.js +51 -247
  71. package/framework/hooks/shared/state-writer.js +9 -54
  72. package/framework/hooks/shared/stdin-reader.js +1 -11
  73. package/framework/hooks/shared/telemetry-logger.js +7 -67
  74. package/framework/hooks/shared/transcript-usage.js +7 -29
  75. package/framework/hooks/shared/worktree-root.js +7 -29
  76. package/framework/mcp/registry.json +3 -4
  77. package/framework/phases.json +23 -39
  78. package/framework/rules/csharp-standards.md +1 -1
  79. package/framework/rules/frontend-standards.md +1 -0
  80. package/framework/rules/hangfire-standards.md +1 -1
  81. package/framework/rules/neon-standards.md +1 -1
  82. package/framework/schemas/config.schema.json +14 -9
  83. package/framework/schemas/feature.schema.json +15 -50
  84. package/framework/skills/README.md +3 -4
  85. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  86. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  87. package/framework/skills/core/morph-apply/SKILL.md +149 -222
  88. package/framework/skills/core/morph-hotfix/SKILL.md +33 -42
  89. package/framework/skills/core/morph-machine/SKILL.md +2 -2
  90. package/framework/skills/core/morph-media/SKILL.md +1 -1
  91. package/framework/skills/core/morph-plan/SKILL.md +137 -299
  92. package/framework/skills/core/morph-review/SKILL.md +111 -136
  93. package/framework/skills/core/morph-standards/SKILL.md +27 -39
  94. package/framework/skills/core/morph-uiux/SKILL.md +94 -152
  95. package/framework/standards/STANDARDS.json +109 -1219
  96. package/framework/standards/STANDARDS.meta.json +1319 -0
  97. package/framework/standards/ai-agents/agent-archetypes.md +2 -2
  98. package/framework/standards/ai-agents/agent-session.md +1 -1
  99. package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
  100. package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
  101. package/framework/standards/ai-agents/evals-with-cache.md +1 -2
  102. package/framework/standards/ai-agents/guardrails.md +1 -1
  103. package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
  104. package/framework/standards/ai-agents/observability-patterns.md +1 -1
  105. package/framework/standards/ai-agents/production.md +1 -1
  106. package/framework/standards/ai-agents/prompt-sources.md +2 -2
  107. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  108. package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
  109. package/framework/standards/ai-agents/setup.md +3 -3
  110. package/framework/standards/ai-agents/sweet-spot.md +2 -2
  111. package/framework/standards/ai-agents/tool-adapters.md +0 -2
  112. package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
  113. package/framework/standards/ai-agents/workflows.md +1 -1
  114. package/framework/standards/architecture/code-comments.md +7 -7
  115. package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
  116. package/framework/standards/backend/api/validation.md +1 -1
  117. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
  118. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
  119. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
  120. package/framework/standards/data/nosql/cache/redis.md +1 -1
  121. package/framework/standards/data/nosql/cosmos-db.md +3 -3
  122. package/framework/standards/frontend/admin-app/forms.md +1 -1
  123. package/framework/standards/frontend/design-system/art-direction.md +88 -0
  124. package/framework/standards/frontend/design-system/premium-finish.md +77 -2
  125. package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
  126. package/framework/standards/integration/mcp/mcp-tools.md +7 -7
  127. package/framework/templates/CLAUDE.md.core.template +3 -3
  128. package/framework/templates/README.md +2 -3
  129. package/framework/templates/REGISTRY.json +15 -179
  130. package/framework/templates/docs/plan.md +72 -0
  131. package/framework/templates/docs/ui-mockups.md +4 -0
  132. package/framework/templates/dotnet/ai-kit/README.md +1 -9
  133. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
  134. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
  135. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
  136. package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
  137. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
  138. package/package.json +1 -1
  139. package/src/commands/context/context-refresh.js +0 -16
  140. package/src/commands/dashboard/dashboard.js +2 -8
  141. package/src/commands/e2e/e2e.js +18 -105
  142. package/src/commands/graph/affected.js +0 -14
  143. package/src/commands/graph/build.js +11 -50
  144. package/src/commands/graph/explain.js +0 -16
  145. package/src/commands/graph/status.js +1 -12
  146. package/src/commands/mcp/mcp-setup.js +4 -62
  147. package/src/commands/observability/cost.js +17 -111
  148. package/src/commands/observability/telemetry.js +1 -14
  149. package/src/commands/project/branch.js +4 -45
  150. package/src/commands/project/claim.js +4 -48
  151. package/src/commands/project/doctor-procs.js +1 -1
  152. package/src/commands/project/doctor.js +53 -307
  153. package/src/commands/project/env.js +1 -27
  154. package/src/commands/project/fleet.js +6 -35
  155. package/src/commands/project/heavy.js +3 -3
  156. package/src/commands/project/init.js +8 -68
  157. package/src/commands/project/install-plugin-cmd.js +0 -1
  158. package/src/commands/project/setup-infra-cmd.js +2 -1
  159. package/src/commands/project/stack-scan.js +58 -0
  160. package/src/commands/project/standards.js +65 -72
  161. package/src/commands/project/status.js +28 -121
  162. package/src/commands/project/update.js +31 -223
  163. package/src/commands/project/worktree.js +109 -883
  164. package/src/commands/scaffold/scaffold.js +0 -25
  165. package/src/commands/state/advance.js +65 -120
  166. package/src/commands/state/approve.js +87 -229
  167. package/src/commands/state/archive.js +23 -139
  168. package/src/commands/state/auto-approval.js +65 -0
  169. package/src/commands/state/create.js +49 -109
  170. package/src/commands/state/delete.js +11 -63
  171. package/src/commands/state/finish.js +105 -494
  172. package/src/commands/state/gate-check.js +74 -183
  173. package/src/commands/state/gate-decision.js +8 -42
  174. package/src/commands/state/handoff.js +4 -31
  175. package/src/commands/state/list.js +21 -91
  176. package/src/commands/state/report-check.js +134 -338
  177. package/src/commands/state/retype.js +12 -28
  178. package/src/commands/validation/index.js +0 -3
  179. package/src/commands/validation/mutate.js +79 -19
  180. package/src/commands/validation/plan-check.js +81 -0
  181. package/src/commands/validation/validate-feature.js +52 -216
  182. package/src/commands/validation/verify.js +45 -160
  183. package/src/core/paths/output-schema.js +9 -155
  184. package/src/core/state/feature-identity.js +1 -25
  185. package/src/core/state/gate-order.js +2 -30
  186. package/src/core/state/lifecycle-audit.js +8 -100
  187. package/src/core/state/state-manager.js +32 -395
  188. package/src/core/templates/template-registry.js +2 -97
  189. package/src/core/templates/template-renderer.js +13 -93
  190. package/src/core/templates/template-seed.js +21 -0
  191. package/src/core/work-types.js +10 -45
  192. package/src/dashboard/app/App.jsx +1 -8
  193. package/src/dashboard/app/components/ActivityFeed.jsx +2 -8
  194. package/src/dashboard/app/components/CommandPalette.jsx +4 -11
  195. package/src/dashboard/app/components/DetailPanel.jsx +1 -5
  196. package/src/dashboard/app/components/EntityLink.jsx +0 -12
  197. package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
  198. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
  199. package/src/dashboard/app/components/Markdown.jsx +0 -9
  200. package/src/dashboard/app/components/PhaseRail.jsx +0 -11
  201. package/src/dashboard/app/components/SearchFilter.jsx +0 -4
  202. package/src/dashboard/app/components/Sidebar.jsx +0 -5
  203. package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
  204. package/src/dashboard/app/hooks/usePref.js +1 -5
  205. package/src/dashboard/app/lib/format.js +0 -2
  206. package/src/dashboard/app/lib/phases.js +3 -9
  207. package/src/dashboard/app/lib/project.js +0 -12
  208. package/src/dashboard/app/pages/Activity.jsx +1 -1
  209. package/src/dashboard/app/pages/AgentMap.jsx +1 -8
  210. package/src/dashboard/app/pages/FeatureDetail.jsx +139 -133
  211. package/src/dashboard/app/pages/Features.jsx +9 -16
  212. package/src/dashboard/app/pages/Guide.jsx +2 -3
  213. package/src/dashboard/app/pages/HookInspector.jsx +2 -23
  214. package/src/dashboard/app/pages/PhasePipeline.jsx +3 -7
  215. package/src/dashboard/app/pages/Portfolio.jsx +0 -1
  216. package/src/dashboard/app/pages/RulesMap.jsx +1 -16
  217. package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
  218. package/src/dashboard/app/pages/Standards.jsx +2 -17
  219. package/src/dashboard/scanner/activity-scanner.js +14 -83
  220. package/src/dashboard/scanner/agents-scanner.js +1 -23
  221. package/src/dashboard/scanner/docs-scanner.js +1 -7
  222. package/src/dashboard/scanner/feature-status.js +2 -5
  223. package/src/dashboard/scanner/features-scanner.js +92 -113
  224. package/src/dashboard/scanner/hooks-scanner.js +5 -28
  225. package/src/dashboard/scanner/index.js +0 -4
  226. package/src/dashboard/scanner/portfolio.js +5 -29
  227. package/src/dashboard/scanner/project-api.js +8 -54
  228. package/src/dashboard/scanner/rules-scanner.js +1 -2
  229. package/src/dashboard/scanner/skills-scanner.js +2 -12
  230. package/src/dashboard/scanner/standards-scanner.js +4 -7
  231. package/src/dashboard/vite-plugin-morph-data.js +5 -19
  232. package/src/lib/ai/ai-checks.js +7 -52
  233. package/src/lib/ai/ai-project-scan.js +14 -108
  234. package/src/lib/ai/code-audit.js +7 -63
  235. package/src/lib/ai/pin-drift.js +8 -51
  236. package/src/lib/ai/registry-audit.js +11 -90
  237. package/src/lib/ci/ci-verify.js +14 -13
  238. package/src/lib/ci/github-ci.js +9 -37
  239. package/src/lib/ci/workflow-scaffold.js +2 -10
  240. package/src/lib/detectors/claude-config-detector.js +13 -74
  241. package/src/lib/detectors/index.js +8 -59
  242. package/src/lib/e2e/compose-env.js +5 -66
  243. package/src/lib/e2e/stack-teardown.js +6 -40
  244. package/src/lib/generators/architecture-scanner.js +18 -156
  245. package/src/lib/generators/handoff-generator.js +37 -112
  246. package/src/lib/generators/scaffold-generator.js +5 -101
  247. package/src/lib/generators/settings-generator.js +11 -122
  248. package/src/lib/git/branch-classifier.js +11 -84
  249. package/src/lib/git/default-branch.js +14 -169
  250. package/src/lib/git/evidence-staleness.js +2 -1
  251. package/src/lib/git/gh.js +2 -17
  252. package/src/lib/git/pending-prs.js +6 -37
  253. package/src/lib/git/pr-files.js +3 -28
  254. package/src/lib/git/pr-index.js +3 -34
  255. package/src/lib/git/tree-fingerprint.js +17 -17
  256. package/src/lib/git/tree-state.js +6 -33
  257. package/src/lib/graph/god-nodes.js +2 -22
  258. package/src/lib/graph/graph-health.js +3 -24
  259. package/src/lib/graph/graph-loader.js +5 -56
  260. package/src/lib/graph/graphify-runtime.js +7 -37
  261. package/src/lib/graph/graphifyignore.js +8 -74
  262. package/src/lib/graph/sanitize.js +16 -136
  263. package/src/lib/heavy/cleanup.js +2 -6
  264. package/src/lib/installers/effects.js +8 -93
  265. package/src/lib/installers/mcp-installer.js +9 -169
  266. package/src/lib/installers/memory-installer.js +8 -33
  267. package/src/lib/installers/templates-reconcile.js +5 -60
  268. package/src/lib/mutation/commands.js +3 -2
  269. package/src/lib/mutation/dotnet-introspection.js +281 -0
  270. package/src/lib/mutation/plan.js +14 -6
  271. package/src/lib/mutation/runner.js +1 -1
  272. package/src/lib/mutation/subset.js +47 -13
  273. package/src/lib/plan/artifact-vocabulary.js +40 -0
  274. package/src/lib/plan/checks.js +208 -0
  275. package/src/lib/plan/plan-parser.js +204 -0
  276. package/src/lib/plan/plan-template.js +18 -0
  277. package/src/lib/projects/registry.js +7 -44
  278. package/src/lib/review-reports.js +204 -0
  279. package/src/lib/risk-detector.js +131 -274
  280. package/src/lib/specs/adr-index.js +38 -85
  281. package/src/lib/specs/design-seed.js +18 -0
  282. package/src/lib/specs/frontmatter.js +31 -0
  283. package/src/lib/specs/gfm-table.js +11 -0
  284. package/src/lib/specs/revocations.js +31 -102
  285. package/src/lib/specs/screen-checklist.js +56 -132
  286. package/src/lib/stack/dotnet-build-ignores.js +43 -0
  287. package/src/lib/stack/stack-profile.js +1 -28
  288. package/src/lib/stack/stack-scan.js +260 -0
  289. package/src/lib/stack-filter.js +4 -27
  290. package/src/lib/standards/registry.js +37 -0
  291. package/src/lib/standards/sections.js +47 -0
  292. package/src/lib/standards/verified-against.js +19 -191
  293. package/src/lib/tasks/eval-node.js +23 -179
  294. package/src/lib/tasks/linux-runner.js +18 -142
  295. package/src/lib/tasks/test-nodes.js +43 -313
  296. package/src/lib/tasks/test-runner.js +107 -447
  297. package/src/lib/telemetry/metrics.js +13 -103
  298. package/src/lib/telemetry/telemetry-reader.js +13 -238
  299. package/src/lib/telemetry/verify-event.js +4 -34
  300. package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
  301. package/src/lib/validators/code-budget/index.js +0 -7
  302. package/src/lib/validators/contracts/contract-compliance-validator.js +4 -26
  303. package/src/lib/validators/contracts/index.js +0 -6
  304. package/src/lib/validators/nextjs/index.js +0 -5
  305. package/src/lib/validators/nextjs/next-component-validator.js +4 -81
  306. package/src/lib/validators/packages/index.js +0 -6
  307. package/src/lib/validators/packages/package-validator.js +6 -56
  308. package/src/lib/validators/reuse/index.js +0 -8
  309. package/src/lib/validators/reuse/reuse-validator.js +24 -137
  310. package/src/lib/validators/reuse/symbol-scanner.js +6 -62
  311. package/src/lib/validators/traceability-check.js +54 -156
  312. package/src/lib/validators/validation-runner.js +20 -125
  313. package/src/lib/validators/verify-runner.js +152 -575
  314. package/src/lib/work-type-detector.js +5 -67
  315. package/src/lib/worktree/docker-audit.js +17 -137
  316. package/src/lib/worktree/env-allocator.js +10 -98
  317. package/src/lib/worktree/feature-truth.js +12 -115
  318. package/src/lib/worktree/finish-backup.js +32 -242
  319. package/src/lib/worktree/fleet-scanner.js +31 -126
  320. package/src/lib/worktree/ignored-triage.js +15 -91
  321. package/src/lib/worktree/vscode-settings.js +2 -3
  322. package/src/lib/worktree/worktree-audit.js +21 -213
  323. package/src/lib/worktree/worktree-include.js +3 -5
  324. package/src/scripts/global-install.js +3 -9
  325. package/src/scripts/install-plugin.js +1 -13
  326. package/src/scripts/setup-infra.js +17 -138
  327. package/src/utils/agents-installer.js +92 -272
  328. package/src/utils/banner.js +4 -25
  329. package/src/utils/claude-md-injector.js +1 -37
  330. package/src/utils/claude-settings-manager.js +1 -87
  331. package/src/utils/file-copier.js +32 -90
  332. package/src/utils/hooks-installer.js +26 -296
  333. package/src/utils/skills-installer.js +4 -102
  334. package/src/utils/version-checker.js +4 -83
  335. package/framework/commands/morph-proposal.md +0 -57
  336. package/framework/evals/trace-schema.md +0 -67
  337. package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
  338. package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
  339. package/framework/hooks/git/pre-commit/specs.sh +0 -50
  340. package/framework/hooks/shared/tasks-validator.js +0 -111
  341. package/framework/rules/morph-task-tracking.md +0 -54
  342. package/framework/rules/morph-workflow.md +0 -244
  343. package/framework/schemas/tasks.schema.json +0 -79
  344. package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
  345. package/framework/skills/core/morph-implement/SKILL.md +0 -194
  346. package/framework/skills/core/morph-proposal/SKILL.md +0 -152
  347. package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
  348. package/framework/templates/docs/proposal.md +0 -192
  349. package/framework/templates/docs/spec.md +0 -57
  350. package/framework/templates/docs/ui-components.md +0 -52
  351. package/framework/templates/docs/ui-design-system.md +0 -76
  352. package/framework/templates/docs/ui-flows.md +0 -167
  353. package/framework/templates/examples/spec-examples.md +0 -91
  354. package/framework/templates/feature/decisions.md +0 -124
  355. package/framework/templates/feature/recap.md +0 -145
  356. package/framework/templates/tasks.template.json +0 -17
  357. package/src/commands/state/usecase.js +0 -143
  358. package/src/commands/tasks/dag.js +0 -181
  359. package/src/commands/utils/index.js +0 -3
  360. package/src/lib/agents/output-collector.js +0 -76
  361. package/src/lib/detectors/design-system-detector.js +0 -188
  362. package/src/lib/evaluator-report.js +0 -104
  363. package/src/lib/generators/design-system-generator.js +0 -508
  364. package/src/lib/specs/acervo-format.js +0 -70
  365. package/src/lib/specs/acervo-lifecycle.js +0 -264
  366. package/src/lib/specs/done-criteria-check.js +0 -200
  367. package/src/lib/specs/usecase-check.js +0 -301
  368. package/src/lib/specs/usecase-promoter.js +0 -301
  369. package/src/lib/specs/usecase-registry.js +0 -237
  370. package/src/lib/tasks/dispatch-planner.js +0 -319
  371. package/src/lib/tasks/dotnet-test-introspection.js +0 -632
  372. package/src/lib/tasks/mermaid-renderer.js +0 -132
  373. package/src/lib/tasks/task-classifier.js +0 -30
  374. package/src/lib/tasks/task-parser.js +0 -209
  375. package/src/lib/tasks/tasks-json-parser.js +0 -296
  376. package/src/lib/tasks/test-scope.js +0 -597
  377. package/src/utils/agent-id-mapper.js +0 -11
@@ -1,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';
@@ -95,11 +46,11 @@ function classifyReadKind(target) {
95
46
  if (inFeature) {
96
47
  const rel = inFeature[1];
97
48
  const PLAN_ARTIFACT_BASENAMES = new Set([
98
- 'proposal.md', 'spec.md', 'tasks.json',
99
- 'decisions.md', 'recap.md', 'handoff.md',
49
+ 'plan.md', 'checks.json',
50
+ 'handoff.md',
100
51
  ]);
101
52
  const base = rel.split('/').pop();
102
- if (PLAN_ARTIFACT_BASENAMES.has(base) || rel.includes('usecases/') || rel.includes('reports/')) {
53
+ if (PLAN_ARTIFACT_BASENAMES.has(base) || rel.includes('reports/')) {
103
54
  return 'plan-artifact';
104
55
  }
105
56
  }
@@ -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, recap/tasks 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';
@@ -19,121 +10,40 @@ import { contextResult, runStandalone } from '../../shared/hook-response.js';
19
10
  import { logHookActivity, logEvent } from '../../shared/activity-logger.js';
20
11
  import { appendTelemetryEvent } from '../../shared/telemetry-logger.js';
21
12
  import { readTranscriptUsage, usageDelta } from '../../shared/transcript-usage.js';
13
+ import { sliceProgress, sliceReportPath, inspectReport } from '../../shared/slice-progress.js';
22
14
 
23
- /**
24
- * Audit recap.md against tasks.json + standard-citation rule.
25
- *
26
- * Returns an array of human-readable warnings. Empty array = audit clean.
27
- *
28
- * Two checks:
29
- * 1. Task IDs that appear as a "## T<n>" heading in recap.md but still
30
- * carry status != 'done' in tasks.json — claim/state divergence.
31
- * 2. Task headings without a "**Standard:**" line nearby (within 10 lines
32
- * after the heading) — no standard citation = unauditable decision.
33
- */
34
- function auditRecapAndTasks(featurePath) {
15
+ // Advisory: the same rule `report-check` applies when the orchestrator resumes control.
16
+ function auditSliceReports(featurePath) {
17
+ const progress = sliceProgress(featurePath);
18
+ if (!progress) return [];
35
19
  const warnings = [];
36
- const recapPath = join(featurePath, '3-implement', 'recap.md');
37
- const tasksJsonPath = join(featurePath, '2-plan', 'tasks.json');
38
- if (!existsSync(recapPath) || !existsSync(tasksJsonPath)) return warnings;
39
-
40
- let recap, tasksJson;
41
- try {
42
- recap = readFileSync(recapPath, 'utf-8');
43
- tasksJson = JSON.parse(readFileSync(tasksJsonPath, 'utf-8'));
44
- } catch {
45
- return warnings;
46
- }
47
-
48
- const tasks = Array.isArray(tasksJson.tasks) ? tasksJson.tasks : [];
49
- if (tasks.length === 0) return warnings;
50
-
51
- // Index recap by task ID — collect line numbers where each task is mentioned
52
- // as completed. Supports two formats agents commonly use:
53
- // (a) "## T<n>" — heading style (per the SKILL.md template)
54
- // (b) "| T<n> | ... done" — markdown table row with done in same row
55
- const lines = recap.split('\n');
56
- const recapMentions = new Map(); // id → starting line index
57
- const headingRe = /^##\s+(T\d+)\b/;
58
- const tableRowRe = /^\|\s*(T\d+)\s*\|.*\bdone\b/i;
59
- lines.forEach((ln, i) => {
60
- const heading = ln.match(headingRe);
61
- if (heading && !recapMentions.has(heading[1])) {
62
- recapMentions.set(heading[1], i);
63
- return;
64
- }
65
- const tableRow = ln.match(tableRowRe);
66
- if (tableRow && !recapMentions.has(tableRow[1])) {
67
- recapMentions.set(tableRow[1], i);
68
- }
69
- });
70
-
71
- // Check 1: status drift — task mentioned as done in recap but tasks.json
72
- // still shows it as pending/in_progress.
73
- for (const t of tasks) {
74
- if (recapMentions.has(t.id) && t.status !== 'done') {
75
- warnings.push(
76
- `tasks.json drift: ${t.id} appears as done in recap.md but tasks.json shows status="${t.status}". Run writeTasksJson to sync.`
77
- );
20
+ for (const slice of progress.slices) {
21
+ if (slice.status !== 'feito') continue;
22
+ const { missing } = inspectReport(sliceReportPath(featurePath, slice.id), { covers: slice.covers });
23
+ if (missing.length) {
24
+ const hints = missing.map((id) => MISSING_HINT[id] ?? `cite ${id} pelo id`);
25
+ warnings.push(`relatório de ${slice.id} sem: ${missing.join(', ')} — ${hints.join('; ')} (formato na skill morph-apply; morph-spec report-check)`);
78
26
  }
79
27
  }
80
-
81
- // Check 2: missing Standard citation per task mention. Skip pure
82
- // table-row mentions (they're a summary, the heading-style block
83
- // is where the standard belongs) — only flag when the agent did
84
- // not produce a heading block at all for a task that is also marked
85
- // done in tasks.json.
86
- const standardRe = /\*\*Standard:\*\*|\bStandard\s*consulted\b|\bstandards?\s+used\b/i;
87
- const headingMentions = new Map();
88
- lines.forEach((ln, i) => {
89
- const m = ln.match(headingRe);
90
- if (m && !headingMentions.has(m[1])) headingMentions.set(m[1], i);
91
- });
92
- for (const t of tasks) {
93
- if (t.status !== 'done') continue;
94
- const lineIdx = headingMentions.get(t.id);
95
- if (lineIdx === undefined) {
96
- // Task is done but has no heading block at all — recap is summary-only.
97
- warnings.push(
98
- `recap.md task ${t.id} (status=done in tasks.json) has no detailed "## ${t.id}" block. Per morph-implement SKILL.md, each done task needs a heading block with a "**Standard:** ..." citation so the reviewer can audit the decision.`
99
- );
100
- continue;
101
- }
102
- const window = lines.slice(lineIdx, lineIdx + 12).join('\n');
103
- if (!standardRe.test(window)) {
104
- warnings.push(
105
- `recap.md task ${t.id} is missing a "**Standard:** ..." citation within 12 lines of its heading. Cite which standard from STANDARDS.json justified the implementation (or "ad-hoc (see decisions.md ADR-N)" if intentional).`
106
- );
107
- }
108
- }
109
-
110
28
  return warnings;
111
29
  }
112
30
 
113
- // Phases where compaction is strongly recommended before starting
31
+ const MISSING_HINT = {
32
+ what: '2-3 frases do que foi feito e como foi provado',
33
+ files: 'linha **Arquivos:** com todo arquivo criado ou modificado',
34
+ partial: '1ª linha PARCIAL: a fatia não terminou',
35
+ };
36
+
114
37
  const COMPACT_TRIGGER_PHASES = new Set(['implement']);
115
38
 
116
- // Phases where compaction is suggested when arriving (heavy spec phases)
117
39
  const HEAVY_SOURCE_PHASES = new Set(['plan', 'uiux']);
118
40
 
119
- /**
120
- * @param {object} payload - parsed Stop stdin payload. Most of this hook's
121
- * logic reads its own state from disk rather than the payload, EXCEPT the
122
- * session-usage capture below, which needs `payload.transcript_path` (and
123
- * `payload.session_id` for the throttle key) — the one thing only the
124
- * payload carries.
125
- * @returns {Promise<object|null>} a hookSpecificOutput advisory (phase
126
- * transition / build failure / recap drift), or null (pass/silent).
127
- */
128
41
  export async function run(payload) {
129
42
  try {
130
- // Prevent infinite loop
131
43
  if (process.env.MORPH_STOP_HOOK_ACTIVE === '1') return null;
132
44
 
133
45
  if (!stateExists()) return null;
134
46
 
135
- // getActiveFeature() only returns in_progress/draft features.
136
- // Fall back to the most recently updated feature when all features are 'done'.
137
47
  let active = getActiveFeature() || getMostRecentFeature();
138
48
  if (!active) return null;
139
49
 
@@ -142,9 +52,8 @@ export async function run(payload) {
142
52
  const warnings = [];
143
53
 
144
54
  const memDir = join(cwd, '.morph', 'memory');
145
- try { mkdirSync(memDir, { recursive: true }); } catch { /* non-blocking */ }
55
+ try { mkdirSync(memDir, { recursive: true }); } catch {}
146
56
 
147
- // ── Phase transition detection ────────────────────────────────────────────
148
57
  const currentPhase = feature.phase || derivePhaseForFeature(name, cwd);
149
58
 
150
59
  const watchPath = join(memDir, 'phase-watch.json');
@@ -155,13 +64,12 @@ export async function run(payload) {
155
64
 
156
65
  const lastPhase = phaseWatch[name];
157
66
 
158
- // Update stored phase (always, so the alert only fires once per transition)
67
+ // Always stored, so the alert fires once per transition.
159
68
  try {
160
69
  phaseWatch[name] = currentPhase;
161
70
  writeFileSync(watchPath, JSON.stringify(phaseWatch, null, 2), 'utf-8');
162
- } catch { /* non-blocking */ }
71
+ } catch {}
163
72
 
164
- // Alert when entering implement from a heavy spec phase
165
73
  const isPhaseTransition = lastPhase && lastPhase !== currentPhase;
166
74
  const enteringImplement = COMPACT_TRIGGER_PHASES.has(currentPhase);
167
75
  const comingFromHeavyPhase = HEAVY_SOURCE_PHASES.has(lastPhase);
@@ -171,7 +79,7 @@ export async function run(payload) {
171
79
  `⚡ Phase advanced: ${lastPhase} → implement`,
172
80
  ``,
173
81
  `💡 Recommended before starting implementation:`,
174
- ` Run /compact to free up context — spec.md and tasks.json`,
82
+ ` Run /compact to free up context — plan.md and the design`,
175
83
  ` loaded during planning take significant space. A compact now gives`,
176
84
  ` Claude a clean slate focused on writing code.`,
177
85
  ``,
@@ -179,18 +87,10 @@ export async function run(payload) {
179
87
  );
180
88
  }
181
89
 
182
- // ── Recap + tasks.json audit ──────────────────────────────────────────────
183
- // Surface drift between recap.md and tasks.json, plus missing standard
184
- // citations. Advisory warnings only — Stop hook never blocks.
185
- //
186
- // The same audit re-runs every Stop; without dedup it re-injects the same
187
- // ~500-token block turn after turn even when nothing changed. Hash-gate it:
188
- // only inject when the warning SET differs from the last one actually
189
- // surfaced for this feature. Resolved (no more warnings) clears the stored
190
- // 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.
191
91
  try {
192
92
  const featurePath = join(cwd, '.morph', 'features', name);
193
- const recapWarnings = auditRecapAndTasks(featurePath);
93
+ const reportWarnings = auditSliceReports(featurePath);
194
94
 
195
95
  const warnedHashesPath = join(memDir, 'warned-hashes.json');
196
96
  let warnedHashes = {};
@@ -198,28 +98,21 @@ export async function run(payload) {
198
98
  if (existsSync(warnedHashesPath)) warnedHashes = JSON.parse(readFileSync(warnedHashesPath, 'utf-8'));
199
99
  } catch { /* corrupt file — start fresh */ }
200
100
 
201
- if (recapWarnings.length > 0) {
202
- const hash = createHash('sha1').update(recapWarnings.join('\n')).digest('hex');
203
- if (warnedHashes[name]?.recap !== hash) {
204
- warnings.push('📋 Recap audit:');
205
- for (const w of recapWarnings) warnings.push(` • ${w}`);
206
- warnedHashes[name] = { ...warnedHashes[name], recap: hash };
207
- try { writeFileSync(warnedHashesPath, JSON.stringify(warnedHashes, null, 2), 'utf-8'); } catch { /* non-blocking */ }
101
+ if (reportWarnings.length > 0) {
102
+ const hash = createHash('sha1').update(reportWarnings.join('\n')).digest('hex');
103
+ if (warnedHashes[name]?.reports !== hash) {
104
+ warnings.push('📋 Relatórios de fatia:');
105
+ for (const w of reportWarnings) warnings.push(` • ${w}`);
106
+ warnedHashes[name] = { ...warnedHashes[name], reports: hash };
107
+ try { writeFileSync(warnedHashesPath, JSON.stringify(warnedHashes, null, 2), 'utf-8'); } catch {}
208
108
  }
209
- // same hash as last time it fired → already surfaced, stay silent
210
- } else if (warnedHashes[name]?.recap) {
211
- delete warnedHashes[name].recap;
212
- try { writeFileSync(warnedHashesPath, JSON.stringify(warnedHashes, null, 2), 'utf-8'); } catch { /* non-blocking */ }
109
+ } else if (warnedHashes[name]?.reports) {
110
+ delete warnedHashes[name].reports;
111
+ try { writeFileSync(warnedHashesPath, JSON.stringify(warnedHashes, null, 2), 'utf-8'); } catch {}
213
112
  }
214
- } catch {
215
- // Fail-open — audit must never block Stop
216
- }
217
- // ────────────────────────────────────────────────────────────────────────
113
+ } catch {}
218
114
 
219
- // ── Session usage (real tokens, from the transcript) ──────────────────────
220
- // Silent side effect. The transcript total is cumulative, so each event
221
- // also carries the `delta` since the previous sample of the same session:
222
- // 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.
223
116
  try {
224
117
  const usage = readTranscriptUsage(payload?.transcript_path);
225
118
  if (usage) {
@@ -241,13 +134,10 @@ export async function run(payload) {
241
134
  delta,
242
135
  }, cwd);
243
136
  stored[sessionKey] = usage;
244
- 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 {}
245
138
  }
246
139
  }
247
- } catch {
248
- // Fail-open — usage capture must never block Stop
249
- }
250
- // ────────────────────────────────────────────────────────────────────────
140
+ } catch {}
251
141
 
252
142
  if (warnings.length === 0) return null;
253
143
 
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ // Warn only: a source file written by the shell skips every Write/Edit guard. The command still runs.
3
+ import { contextResult, runStandalone } from '../../shared/hook-response.js';
4
+ import { stateExists } from '../../shared/state-reader.js';
5
+ import { writeTargets } from '../../shared/command-tokens.js';
6
+ import { isProjectSource } from '../../shared/source-files.js';
7
+
8
+ export async function run(payload) {
9
+ try {
10
+ if (!stateExists()) return null;
11
+ const sources = writeTargets(payload?.tool_input?.command || '').filter(isProjectSource);
12
+ if (!sources.length) return null;
13
+ return contextResult(
14
+ `⚠️ Código do projeto escrito pelo Bash (${sources.join(', ')}): os guards de escrita (fase, arquivo protegido, comment-guard) ` +
15
+ 'só enxergam Write/Edit. Reescreva esse arquivo por Write/Edit.',
16
+ 'PreToolUse',
17
+ );
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ await runStandalone(run);