@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,83 +1,63 @@
1
1
  import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
3
  import { execFileSync } from 'child_process';
4
- import { existsSync, readFileSync } from 'fs';
4
+ import { existsSync } from 'fs';
5
5
  import { join } from 'path';
6
6
  import { getFeature, setApprovalGate, getApprovalGate, getApprovalHistory, derivePhase } from '../../core/state/state-manager.js';
7
7
  import { unmetPrereqs } from '../../core/state/gate-order.js';
8
- import { evaluatorReportState, VERDICT_TOKEN } from '../../lib/evaluator-report.js';
8
+ import { reviewReports, reviewFileName, REVIEW_ROLES, VERDICT_TOKEN, REVIEWED_SHA_TOKEN } from '../../lib/review-reports.js';
9
9
  import { shouldPauseGate } from '../../lib/risk-detector.js';
10
- import { collectSignals } from './gate-check.js';
10
+ import { collectSignals, collectReviewRequirement } from './gate-check.js';
11
+ import { autoApprovalRefusal, printRefusal } from './auto-approval.js';
11
12
  import { readTrustFromConfig } from './gate-decision.js';
12
13
  import { assertOwnerTree, ownerTreeRefusalLines } from '../../lib/worktree/feature-truth.js';
13
- import { uiuxAutoApprovalBlock } from '../../lib/specs/screen-checklist.js';
14
14
  import { treeMovedSince } from '../../lib/git/evidence-staleness.js';
15
+ import { seedDotnetBuildIgnores } from '../../lib/stack/dotnet-build-ignores.js';
16
+ import { reportDotnetIgnores } from './advance.js';
15
17
 
