@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
@@ -0,0 +1,204 @@
1
+ import { tableCells, isTableLine, SEPARATOR_ROW_RE } from '../specs/gfm-table.js';
2
+ import { ARTIFACT_MARKER_RE } from './artifact-vocabulary.js';
3
+ import { markdownLines } from '../../../framework/hooks/shared/errata.js';
4
+
5
+ export const PROOF_KINDS = ['teste', 'e2e', 'eval', 'manual'];
6
+ export const REUSE_VERDICTS = ['REUSAR', 'ESTENDER', 'CRIAR'];
7
+ export const ADR_SCOPES = ['feature', 'projeto'];
8
+
9
+ export const SECTIONS = {
10
+ problem: 'Problema',
11
+ outOfScope: 'Fora de escopo',
12
+ assumptions: 'Suposições não confirmadas',
13
+ scenarios: 'Cenários',
14
+ decisions: 'Decisões',
15
+ architecture: 'Arquitetura',
16
+ reuse: 'Reuso',
17
+ slices: 'Fatias',
18
+ contract: 'Contrato de implementação',
19
+ errata: 'Errata',
20
+ };
21
+
22
+ export const CONFLICTS_HEADING = 'Conflitos com o acervo';
23
+
24
+ const SLICE_KEYS = {
25
+ persona: 'persona',
26
+ 'superfície': 'surface',
27
+ superficie: 'surface',
28
+ standards: 'standards',
29
+ arquivos: 'files',
30
+ depende: 'depends',
31
+ cobre: 'covers',
32
+ };
33
+
34
+ const DASH = String.raw`\s+[—–-]\s+`;
35
+ const SCENARIO_RE = new RegExp(String.raw`^[-*]\s+\*\*(C\d+)\*\*${DASH}(.+)$`);
36
+ const ADR_RE = new RegExp(String.raw`^###\s+(ADR-\d+)${DASH}(.+?)\s*$`);
37
+ const SLICE_RE = new RegExp(String.raw`^###\s+(F\d+)${DASH}(.+?)\s*$`);
38
+ const PROOF_RE = /^\s*prova\s*:\s*(.*?)\s*$/i;
39
+ const WHY_RE = new RegExp(String.raw`^\s*>\s*` + ARTIFACT_MARKER_RE.source + String.raw`\s*(.*?)\s*$`, 'i');
40
+ const SCOPE_RE = /^\s*\*\*Alcance:\*\*\s*(.*?)\s*$/i;
41
+ const SLICE_LINE_RE = /^\s*[-*]\s+([\p{L}]+)\s*:\s*(.*?)\s*$/u;
42
+
43
+ const fold = (s) => s.normalize('NFD').replace(/\p{M}/gu, '').toLowerCase().trim();
44
+
45
+ const listOf = (value) => value
46
+ .split(',')
47
+ .map((v) => v.replace(/`/g, '').trim())
48
+ .filter((v) => v && !/^[—–-]$/.test(v) && fold(v) !== 'nenhum' && fold(v) !== 'nenhuma');
49
+
50
+ function splitSections(lines) {
51
+ const sections = [];
52
+ let current = null;
53
+ for (const line of lines) {
54
+ const h2 = !line.fenced && /^##\s+(.+?)\s*$/.exec(line.raw);
55
+ if (h2 && !line.raw.startsWith('###')) {
56
+ current = { name: h2[1], line: line.n, lines: [] };
57
+ sections.push(current);
58
+ continue;
59
+ }
60
+ if (current) current.lines.push(line);
61
+ }
62
+ return sections;
63
+ }
64
+
65
+ function parseScenarios(lines) {
66
+ const scenarios = [];
67
+ let current = null;
68
+ for (const line of lines) {
69
+ if (line.fenced) { current = null; continue; }
70
+ const m = SCENARIO_RE.exec(line.raw);
71
+ if (m) {
72
+ current = { id: m[1], text: m[2].trim(), proof: null, artifactWhy: null, line: line.n };
73
+ scenarios.push(current);
74
+ continue;
75
+ }
76
+ if (!current) continue;
77
+ if (/^\s*$/.test(line.raw) || /^#/.test(line.raw) || /^[-*]\s/.test(line.raw)) { current = null; continue; }
78
+ const why = WHY_RE.exec(line.raw);
79
+ if (why) { current.artifactWhy = why[1] || ''; continue; }
80
+ const proof = PROOF_RE.exec(line.raw);
81
+ if (proof && current.proof === null) { current.proof = proof[1]; continue; }
82
+ if (/^\s*>/.test(line.raw)) continue;
83
+ current.text = `${current.text} ${line.raw.trim()}`;
84
+ }
85
+ return scenarios;
86
+ }
87
+
88
+ function parseDecisions(lines) {
89
+ const adrs = [];
90
+ let conflicts = null;
91
+ let current = null;
92
+ for (const line of lines) {
93
+ if (line.fenced) {
94
+ if (current?.kind === 'conflicts') conflicts.body.push(line.raw);
95
+ else if (current?.kind === 'adr') current.entry.body.push(line.raw);
96
+ continue;
97
+ }
98
+ const adr = ADR_RE.exec(line.raw);
99
+ if (adr) {
100
+ current = { kind: 'adr', entry: { id: adr[1], title: adr[2], scope: null, body: [], line: line.n } };
101
+ adrs.push(current.entry);
102
+ continue;
103
+ }
104
+ const h3 = /^###\s+(.+?)\s*$/.exec(line.raw);
105
+ if (h3) {
106
+ if (fold(h3[1]) === fold(CONFLICTS_HEADING)) {
107
+ conflicts = { line: line.n, body: [] };
108
+ current = { kind: 'conflicts' };
109
+ } else {
110
+ current = null;
111
+ }
112
+ continue;
113
+ }
114
+ if (current?.kind === 'adr') {
115
+ current.entry.body.push(line.raw);
116
+ const scope = current.entry.scope === null && SCOPE_RE.exec(line.raw);
117
+ if (scope) current.entry.scope = scope[1].replace(/`/g, '').trim().toLowerCase();
118
+ } else if (current?.kind === 'conflicts') {
119
+ conflicts.body.push(line.raw);
120
+ }
121
+ }
122
+ for (const a of adrs) a.body = a.body.join('\n').trim();
123
+ return {
124
+ adrs,
125
+ conflicts: conflicts && { line: conflicts.line, text: conflicts.body.join('\n').trim() },
126
+ };
127
+ }
128
+
129
+ function parseReuse(lines) {
130
+ const rows = [];
131
+ let header = null;
132
+ for (const line of lines) {
133
+ if (line.fenced || !isTableLine(line.raw) || SEPARATOR_ROW_RE.test(line.raw)) continue;
134
+ const cells = tableCells(line.raw);
135
+ if (!header) { header = cells.map(fold); continue; }
136
+ const at = (name) => cells[header.indexOf(name)] ?? '';
137
+ rows.push({
138
+ concept: at('conceito'),
139
+ verdict: at('veredito').replace(/`|\*/g, '').trim().toUpperCase(),
140
+ target: at('alvo'),
141
+ why: at('why'),
142
+ line: line.n,
143
+ });
144
+ }
145
+ return { header, rows };
146
+ }
147
+
148
+ function parseSlices(lines) {
149
+ const slices = [];
150
+ let current = null;
151
+ for (const line of lines) {
152
+ if (line.fenced) continue;
153
+ const m = SLICE_RE.exec(line.raw);
154
+ if (m) {
155
+ current = {
156
+ id: m[1], title: m[2], persona: null, surface: null,
157
+ standards: [], files: [], depends: [], covers: [], unknownKeys: [], line: line.n,
158
+ };
159
+ slices.push(current);
160
+ continue;
161
+ }
162
+ if (/^###\s/.test(line.raw)) { current = null; continue; }
163
+ if (!current) continue;
164
+ const kv = SLICE_LINE_RE.exec(line.raw);
165
+ if (!kv) continue;
166
+ const key = SLICE_KEYS[fold(kv[1])] ?? SLICE_KEYS[kv[1].toLowerCase()];
167
+ if (!key) { current.unknownKeys.push(kv[1]); continue; }
168
+ if (key === 'persona' || key === 'surface') {
169
+ const value = kv[2].replace(/`/g, '').trim();
170
+ current[key] = value && !/^[—–-]$/.test(value) ? value : null;
171
+ } else {
172
+ current[key] = listOf(kv[2]);
173
+ }
174
+ }
175
+ return slices;
176
+ }
177
+
178
+ /** Pure: no validation, no I/O; `checks.js` judges what this returns. */
179
+ export function parsePlan(text) {
180
+ const lines = markdownLines(text);
181
+ const sections = splitSections(lines);
182
+ const byName = new Map();
183
+ for (const s of sections) {
184
+ const key = Object.keys(SECTIONS).find((k) => fold(SECTIONS[k]) === fold(s.name));
185
+ if (key && !byName.has(key)) byName.set(key, s);
186
+ }
187
+ const body = (key) => byName.get(key)?.lines ?? [];
188
+ const decisions = parseDecisions(body('decisions'));
189
+ const reuse = parseReuse(body('reuse'));
190
+ const errata = byName.get('errata');
191
+
192
+ return {
193
+ sections: sections.map((s) => ({ name: s.name, line: s.line, key: [...byName].find(([, v]) => v === s)?.[0] ?? null })),
194
+ present: Object.fromEntries(Object.keys(SECTIONS).map((k) => [k, byName.has(k)])),
195
+ scenarios: parseScenarios(body('scenarios')),
196
+ adrs: decisions.adrs,
197
+ conflicts: decisions.conflicts,
198
+ reuse: reuse.rows,
199
+ reuseHeader: reuse.header,
200
+ slices: parseSlices(body('slices')),
201
+ errata: errata ? errata.lines.map((l) => l.raw).join('\n').trim() : null,
202
+ sectionsAfterErrata: errata ? sections.filter((s) => s.line > errata.line).map((s) => s.name) : [],
203
+ };
204
+ }
@@ -0,0 +1,18 @@
1
+ import { readFileSync } from 'fs';
2
+ import { templateSourcePath, writeIfAbsent } from '../../core/templates/template-seed.js';
3
+ import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
4
+
5
+ const FEATURE_PLACEHOLDER = /\{\{FEATURE_NAME\}\}/g;
6
+
7
+ export function planTemplatePath(cwd = process.cwd()) {
8
+ return templateSourcePath('plan', cwd);
9
+ }
10
+
11
+ export function renderPlanSkeleton(featureName, cwd = process.cwd()) {
12
+ return readFileSync(planTemplatePath(cwd), 'utf8').replace(FEATURE_PLACEHOLDER, featureName);
13
+ }
14
+
15
+ /** @returns {{ path: string, written: boolean }} `written: false` when the file already existed. */
16
+ export function writePlanSkeleton(featureName, cwd = process.cwd()) {
17
+ return writeIfAbsent(getAbsoluteOutputPath(cwd, featureName, 'plan'), renderPlanSkeleton(featureName, cwd));
18
+ }
@@ -1,19 +1,5 @@
1
- /**
2
- * Where the morph-spec projects on this machine are — the list the dashboard's
3
- * portfolio reads. Nothing else records it: each project only knows its own
4
- * `.morph/`.
5
- *
6
- * Three writers register a project as a side note of what they already do —
7
- * `init`, `update` (never under `--dry-run`) and `dashboard` — and the
8
- * dashboard adds and removes on request. Registering is fail-open: a registry
9
- * that cannot be read or written never fails the command that tried.
10
- *
11
- * The file lives in the user's home (`~/.morph-spec/projects.json`, or
12
- * `MORPH_PROJECTS_REGISTRY`), read through `os.homedir()` at call time so a
13
- * suite that isolates the home also isolates this file. Paths under the
14
- * system temp dir are never registered: the test suites run `init`/`update`
15
- * there by the hundred, including through spawned CLIs that keep the real home.
16
- */
1
+ // Paths under the system temp dir are never registered: test suites run `init`/`update` there by the
2
+ // hundred, through spawned CLIs that keep the real home. The home is read at call time so isolating it works.
17
3
 
18
4
  import { existsSync, readFileSync, readdirSync } from 'fs';
19
5
  import { homedir, tmpdir } from 'os';
@@ -35,7 +21,6 @@ export function projectKey(dir) {
35
21
 
36
22
  const sameDir = (a, b) => (process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b);
37
23
 
38
- /** A folder is a morph-spec project when morph-spec installed its config there. */
39
24
  export function isMorphProject(dir) {
40
25
  return existsSync(join(dir, '.morph', 'config', 'config.json'));
41
26
  }
@@ -61,18 +46,13 @@ function projectName(dir) {
61
46
  }
62
47
  }
63
48
 
64
- /**
65
- * The registered projects that still exist. A folder that was deleted or lost
66
- * its `.morph/` drops out on read — the file is rewritten on the next write.
67
- *
68
- * @returns {{ version: number, projects: Array<{ dir: string, name: string, addedAt: string, lastSeenAt: string, source: string }> }}
69
- */
49
+ /** Folders that were deleted or lost `.morph/` drop out on read. */
70
50
  export function readRegistry() {
71
51
  let data = null;
72
52
  try {
73
53
  const path = registryPath();
74
54
  if (existsSync(path)) data = JSON.parse(readFileSync(path, 'utf8'));
75
- } catch { /* unreadable registry reads as empty */ }
55
+ } catch {}
76
56
  const projects = Array.isArray(data?.projects) ? data.projects : [];
77
57
  return {
78
58
  version: REGISTRY_VERSION,
@@ -84,16 +64,7 @@ function write(projects) {
84
64
  atomicWriteJson(registryPath(), { version: REGISTRY_VERSION, projects });
85
65
  }
86
66
 
87
- /**
88
- * Note that `dir` is a morph-spec project. Idempotent; refreshes `lastSeenAt`.
89
- * Never throws.
90
- *
91
- * @param {string} dir
92
- * @param {'init'|'update'|'dashboard'|'manual'} source
93
- * @param {{ tempRoot?: string }} [opts] - test seam: the folder whose
94
- * projects are never registered (the system temp dir)
95
- * @returns {boolean} true when the project is in the registry afterwards
96
- */
67
+ /** Idempotent, never throws; `tempRoot` is the test seam for the never-registered folder. */
97
68
  export function registerProject(dir, source, { tempRoot = tmpdir() } = {}) {
98
69
  try {
99
70
  const key = projectKey(dir);
@@ -115,7 +86,7 @@ export function registerProject(dir, source, { tempRoot = tmpdir() } = {}) {
115
86
  }
116
87
  }
117
88
 
118
- /** Take a project off the list. Its files are not touched. Never throws. */
89
+ /** Never throws; the project's files are not touched. */
119
90
  export function unregisterProject(dir) {
120
91
  try {
121
92
  const key = projectKey(dir);
@@ -128,15 +99,7 @@ export function unregisterProject(dir) {
128
99
  }
129
100
  }
130
101
 
131
- /**
132
- * morph-spec projects directly under each root that are not registered yet —
133
- * the portfolio offers them, the person decides. One level deep: a repos
134
- * folder is the expected root, and a deeper walk would enter node_modules and
135
- * worktrees for nothing.
136
- *
137
- * @param {string[]} roots
138
- * @returns {Array<{ dir: string, name: string }>}
139
- */
102
+ /** Unregistered projects one level under each root: deeper would walk node_modules and worktrees. */
140
103
  export function discoverProjects(roots) {
141
104
  const registered = readRegistry().projects.map((p) => p.dir);
142
105
  const out = [];
@@ -0,0 +1,204 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { OUTPUT_SCHEMA, getAbsoluteOutputPath } from '../core/paths/output-schema.js';
4
+ import { PARTIAL_MARKER } from '../../framework/hooks/shared/slice-progress.js';
5
+
6
+ export const VERDICT_TOKEN = '**Verdict:**';
7
+ export const REVIEWED_SHA_TOKEN = '**Reviewed-SHA:**';
8
+ export const VERDICTS = ['SHIP', 'FIX'];
9
+ export const BLOCKER_TYPES = ['check', 'teste', 'regressao', 'seguranca', 'escopo', 'arquitetura', 'performance'];
10
+ export const CHECK_STATUSES = ['PROVADO', 'NÃO PROVADO', 'FALHA'];
11
+
12
+ export const REVIEW_ROLES = {
13
+ verifier: { output: 'verifierReport', persona: 'morph-verifier' },
14
+ reviewer: { output: 'reviewerReport', persona: 'morph-reviewer' },
15
+ security: { output: 'securityReport', persona: 'morph-security' },
16
+ };
17
+ const ROLE_ORDER = Object.keys(REVIEW_ROLES);
18
+
19
+ export const reviewFileName = (role) => OUTPUT_SCHEMA[REVIEW_ROLES[role].output].filename;
20
+
21
+ // Append-only: uma reavaliação acrescenta ao fim, e vale o ÚLTIMO token/seção.
22
+ const VERDICT_RE = /\*\*Verdict:\*\*\s*([A-Za-z]+)/g;
23
+ const REVIEWED_SHA_RE = /\*\*Reviewed-SHA:\*\*\s*`?([0-9a-f]{7,40})/gi;
24
+ const sectionRe = (title) => new RegExp(`^##\\s+${title}\\s*$`, 'gim');
25
+ const BLOCKERS_SECTION_RE = sectionRe('Bloqueadores');
26
+ const CHECKS_SECTION_RE = sectionRe('Checks');
27
+ const MEDIA_SECTION_RE = /^##\s+M[íi]dia\s+gerada\s*$/m;
28
+ const SCREEN_CHECKLIST_SECTION_RE = sectionRe('Checklist\\s+de\\s+tela');
29
+
30
+ /** Hotfix: só o verificador, escrito pelo orquestrador sem dispatch. */
31
+ export function requiredReviewRoles({ workType, securityRequired = false } = {}) {
32
+ if (workType === 'hotfix') return ['verifier'];
33
+ return securityRequired ? ['verifier', 'reviewer', 'security'] : ['verifier', 'reviewer'];
34
+ }
35
+
36
+ function sectionBodies(text, re) {
37
+ return [...text.matchAll(re)].map((m) => {
38
+ const rest = text.slice(m.index + m[0].length);
39
+ const next = rest.search(/^#{1,2}\s/m);
40
+ return next === -1 ? rest : rest.slice(0, next);
41
+ });
42
+ }
43
+
44
+ function lastSectionBody(text, re) {
45
+ const bodies = sectionBodies(text, re);
46
+ return bodies.length ? bodies[bodies.length - 1] : null;
47
+ }
48
+
49
+ const normalize = (s) => String(s ?? '').toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/\s+/g, ' ').trim();
50
+ const cleanId = (s) => String(s ?? '').replace(/[*`_]/g, '').trim().toUpperCase();
51
+
52
+ function tableRows(body) {
53
+ const rows = [];
54
+ for (const line of body.split(/\r?\n/)) {
55
+ if (!/^\s*\|/.test(line)) continue;
56
+ const cells = line.trim().replace(/^\||\|$/g, '').split('|').map((c) => c.trim());
57
+ if (cells.every((c) => /^:?-{2,}:?$/.test(c) || c === '')) continue;
58
+ rows.push(cells);
59
+ }
60
+ return rows;
61
+ }
62
+
63
+ // `| id | tipo | onde | evidência |`; `untyped` = tipo fora de BLOCKER_TYPES.
64
+ function parseBlockers(body) {
65
+ const blockers = [];
66
+ const untyped = [];
67
+ for (const cells of tableRows(body)) {
68
+ if (/^#$|^id$/i.test(cells[0]) && /^tipo$/i.test(cells[1] ?? '')) continue;
69
+ const row = { id: cleanId(cells[0]), type: normalize(cells[1]), where: cells[2] ?? '', evidence: cells[3] ?? '' };
70
+ (BLOCKER_TYPES.includes(row.type) ? blockers : untyped).push(row);
71
+ }
72
+ return { blockers, untyped };
73
+ }
74
+
75
+ const STATUS_BY_KEY = Object.fromEntries(CHECK_STATUSES.map((s) => [normalize(s), s]));
76
+
77
+ // Vale a última linha de cada Cn em todas as seções: uma confirmação traz só os Cn que mudou.
78
+ function parseChecks(bodies) {
79
+ const byId = new Map();
80
+ for (const cells of bodies.flatMap(tableRows)) {
81
+ const id = cleanId(cells[0]);
82
+ if (!/^C\d+$/.test(id)) continue;
83
+ const raw = String(cells[1] ?? '').replace(/[*`]/g, '').trim();
84
+ byId.delete(id);
85
+ byId.set(id, { id, status: STATUS_BY_KEY[normalize(raw)] ?? raw, evidence: cells[2] ?? '' });
86
+ }
87
+ return [...byId.values()];
88
+ }
89
+
90
+ /** Um relatório de review lido do texto. `checks` é `null` sem a seção `## Checks`. */
91
+ export function parseReviewReport(text) {
92
+ if (typeof text !== 'string') {
93
+ return { partial: false, verdict: undefined, blockers: [], untyped: [], superseded: [], checks: null, reviewedSha: null };
94
+ }
95
+ const firstLine = text.split(/\r?\n/).find((l) => l.trim() !== '') ?? '';
96
+ const verdicts = [...text.matchAll(VERDICT_RE)].map((m) => m[1].toUpperCase());
97
+ const blockersBody = lastSectionBody(text, BLOCKERS_SECTION_RE);
98
+ const checksBodies = sectionBodies(text, CHECKS_SECTION_RE);
99
+ const shas = [...text.matchAll(REVIEWED_SHA_RE)].map((m) => m[1]);
100
+ return {
101
+ partial: PARTIAL_MARKER.test(firstLine),
102
+ verdict: verdicts.length ? verdicts[verdicts.length - 1] : undefined,
103
+ ...(blockersBody === null ? { blockers: [], untyped: [] } : parseBlockers(blockersBody)),
104
+ superseded: verdicts.slice(0, -1),
105
+ checks: checksBodies.length === 0 ? null : parseChecks(checksBodies),
106
+ reviewedSha: shas.length ? shas[shas.length - 1] : null,
107
+ };
108
+ }
109
+
110
+ // `partial`: `PARCIAL` na 1ª linha, com ou sem veredito. `ok` só diz legível: quem decide é o risk-detector.
111
+ function stateOf(path, text) {
112
+ if (text === null) return { status: 'missing', path };
113
+ const parsed = parseReviewReport(text);
114
+ if (parsed.partial) return { status: 'partial', path, ...parsed };
115
+ if (!VERDICTS.includes(parsed.verdict)) return { status: 'unverdicted', path, ...parsed };
116
+ return { status: 'ok', path, ...parsed };
117
+ }
118
+
119
+ function readText(path) {
120
+ try {
121
+ return existsSync(path) ? readFileSync(path, 'utf8') : null;
122
+ } catch {
123
+ return null;
124
+ }
125
+ }
126
+
127
+ /** Os três relatórios a partir do diretório da feature — sem git; o dashboard lê árvores arquivadas. */
128
+ export function reviewReportsFromDir(featureDir) {
129
+ const out = {};
130
+ for (const role of ROLE_ORDER) {
131
+ const path = join(featureDir, '4-review', reviewFileName(role));
132
+ out[role] = stateOf(path, readText(path));
133
+ }
134
+ return out;
135
+ }
136
+
137
+ export function reviewReportPath(cwd, featureName, role) {
138
+ return getAbsoluteOutputPath(cwd, featureName, REVIEW_ROLES[role].output);
139
+ }
140
+
141
+ export function reviewReports(cwd, featureName) {
142
+ return reviewReportsFromDir(join(cwd, '.morph', 'features', featureName));
143
+ }
144
+
145
+ /** Todo Cn precisa de uma linha, e só `PROVADO` cobre. */
146
+ export function checksCoverage(checkIds, rows) {
147
+ const byId = new Map((rows ?? []).map((r) => [r.id, r]));
148
+ const missing = [];
149
+ const notProven = [];
150
+ for (const id of checkIds.map(cleanId)) {
151
+ const row = byId.get(id);
152
+ if (!row) missing.push(id);
153
+ else if (row.status !== 'PROVADO') notProven.push({ id, status: row.status || 'vazio' });
154
+ }
155
+ return { missing, notProven };
156
+ }
157
+
158
+ /** O sinal que `reviewPause` lê; `checkIds: null` = cobertura não conferível. */
159
+ export function reviewSignal(reports, { required, checkIds }) {
160
+ const out = { required, reports: {} };
161
+ for (const role of ROLE_ORDER) {
162
+ const r = reports[role];
163
+ if (!required.includes(role) && r.status === 'missing') continue;
164
+ out.reports[role] = r.status === 'missing'
165
+ ? { status: 'missing' }
166
+ : {
167
+ status: r.status,
168
+ verdict: r.verdict ?? null,
169
+ blockers: r.blockers.length,
170
+ blockerIds: r.blockers.map((b) => b.id),
171
+ untyped: r.untyped.length,
172
+ superseded: r.superseded,
173
+ };
174
+ }
175
+ out.checks = checkIds === null
176
+ ? { status: 'no-checks', missing: [], notProven: [] }
177
+ : { status: 'ok', total: checkIds.length, ...checksCoverage(checkIds, reports.verifier.checks) };
178
+ return out;
179
+ }
180
+
181
+ /**
182
+ * Veredito combinado para leitura (dashboard, list): FIX se qualquer relatório
183
+ * presente diz FIX ou tem bloqueador; SHIP só com todo exigido em SHIP.
184
+ */
185
+ export function combinedVerdict(reports, required) {
186
+ const present = ROLE_ORDER.filter((r) => reports[r].status !== 'missing');
187
+ if (present.some((r) => reports[r].verdict === 'FIX' || reports[r].blockers.length > 0)) return 'FIX';
188
+ return required.every((r) => reports[r].status === 'ok' && reports[r].verdict === 'SHIP') ? 'SHIP' : null;
189
+ }
190
+
191
+ export function allBlockers(reports) {
192
+ return ROLE_ORDER.flatMap((role) => (reports[role].blockers ?? []).map((b) => ({ ...b, role })));
193
+ }
194
+
195
+ export function verifierMediaSection(cwd, featureName) {
196
+ const text = readText(reviewReportPath(cwd, featureName, 'verifier'));
197
+ return text !== null && MEDIA_SECTION_RE.test(text);
198
+ }
199
+
200
+ export function verifierScreenChecklist(cwd, featureName) {
201
+ const text = readText(reviewReportPath(cwd, featureName, 'verifier'));
202
+ const body = text === null ? null : lastSectionBody(text, SCREEN_CHECKLIST_SECTION_RE);
203
+ return body === null ? { section: false, body: '' } : { section: true, body };
204
+ }