@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,7 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import { resolve } from 'path';
3
3
  import { getFeature, updateFeature } from '../../core/state/state-manager.js';
4
- import { runVerify, readTask, VerifyUsageError } from '../../lib/validators/verify-runner.js';
4
+ import { runVerify, VerifyUsageError } from '../../lib/validators/verify-runner.js';
5
5
  import { resolvePrimaryRoot } from '../project/worktree.js';
6
6
  import { resolveFeatureTree } from '../../lib/worktree/feature-truth.js';
7
7
  import { canonicalPath, readMorphConfig } from '../../lib/tasks/test-runner.js';
@@ -12,43 +12,19 @@ import { treeFingerprint } from '../../lib/git/tree-fingerprint.js';
12
12
  import { HeavyBusyError } from '../../lib/heavy/gate.js';
13
13
  import { afterHeavyRun } from '../../lib/heavy/cleanup.js';
14
14
  import { readHeavyConfig } from '../../../framework/hooks/shared/heavy-config.js';
15
- import { runCiVerify, CiUsageError } from '../../lib/ci/ci-verify.js';
15
+ import { runCiVerify, CiUsageError, reusableLocalStamp } from '../../lib/ci/ci-verify.js';
16
16
 
17
- /** ✓ pass · ✗ fail · – skip */
18
17
  function statusIcon(status) {
19
18
  if (status === 'pass') return chalk.green('✓');
20
19
  if (status === 'fail') return chalk.red('✗');
21
20
  return chalk.gray('–');
22
21
  }
23
22
 
24
- /**
25
- * One line per test run naming what the task-scope recorte did to it (#87):
26
- * narrowed (how many selectors, from how many test files), whole, out of scope
27
- * or full, each with its reason. A green `tests` line alone cannot say that the
28
- * task ran 12 tests instead of 3.082. Feature-scope runs carry no selection and
29
- * print nothing extra; `feedback[]` is untouched either way.
30
- *
31
- * @param {Array<{id: string, selection: object|null}>} [runs]
32
- * @returns {string[]}
33
- */
34
- function selectionLines(runs = []) {
35
- return runs
36
- .filter((run) => run.selection)
37
- .map((run) => {
38
- const { mode, reason, selectors = [], files = [] } = run.selection;
39
- const detail = mode === 'narrowed'
40
- ? `${selectors.length} selector(s) from ${files.length} test file(s)`
41
- : (reason ?? '');
42
- return chalk.gray(` ↳ ${run.id.padEnd(10)} ${mode.padEnd(12)} ${detail}`);
43
- });
44
- }
45
-
46
23
  function formatVerifyResult(result) {
47
24
  const lines = [];
48
- const scopeLabel = result.scope === 'task' ? `${result.feature}/${result.task}` : result.feature;
49
25
 
50
26
  lines.push('');
51
- lines.push(chalk.bold(`Verify — ${scopeLabel}`));
27
+ lines.push(chalk.bold(`Verify — ${result.feature}`));
52
28
  if (result.stack) lines.push(chalk.gray(`stack: ${result.stack}`));
53
29
  lines.push('');
54
30
  lines.push(result.status === 'pass' ? chalk.green.bold('✓ PASS') : chalk.red.bold('✗ FAIL'));
@@ -72,7 +48,6 @@ function formatVerifyResult(result) {
72
48
  detail = `${node.durationMs}ms`;
73
49
  }
74
50
  lines.push(` ${icon} ${name.padEnd(11)} ${node.status.padEnd(5)} ${chalk.gray(detail)}`);
75
- if (name === 'tests') lines.push(...selectionLines(node.runs));
76
51
  }
77
52
 
