@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,361 +1,179 @@
1
- /**
2
- * `morph-spec report-check <feature> [task] [--json]` — the deterministic half
3
- * of the missing-report protocol (B4).
4
- *
5
- * MORPH.md already made the sub-agent's canonical report file the authoritative
6
- * return value ("o ARQUIVO é o retorno, a última mensagem é cortesia"), but it
7
- * never said what to do when the file is not there. The naive reading — "no
8
- * report, so the agent failed" — is wrong and expensive: the agent may have
9
- * done every bit of the work and lost the means to record it (observed: a
10
- * worktree removed out from under a running sub-agent).
11
- *
12
- * So this command answers the question the orchestrator actually has, and
13
- * answers it from evidence rather than from the agent's own word: **did the
14
- * WORK land, independently of whether the REPORT did?**
15
- *
16
- * verdict action
17
- * ───────────────────────────── ──────────────────
18
- * ok proceed
19
- * report-incomplete complete-degraded
20
- * work-landed-report-missing accept-degraded
21
- * no-work-no-report redispatch
22
- * report-missing-evidence-unknown redispatch (conservative default)
23
- *
24
- * Exit 0 only for `ok` — anything needing an action exits non-zero so a caller
25
- * can branch on it. `--json` always prints the machine-readable verdict.
26
- *
27
- * The output also carries `tree` (see tree-state.js): the working tree the
28
- * orchestrator would otherwise ask git for, call by call, right after the same
29
- * dispatch. It is information only and never changes the verdict.
30
- */
1
+ // Judged from observed evidence, never the agent's word: did the WORK land, independently of
2
+ // the REPORT? The `tree` block is information only and never changes the verdict.
31
3
 
32
4
  import chalk from 'chalk';
33
5
  import { existsSync, readFileSync, statSync } from 'fs';
34
6
  import { join } from 'path';
35
- import { readTelemetryEvents, isFailedToolCall } from '../../../framework/hooks/shared/telemetry-logger.js';
7
+ import { isFailedToolCall } from '../../../framework/hooks/shared/telemetry-logger.js';
8
+ import { sliceReportPath, inspectReport, REPORT_MARKERS } from '../../../framework/hooks/shared/slice-progress.js';
9
+ import { loadTelemetryAllTrees, dedupeEvents } from '../../lib/telemetry/telemetry-reader.js';
36
10
  import { treeState, snapshotTree } from '../../lib/git/tree-state.js';
37
11
  import { atomicWriteJson } from '../../core/state/state-manager.js';
38
12
  import { minimatch } from 'minimatch';
39
- import { evaluatorVerdictFromText, VERDICTS } from '../../lib/evaluator-report.js';
40
- import { evaluatorModelFor } from '../../lib/risk-detector.js';
13
+ import { reviewReports, reviewFileName } from '../../lib/review-reports.js';
14
+ import { verifierModelFor } from '../../lib/risk-detector.js';
41
15
  import { changedPathsAgainst, detectDefaultBranch } from '../../lib/git/default-branch.js';
42
16
  import { readMorphConfig } from '../../lib/tasks/test-runner.js';
