@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,19 +1,3 @@
1
- /**
2
- * Skills Installer
3
- *
4
- * Copies framework skills to .claude/skills/ during morph-spec init.
5
- * Installs only the `core/` skill set — the canonical NLH skills referenced by
6
- * MORPH.md (morph-brainstorming, morph-stack-scan, morph-plan, morph-implement,
7
- * morph-review, morph-standards). The 3-tier system was collapsed
8
- * into a single source of truth in framework/skills/core/.
9
- *
10
- * Source skills are directory-based: framework/skills/core/{skill-name}/SKILL.md
11
- * (plus optional scripts/, references/, assets/ subdirectories).
12
- *
13
- * Directory-based skills have their entire directory copied so Claude Code can
14
- * execute scripts and load references on demand.
15
- */
16
-
17
1
  import { existsSync, readdirSync, statSync, readFileSync } from 'fs';
18
2
  import { basename, join } from 'path';
19
3
  import { fileURLToPath } from 'url';
@@ -23,59 +7,16 @@ import { realEffects } from '../lib/installers/effects.js';
23
7
  const __dirname = fileURLToPath(new URL('.', import.meta.url));
24
8
  const FRAMEWORK_SKILLS_DIR = join(__dirname, '..', '..', 'framework', 'skills');
25
9
 
26
- /**
27
- * Skill levels to install in .claude/skills/.
28
- * Only `core/` survives — single source of truth aligned with MORPH.md.
29
- */
30
10
  const SKILL_LEVELS_TO_INSTALL = ['core'];
31
11
 
32
- /**
33
- * Check if a skill should be installed based on its stacks: frontmatter.
34
- * @param {string} skillContent - Raw SKILL.md content
35
- * @param {string[]} projectTags - Stack tags from config
36
- * @returns {boolean}
37
- */
38
12
  export function shouldInstallSkill(skillContent, projectTags) {
39
13
  return shouldInstall(parseStacks(skillContent), projectTags);
40
14
  }
41
15
 
42
- /**
43
- * Copy a skill directory tree from src to dest, skipping README.md files.
44
- *
45
- * UM efeito por skill (não um por arquivo): é assim que o instalador pensa
46
- * ("instalar a skill X"), e é o que o relatório do `--dry-run` precisa dizer.
47
- *
48
- * @param {string} src - Source directory path
49
- * @param {string} dest - Destination directory path
50
- * @param {ReturnType<import('../lib/installers/effects.js').createEffects>} fx
51
- */
52
16
  function copySkillDirectory(src, dest, fx) {
53
17
  fx.copyDir(src, dest, { filter: (from) => basename(from) !== 'README.md' });
54
18
  }
55
19
 
