@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,95 +1,23 @@
1
- /**
2
- * Risk Detector — decides whether a gate should pause execution.
3
- *
4
- * In `trust: "auto"` mode (default), gates auto-pass UNLESS one of these
5
- * risk signals triggers a forced pause. In `trust: "manual"` mode, gates
6
- * always pause via AskUserQuestion.
7
- *
8
- * Signals checked (in order):
9
- * - designSystemDivergence: boolean — conflicting design sources detected
10
- * - standardsViolations: string[] — violations of the plan's forbidden patterns
11
- * - verificationStatus: string — último `morph-spec verify` ('fail' pausa, qualquer gate)
12
- * - verificationScope + gate: string — presente e != 'feature' pausa SÓ no ship: o
13
- * último `verify` foi um recorte de task, não da feature inteira (`null`/ausente
14
- * não conta — narrow, nunca widen)
15
- * - e2eStatus + e2eReason + gate — nó e2e do último verify. Um `skip`
16
- * cuja razão NÃO seja dispensa legítima pausa SÓ no ship (gate 'review').
17
- * Sem escopo (stamp sem o campo) cai do mesmo lado seguro que `unknown`
18
- * - verificationStale + gate: bool/str — stamp de um commit cujo código já mudou
19
- * (fora de .morph/) pausa SÓ no ship (gate 'review')
20
- * - evaluatorVerdict + gate: object — { status, verdict, blockers, untyped } do
21
- * evaluator-report.md. No ship, só `SHIP` sem bloqueador libera
22
- * - mediaClaims + gate: object — { section, featureGeneratesMedia }
23
- * - screenChecklist + gate: object — { mockups, legacy, screens, problems, exempt?,
24
- * section?, uncovered? } derivado do 1-design/mockups.md. No uiux, checklist
25
- * de tela incompleto pausa; no ship, item decidido sem a conferência do
26
- * avaliador pausa. Mockup legado é isento nos dois, com a razão listada
27
- * - oversizedTasks + gate: array — tasks acima de MAX_TASK_OUTPUTS outputs;
28
- * pausa SÓ no plano (gate 'plan'): a task tem de caber num dispatch
29
- * - taskCount: number — total tasks (pause if > 20)
30
- */
31
-
32
- const SCOPE_TASK_THRESHOLD = 20;
33
-
34
- // Caminho alterado que pede o avaliador em opus: o resto da review roda em sonnet.
35
- const OPUS_PATH_RE = /(^|[/._-])(auth|login|senha|password|token|secret|crypto|permiss|rbac|acl|lgpd|pii|privacy|payment|pagamento|billing|cobranca|invoice|asaas|stripe|migrations?)([/._-]|$)/i;
36
-
37
- // Work types whose ship gate (review) always pauses for a human, regardless
38
- // of trust mode — a type policy, not a per-feature risk signal. Mirrors
39
- // `forcedPauseGates` in src/core/work-types.js (hotfix.review); kept here too
40
- // because risk-detector is the deterministic layer consumed independent of
41
- // the work-type config object.
1
+ import { MAX_SLICES } from './plan/checks.js';
2
+ import { BLOCKER_TYPES, reviewFileName } from './review-reports.js';
3
+
4
+ const SECURITY_PATH_RE = /(^|[/._-])(auth|login|senha|password|token|secret|crypto|permiss|rbac|acl|lgpd|pii|privacy|payment|pagamento|billing|cobranca|invoice|asaas|stripe|migrations?)([/._-]|$)/i;
5
+
6
+ // Espelha `forcedPauseGates` (hotfix.review) de src/core/work-types.js: os dois mudam juntos.
42
7
  const FORCED_PAUSE_WORK_TYPES = ['hotfix'];
43
8
 
44
- // As razões literais que `framework/hooks/shared/e2e-check.js` emite quando o
45
- // nó e2e pula. Casadas por PADRÃO e não por igualdade porque duas delas
46
- // carregam dado variável (o nome do grupo da task). Ordem irrelevante: os
47
- // padrões são disjuntos.
9
+ // Razões literais que `e2e-check.js` emite ao pular; casadas por padrão porque algumas carregam dado variável.
48
10
  const E2E_SKIP_PATTERNS = [
49
- // "docker is not available in this environment — e2e could not run"
50
- // (e2e-check.js). A app não foi exercida porque a MÁQUINA não podia.
51
11
  [/docker is not available/i, 'environment'],
52
- // `--skip-e2e` — o operador pediu para pular.
53
12
  [/^requested$/i, 'requested'],
54
- // "project does not containerize (no compose file)" — não há app para subir.
55
13
  [/does not containerize/i, 'project'],
56
- // 'task group "docs" does not touch runtime' — escopo da task, não da app.
57
- [/does not touch runtime/i, 'scope'],
58
14
  ];
59
15
 