43
- import { featureGeneratesMedia } from './gate-check.js';
44
-
45
- /**
46
- * The mandatory items of the report contract, as detectable markers.
47
- *
48
- * HEURISTIC BY CONSTRUCTION, and deliberately loose: the contract asks for
49
- * content, not for a fixed template, and reports written before this command
50
- * existed must still audit cleanly. A false "complete" is the cheap error here
51
- * (the orchestrator reads the report anyway); a false "incomplete" would send
52
- * it re-dispatching finished work, which is the expensive one.
53
- */
54
- const REPORT_MARKERS = [
55
- // (2) files touched — a list line carrying a path or a backticked identifier
56
- { id: 'files', re: /^\s*[-*]\s+.*[`/\\].*$/m },
57
- // (3) each doneCriteria, met or not, with its evidence
58
- { id: 'criteria', re: /crit[ée]rio|doneCriteria|p[óo]s-condi[çc][ãa]o|atendid/i },
59
- ];
60
-
61
- /** (1) "what was done" — prose beyond the structural markers. */
62
- const MIN_PROSE_CHARS = 80;
63
-
64
- /**
65
- * The partial-report marker of the dispatch contract (clause 5, ADR-004): a
66
- * sub-agent nearing its `maxTurns` ceiling writes `PARCIAL` on the FIRST line.
67
- * Tolerates heading/bold/quote typography (`# PARCIAL`, `**PARCIAL**`).
68
- */
69
- const PARTIAL_MARKER = /^[\s#>*_]*PARCIAL\b/;
70
-
71
- function reportPath(cwd, feature, taskId) {
72
- return join(cwd, '.morph', 'features', feature, '3-implement', 'reports', `${taskId}.md`);
73
- }
74
-
75
- /**
76
- * `.morph/logs/task-times.json` — `{ [feature]: { [taskId]: {status,start,end} } }`,
77
- * escrito pelo hook `trace-autogen` nas transições de status da task. É a
78
- * janela temporal de cada task; sem ela não há como recortar quais writes do
79
- * `events.jsonl` pertencem a qual task.
80
- */
81
- function readTaskTimes(cwd) {
82
- try {
83
- const p = join(cwd, '.morph', 'logs', 'task-times.json');
84
- if (!existsSync(p)) return null;
85
- return JSON.parse(readFileSync(p, 'utf8'));
86
- } catch {
87
- return null;
88
- }
89
- }
90
-
91
- function evaluatorPath(cwd, feature) {
92
- return join(cwd, '.morph', 'features', feature, '4-review', 'evaluator-report.md');
93
- }
94
-
95
- /**
96
- * Inspect one report file: does it exist, and which of the four mandatory
97
- * items can be found in it.
98
- */
99
- export function inspectReport(path) {
100
- if (!existsSync(path)) return { exists: false, complete: false, missing: ['what', 'files', 'criteria'] };
101
- let body = '';
102
- try {
103
- body = readFileSync(path, 'utf8');
104
- } catch {
105
- return { exists: true, complete: false, missing: ['what', 'files', 'criteria'], unreadable: true };
17
+ import { readPlanChecks } from '../../lib/plan/checks.js';
18
+ import { featureGeneratesMedia, collectReviewRequirement } from './gate-check.js';
19
+
20
+ export { inspectReport, REPORT_MARKERS };
21
+
22
+ /** Each review report: complete when it carries a readable verdict and is not partial. */
23
+ export function inspectReviewReports(cwd, feature) {
24
+ const out = {};
25
+ for (const [role, r] of Object.entries(reviewReports(cwd, feature))) {
26
+ out[role] = r.status === 'missing'
27
+ ? { exists: false, complete: false, partial: false, verdict: null }
28
+ : { exists: true, complete: r.status === 'ok', partial: r.status === 'partial', verdict: r.verdict ?? null };
106
29
  }
107
-
108
- const missing = REPORT_MARKERS.filter((m) => !m.re.test(body)).map((m) => m.id);
109
- // Prose = the body minus headings and list lines, so a bare file list does
110
- // not pass as "what was done".
111
- const prose = body
112
- .split('\n')
113
- .filter((l) => !/^\s*(#|[-*]\s)/.test(l))
114
- .join(' ')
115
- .replace(/\s+/g, ' ')
116
- .trim();
117
- if (prose.length < MIN_PROSE_CHARS) missing.unshift('what');
118
-
119
- const firstLine = body.split('\n').find((l) => l.trim() !== '') ?? '';
120
- const partial = PARTIAL_MARKER.test(firstLine);
121
- if (partial) missing.push('partial');
122
-
123
- return { exists: true, complete: missing.length === 0, missing, partial };
124
- }
125
-
126
- /** The evaluator report is complete when it carries a readable verdict and is not partial. */
127
- export function inspectEvaluatorReport(path) {
128
- if (!existsSync(path)) return { exists: false, complete: false };
129
- let body = '';
130
- try { body = readFileSync(path, 'utf8'); } catch { return { exists: true, complete: false, unreadable: true }; }
131
- const firstLine = body.split('\n').find((l) => l.trim() !== '') ?? '';
132
- const partial = PARTIAL_MARKER.test(firstLine);
133
- const { verdict } = evaluatorVerdictFromText(body);
134
- return { exists: true, complete: !partial && VERDICTS.includes(verdict), partial, verdict: verdict ?? null };
30
+ return out;
135
31
  }
136
32
 
137
- function evaluatorDispatch(cwd, tasks) {
33
+ // The verifier routes to opus on prompt/model or media; security is dispatched only when required.
34
+ function reviewDispatch(cwd, feature, slices) {
138
35
  let changedFiles = [];
139
36
  try { changedFiles = changedPathsAgainst(detectDefaultBranch(cwd), cwd); } catch { changedFiles = []; }
140
37
  const watch = readMorphConfig(cwd)?.project?.evals?.watch;
141
38
  const globs = Array.isArray(watch) ? watch.filter((g) => typeof g === 'string' && g.trim()) : [];
142
- return evaluatorModelFor({
143
- changedFiles,
144
- promptModelTouched: changedFiles.some((f) => globs.some((g) => minimatch(f, g))),
145
- generatesMedia: featureGeneratesMedia(tasks),
146
- });
147
- }
148
-
149
- /** How many of a task's declared outputs actually exist on disk. */
150
- function outputEvidence(cwd, taskOutputs) {
151
- const expected = Array.isArray(taskOutputs) ? taskOutputs : [];
152
- const present = expected.filter((rel) => {
153
- try {
154
- return existsSync(join(cwd, rel));
155
- } catch {
156
- return false;
157
- }
158
- }).length;
159
- return { outputsExpected: expected.length, outputsPresent: present };
39
+ let workType;
40
+ try { workType = JSON.parse(readFileSync(join(cwd, '.morph', 'features', feature, 'feature.json'), 'utf8')).workType; } catch { workType = undefined; }
41
+ const { required, security } = collectReviewRequirement(cwd, { workType });
42
+ return {
43
+ required,
44
+ verifier: verifierModelFor({
45
+ promptModelTouched: changedFiles.some((f) => globs.some((g) => minimatch(f, g))),
46
+ generatesMedia: featureGeneratesMedia(slices),
47
+ }),
48
+ reviewer: { model: 'sonnet', reasons: [] },
49
+ security: { required: security.required, model: 'opus', reasons: security.reasons },
50
+ };
160
51
  }
161
52
 
162
- /**
163
- * Folga entre o `mtime` do relatório e o `ts` do último write observado.
164
- *
165
- * São DOIS RELÓGIOS: `mtime` vem do filesystem, `ts` vem de
166
- * `new Date().toISOString()` dentro do hook de telemetria. O write do próprio
167
- * relatório e o evento que o registra caem a milissegundos de distância, com
168
- * arredondamentos diferentes — sem folga, um relatório perfeitamente fresco
169
- * seria acusado de velho. Dois segundos são muito mais que o skew real e muito
170
- * menos que qualquer trabalho de verdade feito depois de relatar.
171
- */
53
+ // Two clocks: the report mtime comes from the filesystem, the event `ts` from the hook. Two
54
+ // seconds absorb their skew and are far less than any real work done after reporting.
172
55
  export const STALE_TOLERANCE_MS = 2000;
173
56
 
174
- /**
175
- * Traz o target do evento para a MESMA forma que `task.outputs`.
176
- *
177
- * Os dois lados chegam em convenções diferentes, e cruzá-los crus não casa
178
- * nunca: `event.target` é o `file_path` como a ferramenta o recebeu, e
179
- * `Write`/`Edit` exigem caminho ABSOLUTO (`telemetry-log.js` só normaliza as
180
- * barras, não relativiza); `task.outputs` é relativo à raiz — é assim que
181
- * `outputEvidence` o resolve, com `join(cwd, rel)`. Sem este passo,
182
- * `undeclared`/`unobserved` listariam TUDO em toda task, e um sinal que sempre
183
- * grita é um sinal que se aprende a ignorar.
184
- *
185
- * Um target que não fica sob a raiz (outra árvore) volta como veio, em vez de
186
- * virar `../../..` — fora da árvore ele é evidência legítima de escrita alheia,
187
- * não um output que faltou declarar.
188
- */
189
- function toRepoRelative(target, cwd) {
190
- const norm = String(target || '').replace(/\\/g, '/');
191
- const root = String(cwd || '').replace(/\\/g, '/').replace(/\/+$/, '');
57
+ const slash = (p) => String(p || '').replace(/\\/g, '/');
58
+
59
+ function toTreeRelative(target, treePath) {
60
+ const norm = slash(target);
61
+ const root = slash(treePath).replace(/\/+$/, '');
192
62
  if (!root) return norm;
193
- // Prefixo comparado sem caixa: no Windows a mesma raiz chega como `R:/repo`
194
- // ou `r:/repo` conforme quem chamou o comando.
63
+ // Case-insensitive prefix: on Windows the same root arrives as `R:/repo` or `r:/repo`.
195
64
  return norm.toLowerCase().startsWith(`${root.toLowerCase()}/`) ? norm.slice(root.length + 1) : norm;
196
65
  }
197
66
 
198
- /**
199
- * Arquivos que NÃO contam como "trabalho feito depois do relatório".
200
- *
201
- * Sem este recorte o veredito vira máquina de falso positivo, e um aviso que
202
- * dispara à toa é um aviso que se aprende a ignorar:
203
- * - o próprio relatório: gravá-lo É um Write, e ele cai na janela da task por
204
- * construção — todo relatório bem-comportado pareceria stale;
205
- * - qualquer coisa sob `.morph/`: `tasks.json`, `feature.json`, `recap.md`,
206
- * `decisions.md` são escritas do ORQUESTRADOR, depois que o sub-agent
207
- * terminou. São o fluxo normal, não trabalho não relatado.
208
- *
209
- * `includes` e não `startsWith`, de propósito: o alvo já vem relativizado, mas
210
- * um write no `.morph/` de OUTRA árvore não fica sob esta raiz e continua
211
- * absoluto — `startsWith('.morph/')` deixaria esse passar como trabalho. Não há
212
- * código-fonte sob `.morph/` para o `includes` pegar por engano: a pasta é
213
- * estado, por design.
214
- */
215
- function isBookkeepingWrite(target, reportRel) {
216
- if (!target) return true;
217
- if (target === reportRel) return true;
218
- return target.includes('.morph/');
67
+ const reportSuffix = (feature, sliceId) => `.morph/features/${feature}/3-implement/reports/${sliceId}.md`.toLowerCase();
68
+
69
+ function loadWrites(cwd) {
70
+ let trees = [];
71
+ try { trees = loadTelemetryAllTrees(cwd); } catch { trees = []; }
72
+ const events = [];
73
+ for (const tree of trees) {
74
+ for (const e of tree.events) {
75
+ if (e?.tool !== 'Write' && e?.tool !== 'Edit') continue;
76
+ if (isFailedToolCall(e)) continue;
77
+ const ts = Date.parse(e?.ts ?? '');
78
+ if (!Number.isFinite(ts)) continue;
79
+ events.push({ ...e, rel: toTreeRelative(e.target, tree.path), tsMs: ts });
80
+ }
81
+ }
82
+ return { available: trees.some((t) => t.events.length > 0), writes: dedupeEvents(events) };
219
83
  }
220
84
 
221
- /**
222
- * Evidência OBSERVADA: arquivos que a task de fato escreveu, segundo a
223
- * telemetria — não segundo o que o agente declarou em `outputs`.
224
- *
225
- * A distinção é o ponto todo. `outputs` é auto-declarado pelo agente, e
226
- * `trace-autogen` lê a MESMA lista — as duas checagens que pareciam
227
- * independentes eram a mesma afirmação lida duas vezes. `events.jsonl` registra
228
- * cada `Write`/`Edit` no momento em que aconteceu, com `ts`; `task-times.json`
229
- * dá a janela `[start, end]` da task. Cruzando os dois sai o que de fato foi
230
- * tocado, sem depender da palavra de ninguém.
231
- *
232
- * Fail-open: sem telemetria (projeto legado, hook desligado) devolve
233
- * `{ available: false }` e o comando volta ao comportamento anterior.
234
- */
235
- function observedEvidence(cwd, feature, task) {
236
- const times = readTaskTimes(cwd)?.[feature]?.[task.id];
237
- if (!times?.start) return { available: false };
238
-
239
- const events = readTelemetryEvents(cwd);
240
- if (!events.length) return { available: false };
241
-
242
- const startMs = Date.parse(times.start);
243
- // Janela aberta à direita quando a task não fechou: uma task ainda em
244
- // andamento tem trabalho observável, e recusar auditá-la esconde exatamente
245
- // o caso do relatório escrito cedo demais.
246
- const endMs = times.end ? Date.parse(times.end) : Number.POSITIVE_INFINITY;
247
- if (!Number.isFinite(startMs)) return { available: false };
248
-
249
- const reportRel = `.morph/features/${feature}/3-implement/reports/${task.id}.md`;
250
- const writes = [];
251
- for (const e of events) {
252
- if (e?.tool !== 'Write' && e?.tool !== 'Edit') continue;
253
- if (isFailedToolCall(e)) continue;
254
- const ts = Date.parse(e?.ts ?? '');
255
- if (!Number.isFinite(ts) || ts < startMs || ts > endMs) continue;
256
- const target = toRepoRelative(e.target, cwd);
257
- if (isBookkeepingWrite(target, reportRel)) continue;
258
- writes.push({ target, ts });
85
+ // The owning dispatch is the latest sub-agent write of the report; a later orchestrator edit
86
+ // does not hide it. Without a report, any write to one of the slice files says the work landed.
87
+ function observedEvidence(telemetry, feature, slice) {
88
+ if (!telemetry.available) return { available: false };
89
+ const suffix = reportSuffix(feature, slice.id);
90
+ const isReport = (w) => slash(w.target).toLowerCase().endsWith(suffix);
91
+ const declared = new Set((slice.files ?? []).map((f) => slash(f).replace(/^\.\//, '').toLowerCase()));
92
+ const isSliceFile = (w) => declared.has(w.rel.toLowerCase());
93
+
94
+ const reportWrites = telemetry.writes.filter((w) => isReport(w) && w.agentId);
95
+ const agentId = reportWrites.length ? reportWrites[reportWrites.length - 1].agentId : null;
96
+ if (!agentId) {
97
+ return { available: true, agentId: null, sliceWrites: telemetry.writes.filter(isSliceFile).length };
259
98
  }
260
99
 
261
- const declared = new Set((Array.isArray(task.outputs) ? task.outputs : [])
262
- .map((p) => String(p).replace(/\\/g, '/')));
263
- const observed = [...new Set(writes.map((w) => w.target))];
264
-
100
+ const work = telemetry.writes.filter((w) => w.agentId === agentId && !isReport(w) && !w.rel.includes('.morph/'));
101
+ const observed = [...new Set(work.map((w) => w.rel))];
102
+ const observedKeys = new Set(observed.map((f) => f.toLowerCase()));
265
103
  return {
266
104
  available: true,
105
+ agentId,
267
106
  observed,
268
- lastWriteAt: writes.length ? new Date(Math.max(...writes.map((w) => w.ts))).toISOString() : null,
269
- undeclared: observed.filter((p) => !declared.has(p)),
270
- unobserved: [...declared].filter((p) => !observed.includes(p)),
107
+ lastWriteAt: work.length ? new Date(Math.max(...work.map((w) => w.tsMs))).toISOString() : null,
108
+ undeclared: observed.filter((f) => !declared.has(f.toLowerCase())),
109
+ unobserved: (slice.files ?? []).filter((f) => !observedKeys.has(slash(f).replace(/^\.\//, '').toLowerCase())),
110
+ sliceWrites: work.filter(isSliceFile).length,
271
111
  };
272
112
  }
273
113
 
274
- /** O relatório é mais velho que o último trabalho observado da própria task? */
275
114
  function isReportStale(reportFsPath, observed) {
276
- if (!observed?.available || !observed.lastWriteAt) return false;
115
+ if (!observed?.agentId || !observed.lastWriteAt) return false;
277
116
  try {
278
- const writtenAt = statSync(reportFsPath).mtimeMs;
279
- return writtenAt + STALE_TOLERANCE_MS < Date.parse(observed.lastWriteAt);
117
+ return statSync(reportFsPath).mtimeMs + STALE_TOLERANCE_MS < Date.parse(observed.lastWriteAt);
280
118
  } catch {
281
119
  return false;
282
120
  }
283
121
  }
284
122
 
285
- /** Combine report state + evidence into a verdict and the action it implies. */
286
- function decide(report, evidence, stale) {
123
+ function decide(report, observed, stale) {
287
124
  if (report.exists && report.complete) {
288
- // A integridade estrutural (os 4 itens presentes) responde "o relatório
289
- // está completo?", nunca "o relatório ainda é verdade?". Caso real: um
290
- // sub-agent gravou o esqueleto dizendo "teste ainda não escrito — em
291
- // andamento", seguiu trabalhando, escreveu os 9 testes e foi cortado. O
292
- // arquivo ficou completo e MENTINDO, e saía `ok` aqui.
125
+ // Structural integrity answers "is the report complete?", never "is it
126
+ // still true?": an agent can write it early, keep working and be cut off.
293
127
  return stale
294
128
  ? { verdict: 'report-stale', action: 'refresh-from-evidence' }
295
129
  : { verdict: 'ok', action: 'proceed' };
296
130
  }
297
- // A PARCIAL report is an agent that stopped short (turn ceiling): the work
298
- // resumes from its last `wip` commit — completing it DEGRADADO would stamp
299
- // unfinished work as done.
131
+ // PARCIAL: the agent stopped short (turn ceiling) and resumes from its last
132
+ // `wip` commit — completing it DEGRADADO would stamp unfinished work as done.
300
133
  if (report.exists && report.partial) return { verdict: 'report-incomplete', action: 'resume' };
301
134
  if (report.exists) return { verdict: 'report-incomplete', action: 'complete-degraded' };
302
- if (evidence.outputsExpected === 0) {
303
- // No declared outputs means no evidence either way. Re-dispatching costs a
304
- // sub-agent; silently accepting a phantom completion costs correctness.
305
- return { verdict: 'report-missing-evidence-unknown', action: 'redispatch' };
306
- }
307
- return evidence.outputsPresent > 0
135
+ if (!observed.available) return { verdict: 'report-missing-evidence-unknown', action: 'redispatch' };
136
+ return observed.sliceWrites > 0
308
137
  ? { verdict: 'work-landed-report-missing', action: 'accept-degraded' }
309
138
  : { verdict: 'no-work-no-report', action: 'redispatch' };
310
139
  }
311
140
 
312
- function checkTask(cwd, feature, task) {
313
- const fsPath = reportPath(cwd, feature, task.id);
314
- const report = inspectReport(fsPath);
315
- const evidence = outputEvidence(cwd, task.outputs);
316
- const observed = observedEvidence(cwd, feature, task);
141
+ function checkSlice(cwd, feature, slice, telemetry) {
142
+ const fsPath = sliceReportPath(join(cwd, '.morph', 'features', feature), slice.id);
143
+ const report = inspectReport(fsPath, { covers: slice.covers ?? [] });
144
+ const observed = observedEvidence(telemetry, feature, slice);
317
145
  const stale = report.exists && isReportStale(fsPath, observed);
318
- return { task: task.id, report, evidence, observed, ...decide(report, evidence, stale) };
146
+ return { slice: slice.id, report, observed, ...decide(report, observed, stale) };
319
147
  }
320
148
 
321
149
  const ACTION_HINT = {
322
150
  proceed: 'relatório íntegro — siga o fluxo normal.',
323
- 'refresh-from-evidence': 'o relatório está COMPLETO mas VELHO: a task seguiu escrevendo arquivos depois que ele foi gravado, então ele descreve um estado já ultrapassado. NÃO re-despache e não confie no texto — confira os arquivos em `observed` (evidência real, não auto-declarada) e atualize o relatório a partir deles.',
324
- 'complete-degraded': 'complete os itens faltantes a partir da mensagem de retorno do sub-agent, carimbe o relatório como DEGRADADO e registre a falha de protocolo no recap.',
325
- resume: 'relatório PARCIAL: o sub-agent parou antes do fim (limite de turnos) e a task NÃO está pronta. Não carimbe DEGRADADO nem pontue — retome o MESMO agente por `SendMessage` (cole no recado `tree.dirty` e `tree.commits` desta saída e o que falta) ou re-despache só o que falta, a partir do último commit `wip`.',
326
- 'accept-degraded': 'o TRABALHO entrou — não re-despache. Grave você mesmo o relatório canônico a partir da mensagem de retorno, carimbado como DEGRADADO, e registre a falha de protocolo no recap e nas `notes` da task.',
327
- redispatch: 're-despache a task UMA vez com o mesmo prompt. Segunda ausência sem trabalho entregue = falha real: escale ao humano.',
151
+ 'refresh-from-evidence': 'o relatório está COMPLETO mas VELHO: o agente da fatia seguiu escrevendo arquivos depois de gravá-lo, então ele descreve um estado já ultrapassado. NÃO re-despache e não confie no texto — confira os arquivos em `observed` (evidência real, não auto-declarada) e atualize o relatório a partir deles.',
152
+ 'complete-degraded': 'complete os itens faltantes a partir da mensagem de retorno do sub-agent, carimbe o relatório como DEGRADADO e registre nele a falha de protocolo.',
153
+ resume: 'relatório PARCIAL: o sub-agent parou antes do fim (limite de turnos) e a fatia NÃO está pronta. Não carimbe DEGRADADO — retome o MESMO agente por `SendMessage` (cole no recado `tree.dirty` e `tree.commits` desta saída e o que falta) ou re-despache só o que falta, a partir do último commit `wip`.',
154
+ 'accept-degraded': 'o TRABALHO entrou — não re-despache. Grave você mesmo o relatório da fatia a partir da mensagem de retorno, carimbado como DEGRADADO, com a falha de protocolo registrada nele.',
155
+ redispatch: 're-despache a fatia UMA vez com o mesmo prompt. Segunda ausência sem trabalho entregue = falha real: escale ao humano.',
328
156
  };
329
157
 
330
158
  function snapshotPath(cwd, feature) {
331
159
  return join(cwd, '.morph', 'features', feature, '4-review', 'tree-before-eval.json');
332
160
  }
333
161
 
334
- /**
335
- * The tree block, compared with the snapshot taken before the evaluator was
336
- * dispatched when there is one. `beforeEval` takes a new snapshot after the
337
- * comparison, so the output still shows what changed since the previous one.
338
- */
339
- function treeFor(cwd, feature, beforeEval, tasks = []) {
162
+ // `beforeEval` snapshots after comparing, so the output still shows what changed since the last one.
163
+ function treeFor(cwd, feature, beforeEval, slices = []) {
340
164
  const path = snapshotPath(cwd, feature);
341
165
  let snapshot = null;
342
166
  try { if (existsSync(path)) snapshot = JSON.parse(readFileSync(path, 'utf8')); } catch { snapshot = null; }
343
167
  const tree = treeState(cwd, { snapshot });
344
168
  if (!beforeEval) return { tree };
345
- const evaluatorModel = evaluatorDispatch(cwd, tasks);
169
+ const reviewModels = reviewDispatch(cwd, feature, slices);
346
170
  const next = snapshotTree(cwd);
347
- if (!next) return { tree, beforeEval: null, evaluatorModel };
171
+ if (!next) return { tree, beforeEval: null, reviewModels };
348
172
  atomicWriteJson(path, next);
349
- return { tree, beforeEval: { at: next.at, files: Object.keys(next.files).length }, evaluatorModel };
173
+ return { tree, beforeEval: { at: next.at, files: Object.keys(next.files).length }, reviewModels };
350
174
  }
351
175
 
352
- /**
353
- * @param {string} feature
354
- * @param {string|string[]|undefined} taskId - several ids audit a parallel
355
- * batch whose tasks are not `done` yet, which the whole-feature mode skips
356
- * @param {{ json?: boolean, cwd?: string }} [options]
357
- */
358
- export function reportCheckCommand(feature, taskId, options = {}) {
176
+ export function reportCheckCommand(feature, sliceIds, options = {}) {
359
177
  const cwd = options.cwd || process.cwd();
360
178
  const emit = (obj, code) => {
361
179
  if (options.json) console.log(JSON.stringify(obj, null, 2));
@@ -363,39 +181,24 @@ export function reportCheckCommand(feature, taskId, options = {}) {
363
181
  process.exit(code);
364
182
  };
365
183
 
366
- const tasksPath = join(cwd, '.morph', 'features', feature, '2-plan', 'tasks.json');
367
- if (!existsSync(tasksPath)) {
368
- emit({ error: `feature "${feature}" não tem 2-plan/tasks.json — nada a auditar`, feature }, 1);
369
- }
370
-
371
- let tasks = [];
372
- try {
373
- const parsed = JSON.parse(readFileSync(tasksPath, 'utf8'));
374
- tasks = Array.isArray(parsed.tasks) ? parsed.tasks : [];
375
- } catch {
376
- emit({ error: `2-plan/tasks.json de "${feature}" está corrompido`, feature }, 1);
184
+ const { status, checks } = readPlanChecks(cwd, feature);
185
+ if (!checks) {
186
+ emit({ error: `feature "${feature}" sem 2-plan/checks.json legível (${status}) — rode morph-spec plan-check ${feature}`, feature }, 1);
377
187
  }
378
-
379
- if (Array.isArray(taskId)) {
380
- const unknown = taskId.filter((id) => !tasks.some((t) => t.id === id));
381
- if (unknown.length) emit({ error: `task(s) ${unknown.join(', ')} não existe(m) em tasks.json`, feature }, 1);
382
- const results = taskId.map((id) => checkTask(cwd, feature, tasks.find((t) => t.id === id)));
383
- const clean = results.every((r) => r.verdict === 'ok');
384
- emit({ feature, results, clean, ...treeFor(cwd, feature, options.beforeEval, tasks) }, clean ? 0 : 1);
385
- }
386
-
387
- if (taskId) {
388
- const task = tasks.find((t) => t.id === taskId);
389
- if (!task) emit({ error: `task "${taskId}" não existe em tasks.json`, feature }, 1);
390
- const result = checkTask(cwd, feature, task);
391
- emit({ feature, ...result, hint: ACTION_HINT[result.action], ...treeFor(cwd, feature, options.beforeEval, tasks) }, result.verdict === 'ok' ? 0 : 1);
392
- }
393
-
394
- // Whole-feature audit: every DONE task owes a report; a pending one does not.
395
- const results = tasks.filter((t) => t.status === 'done').map((t) => checkTask(cwd, feature, t));
396
- const evaluator = inspectEvaluatorReport(evaluatorPath(cwd, feature));
188
+ const slices = checks.slices;
189
+ const ids = Array.isArray(sliceIds) ? sliceIds : sliceIds ? [sliceIds] : [];
190
+ const unknown = ids.filter((id) => !slices.some((s) => s.id === id));
191
+ if (unknown.length) emit({ error: `fatia(s) ${unknown.join(', ')} não existe(m) no plano de ${feature}`, feature }, 1);
192
+
193
+ const telemetry = loadWrites(cwd);
194
+ const chosen = ids.length ? ids.map((id) => slices.find((s) => s.id === id)) : slices;
195
+ const results = chosen.map((s) => {
196
+ const r = checkSlice(cwd, feature, s, telemetry);
197
+ return { ...r, hint: ACTION_HINT[r.action] };
198
+ });
397
199
  const clean = results.every((r) => r.verdict === 'ok');
398
- emit({ feature, results, evaluator, clean, ...treeFor(cwd, feature, options.beforeEval, tasks) }, clean ? 0 : 1);
200
+ const reviews = ids.length ? undefined : inspectReviewReports(cwd, feature);
201
+ emit({ feature, results, ...(reviews ? { reviews } : {}), clean, ...treeFor(cwd, feature, options.beforeEval, slices) }, clean ? 0 : 1);
399
202
  }
400
203
 
401
204
  function printHuman(obj) {
@@ -403,37 +206,30 @@ function printHuman(obj) {
403
206
  console.error(chalk.red(`\n❌ ${obj.error}\n`));
404
207
  return;
405
208
  }
406
- const line = (r) => {
209
+ console.log(chalk.bold(`\n📄 report-check ${obj.feature}`));
210
+ if (!obj.results.length) console.log(chalk.gray(' nenhuma fatia no plano — nada a auditar'));
211
+ for (const r of obj.results) {
407
212
  const icon = r.verdict === 'ok' ? chalk.green('✓') : chalk.yellow('⚠');
408
- const ev = r.evidence.outputsExpected
409
- ? `outputs ${r.evidence.outputsPresent}/${r.evidence.outputsExpected}`
410
- : 'outputs não declarados';
411
- console.log(` ${icon} ${String(r.task).padEnd(6)} ${r.verdict.padEnd(32)} ${ev}`);
213
+ const who = r.observed.agentId ? `agente ${String(r.observed.agentId).slice(0, 8)}` : (r.observed.available ? 'sem dispatch' : 'sem telemetria');
214
+ console.log(` ${icon} ${String(r.slice).padEnd(5)} ${r.verdict.padEnd(32)} ${who}`);
412
215
  if (r.verdict !== 'ok') {
413
216
  if (r.report.exists && r.report.missing.length) {
414
217
  console.log(chalk.gray(` faltando no relatório: ${r.report.missing.join(', ')}`));
415
218
  }
416
- console.log(chalk.gray(` → ${ACTION_HINT[r.action]}`));
219
+ console.log(chalk.gray(` → ${r.hint}`));
417
220
  }
418
- };
419
-
420
- if (obj.task) {
421
- console.log(chalk.bold(`\n📄 report-check ${obj.feature} ${obj.task}`));
422
- line(obj);
423
- printTree(obj.tree);
424
- console.log('');
425
- return;
426
221
  }
427
-
428
- console.log(chalk.bold(`\n📄 report-check ${obj.feature}`));
429
- if (!obj.results.length) console.log(chalk.gray(' nenhuma task done — nada a auditar'));
430
- for (const r of obj.results) line(r);
431
- if (obj.evaluator) {
222
+ for (const [role, r] of Object.entries(obj.reviews ?? {})) {
223
+ if (!r.exists && role === 'security') continue;
432
224
  console.log(
433
- ` ${obj.evaluator.exists ? chalk.green('✓') : chalk.yellow('⚠')} ${'Gate 3'.padEnd(6)} `
434
- + `evaluator-report.md ${obj.evaluator.exists ? (obj.evaluator.complete ? 'íntegro' : obj.evaluator.partial ? 'PARCIAL' : 'incompleto') : 'ausente'}`,
225
+ ` ${r.complete ? chalk.green('✓') : chalk.yellow('⚠')} ${'Gate 3'.padEnd(6)} `
226
+ + `${reviewFileName(role)} ${r.exists ? (r.complete ? `íntegro (${r.verdict})` : r.partial ? 'PARCIAL' : 'incompleto') : 'ausente'}`,
435
227
  );
436
228
  }
229
+ if (obj.reviewModels) {
230
+ const m = obj.reviewModels;
231
+ console.log(chalk.gray(` dispatch: verifier ${m.verifier.model}, reviewer ${m.reviewer.model}${m.security.required ? `, security ${m.security.model}` : ''}`));
232
+ }
437
233
  printTree(obj.tree);
438
234
  console.log('');
439
235
  }