78
53
  if (result.feedback.length > 0) {
@@ -93,27 +68,9 @@ function formatVerifyResult(result) {
93
68
 
94
69
  const samePath = (a, b) => canonicalPath(a).replace(/\\/g, '/') === canonicalPath(b).replace(/\\/g, '/');
95
70
 
96
- /**
97
- * Persist only in the tree that OWNS the feature — the same predicate
98
- * `list`/`handoff`/the mutator guard read (`resolveFeatureTree`), asked from
99
- * the PRIMARY ROOT so every tree gets the same answer. Any other tree (a
100
- * sub-agent's `morph-task/*` tree branched from `morph/{f}`, a scratch
101
- * checkout) must never write feature.json: its copy is not the one the gates
102
- * read, and the write would merge-conflict with the owner's.
103
- *
104
- * The old rule read only the root's `worktree.path` pointer, which a feature
105
- * created INSIDE its worktree never has: a `worktrees/homolog-v2` on
106
- * `morph/chat-demo-simulacao` measured `unregistered-worktree` on 11/11 runs,
107
- * and `repeatOf` (computed only when persisting) never fired. Git's worktree
108
- * registry names that tree as the owner.
109
- *
110
- * The root is not special: with a live worktree owning the feature, the
111
- * root's copy is the birth-instant skeleton and a stamp there is drift —
112
- * `owner-tree-elsewhere`, the verify side of `assertOwnerTree`.
113
- *
114
- * @param {{ cwd: string, featureName: string, noPersist: boolean }} params
115
- * @returns {{ persist: boolean, reason: string|null }}
116
- */
71
+ // Persists only in the tree that OWNS the feature (`resolveFeatureTree`, asked from the PRIMARY
72
+ // ROOT so every tree agrees): another tree's copy is not what the gates read, and writing it
73
+ // would merge-conflict with the owner's.
117
74
  function decidePersistence({ cwd, featureName, noPersist }) {
118
75
  if (noPersist) return { persist: false, reason: 'no-persist-flag' };
119
76
 
@@ -130,17 +87,8 @@ function baselineStampBlock(baseline) {
130
87
  return `record-baseline: a contagem caiu em ${fell.map((u) => `${u.id} (${u.from} → ${u.to})`).join(', ')}`;
131
88
  }
132
89
 
133
- /**
134
- * The per-node status map, in the shape `feature.schema.json` fixes for BOTH
135
- * writers of it — the per-scope history entry (`verifications.*`) and the
136
- * singular gate stamp (`verification.nodes`). One function because they were
137
- * two literal copies twenty-five lines apart: a fifth node meant remembering
138
- * both, and a stamp missing a node the history has is precisely the kind of
139
- * half-truth the gate reads as fact.
140
- *
141
- * @param {{ nodes: Record<string, { status: string }> }} result - runVerify() output
142
- * @returns {{ build: string, tests: string, validators: string, e2e: string }}
143
- */
90
+ // The single writer of both node maps the schema fixes (`verification.nodes` and the history
91
+ // entry): a stamp missing a node the history has is a half-truth the gate reads as fact.
144
92
  function nodeStatuses(result) {
145
93
  return {
146
94
  build: result.nodes.build.status,
@@ -151,25 +99,8 @@ function nodeStatuses(result) {
151
99
  };
152
100
  }
153
101
 
154
- /**
155
- * The status of the eval suites, pulled OUT of the tests rollup.
156
- *
157
- * The rollup cannot answer the question Gate 3 asks. A red eval and a red unit
158
- * test both arrive as `tests: 'fail'`, and — worse — an eval node that was
159
- * SKIPPED (task scope) leaves the rollup green, so a gate reading `tests` alone
160
- * cannot tell "the eval ran and passed" from "the eval never ran".
161
- *
162
- * Four values, and the distinction between the last two is the whole point:
163
- * - `'pass'` / `'fail'`: every declared eval node agreed, or at least one did
164
- * not. Same pessimistic rollup rule the tests node already uses;
165
- * - `'skip'`: eval nodes ARE declared, but this run did not execute them;
166
- * - `null`: this project declares NO eval node. Not a skip — there is nothing
167
- * to skip. `risk-detector.js` pauses on the two for different reasons,
168
- * because they call for different actions.
169
- *
170
- * @param {{nodes: {tests: {runs?: Array}}}} result - runVerify() output
171
- * @returns {'pass'|'fail'|'skip'|null}
172
- */
102
+ // `'skip'` = eval nodes declared but not run; `null` = no eval node declared. A skipped eval
103
+ // leaves the tests rollup green, so the gate needs this split to tell them apart.
173
104
  function evalsStatusOf(result) {
174
105
  const runs = (result.nodes.tests?.runs || []).filter((run) => run.runner === EVAL_RUNNER);
175
106
  if (runs.length === 0) return null;
@@ -178,10 +109,7 @@ function evalsStatusOf(result) {
178
109
  return 'skip';
179
110
  }
180
111
 
181
- /**
182
- * The options that make a FEATURE-scope run partial, each with the `reason` it
183
- * reports when it keeps the stamp from being written — first match wins.
184
- */
112
+ // First match wins; each keeps the stamp from being written, with its `reason`.
185
113
  const PARTIAL_RUN_FLAGS = [['filter', 'filtered'], ['skipTests', 'skip-tests'], ['skipBuild', 'skip-build']];
186
114
 
187
115
  const CI_EXCLUSIVE_FLAGS = [
@@ -190,9 +118,9 @@ const CI_EXCLUSIVE_FLAGS = [
190
118
  ['noPersist', '--no-persist'],
191
119
  ];
192
120
 
193
- async function runLocalVerify(featureName, taskId, options) {
121
+ async function runLocalVerify(featureName, options) {
194
122
  try {
195
- return await runVerify(process.cwd(), featureName, taskId || null, {
123
+ return await runVerify(process.cwd(), featureName, {
196
124
  timeout: options.timeout,
197
125
  skipTests: options.skipTests,
198
126
  skipBuild: options.skipBuild,
@@ -203,14 +131,9 @@ async function runLocalVerify(featureName, taskId, options) {
203
131
  refreshCache: options.refreshCache,
204
132
  linux: options.linux,
205
133
  linuxImage: options.linuxImage,
206
- // Task scope: switch the recorte off (#87). At feature scope nothing is
207
- // ever narrowed, so the flag is a documented no-op there, not an error.
208
- full: options.full,
209
134
  });
210
135
  } catch (err) {
211
- // A wrong invocation is exit 2, like an unknown feature or task — never a
212
- // verdict. `--filter` that selects nothing must not print green (or red):
213
- // there is no verification to report, only a command to fix.
136
+ // Exit 2, never a verdict: a `--filter` that selects nothing must not print green or red.
214
137
  if (err instanceof VerifyUsageError) {
215
138
  console.error(chalk.red(`\n❌ ${err.message}\n`));
216
139
  process.exit(2);
@@ -231,28 +154,9 @@ async function runLocalVerify(featureName, taskId, options) {
231
154
  }
232
155
  }
233
156
 
234
- /**
235
- * `morph-spec verify <feature> [task]` — the deterministic code-before-judge
236
- * gate: build + tests + validators, BEFORE the LLM eval. See verify-runner.js
237
- * for the node cadence; this command wraps it with CLI ergonomics
238
- * (human/JSON output, exit codes) and context-aware persistence.
239
- *
240
- * Exit codes: 0 = pass (including all-skip) · 1 = fail (>=1 red node, or a
241
- * `--record-baseline` the run refused to write) · 2 = usage error.
242
- *
243
- * Exit 2 is "the command cannot mean what you typed", and there are four
244
- * paths to it — never a verdict, because a verdict about nothing is worse
245
- * than a refusal:
246
- * - unknown feature, or unknown task id (the task's `group` and `outputs`
247
- * decide the scope, so it is a hard usage error here);
248
- * - `--filter` that matches no declared node (a filtered-to-empty run is
249
- * green and proves nothing);
250
- * - `--record-baseline` with nowhere to write (no `config.json`), or with a
251
- * task id (a recorte de task is never a fleet baseline, #87);
252
- * - `--linux` without Docker.
253
- * The last three arrive as `VerifyUsageError` from the runner.
254
- */
255
- export async function verifyCommand(featureName, taskId, options = {}) {
157
+ // Exit 2 is "the command cannot mean what you typed" (unknown feature, `--filter` matching
158
+ // nothing, `--record-baseline` without config.json, `--linux` without Docker): never a verdict.
159
+ export async function verifyCommand(featureName, options = {}) {
256
160
  let feature;
257
161
  try {
258
162
  feature = getFeature(featureName);
@@ -265,22 +169,13 @@ export async function verifyCommand(featureName, taskId, options = {}) {
265
169
  }
266
170
 
267
171
  if (options.ci) {
268
- const conflict = taskId ? `a task ${taskId} (o loop por task é local)` : CI_EXCLUSIVE_FLAGS.find(([flag]) => options[flag])?.[1];
172
+ const conflict = CI_EXCLUSIVE_FLAGS.find(([flag]) => options[flag])?.[1];
269
173
  if (conflict) {
270
174
  console.error(chalk.red(`\n❌ --ci roda a suíte inteira da feature no CI; não combina com ${conflict}\n`));
271
175
  process.exit(2);
272
176
  }
273
177
  }
274
178
 
275
- // The runner's own reader (one reader of tasks.json for verify, #87): null
276
- // means tasks.json or the task is missing, and both are this usage error.
277
- if (taskId) {
278
- if (!readTask(process.cwd(), featureName, taskId)) {
279
- console.error(chalk.red(`\n❌ Task "${taskId}" not found in tasks.json for feature "${featureName}"\n`));
280
- process.exit(2);
281
- }
282
- }
283
-
284
179
  let ci = null;
285
180
  if (options.ci) {
286
181
  try {
@@ -294,6 +189,17 @@ export async function verifyCommand(featureName, taskId, options = {}) {
294
189
  console.error(chalk.yellow(`\n⏳ ${outcome.reason}. Não é vermelho e não há stamp: não repita em laço — siga e deixe o gate-check decidir.\n`));
295
190
  process.exit(3);
296
191
  } else {
192
+ const reused = reusableLocalStamp(process.cwd(), feature.verification);
193
+ if (reused) {
194
+ console.error(chalk.green(`\n↪ CI indisponível (${outcome.reason}) — stamp fresco reaproveitado (sha ${reused.sha.slice(0, 8)}, ${reused.ts}): nada mudou fora de .morph/ desde aquela rodada verde.\n`));
195
+ if (options.json) {
196
+ console.log(JSON.stringify({
197
+ feature: featureName, status: reused.status, nodes: reused.nodes, e2eReason: reused.e2eReason ?? null,
198
+ reused: { sha: reused.sha, ts: reused.ts, source: reused.source ?? 'local' }, persisted: false, reason: 'stamp-reused',
199
+ }, null, 2));
200
+ }
201
+ process.exit(0);
202
+ }
297
203
  console.error(chalk.yellow(`\n↪ CI indisponível (${outcome.reason}) — verify local.\n`));
298
204
  }
299
205
  } catch (err) {
@@ -308,31 +214,27 @@ export async function verifyCommand(featureName, taskId, options = {}) {
308
214
  // Taken before the run: it is the tree the run is about to read.
309
215
  const fingerprint = treeFingerprint(process.cwd());
310
216
 
311
- const result = ci ? ci.result : await runLocalVerify(featureName, taskId, options);
217
+ const result = ci ? ci.result : await runLocalVerify(featureName, options);
312
218
 
313
219
  const cleanup = ci ? { status: 'skipped', reason: 'ci' } : afterHeavyRun({
314
220
  projectPath: process.cwd(),
315
221
  config: readHeavyConfig(process.cwd()),
316
- scope: taskId ? 'task' : 'feature',
317
222
  });
318
223
 
319
- const scopeKey = taskId ? `taskVerifications.${taskId}` : 'verifications.feature';
224
+ const historyKey = 'verifications.feature';
320
225
  // A run that measured only part of the suite must not stand in for the proof a gate asks for:
321
- // --filter and --skip-* at feature scope, or a --record-baseline that recorded nothing. Task scope
322
- // is unaffected (its stamp already says scope 'task:{id}'); --no-persist keeps its own reason.
323
- const partialFeatureRun = (!options.noPersist && !taskId)
324
- ? PARTIAL_RUN_FLAGS.find(([flag]) => options[flag])?.[1] ?? null
325
- : null;
226
+ // --filter and --skip-*, or a --record-baseline that recorded nothing; --no-persist keeps its own reason.
227
+ const partialRun = options.noPersist ? null : PARTIAL_RUN_FLAGS.find(([flag]) => options[flag])?.[1] ?? null;
326
228
  const baselineBlock = options.recordBaseline ? baselineStampBlock(result.baseline) : null;
327
229
  const { persist, reason } = baselineBlock
328
230
  ? { persist: false, reason: baselineBlock }
329
- : partialFeatureRun
330
- ? { persist: false, reason: partialFeatureRun }
231
+ : partialRun
232
+ ? { persist: false, reason: partialRun }
331
233
  : decidePersistence({ cwd: process.cwd(), featureName, noPersist: options.noPersist });
332
234
 
333
235
  let compact = null;
334
236
  if (persist) {
335
- const prevEntry = taskId ? feature.taskVerifications?.[taskId] : feature.verifications?.feature;
237
+ const prevEntry = feature.verifications?.feature;
336
238
  const now = new Date().toISOString();
337
239
  compact = {
338
240
  status: result.status,
@@ -340,42 +242,28 @@ export async function verifyCommand(featureName, taskId, options = {}) {
340
242
  attempts: (prevEntry?.attempts ?? 0) + 1,
341
243
  updatedAt: now,
342
244
  };
343
- await updateFeature(featureName, scopeKey, compact);
245
+ await updateFeature(featureName, historyKey, compact);
344
246
 
345
- // Singular, scope-agnostic "latest stamp" — distinct from the
346
- // verifications/taskVerifications history above. `sha` is what lets a
347
- // consumer (morph-spec gate-check) correlate this pass/fail with the
348
- // EXACT commit it covers, the same way review-report.md's
349
- // `**Reviewed-SHA:**` token lets approve.js detect a stale review. null
350
- // in a non-git checkout (or before the first commit) — fail-open, never
351
- // blocks persistence.
352
- //
353
- // `nodes` + `e2eReason` são acréscimo de campo, não mudança de forma: o
354
- // rollup `status` continua onde estava. Existem porque um nó e2e que PULA
355
- // não reprova o rollup — sem o status do nó e a razão LITERAL do skip, o
356
- // gate-check lê 'pass' tanto quando o e2e rodou e passou quanto quando ele
357
- // nunca rodou, e não tem como separar dispensa legítima ("o projeto não
358
- // containeriza") de ausência de prova ("docker indisponível"). `e2eReason`
359
- // é null quando o nó não pulou — e um null NUM SKIP é lido como dispensa
360
- // sem motivo, que risk-detector.js recusa.
247
+ // `sha` ties the stamp to the exact commit (null without git: fail-open). `nodes` and `e2eReason`
248
+ // exist because a skipped e2e does not fail the rollup; a null reason ON A SKIP reads as a
249
+ // waiver without motive, which risk-detector.js refuses.
361
250
  const stamp = {
362
251
  status: result.status,
363
252
  nodes: nodeStatuses(result),
364
253
  e2eReason: result.nodes.e2e.status === 'skip' ? (result.nodes.e2e.reason ?? null) : null,
365
254
  sha: ci ? ci.run.headSha : getHeadSha(process.cwd()),
366
255
  ts: now,
367
- scope: taskId ? `task:${taskId}` : 'feature',
368
256
  ...(ci ? { source: 'ci', ciRun: ci.run.url } : {}),
369
257
  };
370
258
  await updateFeature(featureName, 'verification', stamp);
371
259
  }
372
260
 
373
- // A feature-scope run over the exact tree of the previous one proves nothing
261
+ // A run over the exact tree of the previous one proves nothing
374
262
  // new. Advisory only: the stamp and the exit code are the same either way.
375
263
  let repeatOf = null;
376
264
  try {
377
265
  const cwd = process.cwd();
378
- if (!taskId && persist && !ci) repeatOf = findRepeatedVerify(cwd, { feature: featureName, task: null, scope: result.scope, fingerprint });
266
+ if (persist && !ci) repeatOf = findRepeatedVerify(cwd, { feature: featureName, fingerprint });
379
267
  const tree = resolve(cwd).replace(/\\/g, '/') === resolvePrimaryRoot(cwd) ? 'root' : 'worktree';
380
268
  recordVerifyEvent(cwd, buildVerifyEvent(result, { sha: ci ? ci.run.headSha : getHeadSha(cwd), tree, persisted: persist, reason, fingerprint, source: ci ? 'ci' : 'local' }));
381
269
  } catch { /* telemetry never changes the verdict */ }
@@ -406,7 +294,7 @@ export async function verifyCommand(featureName, taskId, options = {}) {
406
294
  console.log(chalk.gray(`${mark} dotnet build-server shutdown: ${cleanup.status}${cleanup.reason ? ` (${cleanup.reason})` : ''}\n`));
407
295
  }
408
296
  if (repeatOf) {
409
- console.log(chalk.yellow(`⚠️ Mesma árvore do verify de feature de ${repeatOf.ts} (${repeatOf.status}): nada mudou fora de .morph/ desde então. `
297
+ console.log(chalk.yellow(`⚠️ Mesma árvore do verify de ${repeatOf.ts} (${repeatOf.status}): nada mudou fora de .morph/ desde então. `
410
298
  + 'Esta rodada não prova nada novo — reuse o resultado anterior, a menos que o ambiente (Docker, banco, serviço externo) tenha mudado.\n'));
411
299
  }
412
300
  if (result.baseline?.recorded) {
@@ -417,16 +305,13 @@ export async function verifyCommand(featureName, taskId, options = {}) {
417
305
  `));
418
306
  }
419
307
  if (persist) {
420
- console.log(chalk.gray(`Persisted: ${scopeKey} (attempts: ${compact.attempts})\n`));
308
+ console.log(chalk.gray(`Persisted: ${historyKey} (attempts: ${compact.attempts})\n`));
421
309
  } else {
422
310
  console.log(chalk.yellow(`⚠️ Not persisted (${reason})\n`));
423
311
  }
424
312
  }
425
313
 
426
- // A recording the user asked for and did not get is a failure of the
427
- // invocation even when the run itself was green (a filtered run is green and
428
- // unrecordable): exiting 0 there would report success for something that did
429
- // not happen.
314
+ // A recording asked for and not made fails the invocation, even when the run was green.
430
315
  const baselineRefused = Boolean(result.baseline && !result.baseline.recorded);
431
316
  process.exit(result.status === 'pass' && !baselineRefused ? 0 : 1);
432
317
  }
@@ -1,115 +1,27 @@
1
- /**
2
- * Output Schema — Single Source of Truth (v5)
3
- *
4
- * ALL output type definitions live here. Edit ONLY this file to add, rename,
5
- * or relocate output types.
6
- */
7
-
8
1
  import { join } from 'path';
9
2
 
10
- // ============================================================================
11
- // Schema Definition
12
- // ============================================================================
13
-
14
- /**
15
- * Schema for output types produced by morph-spec NLH phases.
16
- *
17
- * Fields:
18
- * filename - The file's basename (e.g. 'spec.md'). Single-file types only.
19
- * phaseDir - Subdirectory within .morph/features/{feature}/
20
- * phase - The morph-spec phase that produces this output
21
- * protected - Whether the file requires an approval gate before editing
22
- * approvalGate - (optional) Which gate protects this file
23
- * mutableFields- (optional) For protected JSON files: top-level array-item
24
- * field names that MAY still change after the gate is approved.
25
- * Lets the implement phase update task progress without
26
- * unfreezing the plan contract. When present, the protect
27
- * hook does a semantic diff instead of a blanket block.
28
- *
29
- * Multi-file types (`multi: true`) hold N files in a directory instead of one
30
- * fixed basename. They carry `dirname` + `filenamePattern` + `displayFilename`
31
- * INSTEAD of `filename`, and every derivation keyed by basename
32
- * (FILENAME_TO_OUTPUT, PROTECTED_SPEC_FILES, MUTABLE_SPEC_FIELDS) skips them —
33
- * there is no single basename to key on. `getOutputPath` returns the DIRECTORY
34
- * for these types.
35
- */
36
3
  export const OUTPUT_SCHEMA = {
37
- // 0-proposal/
38
- proposal: { filename: 'proposal.md', phaseDir: '0-proposal', phase: 'proposal', protected: false },
39
- stackScan: { filename: 'stack-scan.md', phaseDir: '0-proposal', phase: 'proposal', protected: false },
4
+ plan: { filename: 'plan.md', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
5
+ checks: { filename: 'checks.json', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
40
6
 
41
- // 1-design/ (optional)
42
7
  uiDesignSystem: { filename: 'design-system.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
43
8
  uiMockups: { filename: 'mockups.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
44
9
  uiComponents: { filename: 'components.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
45
10
  uiFlows: { filename: 'flows.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
46
11
 
47
- // 2-plan/
48
- spec: { filename: 'spec.md', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
49
- tasks: { filename: 'tasks.json', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan', mutableFields: ['status', 'outputs', 'notes'] },
50
- decisions: { filename: 'decisions.md', phaseDir: '2-plan', phase: 'plan', protected: false },
51
- // Veredito REUSAR/ESTENDER/CRIAR por conceito que a feature introduz, com a
52
- // justificativa de cada CRIAR. É o ARTEFATO do ciclo de reuso (a prosa está
53
- // em morph-plan §2b, a cobrança em src/lib/validators/reuse/). NÃO protegido,
54
- // como decisions.md: é registro vivo — o implementador que descobre um alvo
55
- // melhor no meio da task precisa poder anotar, e congelá-lo transformaria a
56
- // correção num bloqueio de gate.
57
- // N files, one per system use case — the behaviour layer the plan distils
58
- // BEFORE spec.md/tasks.json. Deliberately NOT protected: editing a use case
59
- // after Gate 2 is the supported way to change behaviour and re-run implement;
60
- // the audit trail lives in git.
61
- usecases: {
62
- dirname: 'usecases',
63
- filenamePattern: '^UC-\\d+-[a-z0-9-]+\\.md$',
64
- displayFilename: 'UC-{nn}-{slug}.md',
65
- phaseDir: '2-plan',
66
- phase: 'plan',
67
- protected: false,
68
- multi: true,
69
- },
70
-
71
- // 3-implement/
72
- recap: { filename: 'recap.md', phaseDir: '3-implement', phase: 'implement', protected: false },
73
- // N files, one per task whose doneCriteria judges what a MODEL answers
74
- // (morph-plan §5). Runtime evidence: input, model, output, verdict — pointing
75
- // at the `runner: "evals"` node when the stack declares one. CONDITIONAL by
76
- // construction: never in phases.json requiredOutputs. Readers: morph-review
77
- // dispatch prompt.
78
- evidencias: { dirname: 'evidencias', filenamePattern: '^T\\d+\\.md$', displayFilename: '{taskId}.md',
12
+ evidencias: { dirname: 'evidencias', filenamePattern: '^C\\d+\\.md$', displayFilename: '{Cn}.md',
79
13
  phaseDir: '3-implement', phase: 'implement', protected: false, multi: true },
80
14
 
81
- // 4-review/
82
- reviewReport: { filename: 'review-report.md', phaseDir: '4-review', phase: 'review', protected: false },
83
- // Durable report the dispatched morph-evaluator sub-agent writes itself
84
- // (MORPH.md §6) — the orchestrator reads it and synthesizes reviewReport
85
- // from it; the two never collide because one is raw sub-agent output and
86
- // the other is the orchestrator's curated Gate 3 presentation.
87
- evaluatorReport:{ filename: 'evaluator-report.md', phaseDir: '4-review', phase: 'review', protected: false },
15
+ verifierReport: { filename: 'verifier.md', phaseDir: '4-review', phase: 'review', protected: false },
16
+ reviewerReport: { filename: 'reviewer.md', phaseDir: '4-review', phase: 'review', protected: false },
17
+ securityReport: { filename: 'security.md', phaseDir: '4-review', phase: 'review', protected: false },
88
18
  };
89
19
 
90
- // ============================================================================
91
- // Builder Functions (Windows-safe via path.join)
92
- // ============================================================================
93
-
94
- /**
95
- * Leaf segment of a schema entry: the basename for single-file types, the
96
- * directory name for multi-file ones. Keeps every path builder from having to
97
- * know about `multi` on its own.
98
- */
99
20
  function leafSegment(entry) {
100
21
  return entry.multi ? entry.dirname : entry.filename;
101
22
  }
102
23
 
103
- /**
104
- * Get the morph-relative path for a specific output type of a feature.
105
- *
106
- * For `multi` types this is the DIRECTORY holding the N files
107
- * (".morph/features/my-feature/2-plan/usecases"), not a single file.
108
- *
109
- * @param {string} featureName
110
- * @param {string} outputType - camelCase output type key from OUTPUT_SCHEMA
111
- * @returns {string} e.g. ".morph/features/my-feature/2-plan/spec.md"
112
- */
24
+ /** For `multi` types this is the directory holding the N files. */
113
25
  export function getOutputPath(featureName, outputType) {
114
26
  const entry = OUTPUT_SCHEMA[outputType];
115
27
  if (!entry) {
@@ -119,10 +31,6 @@ export function getOutputPath(featureName, outputType) {
119
31
  .replace(/\\/g, '/');
120
32
  }
121
33
 
122
- /**
123
- * Get the absolute filesystem path for a specific output type.
124
- * For `multi` types this is the directory — see {@link getOutputPath}.
125
- */
126
34
  export function getAbsoluteOutputPath(projectRoot, featureName, outputType) {
127
35
  const entry = OUTPUT_SCHEMA[outputType];
128
36
  if (!entry) {
@@ -131,42 +39,20 @@ export function getAbsoluteOutputPath(projectRoot, featureName, outputType) {
131
39
  return join(projectRoot, '.morph', 'features', featureName, entry.phaseDir, leafSegment(entry));
132
40
  }
133
41
 
134
- /**
135
- * Get the feature base directory (morph-relative, forward slashes).
136
- */
137
42
  export function getFeatureDir(featureName) {
138
43
  return join('.morph', 'features', featureName).replace(/\\/g, '/');
139
44
  }
140
45
 
141
- /**
142
- * Per-task sub-agent report — the durable "return value" channel for a
143
- * dispatched implementation task (MORPH.md §6). A sub-agent's final text
144
- * message may never reach the orchestrator (idle_notification with no text
145
- * is an observed failure mode); the file it writes here always does. Doesn't
146
- * fit OUTPUT_SCHEMA's one-fixed-filename-per-type model since the filename
147
- * is parameterized by taskId, so it's a sibling helper instead of a schema
148
- * entry.
149
- *
150
- * @param {string} featureName
151
- * @param {string} taskId - e.g. "T7"
152
- * @returns {string} morph-relative, forward-slash path
153
- */
154
46
  export function getTaskReportPath(featureName, taskId) {
155
47
  return join('.morph', 'features', featureName, '3-implement', 'reports', `${taskId}.md`)
156
48
  .replace(/\\/g, '/');
157
49
  }
158
50
 
159
- /** Absolute filesystem variant of {@link getTaskReportPath}. */
160
51
  export function getAbsoluteTaskReportPath(projectRoot, featureName, taskId) {
161
52
  return join(projectRoot, '.morph', 'features', featureName, '3-implement', 'reports', `${taskId}.md`);
162
53
  }
163
54
 
164
- /**
165
- * Get all output paths for a feature, ready for state.json `outputs` block.
166
- *
167
- * Multi-file types contribute their DIRECTORY, so `created` for them means
168
- * "the folder exists" — the state index tracks presence, not file count.
169
- */
55
+ /** `multi` types contribute their directory, so `created` means "the folder exists". */
170
56
  export function getAllOutputPaths(featureName) {
171
57
  const outputs = {};
172
58
  for (const [key] of Object.entries(OUTPUT_SCHEMA)) {
@@ -178,14 +64,6 @@ export function getAllOutputPaths(featureName) {
178
64
  return outputs;
179
65
  }
180
66
 
181
- // ============================================================================
182
- // Derived Constants
183
- // ============================================================================
184
-
185
- /**
186
- * filename → output type. Multi-file types are absent by construction: they
187
- * have N basenames, so there is nothing to key on.
188
- */
189
67
  export const FILENAME_TO_OUTPUT = Object.fromEntries(
190
68
  Object.entries(OUTPUT_SCHEMA)
191
69
  .filter(([, entry]) => !entry.multi)
@@ -196,36 +74,12 @@ export const OUTPUT_PHASE_MAP = Object.fromEntries(
196
74
  Object.entries(OUTPUT_SCHEMA).map(([key, entry]) => [key, entry.phase])
197
75
  );
198
76
 
199
- /**
200
- * Protected spec files and the approval gate that locks them.
201
- */
202
77
  export const PROTECTED_SPEC_FILES = Object.fromEntries(
203
78
  Object.entries(OUTPUT_SCHEMA)
204
79
  .filter(([, entry]) => entry.protected && entry.approvalGate && !entry.multi)
205
80
  .map(([, entry]) => [entry.filename, entry.approvalGate])
206
81
  );
207
82
 
208
- /**
209
- * For protected files that carry mutable progress fields: filename → the
210
- * top-level array-item fields that may change after the gate is approved.
211
- * The protect-spec-files hook uses this to allow progress updates (e.g.
212
- * task status) while still freezing the plan contract.
213
- */
214
- export const MUTABLE_SPEC_FIELDS = Object.fromEntries(
215
- Object.entries(OUTPUT_SCHEMA)
216
- .filter(([, entry]) => entry.protected && entry.approvalGate && entry.mutableFields && !entry.multi)
217
- .map(([, entry]) => [entry.filename, entry.mutableFields])
218
- );
219
-
220
- // ============================================================================
221
- // Backwards-compat shim (will be removed in v5.1)
222
- // ============================================================================
223
-
224
- /**
225
- * v4 had separate contracts.cs / contracts.ts / contracts-vsa.cs output types.
226
- * In v5 contracts live inside spec.md (or in source files for VSA). This
227
- * function keeps callers happy until they're updated.
228
- */
229
83
  export function resolveContractsOutputType() {
230
- return 'spec';
84
+ return 'plan';
231
85
  }
@@ -1,32 +1,8 @@
1
- /**
2
- * Feature identity — the fields that keep a feature.json from being an
3
- * anonymous shell (the {status, updatedAt}-only scaffolds nobody could later
4
- * identify). name/createdAt/workType are auto-stamped at birth (explicit
5
- * `morph-spec create` or the state-sync hook's implicit registration);
6
- * `description` is the one field a human must supply.
7
- *
8
- * `missingIdentity` powers the audit surfaces (`list`, `doctor`) that flag
9
- * pre-existing anonymous features LENIENTLY (warn, never invalidate old
10
- * feature.json). `create` enforces `description` HARD at birth — the two-tier
11
- * split the schema's `required` array can't express (adding fields there would
12
- * invalidate every feature.json that predates this).
13
- *
14
- * Pure module — no filesystem/CLI side effects.
15
- */
16
-
17
1
  export const IDENTITY_FIELDS = ['name', 'description', 'workType', 'createdAt'];
18
2
 
19
3
  const blank = (v) => v === undefined || v === null || String(v).trim() === '';
20
4
 
21
- /**
22
- * Identity fields absent from a feature.json (empty array = fully identified).
23
- * `description` leads because its absence is the real anonymity signal.
24
- * `workType` is intentionally excluded — absent workType defaults to 'feature',
25
- * so it is never a marker of an anonymous shell.
26
- *
27
- * @param {object} feature - feature.json contents
28
- * @returns {string[]} missing identity field names
29
- */
5
+ // `workType` is excluded on purpose: absent, it defaults to `feature`.
30
6
  export function missingIdentity(feature) {
31
7
  const missing = [];
32
8
  if (blank(feature?.description)) missing.push('description');