16
- /**
17
- * The literal token `review-report.md` carries to declare which tree it
18
- * reviewed. Kept as one exported constant so the CLI parser, the warning text
19
- * and the `morph-review` skill can never drift apart (the A5 lesson).
20
- */
21
- export const REVIEWED_SHA_TOKEN = '**Reviewed-SHA:**';
22
-
23
- const REVIEWED_SHA_RE = /\*\*Reviewed-SHA:\*\*\s*([0-9a-f]{7,40})/i;
24
-
25
- /**
26
- * Is the feature's `review-report.md` still describing the current tree?
27
- *
28
- * @param {string} featureName
29
- * @returns {{ status: 'ok'|'stale'|'unstamped'|'skip', reviewed?: string, head?: string, ahead?: number }}
30
- * `skip` covers every case where the question cannot be answered honestly:
31
- * no report, no git repo, an unresolvable SHA. Never a false refusal.
32
- */
33
- function reviewStaleness(featureName) {
34
- const cwd = process.cwd();
35
- const reportPath = join(cwd, '.morph', 'features', featureName, '4-review', 'review-report.md');
36
- if (!existsSync(reportPath)) return { status: 'skip' };
18
+ export { REVIEWED_SHA_TOKEN };
19
+
20
+ const SIGNED_OFF = { required: [], reports: {}, checks: { status: 'ok', total: 0, missing: [], notProven: [] } };
37
21
 
22
+ // The worst answer across every report present wins. `skip` (no verifier report, no git) never
23
+ // refuses falsely.
24
+ function reviewStaleness(reports) {
25
+ if (reports.verifier.status === 'missing') return { status: 'skip' };
26
+ const cwd = process.cwd();
38
27
  let head;
39
28
  try {
40
29
  head = execFileSync('git', ['rev-parse', 'HEAD'], { cwd, stdio: 'pipe' }).toString().trim();
41
- } catch {
42
- return { status: 'skip' }; // not a git repo (or no commits) — nothing to compare
43
- }
44
-
45
- let body = '';
46
- try {
47
- body = readFileSync(reportPath, 'utf8');
48
30
  } catch {
49
31
  return { status: 'skip' };
50
32
  }
51
33
 
52
- const stamped = body.match(REVIEWED_SHA_RE)?.[1];
53
- if (!stamped) return { status: 'unstamped', head };
54
-
55
- let reviewed;
56
- try {
57
- reviewed = execFileSync('git', ['rev-parse', '--verify', `${stamped}^{commit}`], { cwd, stdio: 'pipe' })
58
- .toString().trim();
59
- } catch {
60
- // A stamp git cannot resolve (rewritten history, a typo) is not evidence
61
- // of staleness — say "unstamped" rather than block on a guess.
62
- return { status: 'unstamped', head };
34
+ let unstamped = null;
35
+ for (const [role, report] of Object.entries(reports)) {
36
+ if (report.status === 'missing') continue;
37
+ const file = reviewFileName(role);
38
+ let reviewed = null;
39
+ try {
40
+ if (report.reviewedSha) {
41
+ reviewed = execFileSync('git', ['rev-parse', '--verify', `${report.reviewedSha}^{commit}`], { cwd, stdio: 'pipe' })
42
+ .toString().trim();
43
+ }
44
+ } catch {
45
+ reviewed = null;
46
+ }
47
+ if (!reviewed) {
48
+ unstamped ??= { status: 'unstamped', file, head };
49
+ continue;
50
+ }
51
+ if (!treeMovedSince(cwd, reviewed, head)) continue;
52
+ let ahead = 0;
53
+ try {
54
+ ahead = Number(execFileSync('git', ['rev-list', '--count', `${reviewed}..${head}`], { cwd, stdio: 'pipe' }).toString().trim()) || 0;
55
+ } catch { ahead = 0; }
56
+ return { status: 'stale', file, reviewed, head, ahead };
63
57
  }
64
- if (!treeMovedSince(cwd, reviewed, head)) return { status: 'ok', reviewed, head };
65
-
66
- let ahead = 0;
67
- try {
68
- ahead = execFileSync('git', ['rev-list', '--count', `${reviewed}..${head}`], { cwd, stdio: 'pipe' })
69
- .toString().trim();
70
- ahead = Number(ahead) || 0;
71
- } catch { ahead = 0; }
72
-
73
- // HEAD not descending from the reviewed commit (a rebase, a different branch)
74
- // is still "not the tree that was reviewed".
75
- return { status: 'stale', reviewed, head, ahead };
58
+ return unstamped ?? { status: 'ok', head };
76
59
  }
77
60
 
78
- /**
79
- * Unapprove action function - Revoke a previously approved gate
80
- */
81
61
  export async function unapproveCommand(featureName, gateName, options = {}) {
82
62
  try {
83
63
  if (!VALID_GATES.includes(gateName)) {
@@ -106,8 +86,7 @@ export async function unapproveCommand(featureName, gateName, options = {}) {
106
86
  return;
107
87
  }
108
88
 
109
- // Same honesty rule as approveCommand: don't pretend a human revoked
110
- // the gate when there's no interactive shell behind the invocation.
89
+ // Don't pretend a human revoked the gate without an interactive shell behind the call.
111
90
  let revokedBy;
112
91
  if (options.revoker) {
113
92
  revokedBy = options.revoker;
@@ -134,44 +113,25 @@ export async function unapproveCommand(featureName, gateName, options = {}) {
134
113
  }
135
114
  }
136
115
 
137
- /**
138
- * Approval Commands - Manage approval gates for MORPH workflow
139
- *
140
- * Commands:
141
- * - approve: Mark a gate as approved
142
- * - unapprove: Revoke a gate approval
143
- * - approval-status: Show approval status for all gates
144
- */
145
-
146
116
  const program = new Command();
147
117
 
148
- /**
149
- * Valid approval gate names
150
- */
151
- const VALID_GATES = ['proposal', 'uiux', 'plan', 'review'];
118
+ const VALID_GATES = ['plan', 'uiux', 'review'];
152
119
 
153
- /**
154
- * Approve action function - Mark a gate as approved
155
- */
156
120
  export async function approveCommand(featureName, gateName, options = {}) {
157
121
  try {
158
- // Validate gate name
159
122
  if (!VALID_GATES.includes(gateName)) {
160
123
  console.error(chalk.red(`\n❌ Invalid gate: ${gateName}`));
161
124
  console.log(chalk.gray(`Valid gates: ${VALID_GATES.join(', ')}\n`));
162
125
  process.exit(1);
163
126
  }
164
127
 
165
- // Get feature state
166
128
  const feature = getFeature(featureName);
167
129
  if (!feature) {
168
130
  console.error(chalk.red(`\n❌ Feature not found: ${featureName}\n`));
169
131
  process.exit(1);
170
132
  }
171
133
 
172
- // Owner-tree guard: a live worktree elsewhere owns this feature's state —
173
- // never stamp a gate onto the root's birth-instant skeleton (branch-drift
174
- // created by this command, not merely left behind by one).
134
+ // A live worktree elsewhere owns the state: never stamp a gate onto the root skeleton.
175
135
  const owner = assertOwnerTree(featureName, process.cwd());
176
136
  if (owner.blocked) {
177
137
  for (const { tone, text } of ownerTreeRefusalLines(featureName, owner.tree, owner.branch, `morph-spec approve ${featureName} ${gateName}`)) {
@@ -180,26 +140,18 @@ export async function approveCommand(featureName, gateName, options = {}) {
180
140
  process.exit(1);
181
141
  }
182
142
 
183
- // Check if already approved
184
143
  const currentStatus = getApprovalGate(featureName, gateName);
185
144
  if (currentStatus?.approved) {
186
145
  console.log(chalk.yellow(`\n⚠️ Gate "${gateName}" is already approved`));
187
146
  console.log(chalk.gray(`Approved at: ${currentStatus.timestamp}`));
188
147
  console.log(chalk.gray(`Approved by: ${currentStatus.approvedBy || 'Unknown'}`));
189
- // This early-out is a no-op that still exits 0 — deliberate, and
190
- // `--force` does NOT cover it (that flag only covers gate ORDER). A
191
- // caller who meant to re-approve therefore reads success and is left
192
- // with nowhere to go. The way out already exists as `unapprove`; the
193
- // message just never named it. Naming it is the whole fix (no new flag).
148
+ // `--force` does not cover this no-op (it only covers gate ORDER), so name the way out.
194
149
  console.log(chalk.gray(`To approve it again, revoke it first: morph-spec unapprove ${featureName} ${gateName}\n`));
150
+ if (gateName === 'plan') reportDotnetIgnores(seedDotnetBuildIgnores(process.cwd(), { featureName }));
195
151
  return;
196
152
  }
197
153
 
198
- // Gate monotonicity: a gate may only be approved once its predecessors are
199
- // approved (proposal → plan → review; uiux requires proposal). Prevents the
200
- // lead-termometro shape — plan.approved with proposal pending. Policy-stamped
201
- // gates count as approved, so chore/hotfix satisfy prereqs naturally.
202
- // Escape via --force for deliberate recovery.
154
+ // Gate monotonicity: predecessors first; policy stamps count (`--force` to recover).
203
155
  const unmet = unmetPrereqs(gateName, feature.approvalGates);
204
156
  if (unmet.length && !options.force) {
205
157
  console.error(chalk.red(`\n❌ Cannot approve "${gateName}" — prerequisite gate(s) not approved: ${unmet.join(', ')}.`));
@@ -207,22 +159,8 @@ export async function approveCommand(featureName, gateName, options = {}) {
207
159
  process.exit(1);
208
160
  }
209
161
 
210
- // Resolve who approved BEFORE the hotfix backstop — the backstop gates on
211
- // the resolved signature, not the raw --mode string (see below).
212
- //
213
- // approvedBy resolution (most explicit wins):
214
- // 1. --approver flag (CLI caller signalled who approved)
215
- // 2. --mode auto → "auto" (intentionally non-human signature)
216
- // 3. interactive shell (TTY on stdin) → $USER / $USERNAME (a real
217
- // human is at the terminal; their name is the honest answer)
218
- // 4. non-interactive (no TTY) → "auto"
219
- //
220
- // The TTY check exists because morph-spec is increasingly invoked
221
- // from headless contexts (claude -p subprocesses, CI, the
222
- // morph-e2e-tester). Stamping the host's $USERNAME on a gate that
223
- // no human actually approved is misleading — it lies to the audit
224
- // trail. "auto" reads as what it is: a deterministic policy gate,
225
- // not a human signing off.
162
+ // Resolved BEFORE the hotfix backstop, which gates on the signature. Without a TTY the answer
163
+ // is "auto": stamping $USERNAME on a gate no human approved lies to the audit trail.
226
164
  let approvedBy;
227
165
  if (options.approver) {
228
166
  approvedBy = options.approver;
@@ -234,119 +172,57 @@ export async function approveCommand(featureName, gateName, options = {}) {
234
172
  approvedBy = 'auto';
235
173
  }
236
174
 
237
- // Hotfix backstop: Gate 3 (review) requires a genuine HUMAN signature even
238
- // in trust:auto — a work-type policy (forcedPauseGates in
239
- // src/core/work-types.js, mirrored by risk-detector's
240
- // FORCED_PAUSE_WORK_TYPES), enforced server-side rather than trusting the
241
- // calling skill. It gates on the RESOLVED approvedBy, NOT the raw --mode
242
- // string: every headless context (claude -p, CI, the Bash tool) runs
243
- // without a TTY, so an omitted --mode — or any --mode ≠ 'auto' — still
244
- // resolves to "auto" and would otherwise rubber-stamp the gate. A human
245
- // approval is --approver <name> or an interactive TTY ($USER); anything
246
- // resolving to "auto" is refused here.
247
- if (gateName === 'review' && feature.workType === 'hotfix' && approvedBy === 'auto') {
248
- console.error(chalk.red('\n❌ Hotfix exige aprovação humana no Gate 3 — trust:auto não se aplica.'));
249
- console.error(chalk.gray(' Aprove com --approver <nome>, num terminal interativo, ou pela resposta humana "aprovado" (state-sync) ao review-report via AskUserQuestion.\n'));
175
+ const refusal = autoApprovalRefusal({ cwd: process.cwd(), featureName, feature, gate: gateName, approvedBy });
176
+ if (refusal) {
177
+ printRefusal(chalk, refusal);
250
178
  process.exit(1);
251
179
  }
252
180
 
253
- // Gate 3 staleness backstop (D1): a review reviews a TREE, and the tree
254
- // moves. Observed: review-report.md written at b30ac6e, then T3 landed and
255
- // inverted the default of a business rule; the report stayed in the repo
256
- // claiming 976/976 tests (they were 982) and never mentioning T3. Nothing
257
- // detected that the review artifact had fallen behind the code it reviews.
258
- //
259
- // The report stamps the SHA it reviewed; approval compares it with HEAD.
260
- // A stamp that is BEHIND HEAD refuses. A MISSING stamp only warns — reports
261
- // written before this existed must not brick a legacy feature — but the
262
- // warning names the exact token to add, so it teaches instead of nagging.
181
+ // A review reviews a TREE: a `Reviewed-SHA` behind HEAD refuses. A missing stamp only warns,
182
+ // naming the token to add, so a legacy report does not brick the feature.
183
+ const reports = gateName === 'review' ? reviewReports(process.cwd(), featureName) : null;
263
184
  if (gateName === 'review') {
264
- const stale = reviewStaleness(featureName);
185
+ const stale = reviewStaleness(reports);
265
186
  if (stale.status === 'stale') {
266
- console.error(chalk.red('\n❌ O `4-review/review-report.md` está desatualizado em relação ao código.'));
187
+ console.error(chalk.red(`\n❌ O \`4-review/${stale.file}\` está desatualizado em relação ao código.`));
267
188
  console.error(chalk.gray(` Revisado em: ${stale.reviewed.slice(0, 8)}`));
268
189
  console.error(chalk.gray(` HEAD atual: ${stale.head.slice(0, 8)}${stale.ahead ? ` (${stale.ahead} commit(s) depois da revisão)` : ''}`));
269
190
  console.error(chalk.gray(' Um review revisa uma ÁRVORE, e a árvore andou. Re-rode a `morph-review`'));
270
- console.error(chalk.gray(' (incluindo o avaliador independente) e atualize o `**Reviewed-SHA:**`.\n'));
191
+ console.error(chalk.gray(` e atualize o \`${REVIEWED_SHA_TOKEN}\` de cada relatório.\n`));
271
192
  process.exit(1);
272
193
  }
273
194
  if (stale.status === 'unstamped') {
274
- console.log(chalk.yellow('\n⚠️ O `review-report.md` não carrega `**Reviewed-SHA:**` — impossível saber se ele revisa o código atual.'));
275
- console.log(chalk.gray(` Adicione a linha \`**Reviewed-SHA:** ${stale.head}\` ao relatório para que o Gate 3 possa verificar.`));
276
- }
277
- }
278
-
279
- // Skipped-pause backstop: a gate whose recorded decision demanded a human
280
- // pause (gateDecisions[gate].pause === true) cannot then be stamped by a
281
- // non-human signature. The detection already existed — in gate-guard.js —
282
- // but that hook is registered for `Write|Edit|AskUserQuestion`, and a Bash
283
- // tool call fires no hook at all. Since MORPH.md requires gate approval to
284
- // go through THIS command and forbids hand-editing the state, the only path
285
- // the hook could ever see was the one the charter bans. The check belongs
286
- // where the stamp is applied, not beside it.
287
- //
288
- // Gated on the RESOLVED `approvedBy`, matching the hotfix backstop above:
289
- // an omitted --mode in a headless context also resolves to "auto" and must
290
- // not slip through. `--force` does NOT bypass this either — it is the
291
- // out-of-order recovery flag, a different concern. The way out is a human
292
- // signature, or rewriting the decision that asked for the pause.
293
- const decision = feature.gateDecisions?.[gateName];
294
- if (decision?.pause === true && approvedBy === 'auto') {
295
- console.error(chalk.red(`\n❌ O Gate "${gateName}" registrou pause:true — auto-aprovação não se aplica.`));
296
- console.error(chalk.gray(` Sinal: ${decision.reason || 'risco detectado'}`));
297
- console.error(chalk.gray(' A decisão gravada exigia pausa humana. Aprove com --approver <nome>, num'));
298
- console.error(chalk.gray(' terminal interativo, ou pela resposta humana via AskUserQuestion (state-sync).'));
299
- console.error(chalk.gray(' Se o sinal de risco já não vale, regrave a decisão:'));
300
- console.error(chalk.gray(` morph-spec gate-decision ${featureName} ${gateName} --pause false --reason "<motivo>"\n`));
301
- process.exit(1);
302
- }
303
-
304
- // The same backstop when no gate-check ran: the pause it WOULD record for
305
- // the screen checklist, by the predicate advance.js reads too. `--force`
306
- // does not bypass it, for the reason above.
307
- if (gateName === 'uiux' && approvedBy === 'auto') {
308
- const block = uiuxAutoApprovalBlock(process.cwd(), featureName, feature);
309
- if (block) {
310
- console.error(chalk.red(`\n❌ O Gate "uiux" não pode ser auto-aprovado — ${block}.`));
311
- console.error(chalk.gray(' Complete o checklist (ver a skill morph-uiux) e rode de novo, aprove com'));
312
- console.error(chalk.gray(' --approver <nome>, ou, se a pausa já foi decidida, grave a decisão:'));
313
- console.error(chalk.gray(` morph-spec gate-decision ${featureName} uiux --pause false --reason "<motivo>"\n`));
314
- process.exit(1);
195
+ console.log(chalk.yellow(`\n⚠️ O \`${stale.file}\` não carrega \`${REVIEWED_SHA_TOKEN}\` — impossível saber se ele revisa o código atual.`));
196
+ console.log(chalk.gray(` Adicione a linha \`${REVIEWED_SHA_TOKEN} ${stale.head}\` ao relatório para que o Gate 3 possa verificar.`));
315
197
  }
316
198
  }
317
199
 
318
200
  let selfAssessed = false;
319
201
  if (gateName === 'review') {
320
202
  if (feature.workType === 'hotfix') {
321
- // Hotfix Gate 3 does NOT require the independent evaluator: `morph-hotfix`
322
- // (SKILL.md §7) never dispatches `morph-evaluator` — speed over ceremony
323
- // is the point of a hotfix — and instead writes `4-review/review-report.md`
324
- // (diff + real test evidence) before asking the human to approve. Requiring
325
- // evaluator-report.md here made the skill's own documented flow unable to
326
- // clear its own Gate 3 (the only escape was --self-assessed, which stamps
327
- // a degradation for a report that was never supposed to exist). The human
328
- // signature is already enforced above (line ~215) — this only checks the
329
- // artifact the skill actually produces.
330
- const reviewReport = join(process.cwd(), '.morph', 'features', featureName, '4-review', 'review-report.md');
331
- if (!existsSync(reviewReport)) {
332
- console.error(chalk.red('\n❌ Gate 3 de hotfix exige `4-review/review-report.md` — não existe.'));
333
- console.error(chalk.gray(' É o artefato que a `morph-hotfix` produz (diff do fix + evidência real de'));
334
- console.error(chalk.gray(' teste do `morph-spec verify`), não o `evaluator-report.md` do avaliador'));
335
- console.error(chalk.gray(' independente — hotfix nunca despacha o avaliador. Escreva o relatório antes'));
203
+ // Hotfix never dispatches: the orchestrator writes `verifier.md` itself
204
+ // (diff + real verify evidence); the human signature above decides.
205
+ if (reports.verifier.status === 'missing') {
206
+ console.error(chalk.red('\n❌ Gate 3 de hotfix exige `4-review/verifier.md` — não existe.'));
207
+ console.error(chalk.gray(' No hotfix o orquestrador o escreve (diff do fix + evidência real do'));
208
+ console.error(chalk.gray(' `morph-spec verify`) sem despachar ninguém. Escreva o relatório antes'));
336
209
  console.error(chalk.gray(' de aprovar; a assinatura humana já é exigida pelo backstop acima.\n'));
337
210
  process.exit(1);
338
211
  }
339
212
  } else {
340
- const report = evaluatorReportState(process.cwd(), featureName);
341
- if (report.status !== 'ok') {
213
+ const { required } = collectReviewRequirement(process.cwd(), feature);
214
+ const unusable = required.filter((role) => reports[role].status !== 'ok');
215
+ if (unusable.length) {
342
216
  if (!options.selfAssessed) {
343
- if (report.status === 'missing') {
344
- console.error(chalk.red('\n❌ Gate 3 exige o avaliador independente — `4-review/evaluator-report.md` não existe.'));
345
- } else {
346
- console.error(chalk.red(`\n❌ Gate 3: o \`4-review/evaluator-report.md\` não tem \`${VERDICT_TOKEN} SHIP|FIX\`.`));
347
- console.error(chalk.gray(' Peça ao avaliador o relatório completo (morph-review §4) ou retome-o por SendMessage.'));
217
+ console.error(chalk.red(`\n❌ Gate 3 exige ${required.map((r) => `\`4-review/${reviewFileName(r)}\``).join(', ')}.`));
218
+ for (const role of unusable) {
219
+ const { status } = reports[role];
220
+ const what = status === 'missing' ? 'não existe'
221
+ : status === 'partial' ? 'está PARCIAL — retome o mesmo agente por SendMessage'
222
+ : `não tem \`${VERDICT_TOKEN} SHIP|FIX\``;
223
+ console.error(chalk.gray(` ${reviewFileName(role)} ${what}.`));
348
224
  }
349
- console.error(chalk.gray(' O veredito vem do avaliador (persona `evaluator`, contexto fresco), nunca de quem implementou.'));
225
+ console.error(chalk.gray(` Os relatórios vêm de ${unusable.map((r) => `\`${REVIEW_ROLES[r].persona}\``).join(', ')} em contexto fresco, nunca de quem implementou.`));
350
226
  console.error(chalk.gray(' Se o dispatch for impossível nesta sessão, aprove com --self-assessed —'));
351
227
  console.error(chalk.gray(' a degradação fica registrada no feature.json e aparece no `list`/`fleet`.\n'));
352
228
  process.exit(1);
@@ -354,12 +230,11 @@ export async function approveCommand(featureName, gateName, options = {}) {
354
230
  selfAssessed = true;
355
231
  }
356
232
 
357
- // Sem gate-check prévio não há `gateDecisions.review` para o backstop acima morder; em
358
- // campo, ships passaram assim com verify vermelho ou ausente. A auto-aprovação recalcula
359
- // a decisão pela MESMA régua do gate-check. --self-assessed já dispensou o veredito.
233
+ // Sem gate-check prévio não há `gateDecisions.review` para o backstop morder: a auto-aprovação
234
+ // recalcula a decisão pela MESMA régua do gate-check. --self-assessed já dispensou os relatórios.
360
235
  if (approvedBy === 'auto') {
361
236
  const signals = collectSignals(featureName, feature, process.cwd(), 'review');
362
- if (selfAssessed) signals.evaluatorVerdict = { status: 'ok', verdict: 'SHIP', blockers: 0, untyped: 0 };
237
+ if (selfAssessed) signals.reviewReports = SIGNED_OFF;
363
238
  const { pause, reason } = shouldPauseGate({ trust: readTrustFromConfig() }, signals);
364
239
  if (pause) {
365
240
  console.error(chalk.red(`\n❌ Gate 3 não pode ser auto-aprovado — ${reason}.`));
@@ -368,9 +243,12 @@ export async function approveCommand(featureName, gateName, options = {}) {
368
243
  }
369
244
  }
370
245
 
371
- if (report.status === 'ok' && report.superseded.length > 0) {
372
- console.log(chalk.yellow(`\n⚠️ O relatório do avaliador tem ${report.superseded.length + 1} vereditos (append-only).`));
373
- console.log(chalk.gray(` Vigente: ${report.verdict}, ${report.blockers.length} bloqueador(es). Superados: ${report.superseded.join(', ')}\n`));
246
+ for (const role of required) {
247
+ const r = reports[role];
248
+ if (r.status === 'ok' && r.superseded.length > 0) {
249
+ console.log(chalk.yellow(`\n⚠️ ${reviewFileName(role)} tem ${r.superseded.length + 1} vereditos (append-only).`));
250
+ console.log(chalk.gray(` Vigente: ${r.verdict}, ${r.blockers.length} bloqueador(es). Superados: ${r.superseded.join(', ')}\n`));
251
+ }
374
252
  }
375
253
  }
376
254
  }
@@ -382,23 +260,19 @@ export async function approveCommand(featureName, gateName, options = {}) {
382
260
  ...(selfAssessed ? { selfAssessed: true } : {}),
383
261
  });
384
262
 
385
- // Fleet handoff (mirrors advance.js): approving a gate is exactly when a
386
- // feature hands over between sessions too — `approve` is the real path
387
- // Gate 2 took in the field (`approve <f> plan --mode auto`), not just
388
- // `advance`, so a handoff.md left stale after approve was the common
389
- // case, not the edge case. Same helper, same fail-open contract: a
390
- // regeneration failure must never fail the approval itself.
263
+ // Approving is a session handoff too; a regeneration failure must never fail the approval.
391
264
  try {
392
265
  const { writeHandoff } = await import('../../lib/generators/handoff-generator.js');
393
266
  writeHandoff(featureName, process.cwd());
394
267
  } catch { /* fail-open */ }
395
268
 
396
269
  if (selfAssessed) {
397
- console.log(chalk.yellow('\n⚠️ Gate 3 aprovado SEM avaliador independente (--self-assessed).'));
270
+ console.log(chalk.yellow('\n⚠️ Gate 3 aprovado SEM os relatórios de review independentes (--self-assessed).'));
398
271
  console.log(chalk.gray(' Registrado em feature.json → approvalGates.review.selfAssessed.'));
399
272
  }
400
273
 
401
274
  console.log(chalk.green(`\n✅ Gate "${gateName}" approved for feature: ${featureName}`));
275
+ if (gateName === 'plan') reportDotnetIgnores(seedDotnetBuildIgnores(process.cwd(), { featureName }));
402
276
  console.log(chalk.gray(`Workflow can now proceed past ${gateName} phase\n`));
403
277
 
404
278
  } catch (error) {
@@ -407,28 +281,20 @@ export async function approveCommand(featureName, gateName, options = {}) {
407
281
  }
408
282
  }
409
283
 
410
- /**
411
- * Approve command - Mark a gate as approved
412
- */
413
284
  program
414
285
  .command('approve <feature> <gate>')
415
286
  .description('Approve a phase gate to allow workflow progression')
416
287
  .action(approveCommand);
417
288
 
418
- /**
419
- * Approval status action function - Show all gates and their status
420
- */
421
289
  export async function approvalStatusCommand(featureName, options = {}) {
422
290
  try {
423
- // Get feature state
424
291
  const feature = getFeature(featureName);
425
292
  if (!feature) {
426
293
  console.error(chalk.red(`\n❌ Feature not found: ${featureName}\n`));
427
294
  process.exit(1);
428
295
  }
429
296
 
430
- // v5 removed the stored feature.phase — derive it from the filesystem +
431
- // approved gates (same as `morph-spec status`), never print "unknown".
297
+ // Phase derived from the filesystem + approved gates, never "unknown".
432
298
  const featurePath = join(process.cwd(), '.morph', 'features', featureName);
433
299
  const phase = feature.phase || derivePhase(featurePath);
434
300
 
@@ -437,7 +303,6 @@ export async function approvalStatusCommand(featureName, options = {}) {
437
303
  const uiuxGate = getApprovalGate(featureName, 'uiux');
438
304
  const uiuxTaken = uiuxGate?.approved === true || existsSync(join(featurePath, '1-design'));
439
305
 
440
- // JSON output
441
306
  if (options.json) {
442
307
  const status = {
443
308
  feature: featureName,
@@ -458,11 +323,9 @@ export async function approvalStatusCommand(featureName, options = {}) {
458
323
  console.log(chalk.bold(`\n📋 Approval Status for: ${featureName}`));
459
324
  console.log('━'.repeat(60));
460
325
 
461
- // Show current phase
462
326
  console.log(chalk.cyan(`\nCurrent Phase: ${phase}`));
463
327
  console.log(chalk.gray(`Feature Status: ${feature.status || 'unknown'}\n`));
464
328
 
465
- // Show each gate
466
329
  console.log(chalk.bold('Approval Gates:'));
467
330
 
468
331
  for (const gateName of VALID_GATES) {
@@ -504,7 +367,6 @@ export async function approvalStatusCommand(featureName, options = {}) {
504
367
  }
505
368
  }
506
369
 
507
- // Show approval history if available
508
370
  const history = getApprovalHistory(featureName);
509
371
  if (history && history.length > 0) {
510
372
  console.log(chalk.bold('\nApproval History:'));
@@ -522,16 +384,12 @@ export async function approvalStatusCommand(featureName, options = {}) {
522
384
  }
523
385
  }
524
386
 
525
- /**
526
- * Approval status command - Show all gates and their status
527
- */
528
387
  program
529
388
  .command('approval-status <feature>')
530
389
  .alias('status')
531
390
  .description('Show approval status for all gates')
532
391
  .action(approvalStatusCommand);
533
392
 
534
- // Only parse if run directly (not imported as module)
535
393
  if (import.meta.url === `file://${process.argv[1]}`) {
536
394
  if (process.argv.length > 2) {
537
395
  program.parse(process.argv);