60
- // Classes de skip que NÃO são dispensa: o e2e não rodou e nada no repositório
61
- // diz que ele não precisava rodar. `unknown` está aqui de propósito — inclui a
62
- // razão vazia/nula, e uma dispensa sem motivo é o mesmo no-op silencioso que
63
- // esta regra existe para eliminar.
16
+ // `unknown` (razão vazia) pausa de propósito: dispensa sem motivo é o no-op silencioso que a regra elimina.
64
17
  const E2E_SKIP_CLASSES_THAT_PAUSE = ['environment', 'requested', 'unknown'];
65
18
 
66
- // O ÚNICO escopo de stamp que responde pela feature inteira. `morph-spec verify
67
- // {f} {task}` carimba `scope: 'task:{id}'`, e o nó e2e de um verify por task
68
- // pula com a razão do GRUPO daquela task ('task group "docs" does not touch
69
- // runtime') — uma afirmação sobre UMA task, nunca sobre a app. Sem este teste,
70
- // a última task de uma feature (tipicamente `group: docs`) compra o Gate 3 da
71
- // feature que ela mesma acabou de escrever, numa máquina onde a app nunca
72
- // subiu. Ausente/desconhecido cai do mesmo lado seguro que `unknown`: recusar
73
- // é barato, aprovar sem prova não é.
74
- const FEATURE_SCOPE = 'feature';
75
-
76
- /**
77
- * Classifica a razão literal de um `skip` do nó e2e — de QUEM é a ausência de
78
- * prova. Existe porque `verificationStatus` é o rollup do `verify` e um nó que
79
- * pula NÃO o reprova: sem classificar a razão, "o e2e rodou e passou" e "o e2e
80
- * nunca rodou" chegam ao gate como o mesmo `pass`.
81
- *
82
- * A assimetria é o ponto: `project`/`scope` descrevem o REPOSITÓRIO dizendo que
83
- * não há runtime a exercer (dispensa legítima, registrada com motivo);
84
- * `environment`/`requested` descrevem a MÁQUINA ou o operador, e nenhum dos
85
- * dois é evidência sobre o código. `unknown` cai no lado seguro pelo mesmo
86
- * motivo: recusar é barato, aprovar sem prova não é.
87
- *
88
- * @param {string|null|undefined} reason - `verification.e2eReason` (a razão
89
- * literal que o `morph-spec verify` carimbou). Vazia/ausente ⇒ 'unknown' —
90
- * DESCONHECIDO, jamais lido como dispensa.
91
- * @returns {'environment'|'requested'|'project'|'scope'|'unknown'}
92
- */
19
+ // `project` diz que o repositório não tem runtime (dispensa legítima); `environment`/`requested` falam
20
+ // da máquina ou do operador, nunca do código. Razão vazia é `unknown`, jamais dispensa.
93
21
  export function classifyE2eSkip(reason) {
94
22
  const text = typeof reason === 'string' ? reason.trim() : '';
95
23
  if (text === '') return 'unknown';
@@ -99,7 +27,6 @@ export function classifyE2eSkip(reason) {
99
27
  return 'unknown';
100
28
  }
101
29
 
102
- /** Mensagem de pausa por classe — nomeia SEMPRE a razão literal do verify. */
103
30
  function e2ePauseReason(klass, reason) {
104
31
  const literal = typeof reason === 'string' && reason.trim() !== '' ? reason.trim() : null;
105
32
  if (klass === 'environment') {
@@ -116,98 +43,115 @@ function e2ePauseReason(klass, reason) {
116
43
  return `e2e pulado por razão não classificada: "${literal}" — sem classificação, não há dispensa`;
117
44
  }
118
45
 
119
- /**
120
- * Mensagem de recusa por ESCOPO do stamp: a razão do skip É uma dispensa
121
- * legítima (`project`/`scope`), mas quem a carimbou foi um verify que não
122
- * responde pela feature. Nomeia o escopo real para que o leitor saiba qual
123
- * comando rodar, em vez de rodar o mesmo de novo.
124
- */
125
- function e2eScopeRefusal(klass, scope, reason) {
126
- const literal = typeof reason === 'string' && reason.trim() !== '' ? reason.trim() : 'sem motivo registrado';
127
- const where = typeof scope === 'string' && scope.trim() !== '' ? scope.trim() : 'ausente';
128
- return `dispensa de e2e [${klass}] veio de um verify de escopo "${where}", não da feature:`
129
- + ` "${literal}" — um stamp de task responde por UMA task, não pela app inteira;`
130
- + ' rode `morph-spec verify {feature}` (sem task) antes do ship';
131
- }
132
-
133
- /** A razão gravada no sinal quando o checklist de tela isenta um mockup legado. */
134
46
  export const LEGACY_MOCKUP_EXEMPTION = 'checklist dispensado: mockup no formato antigo';
135
47
 
136
- /** Os dois primeiros itens de uma lista, e quantos ficaram de fora. */
137
48
  function firstOf(items) {
138
49
  const shown = items.slice(0, 2).join('; ');
139
50
  return items.length > 2 ? `${shown} (+${items.length - 2})` : shown;
140
51
  }
141
52
 
142
- /**
143
- * Por que o checklist de tela pausa o Gate uiux, ou null. Exportada porque o
144
- * `approve`/`advance` com assinatura `auto` recusam pela MESMA regra: uma
145
- * aprovação automática que passasse onde o gate-check pausa seria o atalho.
146
- *
147
- * Mockup ausente (`mockups: false`) não conta — nem toda feature de UI tem
148
- * tela —, e o legado chega sem problemas: a auditoria o isenta.
149
- *
150
- * @param {object|undefined} screenChecklist - o sinal de `collectScreenChecklist`
151
- * @returns {string|null}
152
- */
53
+ // Exportada porque approve/advance `auto` recusam pela mesma regra. Sem design de tela não conta,
54
+ // e o legado chega sem problemas: a auditoria o isenta. Checklist
55
+ // completo ainda deve o protótipo e os prints 1280/390 (`prototypes`).
153
56
  export function uiuxChecklistPause(screenChecklist) {
154
57
  if (screenChecklist?.mockups !== true) return null;
155
58
  const problems = screenChecklist.problems ?? [];
156
- if (problems.length === 0) return null;
157
- return `checklist de tela incompleto em 1-design/mockups.md: ${firstOf(problems)}`
158
- + ' — volume, paginação, ordenação, filtro, os três estados, mobile e abas por tela'
159
- + ' ("não se aplica" vale, se escrito)';
160
- }
161
-
162
- function blockerList(v) {
163
- return firstOf(v.blockerIds ?? []);
59
+ if (problems.length > 0) {
60
+ return `checklist de tela incompleto em 1-design/mockups.md: ${firstOf(problems)}`
61
+ + ' — volume, paginação, ordenação, filtro, os três estados, mobile e abas por tela'
62
+ + ' ("não se aplica" vale, se escrito)';
63
+ }
64
+ const prototypes = screenChecklist.prototypes ?? [];
65
+ if (prototypes.length === 0) return null;
66
+ return `protótipo incompleto: ${firstOf(prototypes)}`
67
+ + ' — cada tela aponta o HTML renderizado e deixa os prints de 1280 e 390 ao lado dele';
164
68
  }
165
69
 
166
- /** Por que o veredito do avaliador segura o ship, ou null. */
167
- export function verdictPause(v) {
168
- if (!v || v.status === 'missing') {
169
- return 'Gate 3 sem `4-review/evaluator-report.md` — despache o avaliador independente;'
170
- + ' auto-avaliação de quem implementou não decide o ship';
171
- }
172
- if (v.status !== 'ok') {
173
- return 'evaluator-report sem `**Verdict:** SHIP|FIX` — o avaliador não seguiu o contrato do relatório';
70
+ const ROLE_PERSONA = { verifier: 'morph-verifier', reviewer: 'morph-reviewer', security: 'morph-security' };
71
+
72
+ export function reviewPause(signal) {
73
+ const required = signal?.required ?? ['verifier'];
74
+ const reports = signal?.reports ?? {};
75
+ for (const role of required) {
76
+ const r = reports[role];
77
+ const file = `4-review/${reviewFileName(role)}`;
78
+ if (!r || r.status === 'missing') {
79
+ return `Gate 3 sem \`${file}\` — despache o \`${ROLE_PERSONA[role]}\` (contexto fresco);`
80
+ + ' auto-avaliação de quem implementou não decide o ship';
81
+ }
82
+ if (r.status === 'partial') {
83
+ return `\`${file}\` está PARCIAL — retome o mesmo agente por SendMessage; parcial não é veredito`;
84
+ }
85
+ if (r.status !== 'ok') {
86
+ return `\`${file}\` sem \`**Verdict:** SHIP|FIX\` — o relatório não seguiu o contrato`;
87
+ }
174
88
  }
175
- if ((v.untyped ?? 0) > 0) {
176
- return `${v.untyped} bloqueador(es) sem tipo de evidência (uc | teste | regressao | seguranca)`
89
+ const present = Object.entries(reports).filter(([, r]) => r.status === 'ok');
90
+ const untyped = present.reduce((n, [, r]) => n + (r.untyped ?? 0), 0);
91
+ if (untyped > 0) {
92
+ return `${untyped} bloqueador(es) sem tipo de evidência (${BLOCKER_TYPES.join(' | ')})`
177
93
  + ' — sem evidência tipada não é bloqueador: reclassifique como nota ou complete a evidência';
178
94
  }
179
- if ((v.blockers ?? 0) > 0) return `avaliador abriu ${v.blockers} bloqueador(es): ${blockerList(v)}`;
180
- if (v.verdict !== 'SHIP') return 'veredito do avaliador é FIX';
95
+ for (const [role, r] of present) {
96
+ if ((r.blockers ?? 0) > 0) return `${role} abriu ${r.blockers} bloqueador(es): ${firstOf(r.blockerIds ?? [])}`;
97
+ }
98
+ for (const [role, r] of present) {
99
+ if (r.verdict !== 'SHIP') return `veredito do ${role} é ${r.verdict}`;
100
+ }
101
+ const checks = signal?.checks;
102
+ if (!checks || checks.status !== 'ok') {
103
+ return 'sem `2-plan/checks.json` legível — a cobertura dos cenários pelo verificador não é conferível';
104
+ }
105
+ const gaps = [
106
+ ...checks.missing.map((id) => `${id} sem linha`),
107
+ ...checks.notProven.map(({ id, status }) => `${id} ${status}`),
108
+ ];
109
+ if (gaps.length) {
110
+ return `\`## Checks\` do verificador não prova ${firstOf(gaps)}`
111
+ + ' — todo cenário precisa de uma linha PROVADO com evidência';
112
+ }
181
113
  return null;
182
114
  }
183
115
 
184
- function verdictSummary(v) {
185
- if (!v || v.status !== 'ok') return 'Gate 3 sem veredito do avaliador independente';
186
- return `veredito ${v.verdict}, ${v.blockers ?? 0} bloqueador(es)`;
116
+ function reviewSummary(signal) {
117
+ const reports = signal?.reports ?? {};
118
+ const roles = Object.keys(reports);
119
+ if (roles.length === 0) return 'Gate 3 sem relatórios de review';
120
+ const verdicts = roles.map((role) => `${role} ${reports[role].verdict ?? reports[role].status}`).join(', ');
121
+ const blockers = roles.reduce((n, role) => n + (reports[role].blockers ?? 0), 0);
122
+ const checks = signal.checks?.status === 'ok'
123
+ ? `; checks ${signal.checks.total - signal.checks.missing.length - signal.checks.notProven.length}/${signal.checks.total} PROVADO`
124
+ : '';
125
+ return `review: ${verdicts}; ${blockers} bloqueador(es)${checks}`;
187
126
  }
188
127
 
189
- /**
190
- * Modelo do avaliador do Gate 3: `opus` quando a feature toca caminho sensível
191
- * (auth, segredo, permissão, dado pessoal, dinheiro, migration), prompt/modelo
192
- * ou geração de mídia; `sonnet` no resto.
193
- *
194
- * @param {{ changedFiles?: string[], promptModelTouched?: boolean, generatesMedia?: boolean }} input
195
- * @returns {{ model: 'opus'|'sonnet', reasons: string[] }}
196
- */
197
- export function evaluatorModelFor({ changedFiles = [], promptModelTouched = false, generatesMedia = false } = {}) {
128
+ // Caminho sensível não muda o verificador: despacha a persona `security` (`securityReviewFor`).
129
+ export function verifierModelFor({ promptModelTouched = false, generatesMedia = false } = {}) {
198
130
  const reasons = [];
199
- const risky = changedFiles.filter((f) => OPUS_PATH_RE.test(f.replaceAll('\\', '/')));
200
- if (risky.length) reasons.push(`caminho sensível: ${firstOf(risky)}`);
201
131
  if (promptModelTouched) reasons.push('prompt/modelo alterado');
202
132
  if (generatesMedia) reasons.push('gera mídia');
203
133
  return { model: reasons.length ? 'opus' : 'sonnet', reasons };
204
134
  }
205
135
 
206
- /**
207
- * @param {object} config - .morph/config/config.json contents (uses `trust`)
208
- * @param {object} signals
209
- * @returns {{ pause: boolean, reason: string }}
210
- */
136
+ export function securityReviewFor(changedFiles = []) {
137
+ const risky = changedFiles.filter((f) => SECURITY_PATH_RE.test(f.replaceAll('\\', '/')));
138
+ return { required: risky.length > 0, reasons: risky.length ? [`caminho sensível: ${firstOf(risky)}`] : [] };
139
+ }
140
+
141
+ const PLAN_CHECKS_REASONS = {
142
+ 'no-plan': 'sem 2-plan/plan.md',
143
+ 'no-checks': 'sem 2-plan/checks.json',
144
+ unreadable: '2-plan/checks.json ilegível ou de outra versão',
145
+ stale: '2-plan/checks.json não corresponde ao plan.md atual',
146
+ };
147
+
148
+ // Regra única do gate-check (pausa) e do approve/advance automáticos (recusa).
149
+ export function planChecksPause(status) {
150
+ if (status === undefined || status === 'fresh') return null;
151
+ const what = PLAN_CHECKS_REASONS[status] ?? `checks.json em estado "${status}"`;
152
+ return `${what} — rode \`morph-spec plan-check {feature}\` até zero erro antes do gate do plano`;
153
+ }
154
+
211
155
  export function shouldPauseGate(config, signals) {
212
156
  const trust = config?.trust ?? 'auto';
213
157
 
@@ -233,10 +177,7 @@ export function shouldPauseGate(config, signals) {
233
177
  };
234
178
  }
235
179
 
236
- // Verify vermelho no momento do gate = a barreira determinística (build/
237
- // testes/validadores) está falhando — código antes do juiz vale também
238
- // para a decisão de pausa. Só 'fail' pausa: ausência de stamp não é sinal
239
- // (features antigas não têm o campo).
180
+ // Só 'fail' pausa aqui: features antigas não têm o stamp.
240
181
  if (signals.verificationStatus === 'fail') {
241
182
  return {
242
183
  pause: true,
@@ -244,7 +185,7 @@ export function shouldPauseGate(config, signals) {
244
185
  };
245
186
  }
246
187
 
247
- // No ship, ausência de stamp não é "verde": uma feature foi aprovada em campo sem verify nenhum.
188
+ // No ship, ausência de stamp não é verde.
248
189
  if (signals.gate === 'review' && !signals.verificationStatus) {
249
190
  return {
250
191
  pause: true,
@@ -252,68 +193,16 @@ export function shouldPauseGate(config, signals) {
252
193
  };
253
194
  }
254
195
 
255
- // No gate de SHIP: um `verification` PRESENTE cujo escopo não é o da feature
256
- // é um recorte parcial — de UMA task, nunca da app inteira. O stamp é
257
- // SINGULAR, sobrescrito por qualquer verify de qualquer escopo, e vale
258
- // INDEPENDENTE do que os nós individuais dizem: mesmo com e2e 'pass' dentro
259
- // daquele recorte, build/tests/validators também rodaram só para a task, não
260
- // para a feature. Antes desta regra, um `verify {f} {task}` totalmente verde
261
- // comprava o Gate 3 da feature que ele nem tentou verificar por inteiro — já
262
- // acontecia manualmente (`--filter`), e o recorte automático de PR B faz
263
- // acontecer em TODA task. Por isso corre ANTES do bloco de e2e abaixo: ele
264
- // pausa por si só, sem depender de e2eStatus/e2eReason.
265
- //
266
- // `typeof === 'string'` exclui `null`/ausente (features legadas sem stamp) —
267
- // narrow, nunca widen. O caminho de volta é sempre o mesmo, barato e nomeado:
268
- // `morph-spec verify {feature}` (suíte inteira) antes do ship.
269
- if (signals.gate === 'review'
270
- && typeof signals.verificationScope === 'string'
271
- && signals.verificationScope !== FEATURE_SCOPE) {
272
- return {
273
- pause: true,
274
- reason: `último morph-spec verify foi de escopo "${signals.verificationScope}" (recorte parcial de task)`
275
- + ' — rode `morph-spec verify {feature}` (suíte inteira) antes do ship',
276
- };
277
- }
278
-
279
- // No gate de SHIP apenas: o nó e2e do último verify PULOU. Um skip não
280
- // reprova o rollup, então `verificationStatus` sai 'pass' e o gate ficava
281
- // cego — auto-passando um Gate 3 sobre uma app que ninguém subiu. A razão
282
- // literal decide: dispensa legítima (o projeto não containeriza; o grupo da
283
- // task não toca runtime) passa e fica REGISTRADA nos signals; ausência de
284
- // prova (docker indisponível, `--skip-e2e`, razão nenhuma) pausa. É a prosa
285
- // do morph-review virando regra — "docker indisponível não é dispensa" — e
286
- // é o que dispensa o `gate-decision --pause false` manual que ela mandava
287
- // rodar. Só no review: pausar o Gate 1/2 numa máquina sem docker travaria
288
- // trabalho legítimo de planejamento.
196
+ // Skip não reprova o rollup, então só a razão literal separa dispensa de ausência de prova.
197
+ // Só no ship: pausar plan/uiux numa máquina sem docker travaria planejamento legítimo.
289
198
  if (signals.gate === 'review' && signals.e2eStatus === 'skip') {
290
199
  const klass = classifyE2eSkip(signals.e2eReason);
291
200
  if (E2E_SKIP_CLASSES_THAT_PAUSE.includes(klass)) {
292
201
  return { pause: true, reason: e2ePauseReason(klass, signals.e2eReason) };
293
202
  }
294
- // Segunda pergunta, que a razão sozinha não responde: QUEM carimbou. Um
295
- // escopo de TASK (`task:{id}`) já pausou acima, antes deste bloco — o que
296
- // sobra a checar aqui é só a ausência de escopo (`null`/indefinido:
297
- // feature legada, ou stamp anterior à introdução do campo). Cai do mesmo
298
- // lado seguro que `unknown` pelo mesmo motivo de sempre: recusar é barato,
299
- // aprovar sem prova de QUEM respondeu pela app não é. O caminho de volta é
300
- // o mesmo (rodar `verify {feature}`), e é o que morph-review já manda
301
- // fazer antes do Gate 3.
302
- if (signals.verificationScope == null) {
303
- return {
304
- pause: true,
305
- reason: e2eScopeRefusal(klass, signals.verificationScope, signals.e2eReason),
306
- };
307
- }
308
203
  }
309
204
 
310
- // Furo 2: no gate de SHIP (review) apenas, um stamp de verification cujo
311
- // sha diverge do HEAD atual significa que código mudou depois do último
312
- // `morph-spec verify` — o resultado carimbado não cobre mais o que está
313
- // prestes a ser aprovado. Pausa só aqui, nunca em plan/uiux/proposal: essa
314
- // é uma regra de ship, não uma regra geral de gate. Stamp AUSENTE não entra
315
- // aqui (verificationStale é fail-open para `false` em collectSignals
316
- // quando não há stamp) — features legadas sem stamp continuam liberadas.
205
+ // Regra de ship, não de gate em geral; stamp ausente é fail-open (`false`) em collectSignals.
317
206
  if (signals.gate === 'review' && signals.verificationStale === true) {
318
207
  return {
319
208
  pause: true,
@@ -322,23 +211,13 @@ export function shouldPauseGate(config, signals) {
322
211
  }
323
212
 
324
213
  if (signals.gate === 'review') {
325
- const reason = verdictPause(signals.evaluatorVerdict);
214
+ const reason = reviewPause(signals.reviewReports);
326
215
  if (reason) return { pause: true, reason };
327
216
  }
328
217
 
329
- // No gate de SHIP apenas: a feature mexeu num caminho que o PROJETO declarou
330
- // como prompt ou modelo, e não há eval verde carimbado. Prompt e modelo são a
331
- // única parte do produto cujo comportamento não é lido por build nem por teste
332
- // unitário: os dois continuam verdes com o prompt trocado.
333
- //
334
- // DEPOIS da cláusula de procedência do score, de propósito: quando as duas
335
- // valem, "Gate 3 sem score do avaliador independente" continua sendo a razão
336
- // reportada — é o defeito mais grave dos dois.
337
- //
338
- // PAUSA, jamais bloqueia: `approve review` não ganha precondição nova. Uma
339
- // segunda precondição dura ali tornaria inaprovável, inclusive por decisão
340
- // humana, toda feature legada. Pausa é a severidade certa — o humano lê a
341
- // razão e decide.
218
+ // Prompt e modelo não são lidos por build nem por teste. Depois dos relatórios de review de propósito:
219
+ // relatório ausente é o defeito mais grave. Pausa, nunca bloqueia: bloquear tornaria inaprovável
220
+ // toda feature legada.
342
221
  if (signals.gate === 'review' && (signals.promptModel?.touched?.length ?? 0) > 0) {
343
222
  const touched = signals.promptModel.touched;
344
223
  const where = `${touched.slice(0, 3).join(', ')}${touched.length > 3 ? ` (+${touched.length - 3})` : ''}`;
@@ -360,9 +239,7 @@ export function shouldPauseGate(config, signals) {
360
239
  + ' — rode `morph-spec verify {feature}` (escopo de feature) antes do ship',
361
240
  };
362
241
  }
363
- // 'fail' cai fora: um eval vermelho já avermelha o rollup e a regra de
364
- // `verificationStatus` acima pausou antes daqui. 'pass' com stamp velho
365
- // também já pausou, por `verificationStale`.
242
+ // 'fail' já pausou por `verificationStatus`; 'pass' com stamp velho, por `verificationStale`.
366
243
  }
367
244
 
368
245
  // Feature que gera mídia: custo, revisão humana, marcação e hot path só valem provados na seção.
@@ -371,32 +248,26 @@ export function shouldPauseGate(config, signals) {
371
248
  && signals.mediaClaims.section !== true) {
372
249
  return {
373
250
  pause: true,
374
- reason: 'relatório do avaliador sem a seção `## Mídia gerada` — as quatro afirmações de mídia'
251
+ reason: '`4-review/verifier.md` sem a seção `## Mídia gerada` — as quatro afirmações de mídia'
375
252
  + ' (custo, revisão humana, marcação, hot path) não foram provadas'
376
- + ' (evals/review-checklist.md, "Feature que gera mídia")',
253
+ + ' (evals/review-checklist.md, "Verificador")',
377
254
  };
378
255
  }
379
256
 
380
- // No fecho do DESIGN: o mockup declara telas sem o checklist de tela
381
- // completo. É ele que faz paginação, ordenação, filtro e os três estados
382
- // virarem `doneCriteria` — sem ele, uma lista de 2 000 linhas é aprovada
383
- // listando tudo, e o implementador entrega exatamente o critério.
257
+ // Sem o checklist de tela completo, paginação, ordenação, filtro e os três estados não viram cenário.
384
258
  if (signals.gate === 'uiux') {
385
259
  const reason = uiuxChecklistPause(signals.screenChecklist);
386
260
  if (reason) return { pause: true, reason };
387
261
  }
388
262
 
389
- // No SHIP, depois da cláusula de mídia e pela mesma disciplina: o design
390
- // decidiu itens de tela e o avaliador não registrou a conferência deles
391
- // contra a tela entregue. Cobra-se o item DEVIDO (`uncovered`): tela toda
392
- // "não se aplica", mockup legado ou ausente não devem nada.
263
+ // Cobra-se só o item devido (`uncovered`): tela toda "não se aplica", design legado ou ausente não devem nada.
393
264
  if (signals.gate === 'review' && (signals.screenChecklist?.uncovered?.length ?? 0) > 0) {
394
265
  const { section, uncovered } = signals.screenChecklist;
395
266
  if (section !== true) {
396
267
  return {
397
268
  pause: true,
398
- reason: `relatório do avaliador sem a seção \`## Checklist de tela\` — o design decidiu ${firstOf(uncovered)}`
399
- + ' e a avaliação não registrou a conferência contra a tela entregue'
269
+ reason: `\`4-review/verifier.md\` sem a seção \`## Checklist de tela\` — o design decidiu ${firstOf(uncovered)}`
270
+ + ' e o verificador não registrou a conferência contra a tela entregue'
400
271
  + ' (evals/review-checklist.md, "Contrato de tela")',
401
272
  };
402
273
  }
@@ -407,28 +278,21 @@ export function shouldPauseGate(config, signals) {
407
278
  };
408
279
  }
409
280
 
410
- if (signals.gate === 'plan' && Array.isArray(signals.oversizedTasks) && signals.oversizedTasks.length > 0) {
411
- const list = signals.oversizedTasks.map((t) => `${t.id} (${t.outputs})`).join(', ');
412
- return {
413
- pause: true,
414
- reason: `task grande demais para um dispatch: ${list} outputs — fatie por pós-condição`,
415
- };
281
+ if (signals.gate === 'plan') {
282
+ const reason = planChecksPause(signals.planChecks);
283
+ if (reason) return { pause: true, reason };
416
284
  }
417
285
 
418
- if (typeof signals.taskCount === 'number' && signals.taskCount > SCOPE_TASK_THRESHOLD) {
286
+ if (typeof signals.sliceCount === 'number' && signals.sliceCount > MAX_SLICES) {
419
287
  return {
420
288
  pause: true,
421
- reason: `scope of ${signals.taskCount} tasks exceeds threshold ${SCOPE_TASK_THRESHOLD}`,
289
+ reason: `${signals.sliceCount} fatias, acima de ${MAX_SLICES} — o plano é grande demais para uma feature`,
422
290
  };
423
291
  }
424
292
 
425
293
  return { pause: false, reason: '' };
426
294
  }
427
295
 
428
- /**
429
- * Human-readable list of risk signals present (for logging/Gate summary).
430
- * @returns {string[]}
431
- */
432
296
  export function getRiskSignals(signals) {
433
297
  const out = [];
434
298
  if (FORCED_PAUSE_WORK_TYPES.includes(signals.workType)) out.push('hotfix (gate humano obrigatório)');
@@ -437,25 +301,19 @@ export function getRiskSignals(signals) {
437
301
  out.push(`standards violations: ${signals.standardsViolations.join(', ')}`);
438
302
  }
439
303
  if (signals.verificationStatus === 'fail') out.push('verify vermelho');
440
- // Listado mesmo quando NÃO pausa: uma dispensa que passa silenciosamente é
441
- // indistinguível de uma omissão. A classe diz de quem é a ausência de prova.
304
+ // Listado mesmo quando não pausa: dispensa silenciosa é indistinguível de omissão.
442
305
  if (signals.gate === 'review' && signals.e2eStatus === 'skip') {
443
306
  const klass = classifyE2eSkip(signals.e2eReason);
444
307
  const literal = typeof signals.e2eReason === 'string' && signals.e2eReason.trim() !== ''
445
308
  ? signals.e2eReason.trim()
446
309
  : 'sem motivo registrado';
447
- const where = typeof signals.verificationScope === 'string' && signals.verificationScope.trim() !== ''
448
- ? signals.verificationScope.trim()
449
- : 'escopo ausente';
450
- out.push(`e2e pulado [${klass}] (stamp de ${where}): ${literal}`);
310
+ out.push(`e2e pulado [${klass}]: ${literal}`);
451
311
  }
452
312
  if (signals.gate === 'review' && signals.verificationStale === true) {
453
313
  out.push('verification stamp desatualizado (stale) no ship');
454
314
  }
455
- if (signals.gate === 'review') out.push(verdictSummary(signals.evaluatorVerdict));
456
- // Listado mesmo quando NÃO pausa, pela mesma razão do e2e: um projeto que não
457
- // declara caminho de prompt/modelo é indistinguível, no relatório, de um que
458
- // declara e não tocou em nada — e as duas coisas significam coisas diferentes.
315
+ if (signals.gate === 'review') out.push(reviewSummary(signals.reviewReports));
316
+ // Listado mesmo quando não pausa, pela mesma razão do e2e.
459
317
  if (signals.gate === 'review' && signals.promptModel) {
460
318
  const { declared, touched } = signals.promptModel;
461
319
  if (!declared) {
@@ -464,23 +322,18 @@ export function getRiskSignals(signals) {
464
322
  out.push(`prompt/modelo tocado (${touched.length}) com eval ${signals.evalsStatus ?? 'não declarado'}`);
465
323
  }
466
324
  }
467
- if (signals.gate === 'plan' && Array.isArray(signals.oversizedTasks) && signals.oversizedTasks.length > 0) {
468
- out.push(`tasks grandes demais: ${signals.oversizedTasks.map((t) => t.id).join(', ')}`);
325
+ if (signals.gate === 'plan' && signals.planChecks && signals.planChecks !== 'fresh') {
326
+ out.push(`checks.json ${signals.planChecks}`);
469
327
  }
470
- if (typeof signals.taskCount === 'number' && signals.taskCount > SCOPE_TASK_THRESHOLD) {
471
- out.push(`${signals.taskCount} tasks (over threshold)`);
328
+ if (typeof signals.sliceCount === 'number' && signals.sliceCount > MAX_SLICES) {
329
+ out.push(`${signals.sliceCount} fatias (acima de ${MAX_SLICES})`);
472
330
  }
473
- // Listado mesmo quando NÃO pausa, pela mesma razão do sinal irmão: a isenção
474
- // de uma feature que não gera mídia tem de ser VISÍVEL, senão é
475
- // indistinguível de uma omissão.
476
331
  if (signals.gate === 'review' && signals.mediaClaims?.featureGeneratesMedia === true) {
477
332
  out.push(signals.mediaClaims.section === true
478
333
  ? 'prova de mídia registrada'
479
- : 'relatório sem a seção `## Mídia gerada` (prova de mídia ausente)');
334
+ : 'verifier.md sem a seção `## Mídia gerada` (prova de mídia ausente)');
480
335
  }
481
336
 
482
- // Listado mesmo quando NÃO pausa: a isenção (sem mockup, legado, nada
483
- // devido) e o checklist conferido precisam ser distinguíveis de uma omissão.
484
337
  if ((signals.gate === 'uiux' || signals.gate === 'review') && signals.screenChecklist) {
485
338
  const { mockups, legacy, exempt, screens = [], problems = [], section, uncovered = [] } = signals.screenChecklist;
486
339
  if (!mockups) {
@@ -491,13 +344,18 @@ export function getRiskSignals(signals) {
491
344
  out.push(problems.length > 0
492
345
  ? `checklist de tela incompleto (${problems.length} problema(s))`
493
346
  : `checklist de tela completo (${screens.length} tela(s))`);
347
+ const prototypes = signals.screenChecklist.prototypes ?? [];
348
+ if (screens.length > 0) {
349
+ out.push(prototypes.length > 0
350
+ ? `protótipo incompleto em ${prototypes.length} tela(s)`
351
+ : `protótipo e prints 1280/390 presentes (${screens.length} tela(s))`);
352
+ }
494
353
  } else if (uncovered.length > 0) {
495
354
  out.push(section !== true
496
- ? 'relatório sem a seção `## Checklist de tela`'
355
+ ? 'verifier.md sem a seção `## Checklist de tela`'
497
356
  : `checklist de tela sem conferência em ${uncovered.length} tela(s)`);
498
357
  } else if (screens.length === 0) {
499
- // O design passou pela uiux sem tela nenhuma: nada a cobrar no ship, mas
500
- // não pode ler igual a uma tela toda "não se aplica".
358
+ // Sem tela nenhuma: nada a cobrar no ship, mas não pode ler igual a uma tela toda "não se aplica".
501
359
  out.push(`checklist de tela: ${firstOf(problems)}`);
502
360
  } else if (section === true) {
503
361
  out.push(`checklist de tela conferido (${screens.length} tela(s))`);
@@ -510,7 +368,6 @@ export function getRiskSignals(signals) {
510
368
  }
511
369
 
512
370
  export const RISK_CONSTANTS = {
513
- SCOPE_TASK_THRESHOLD,
514
371
  FORCED_PAUSE_WORK_TYPES,
515
372
  E2E_SKIP_CLASSES_THAT_PAUSE,
516
373
  };