56
- /**
57
- * Walk srcDir and install skills into destDir.
58
- *
59
- * Handles two source formats:
60
- * 1. Directory-based skill: srcDir/{skill-name}/SKILL.md
61
- * → copies entire directory (SKILL.md + scripts/ + references/ + assets/)
62
- * → destDir/{skill-name}/
63
- *
64
- * 2. Flat .md skill (legacy): srcDir/{skill-name}.md
65
- * → destDir/{skill-name}/SKILL.md
66
- *
67
- * The `core/` level directory is traversed recursively. A directory is
68
- * recognized as a skill directory when it contains a SKILL.md file.
69
- *
70
- * @param {string} srcDir - Source directory to walk
71
- * @param {string} destDir - Destination base directory (.claude/skills/)
72
- * @param {string[]} projectTags - Stack tags from config
73
- * @param {Set<string>} installedNow - Accumulator: skill names this call
74
- * actually (re)installed. NOT derived by re-scanning destDir afterwards —
75
- * a disk scan can't distinguish "installed this run" from "leftover from a
76
- * previous run with different projectTags", which is exactly the stale
77
- * entry a stack change needs cleaned up.
78
- */
79
20
  function installSkillsFromDir(srcDir, destDir, projectTags, installedNow, fx) {
80
21
  const entries = readdirSync(srcDir);
81
22
  for (const entry of entries) {
@@ -85,20 +26,15 @@ function installSkillsFromDir(srcDir, destDir, projectTags, installedNow, fx) {
85
26
  if (stat.isDirectory()) {
86
27
  const skillMdPath = join(srcPath, 'SKILL.md');
87
28
  if (existsSync(skillMdPath)) {
88
- // Directory-based skill — check stack filter before copying.
89
- // O filtro é PURO e continua sendo o único: previsão e execução
90
- // percorrem esta mesma linha.
91
29
  const content = readFileSync(skillMdPath, 'utf-8');
92
30
  if (!shouldInstallSkill(content, projectTags)) continue;
93
31
  const skillDestDir = join(destDir, entry);
94
32
  copySkillDirectory(srcPath, skillDestDir, fx);
95
33
  installedNow.add(entry);
96
34
  } else {
97
- // Level/category directory — recurse
98
35
  installSkillsFromDir(srcPath, destDir, projectTags, installedNow, fx);
99
36
  }
100
37
  } else if (entry.endsWith('.md') && entry !== 'README.md') {
101
- // Legacy flat .md skill — check stack filter before copying
102
38
  const content = readFileSync(srcPath, 'utf-8');
103
39
  if (!shouldInstallSkill(content, projectTags)) continue;
104
40
  const skillName = entry.slice(0, -3);
@@ -132,32 +68,12 @@ function collectNamesFromDir(dir, names) {
132
68
  }
133
69
  }
134
70
 
135
- /**
136
- * Install morph framework skills to .claude/skills/ in the target project.
137
- * Skills are copied into subdirectories as SKILL.md files so Claude Code can
138
- * discover them natively via /<skill-name> syntax (e.g., /phase-design,
139
- * /nextjs-expert). Directory-based skills also get their scripts/,
140
- * references/, and assets/ subdirectories copied for on-demand loading.
141
- *
142
- * Stack filtering: if projectTags is provided, only skills whose `stacks:`
143
- * frontmatter matches the project tags are installed. Skills without a
144
- * `stacks:` field are always installed (universal).
145
- *
146
- * @param {string} projectDir - Target project root directory
147
- * @param {Object} [options]
148
- * @param {string[]} [options.projectTags=[]] - Stack tags from config
149
- * @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
150
- * Coletor de efeitos; o default executa e registra numa lista descartada.
151
- */
152
71
  export async function installSkills(projectDir, { projectTags = [], fx = realEffects() } = {}) {
153
72
  const claudeSkillsDir = join(projectDir, '.claude', 'skills');
154
73
  fx.ensureDir(claudeSkillsDir);
155
74
 
156
- // Skill names this call actually (re)installs, given projectTags. Tracked
157
- // as we go (NOT by re-scanning claudeSkillsDir afterwards) — a disk scan
158
- // can't tell "installed this run" apart from "leftover from a previous
159
- // run with different projectTags", which is exactly the stale entry a
160
- // stack change (or a re-run that narrows the stack) needs cleaned up.
75
+ // Tracked as we go: a disk scan can't tell this run's installs from leftovers
76
+ // of a run with other projectTags, which a stack change must clean up.
161
77
  const installedNow = new Set();
162
78
  for (const level of SKILL_LEVELS_TO_INSTALL) {
163
79
  const levelDir = join(FRAMEWORK_SKILLS_DIR, level);
@@ -165,22 +81,8 @@ export async function installSkills(projectDir, { projectTags = [], fx = realEff
165
81
  installSkillsFromDir(levelDir, claudeSkillsDir, projectTags, installedNow, fx);
166
82
  }
167
83
 
168
- // Clean-slate: remove any skill dir that is a KNOWN framework skill (still
169
- // present in framework/skills/core/) but did NOT get (re)installed this
170
- // run — i.e. it was filtered out by a `stacks:` mismatch (project.stack
171
- // changed, or a re-run narrowed the tags). Previously scoped to `morph-`
172
- // prefixed names only, which silently exempted every non-`morph-` skill
173
- // (the Umbraco pair) from ever being cleaned up: install once with
174
- // `umbraco` in the stack, drop the stack later, and they'd survive every
175
- // reinstall/update forever. Names outside allSourceSkills (user-added,
176
- // non-framework skills) are never touched.
177
- //
178
- // Em `dryRun` este laço NÃO emite nada, e a razão está na D6: numa execução
179
- // real do `update` ele sempre vê um diretório VAZIO, porque
180
- // `cleanFrameworkDirs()` acabou de apagar `.claude/skills/` inteiro. Num dry
181
- // ingênuo nada foi apagado, ele veria o diretório POPULADO, e o relatório
182
- // preveria remoções que a execução real nunca faz — errado na direção que
183
- // assusta o usuário. O clean subsome este laço, provadamente.
84
+ // Removes known framework skills not reinstalled this run; user skills are never touched.
85
+ // Silent in dryRun: the real update's cleanFrameworkDirs() already emptied the dir.
184
86
  if (!fx.dryRun) {
185
87
  const allSourceSkills = collectSourceSkillNames();
186
88
  for (const entry of readdirSync(claudeSkillsDir)) {
@@ -6,10 +6,6 @@ import { realEffects } from '../lib/installers/effects.js';
6
6
 
7
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
8
 
9
- /**
10
- * Get the current CLI version from package.json
11
- * @returns {string} Version string (e.g., "1.0.4")
12
- */
13
9
  export function getInstalledCLIVersion() {
14
10
  try {
15
11
  const pkgPath = join(__dirname, '..', '..', 'package.json');
@@ -20,11 +16,6 @@ export function getInstalledCLIVersion() {
20
16
  }
21
17
  }
22
18
 
23
- /**
24
- * Get the latest version from npm registry
25
- * @param {string} packageName - The package name (default: @polymorphism-tech/morph-spec)
26
- * @returns {Promise<string|null>} Latest version or null if failed
27
- */
28
19
  export async function getLatestNPMVersion(packageName = '@polymorphism-tech/morph-spec') {
29
20
  try {
30
21
  const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
@@ -36,11 +27,6 @@ export async function getLatestNPMVersion(packageName = '@polymorphism-tech/morp
36
27
  }
37
28
  }
38
29
 
39
- /**
40
- * Get the MORPH version installed in the project
41
- * @param {string} projectPath - Path to the project
42
- * @returns {Promise<object|null>} Version info object or null if not found
43
- */
44
30
  export async function getProjectMorphVersion(projectPath) {
45
31
  try {
46
32
  const versionPath = join(projectPath, '.morph', '.morphversion');
@@ -53,12 +39,6 @@ export async function getProjectMorphVersion(projectPath) {
53
39
  }
54
40
  }
55
41
 
56
- /**
57
- * Save the MORPH version to the project
58
- * @param {string} projectPath - Path to the project
59
- * @param {string} version - Version to save
60
- * @returns {Promise<void>}
61
- */
62
42
  export async function saveProjectMorphVersion(projectPath, version, fx = realEffects()) {
63
43
  const versionPath = join(projectPath, '.morph', '.morphversion');
64
44
  const versionInfo = {
@@ -69,12 +49,7 @@ export async function saveProjectMorphVersion(projectPath, version, fx = realEff
69
49
  fx.writeJson(versionPath, versionInfo);
70
50
  }
71
51
 
72
- /**
73
- * Compare two semver versions
74
- * @param {string} v1 - First version
75
- * @param {string} v2 - Second version
76
- * @returns {number} -1 if v1 < v2, 0 if equal, 1 if v1 > v2
77
- */
52
+ /** @returns {number} -1 if v1 < v2, 0 if equal, 1 if v1 > v2 */
78
53
  export function compareVersions(v1, v2) {
79
54
  if (!v1 || !v2) return 0;
80
55
 
@@ -92,49 +67,17 @@ export function compareVersions(v1, v2) {
92
67
  return 0;
93
68
  }
94
69
 
95
- /**
96
- * Major de uma versão, ou `null` quando a string não permite decidir.
97
- *
98
- * Vive aqui, ao lado de `compareVersions`, de propósito: uma segunda
99
- * aritmética de versão noutro arquivo é exatamente como duas verdades sobre a
100
- * mesma versão nascem.
101
- *
102
- * @param {string|null|undefined} version
103
- * @returns {number|null}
104
- */
70
+ /** @returns {number|null} `null` when the string does not determine a major */
105
71
  export function majorOf(version) {
106
72
  if (typeof version !== 'string') return null;
107
73
  const m = /^\s*v?(\d+)\./.exec(version) ?? /^\s*v?(\d+)\s*$/.exec(version);
108
74
  return m ? Number(m[1]) : null;
109
75
  }
110
76
 
111
- /**
112
- * Limiar do freio: um salto de DOIS majors ou mais recusa sem `--force`.
113
- *
114
- * O limiar não é "quantos majors dá para pular com segurança" — é "a partir de
115
- * onde o usuário merece ler o relatório antes". Medido: um projeto em 7.x indo
116
- * para 9.x recusa; um em 8.x atualiza sem atrito.
117
- */
118
77
  export const MAJOR_JUMP_LIMIT = 2;
119
78
 
120
- /**
121
- * Avalia o salto entre a versão instalada no projeto e a versão de destino.
122
- *
123
- * Três resultados, e a diferença entre eles é o que a mensagem tem de dizer:
124
- *
125
- * - `reason: 'unknown-installed'` — a versão instalada é indeterminável.
126
- * Tratada como salto grande, com motivo PRÓPRIO: "não sei quão velho isto é"
127
- * é precisamente quando sobrescrever às cegas é mais perigoso, e tratá-la
128
- * como zero seria a única leitura que libera o pior caso.
129
- * - `reason: 'major-jump'` — salto medido de `jump` majors, >= ao limiar.
130
- * - `reason: null` — sem freio (mesmo major, um só major de diferença, ou
131
- * destino não superior à instalada).
132
- *
133
- * @param {string|null} installedVersion
134
- * @param {string|null} targetVersion
135
- * @param {{ limit?: number }} [options]
136
- * @returns {{ refuse: boolean, reason: 'unknown-installed'|'major-jump'|null, jump: number|null }}
137
- */
79
+ // An unreadable installed version refuses with its own reason: not knowing how old
80
+ // the project is is when a blind overwrite is most dangerous.
138
81
  export function majorJump(installedVersion, targetVersion, { limit = MAJOR_JUMP_LIMIT } = {}) {
139
82
  const installedMajor = majorOf(installedVersion);
140
83
  const targetMajor = majorOf(targetVersion);
@@ -143,8 +86,6 @@ export function majorJump(installedVersion, targetVersion, { limit = MAJOR_JUMP_
143
86
  return { refuse: true, reason: 'unknown-installed', jump: null };
144
87
  }
145
88
  if (targetMajor === null) {
146
- // Sem destino legível não há salto a medir — e inventar um seria decidir
147
- // por um número que ninguém mantém.
148
89
  return { refuse: false, reason: null, jump: null };
149
90
  }
150
91
  if (compareVersions(installedVersion, targetVersion) >= 0) {
@@ -156,17 +97,11 @@ export function majorJump(installedVersion, targetVersion, { limit = MAJOR_JUMP_
156
97
  return { refuse: false, reason: null, jump };
157
98
  }
158
99
 
159
- /**
160
- * Detect how the CLI was installed
161
- * @returns {'global'|'npx'|'unknown'}
162
- */
163
100
  export function detectInstallMethod() {
164
- // Check if running via npx
165
101
  if (process.env.npm_execpath && process.env.npm_execpath.includes('npx')) {
166
102
  return 'npx';
167
103
  }
168
104
 
169
- // Check if installed globally
170
105
  const isGlobal = process.env.npm_config_global === 'true' ||
171
106
  process.execPath.includes('node_modules');
172
107
 
@@ -177,11 +112,6 @@ export function detectInstallMethod() {
177
112
  return 'unknown';
178
113
  }
179
114
 
180
- /**
181
- * Get update instructions based on install method
182
- * @param {string} method - Install method ('global', 'npx', 'unknown')
183
- * @returns {string[]} Array of instruction strings
184
- */
185
115
  export function getUpdateInstructions(method) {
186
116
  switch (method) {
187
117
  case 'global':
@@ -198,10 +128,6 @@ export function getUpdateInstructions(method) {
198
128
  }
199
129
  }
200
130
 
201
- /**
202
- * Check if CLI version is outdated
203
- * @returns {Promise<{isOutdated: boolean, current: string, latest: string|null}>}
204
- */
205
131
  export async function checkCLIOutdated() {
206
132
  const current = getInstalledCLIVersion();
207
133
  const latest = await getLatestNPMVersion();
@@ -215,11 +141,6 @@ export async function checkCLIOutdated() {
215
141
  return { isOutdated, current, latest };
216
142
  }
217
143
 
218
- /**
219
- * Check if project MORPH version is outdated
220
- * @param {string} projectPath - Path to the project
221
- * @returns {Promise<{isOutdated: boolean, current: string|null, cliVersion: string}>}
222
- */
223
144
  export async function checkProjectOutdated(projectPath) {
224
145
  const cliVersion = getInstalledCLIVersion();
225
146
  const projectVersion = await getProjectMorphVersion(projectPath);
@@ -1,57 +0,0 @@
1
- ---
2
- description: Inicia uma nova feature MORPH-SPEC. Roda morph-brainstorming para entender negócio + escanear stack, depois morph-plan para produzir use cases, spec e tasks. Pausa nos Gates 1 e 2.
3
- argument-hint: <feature-name>
4
- allowed-tools: Read, Write, Edit, Bash, Glob, Grep, Agent, AskUserQuestion, Skill
5
- ---
6
-
7
- # /morph-proposal — NLH Pipeline
8
-
9
- Conduz a feature do request inicial até o Gate 2 (plano aprovado), sem intervenção do usuário entre os gates.
10
-
11
- **Implementação:** este slash command é um atalho. O agente deve invocar `Skill(morph-proposal)` com o nome da feature como argumento. A skill encadeia automaticamente `morph-brainstorming` → Gate 1 → `morph-plan` → Gate 2. Em `trust: "auto"` (default), gates auto-passam sem sinais de risco.
12
-
13
- ## Argumentos
14
-
15
- `{feature-name}` — nome em kebab-case da feature.
16
-
17
- ## Fluxo
18
-
19
- 1. **Detecta contexto:** verifica se `.morph/features/{feature-name}/` já existe.
20
- - Se existe e tem `0-proposal/proposal.md`: pular para passo 3.
21
- - Se não existe: criar a estrutura e seguir o passo 2.
22
-
23
- 2. **Invocar `morph-brainstorming`** (skill em `.claude/skills/morph-brainstorming/`):
24
- - Varre o ambiente **antes** de qualquer pergunta (stack, contexto do projeto, acervo de use cases).
25
- - Monta a árvore de decisões abertas — só o que a varredura não respondeu.
26
- - Abre com a escolha de **modo de descoberta** (entrevista ou automático), recomendando um.
27
- - Produz `proposal.md` em `.morph/features/{feature-name}/0-proposal/`.
28
- - Apresenta o **Gate 1** (que pausa sempre no modo entrevista).
29
-
30
- 3. **Aguardar aprovação do Gate 1.**
31
- - Resposta válida (o que o hook `state-sync` reconhece): começa com "aprovado" ou contém "pode avançar", "pode implementar" ou "pode prosseguir". Outra frase não grava o gate.
32
- - Se ajustes solicitados: aplicar e reapresentar o gate.
33
-
34
- 4. **Após Gate 1, invocar `morph-plan`** (skill em `.claude/skills/morph-plan/`):
35
- - Lê `proposal.md`.
36
- - Produz use cases, `spec.md` e `tasks.json` em `.morph/features/{feature-name}/2-plan/`.
37
- - Apresenta o **Gate 2** com template fixo (ver SKILL.md passo 5).
38
-
39
- 5. **Aguardar aprovação do Gate 2.**
40
- - Resposta válida: as mesmas do Gate 1 (ex.: "aprovado", "pode implementar").
41
- - Se ajustes solicitados: aplicar e reapresentar o gate.
42
-
43
- 6. **Após Gate 2:** sinalizar ao usuário que `/morph-apply {feature-name}` está pronto para iniciar a implementação.
44
-
45
- ## Anti-padrões
46
-
47
- - Não pular o Gate 1 mesmo que o usuário pareça apressado — proposal.md é o contrato de escopo.
48
- - Não escrever código nesta fase. Apenas artefatos de planejamento.
49
- - Não perguntar sobre stack — o stack padrão é VSA + .NET 10 + MAF + Next.js + Neon.
50
-
51
- ## Referências
52
-
53
- - `.morph/framework/MORPH.md` — charter completo
54
- - `.claude/skills/morph-brainstorming/SKILL.md`
55
- - `.claude/skills/morph-plan/SKILL.md`
56
-
57
- **Feature:** $ARGUMENTS
@@ -1,67 +0,0 @@
1
- # Trace Schema — morph-spec NLH
2
-
3
- ## Local de armazenamento
4
-
5
- `.morph/traces/YYYY-MM-DD-{feature}-{phase}.json`
6
-
7
- Um arquivo por fase. Se a fase for executada em múltiplas sessões, o mesmo arquivo é atualizado incrementalmente.
8
-
9
- ## Formato JSON
10
-
11
- ```json
12
- {
13
- "schema_version": "1.0",
14
- "session_id": "YYYY-MM-DD-HH-MM-SS",
15
- "feature": "nome-da-feature",
16
- "phase": "implement | plan | review",
17
- "started_at": "ISO 8601",
18
- "completed_at": "ISO 8601",
19
- "tasks": [
20
- {
21
- "id": "T1",
22
- "title": "descrição da task",
23
- "status": "completed | failed | skipped",
24
- "standards_consulted": ["id-do-standard-1"],
25
- "agent_persona": "dotnet-senior | nextjs-expert | etc",
26
- "sub_agent_dispatched": false,
27
- "files_modified": ["path/relativo/arquivo.cs"],
28
- "duration_seconds": null
29
- }
30
- ],
31
- "phase_summary": {
32
- "tasks_completed": 3,
33
- "tasks_failed": 0,
34
- "standards_most_used": ["architecture-vertical-slice"],
35
- "sub_agents_dispatched": 0
36
- }
37
- }
38
- ```
39
-
40
- ## Como o trace é gerado (automático — não escreva à mão)
41
-
42
- O trace é **gerado pelo hook `trace-autogen`** (PostToolUse em Write|Edit) a cada
43
- write de `2-plan/tasks.json` — o último write da cerimônia de cada task. As skills
44
- (`morph-implement`, `morph-review`) **não escrevem o trace**.
45
-
46
- | Fonte | O que alimenta |
47
- |-------|----------------|
48
- | `tasks.json` | conjunto de tasks, `status` (done→completed, skipped→skipped, blocked→failed; pending/in_progress ficam fora), `outputs` → `files_modified` |
49
- | `recap.md` | `**Standard:**` → `standards_consulted`; `**Persona:**` → `agent_persona` + `sub_agent_dispatched` (`morph-{id}`/`general-purpose` = true) |
50
-
51
- `completed_at` e `phase_summary` são preenchidos quando nenhuma task resta
52
- `pending`/`in_progress`. O arquivo existente da mesma feature+fase é atualizado
53
- in place (`session_id`/`started_at` preservados entre sessões).
54
-
55
- **Se o trace parecer incompleto, corrija a FONTE** (um recap sem o bloco `## T{N}`), nunca o trace.
56
-
57
- ## Regras de geração
58
-
59
- - Se um campo não tiver valor disponível, o hook usa `null` — nunca omite o campo.
60
- - Estrutura completa mantida para compatibilidade com análise de meta-harness.
61
- - `session_id` usa o formato `YYYY-MM-DD-HH-MM-SS` da primeira geração.
62
- - `standards_consulted` contém os `id`s do STANDARDS.json, não os paths.
63
- - `files_modified` usa paths relativos à raiz do repositório (vem de `outputs`).
64
-
65
- ## Motivação
66
-
67
- Raw traces preservam o sinal completo de execução. Summaries comprimem informação e perdem sinais que o meta-harness usa para otimização. Regra: logar o trace completo — nunca substituir por resumo.
@@ -1,83 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * tasks-json-guard.js — PostToolUse hook (validate-and-warn, never block)
4
- *
5
- * Event: PostToolUse | Matcher: Write|Edit
6
- *
7
- * After any Write/Edit that touches a tasks.json under .morph/features/,
8
- * re-reads the file from disk and validates the schema (status enum, effort
9
- * enum). Invalid → injects a warning so the LLM fixes the file immediately;
10
- * valid → logs one activity line and stays silent.
11
- *
12
- * The production failure this guards: a leaf sub-agent wrote
13
- * status:"completed" (not in the enum — the schema accepts "done") into
14
- * tasks.json and nothing flagged it. The blocking pre-write validation for
15
- * post-Gate-2 edits lives in protect-spec-files.js (PreToolUse); this
16
- * PostToolUse pass also covers pre-gate authoring (morph-plan) and anything
17
- * that landed on disk regardless.
18
- *
19
- * Fail-open: exits 0 on any error.
20
- */
21
-
22
- import { readFileSync } from 'fs';
23
- import { isAbsolute, join } from 'path';
24
- import { stateExists } from '../../shared/state-reader.js';
25
- import { contextResult, runStandalone } from '../../shared/hook-response.js';
26
- import { logHookActivity } from '../../shared/activity-logger.js';
27
- import { getFilePath } from '../../shared/payload-utils.js';
28
- import { isFeaturePath, getBasename } from '../../shared/phase-utils.js';
29
- import { VALID_TASK_STATUS, validateTasksDoc } from '../../shared/tasks-validator.js';
30
-
31
- function projectDir() {
32
- return process.env.CLAUDE_PROJECT_DIR || process.cwd();
33
- }
34
-
35
- /**
36
- * @param {object} payload - parsed PostToolUse stdin payload (Write|Edit)
37
- * @returns {Promise<object|null>} a hookSpecificOutput warning when tasks.json
38
- * is unparseable or schema-invalid, or null (pass/silent).
39
- */
40
- export async function run(payload) {
41
- try {
42
- const cwd = projectDir();
43
- if (!stateExists(cwd)) return null;
44
-
45
- const filePath = getFilePath(payload);
46
- if (!filePath) return null;
47
- if (!isFeaturePath(filePath)) return null;
48
- if (getBasename(filePath) !== 'tasks.json') return null;
49
-
50
- const absPath = isAbsolute(filePath) ? filePath : join(cwd, filePath);
51
-
52
- let doc;
53
- try {
54
- doc = JSON.parse(readFileSync(absPath, 'utf-8'));
55
- } catch {
56
- logHookActivity('tasks-json-guard', 'PostToolUse', 'invalid', cwd);
57
- return contextResult(
58
- '⚠️ [morph-spec] tasks.json INVÁLIDO — o arquivo gravado não parseia como JSON. ' +
59
- 'Corrija o arquivo antes de continuar.',
60
- 'PostToolUse'
61
- );
62
- }
63
-
64
- const { valid, errors } = validateTasksDoc(doc);
65
- if (!valid) {
66
- logHookActivity('tasks-json-guard', 'PostToolUse', 'invalid', cwd);
67
- return contextResult(
68
- '⚠️ [morph-spec] tasks.json com schema inválido após o write:\n' +
69
- errors.map(e => ` • ${e}`).join('\n') +
70
- `\nStatus válidos: ${VALID_TASK_STATUS.join(', ')}. Corrija antes de marcar a task como done.`,
71
- 'PostToolUse'
72
- );
73
- }
74
-
75
- logHookActivity('tasks-json-guard', 'PostToolUse', 'ok', cwd);
76
- return null;
77
- } catch {
78
- // Fail-open
79
- return null;
80
- }
81
- }
82
-
83
- await runStandalone(run);