@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,69 +1,24 @@
1
- /**
2
- * Agents Installer (v5)
3
- *
4
- * Installs morph-spec domain personas as native Claude Code subagents
5
- * in .claude/agents/morph-{id}.md.
6
- *
7
- * agents.json is the SINGLE SOURCE OF TRUTH (ADR-001, docs/specs/
8
- * dispatchable-personas/decisions.md). The .md files are DERIVED artifacts:
9
- * generated with a MANAGED header, regenerated on update/--reinstall, and
10
- * drift-checked by `morph-spec doctor` (checkAgentsDrift). Never hand-edit
11
- * them — that is exactly the v4 dual-source drift the v5 migration removed.
12
- *
13
- * Canonical naming: subagent_type = `morph-{id}` — the frontmatter `name`
14
- * matches the filename 1:1, and the skills dispatch that exact type.
15
- *
16
- * In v5 the harness has 8 personas (no tiers) across two axes — domain
17
- * (dotnet-senior, ef-modeler, maf-expert, nextjs-expert, ui-designer,
18
- * infra-engineer, evaluator) and stage (scout, read-only diagnosis before
19
- * implementation). Each persona is a subagent the Specialist (Claude in
20
- * implementer role) can dispatch. The 3 roles (Orchestrator/Specialist/
21
- * Evaluator) are assumed by Claude itself per MORPH.md, not as separate agents.
22
- */
23
-
24
1
  import { readFileSync, existsSync, readdirSync } from 'node:fs';
25
2
  import { join } from 'path';
26
3
  import { shouldInstall } from '../lib/stack-filter.js';
27
4
  import { realEffects } from '../lib/installers/effects.js';
28
5
 
29
- /** Default tool scope for domain personas (override per agent via agents.json `tools`). */
30
6
  export const DEFAULT_AGENT_TOOLS = 'Read, Write, Edit, Bash, Glob, Grep';
31
7
 
32
- /**
33
- * A persona that can change files: its effective tool scope grants Write or Edit.
34
- * Derived from the tools, so a persona made read-only stops being one.
35
- *
36
- * @param {object} agent - agents.json entry
37
- * @returns {boolean}
38
- */
8
+ /** Derived from the effective tool scope (Write or Edit), so a persona made read-only stops being one. */
39
9
  export function isImplementer(agent) {
40
10
  const tools = String(agent?.tools ?? DEFAULT_AGENT_TOOLS).split(',').map((t) => t.trim());
41
11
  return tools.includes('Write') || tools.includes('Edit');
42
12
  }
43
13
 
44
- /**
45
- * Turn ceiling for a persona that omits agents.json `maxTurns` (ADR-004).
46
- *
47
- * 20 is the value the generator hardcoded before the field existed — the v4
48
- * Tier-2 default (commit 93aad05f) that survived the v5 flattening with no
49
- * recorded rationale. Keeping it as the default is what lets an old
50
- * agents.json render byte-for-byte the same .md as before.
51
- */
14
+ // The value hardcoded before the field existed: an agents.json without `maxTurns` renders byte-for-byte as before.
52
15
  export const DEFAULT_MAX_TURNS = 20;
53
16
 
54
- /** Share of the ceiling (percent) at which a sub-agent checkpoints (partial report). */
55
17
  const CHECKPOINT_PERCENT = 70;
56
18
 
57
19
  /**
58
- * The ONE rule for a valid `maxTurns`. Four readers — installAgents,
59
- * checkAgentsDrift, doctor's validateAgentsJson and validate-framework.js
60
- * Check 6 — so the same agents.json can never pass in one and fail in another.
61
- *
62
- * Absent (`undefined`) is valid: it means "use the default". Anything present
63
- * must be an integer >= 1. `null` is NOT absent — whoever wants the default
64
- * removes the field.
65
- *
66
- * @param {unknown} value - the raw agents.json value
20
+ * The ONE validity rule, shared by installAgents, checkAgentsDrift, doctor and validate-framework.
21
+ * `undefined` means default; `null` is NOT absent.
67
22
  * @returns {string|null} what was expected, or null when valid
68
23
  */
69
24
  export function maxTurnsProblem(value) {
@@ -71,14 +26,6 @@ export function maxTurnsProblem(value) {
71
26
  return Number.isInteger(value) && value >= 1 ? null : 'esperado inteiro >= 1';
72
27
  }
73
28
 
74
- /**
75
- * The full message for an invalid `maxTurns` — persona, value received, what
76
- * is expected and how to get the default back. Every reader prints this one.
77
- *
78
- * @param {string} id - persona id (agents.json key)
79
- * @param {unknown} value - the raw agents.json value
80
- * @returns {string|null} null when valid
81
- */
82
29
  export function maxTurnsError(id, value) {
83
30
  const problem = maxTurnsProblem(value);
84
31
  if (!problem) return null;
@@ -89,41 +36,20 @@ export function maxTurnsError(id, value) {
89
36
  `Remova o campo para usar o default ${DEFAULT_MAX_TURNS}.`;
90
37
  }
91
38
 
92
- /**
93
- * Resolve the ceiling a persona is generated with: its `maxTurns`, or the
94
- * default when absent. Throws `maxTurnsError()` on an invalid value.
95
- *
96
- * @param {object} agent - agents.json entry with `id` merged in
97
- * @returns {number}
98
- */
99
39
  export function resolveMaxTurns(agent) {
100
40
  const error = maxTurnsError(agent.id, agent.maxTurns);
101
41
  if (error) throw new Error(error);
102
42
  return agent.maxTurns ?? DEFAULT_MAX_TURNS;
103
43
  }
104
44
 
105
- /** Values Claude Code accepts in a subagent's `effort` frontmatter. */
106
45
  export const VALID_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max'];
107
46
 
108
- /**
109
- * The ONE rule for a valid `effort` — same four readers as `maxTurnsProblem`.
110
- * Absent means the frontmatter carries no `effort:` line and the sub-agent
111
- * inherits the session's effort, byte-for-byte what the generator emitted
112
- * before the field existed.
113
- *
114
- * @param {unknown} value - the raw agents.json value
115
- * @returns {string|null} what was expected, or null when valid
116
- */
47
+ // Absent: no `effort:` line, so the sub-agent inherits the session effort.
117
48
  export function effortProblem(value) {
118
49
  if (value === undefined) return null;
119
50
  return VALID_EFFORTS.includes(value) ? null : `esperado um de ${VALID_EFFORTS.join('|')}`;
120
51
  }
121
52
 
122
- /**
123
- * @param {string} id - persona id (agents.json key)
124
- * @param {unknown} value - the raw agents.json value
125
- * @returns {string|null} null when valid
126
- */
127
53
  export function effortError(id, value) {
128
54
  const problem = effortProblem(value);
129
55
  if (!problem) return null;
@@ -131,26 +57,12 @@ export function effortError(id, value) {
131
57
  'Remova o campo para herdar o effort da sessão.';
132
58
  }
133
59
 
134
- /** A surface id is the key the mockup checklist names (`Superfície: admin-app`). */
135
60
  const SURFACE_ID_RE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
136
61
 
137
62
  const SURFACE_KEYS = new Set(['when', 'standards']);
138
63
 
139
- /**
140
- * The ONE rule for a valid `surfaces` — the optional map that splits a
141
- * persona's standards by the kind of screen it works on
142
- * (`{ "admin-app": { when, standards: [ids] } }`). Same four readers as
143
- * `maxTurnsProblem`, for the same reason.
144
- *
145
- * Absent (`undefined`) is valid: the persona reads only `standards`. A standard
146
- * listed both in `standards` (read on every surface) and in a surface is
147
- * refused: that is exactly how a "separated" persona keeps loading marketing
148
- * standards on a CRUD screen without anyone noticing.
149
- *
150
- * @param {unknown} surfaces - the raw agents.json value
151
- * @param {unknown} [standards] - the persona's universal `standards`
152
- * @returns {string|null} what is wrong, or null when valid
153
- */
64
+ // A standard both universal and in a surface is refused: the persona would keep
65
+ // loading it on every surface without anyone noticing.
154
66
  export function surfacesProblem(surfaces, standards = []) {
155
67
  if (surfaces === undefined) return null;
156
68
  if (surfaces === null || typeof surfaces !== 'object' || Array.isArray(surfaces)) {
@@ -185,14 +97,6 @@ export function surfacesProblem(surfaces, standards = []) {
185
97
  return null;
186
98
  }
187
99
 
188
- /**
189
- * The full message for an invalid `surfaces`. Every reader prints this one.
190
- *
191
- * @param {string} id - persona id (agents.json key)
192
- * @param {unknown} surfaces
193
- * @param {unknown} [standards]
194
- * @returns {string|null} null when valid
195
- */
196
100
  export function surfacesError(id, surfaces, standards) {
197
101
  const problem = surfacesProblem(surfaces, standards);
198
102
  if (!problem) return null;
@@ -200,14 +104,7 @@ export function surfacesError(id, surfaces, standards) {
200
104
  'Remova o campo para a persona ler só `standards`.';
201
105
  }
202
106
 
203
- /**
204
- * Every standard id a persona can cite: the universal list plus each
205
- * surface's. Reference checks read this, so a surface id that does not exist
206
- * fails like any other.
207
- *
208
- * @param {object} persona - agents.json entry
209
- * @returns {string[]} unique ids, universal first
210
- */
107
+ /** @returns {string[]} unique ids of `standards` plus every surface's, universal first */
211
108
  export function personaStandardIds(persona) {
212
109
  const ids = [...(Array.isArray(persona?.standards) ? persona.standards : [])];
213
110
  const surfaces = persona?.surfaces;
@@ -219,48 +116,22 @@ export function personaStandardIds(persona) {
219
116
  return [...new Set(ids)];
220
117
  }
221
118
 
222
- /** The first validity error of a persona entry, or null — what drift reports as `invalid`. */
223
119
  function personaError(agent) {
224
120
  return maxTurnsError(agent.id, agent.maxTurns)
225
121
  ?? surfacesError(agent.id, agent.surfaces, agent.standards)
226
122
  ?? effortError(agent.id, agent.effort);
227
123
  }
228
124
 
229
- /**
230
- * Tools every persona gets on top of its scope. A `tools:` line is a strict
231
- * allowlist: without `Skill` the sub-agent cannot invoke a single skill, and
232
- * without `ToolSearch` it cannot load an MCP tool the session defers.
233
- */
125
+ // `tools:` is a strict allowlist: without Skill no skill runs, without ToolSearch no deferred MCP tool loads.
234
126
  export const PLATFORM_TOOLS = ['Skill', 'ToolSearch'];
235
127
 
236
- /**
237
- * The server names an MCP registry entry is granted under. Claude Code names a
238
- * server's tools after WHERE it is configured — `mcp__context7__*` from
239
- * `.mcp.json`, `mcp__plugin_context7_context7__*` from the plugin — and a grant
240
- * only matches its own spelling (measured 2026-09-24: `mcp__context7` gave a
241
- * plugin-installed context7 nothing). So every spelling becomes a grant; one
242
- * the project does not configure is inert.
243
- *
244
- * @param {string} id - registry key
245
- * @param {object} entry - registry entry
246
- * @returns {string[]}
247
- */
128
+ // A grant matches only its own spelling (`mcp__context7` vs `mcp__plugin_context7_context7`),
129
+ // so every spelling becomes a grant; one the project does not configure is inert.
248
130
  export function mcpServerNames(id, entry) {
249
- // A malformed list falls back to the id instead of breaking the render:
250
- // installAgents and checkAgentsDrift both go through here (fail-open).
251
131
  return serverNamesProblem(id, entry?.serverNames) === null ? entry?.serverNames ?? [id] : [id];
252
132
  }
253
133
 
254
- /**
255
- * The ONE rule for a registry entry's `serverNames` (validate-framework Check 8
256
- * reads it). Absent is valid (`[id]`). Present: non-empty, only the characters
257
- * Claude Code keeps in a tool name, and it must include the id itself — that is
258
- * the name the MCP installer writes into `.mcp.json`.
259
- *
260
- * @param {string} id - registry key
261
- * @param {unknown} value - the raw `serverNames`
262
- * @returns {string|null} what was expected, or null when valid
263
- */
134
+ // Must include the id itself: that is the name the MCP installer writes into `.mcp.json`.
264
135
  export function serverNamesProblem(id, value) {
265
136
  if (value === undefined) return null;
266
137
  if (!Array.isArray(value) || value.length === 0) return 'esperado array não vazio';
@@ -269,15 +140,7 @@ export function serverNamesProblem(id, value) {
269
140
  return value.includes(id) ? null : `precisa incluir o próprio id "${id}"`;
270
141
  }
271
142
 
272
- /**
273
- * Load the MCP servers known to the framework registry
274
- * (framework/mcp/registry.json) with the server names each is granted under.
275
- * Only registry-known ids in agents.json `mcp` become tool grants (fail-open
276
- * on any error).
277
- *
278
- * @param {string} frameworkDir
279
- * @returns {Map<string, string[]>|null} id → server names; null = no registry found (accept nothing)
280
- */
143
+ /** @returns {Map<string, string[]>|null} id → server names; null = no registry (grant nothing) */
281
144
  export function loadKnownMcpIds(frameworkDir) {
282
145
  try {
283
146
  const registryPath = join(frameworkDir, 'mcp', 'registry.json');
@@ -289,21 +152,8 @@ export function loadKnownMcpIds(frameworkDir) {
289
152
  }
290
153
  }
291
154
 
292
- /**
293
- * Personas install-all default (friction fix, MORPH_GHLBrain 2026-07-11):
294
- * with NO known stack tags (greenfield / install before config.json exists)
295
- * EVERY persona is generated — a project that dispatches `morph-{id}` must
296
- * find the subagent registered, and an install that only lands
297
- * morph-evaluator forces every domain dispatch into the general-purpose
298
- * fallback. Stack filtering only applies when the project's stack is KNOWN
299
- * (tags non-empty): the fallback stays reserved for stacks the project
300
- * genuinely does not have. Note this deliberately differs from rules/skills
301
- * filtering (stack-filter.js), which stays minimal on greenfield.
302
- *
303
- * @param {object} agent - agents.json entry (with `stack`)
304
- * @param {string[]} projectTags
305
- * @returns {boolean}
306
- */
155
+ // With no known stack tags every persona is generated: a dispatch of `morph-{id}` must find
156
+ // its subagent. Deliberately differs from rules/skills filtering.
307
157
  export function agentEligible(agent, projectTags = []) {
308
158
  if (projectTags.length === 0) return true;
309
159
  return shouldInstall(agent.stack ?? ['*'], projectTags);
@@ -314,17 +164,6 @@ const MANAGED_HEADER =
314
164
  ' Do NOT edit by hand: changes are overwritten on `morph-spec update` / `setup-infra --reinstall`.\n' +
315
165
  ' To change this persona, edit agents.json and regenerate. Drift is flagged by `morph-spec doctor`. -->';
316
166
 
317
- /**
318
- * Install morph-spec domain personas as native Claude Code subagents.
319
- *
320
- * @param {string} projectDir - Target project directory
321
- * @param {string} frameworkDir - Path to morph-spec framework/ directory
322
- * @param {object} options
323
- * @param {string[]} [options.projectTags=[]] - Stack tags from config (e.g. ['dotnet', 'nextjs'])
324
- * @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
325
- * Coletor de efeitos; o default executa e registra numa lista descartada.
326
- * @returns {Promise<{ installed: number }>}
327
- */
328
167
  export async function installAgents(projectDir, frameworkDir = 'framework', options = {}) {
329
168
  const { projectTags = [], fx = realEffects() } = options;
330
169
 
@@ -336,8 +175,7 @@ export async function installAgents(projectDir, frameworkDir = 'framework', opti
336
175
  .filter(([key]) => !key.startsWith('_comment'))
337
176
  .map(([id, data]) => ({ id, ...data }));
338
177
 
339
- // Validate EVERY persona before the first write: refusing halfway through
340
- // would leave some personas regenerated and others stale.
178
+ // Validate every persona before the first write: refusing halfway leaves a mix of stale and fresh.
341
179
  for (const agent of agents) {
342
180
  const error = personaError(agent);
343
181
  if (error) throw new Error(error);
@@ -348,10 +186,8 @@ export async function installAgents(projectDir, frameworkDir = 'framework', opti
348
186
 
349
187
  const knownMcpIds = loadKnownMcpIds(frameworkDir);
350
188
 
351
- // Filter by stack — install-all when the stack is unknown (empty tags).
352
189
  const eligible = agents.filter(a => agentEligible(a, projectTags));
353
190
 
354
- // Build set of all possible filenames for orphan cleanup
355
191
  const allPossible = new Set(agents.map(a => `morph-${a.id}.md`));
356
192
 
357
193
  let installed = 0;
@@ -373,12 +209,7 @@ export async function installAgents(projectDir, frameworkDir = 'framework', opti
373
209
  installed++;
374
210
  }
375
211
 
376
- // Cleanup orphan morph-* files filtered out by stack.
377
- //
378
- // Calado em `dryRun` pelo mesmo motivo do laço gêmeo em skills-installer.js
379
- // (D6): numa execução real do `update` este diretório acabou de ser apagado
380
- // inteiro por `cleanFrameworkDirs()`, então o laço nunca remove nada. Num
381
- // dry ingênuo ele veria o diretório antigo e preveria remoções inexistentes.
212
+ // Silent in dryRun: the real update's cleanFrameworkDirs() already emptied the dir.
382
213
  if (!fx.dryRun) {
383
214
  for (const file of readdirSync(targetDir)) {
384
215
  if (allPossible.has(file) && !installedFilenames.has(file)) {
@@ -390,20 +221,10 @@ export async function installAgents(projectDir, frameworkDir = 'framework', opti
390
221
  return { installed, agents: installedAgents };
391
222
  }
392
223
 
393
- /**
394
- * Render the full .md content for a persona — deterministic, so the same
395
- * function serves the installer AND the doctor drift check (a hand-edited or
396
- * stale file differs byte-for-byte from this output).
397
- *
398
- * @param {object} agent - agents.json entry with `id` merged in
399
- * @param {Map<string, string[]>|null} [knownMcpIds] - registry-known MCP ids and
400
- * their server names; agents.json `mcp` entries outside it are ignored (null = ignore all)
401
- * @returns {string} complete file content
402
- */
224
+ // Deterministic: the doctor drift check compares files byte-for-byte against this output.
403
225
  export function renderAgentMd(agent, knownMcpIds = null) {
404
226
  const description = buildDescription(agent);
405
- // Resolved ONCE: the frontmatter ceiling and the budget the body announces
406
- // must be the same number — the sub-agent never sees its own frontmatter.
227
+ // One value for the frontmatter and the body: the sub-agent never sees its own frontmatter.
407
228
  const maxTurns = resolveMaxTurns(agent);
408
229
  const surfaces = surfacesError(agent.id, agent.surfaces, agent.standards);
409
230
  if (surfaces) throw new Error(surfaces);
@@ -417,9 +238,6 @@ export function renderAgentMd(agent, knownMcpIds = null) {
417
238
  function buildToolsLine(agent, knownMcpIds) {
418
239
  const base = String(agent.tools ?? DEFAULT_AGENT_TOOLS).split(',').map(t => t.trim()).filter(Boolean);
419
240
  const platform = PLATFORM_TOOLS.filter(t => !base.includes(t));
420
- // Domain-relevant MCP servers (agents.json `mcp: [ids]`) become server-level
421
- // grants, one per server name (`mcp__{name}`). Only registry-known ids are
422
- // emitted; in projects without that server configured the grant is inert.
423
241
  const mcpGrants = (agent.mcp ?? [])
424
242
  .filter(id => knownMcpIds?.has(id))
425
243
  .flatMap(id => knownMcpIds.get(id).map(name => `mcp__${name}`));
@@ -428,16 +246,11 @@ function buildToolsLine(agent, knownMcpIds) {
428
246
 
429
247
  function buildFrontmatter(agent, description, knownMcpIds, maxTurns) {
430
248
  const lines = [
431
- // Canonical 1:1 naming: frontmatter name === filename === subagent_type.
432
249
  `name: morph-${agent.id}`,
433
250
  `description: ${description}`,
434
- // The evaluator is sonnet here; the dispatch routes it to opus when the risk
435
- // detector flags the diff. A persona without the field inherits the session model.
436
251
  `model: ${agent.model ?? 'inherit'}`,
437
252
  `tools: ${buildToolsLine(agent, knownMcpIds)}`,
438
- // Per persona in agents.json (ADR-004); absent → DEFAULT_MAX_TURNS. The
439
- // line keeps its position so an agents.json without the field renders
440
- // exactly what the hardcoded `maxTurns: 20` rendered.
253
+ // Position fixed so an agents.json without the field renders byte-for-byte as before.
441
254
  `maxTurns: ${maxTurns}`,
442
255
  ];
443
256
  if (agent.effort) lines.push(`effort: ${agent.effort}`);
@@ -453,6 +266,57 @@ function buildDescription(agent) {
453
266
  return desc.length > 200 ? desc.slice(0, 197) + '...' : desc;
454
267
  }
455
268
 
269
+ const NEON_CAUTION = 'NEON: o MCP `neon` inspeciona schema e ensaia migração numa branch descartável ' +
270
+ '(prepare_database_migration → complete_database_migration); ele também APAGA branch e projeto — ' +
271
+ 'nunca chame tool destrutiva sem confirmação humana explícita.';
272
+
273
+ const STANDARDS_HINT = 'Digest de cada um: `morph-spec standards --find <id>`; só a seção que importa: ' +
274
+ '`morph-spec standards --show <id>#<âncora>` (`--show <id>` lista as âncoras).';
275
+
276
+ export const DISPATCH_CONTRACT = [
277
+ '## Contrato de dispatch',
278
+ '',
279
+ 'O dispatch traz só o que muda por fatia: id e título, `cobre` com o texto de cada cenário, `arquivos`, `standards`, ' +
280
+ 'o caminho do relatório e, se houver, o worktree. O resto é este contrato.',
281
+ '',
282
+ '1. RELATÓRIO: GRAVE o arquivo de relatório que o dispatch nomeia (`.morph/features/{feature}/3-implement/reports/F{n}.md`) ' +
283
+ 'ANTES de encerrar — é o retorno autoritativo, não a sua mensagem final. `SendMessage` não é substituto do relatório e não deve ser chamado.',
284
+ '2. ESTADO: NÃO toque em `plan.md`, `checks.json`, `feature.json`, `state.json` nem no board de sessão — todo estado é gravado exclusivamente pelo orquestrador.',
285
+ '3. TESTE: a prova é do orquestrador (`morph-spec verify {feature}`, depois do seu retorno), não sua. Escreva os testes pelo orçamento: ' +
286
+ 'um por pós-condição, um de regressão por bug, variantes numa tabela (`[Theory]`/`it.each`). NÃO rode teste a cada marco nem faça ciclo red→green: ' +
287
+ 'escreva código e testes, compile, e rode SÓ o filtro dos seus testes UMA vez, em primeiro plano, antes do relatório; se falhar, corrija e rode só os que falharam. ' +
288
+ 'Grave a saída em arquivo (`… > .morph/logs/test-last.log 2>&1`, sem nada depois) e leia o arquivo — um pipe troca o exit do teste pelo do último comando. ' +
289
+ 'Em batch paralelo SEM isolamento, só compile. Com `isolation: "worktree"`, o mesmo vale no seu worktree, e o último ato é o commit na branch dele.',
290
+ '4. ESCOPO: uma fatia, ~4 arquivos (nunca mais de 8). Escopo maior falha e volta — fatie antes, não depois.',
291
+ '5. SOBREVIVÊNCIA: o limite de turnos corta sem aviso: commite `wip(F{n})` a cada marco (um arquivo ou uma fatia coerente escrita), não só ao fim — ' +
292
+ 'e só os seus arquivos (`git add <paths>`, nunca `-A` nem diretório). Sem commit, a retomada recomeça do zero.',
293
+ '6. PROCESSOS: NUNCA `taskkill /IM node.exe` (derruba os MCPs de todas as sessões). Encerre dev server pelo PID que você mesmo subiu. ' +
294
+ 'Build e suíte pesados rodam por `morph-spec heavy run -- <comando>`, em primeiro plano. Exit 3 é fila cheia depois da espera da própria fila: ' +
295
+ 'não repita nem espere em laço — diga no relatório o que não rodou.',
296
+ '7. REUSO: antes de criar tipo, componente, hook ou serviço NOVO, verifique se já existe: `morph-spec graph explain <nome>` e ' +
297
+ '`graph affected <conceito> --uses-only`; sem grafo (`no-graph`), caia para `grep -rn "<nome>"` e siga — a ausência de grafo nunca bloqueia. ' +
298
+ 'Se criar mesmo assim, diga por que em uma linha no relatório.',
299
+ '8. MUTAÇÃO: nunca altere, reverta, mova ou apague código de produção para ver um teste falhar (`sed`, `mv`, `git show HEAD:… >`, `git stash`, ' +
300
+ '`git checkout --`). A prova de que o teste morde é do verificador (Gate 3).',
301
+ '9. ESCRITA: código e teste do projeto só por Write/Edit, nunca pelo Bash — heredoc, redirecionamento, `sed -i` ou script python/node que grava (`cat > X.cs <<EOF`, `sed -i … X.cs`): ' +
302
+ 'os guards de escrita só enxergam Write/Edit.',
303
+ '',
304
+ 'Formato do relatório (o `report-check` lê estas marcas):',
305
+ '',
306
+ '```',
307
+ '# F1 — <título>',
308
+ 'O que foi feito, em 2-3 frases: o comportamento entregue e onde ele mora, sem repetir a lista de arquivos.',
309
+ '**Arquivos:** `src/a.cs`, `src/a.test.cs`',
310
+ '**Checks:**',
311
+ '- C1 — provado: `a.test.cs` › nome do teste (ou `arquivo:linha`)',
312
+ '- C2 — não provado: por quê',
313
+ '```',
314
+ '',
315
+ '`**Arquivos:**` lista TODO arquivo criado ou modificado, inclusive artefato auxiliar fora do plano (factory, seed, config gerada): ' +
316
+ 'arquivo em produção sem dono é falha de protocolo que o Gate 3 aponta. Todo cenário que a fatia `cobre` aparece em `**Checks:**`. ' +
317
+ 'Problemas e como foram resolvidos vão em prosa depois.',
318
+ ];
319
+
456
320
  function buildBody(agent, maxTurns) {
457
321
  const lines = [
458
322
  `# ${agent.title ?? agent.id}`,
@@ -461,12 +325,20 @@ function buildBody(agent, maxTurns) {
461
325
  '',
462
326
  ];
463
327
 
464
- if (agent.domains?.length) {
328
+ // The shape without effort or surfaces renders byte-for-byte as the old fixture.
329
+ const legacyShape = !agent.effort && !agent.surfaces && isImplementer(agent);
330
+
331
+ if (legacyShape && agent.domains?.length) {
465
332
  lines.push('## Domains', ...agent.domains.map(d => `- ${d}`), '');
466
333
  }
467
334
 
335
+ if (!legacyShape && agent.mcp?.includes('neon')) {
336
+ lines.push(NEON_CAUTION, '');
337
+ }
338
+
468
339
  if (agent.standards?.length) {
469
340
  lines.push('## Standards to consult on demand');
341
+ if (!legacyShape) lines.push(STANDARDS_HINT);
470
342
  for (const s of agent.standards) lines.push(`- ${s}`);
471
343
  lines.push('');
472
344
  }
@@ -485,7 +357,9 @@ function buildBody(agent, maxTurns) {
485
357
  }
486
358
  }
487
359
 
488
- if (isImplementer(agent)) {
360
+ if (agent.effort && isImplementer(agent)) {
361
+ lines.push(...DISPATCH_CONTRACT, '');
362
+ } else if (isImplementer(agent)) {
489
363
  lines.push(
490
364
  '## How you operate',
491
365
  '',
@@ -493,33 +367,27 @@ function buildBody(agent, maxTurns) {
493
367
  '',
494
368
  );
495
369
  } else {
496
- // A read-only persona has no task to do nor doneCriteria to report.
497
370
  lines.push(
498
371
  '## Como você opera',
499
372
  '',
500
- 'Você é read-only: nunca escreva, edite nem reverta código, testes, `tasks.json` ou `feature.json` (nada de `git checkout --`). ' +
373
+ 'Você é read-only: nunca escreva, edite nem reverta código, testes, o plano ou `feature.json` (nada de `git checkout --`). ' +
501
374
  'O entregável é o arquivo de relatório que o dispatch nomeia; sem arquivo nomeado, é a sua mensagem final (no scout, o mapa).',
502
375
  '',
503
376
  );
504
377
  }
505
378
 
506
379
  lines.push(
507
- // `PARCIAL` on line 1 is what report-check reads; "no verdict" keeps an
508
- // evaluator partial from carrying a Verdict the gate would read as final.
380
+ // `PARCIAL` on line 1 is what report-check reads; "no verdict" keeps a partial
381
+ // review from carrying a Verdict the gate would read as final.
509
382
  '## Turn budget',
510
383
  '',
511
- // Pasted into EVERY dispatch of every persona — kept to the minimum that
512
- // still carries the contract (test/framework/maf-expert-persona.test.js
513
- // pins the rendered size).
514
384
  `You have ${maxTurns} turns (\`maxTurns\`); at the ceiling you are cut off mid-work, without warning. ` +
515
385
  'One response is one turn, however many parallel tool calls it makes. ' +
516
386
  `By turn ${checkpointTurn(maxTurns)}, if your dispatch names a report file, save a partial there — ` +
517
387
  '`PARCIAL` on line 1, done, remaining, files, no verdict (final report only) — then continue or stop, your call; the final report replaces it.',
518
388
  );
519
389
 
520
- // The dispatch template is paraphrased by the orchestrator in the field; this
521
- // body is the only text every implementer dispatch is guaranteed to carry.
522
- // Gated on `effort` so a persona without it renders byte-for-byte as before.
390
+ // The only text every implementer dispatch is guaranteed to carry.
523
391
  if (agent.effort && isImplementer(agent)) {
524
392
  lines.push(
525
393
  '',
@@ -529,37 +397,22 @@ function buildBody(agent, maxTurns) {
529
397
  'Use Read/Grep/Glob, never grep/sed/cat/find via Bash. Check a library API in the `context7` MCP before guessing ' +
530
398
  '(load a deferred `mcp__` tool with ToolSearch). Write the tests the budget asks for, then run only your own ' +
531
399
  'filter ONCE before the report — no red→green cycles, no `morph-spec verify` (the orchestrator proves it). ' +
532
- 'Never alter, revert or move production code to watch a test fail: mutation proof belongs to the Gate 3 evaluator.',
400
+ 'Never alter, revert or move production code to watch a test fail: mutation proof belongs to the Gate 3 verifier.',
533
401
  );
534
402
  }
535
403
 
536
404
  return lines.join('\n');
537
405
  }
538
406
 
539
- /** The turn at which a sub-agent with `maxTurns` saves its partial report. */
540
407
  function checkpointTurn(maxTurns) {
541
- // Integer arithmetic: 0.7 × N in floating point is not always exact.
408
+ // Integer arithmetic: 0.7 × N in floating point is not exact.
542
409
  return Math.max(1, Math.floor((maxTurns * CHECKPOINT_PERCENT) / 100));
543
410
  }
544
411
 
545
- /**
546
- * Compatibility shim for older callers that expected installDomainAgents.
547
- * In v5 there is no separate "domain" install step — installAgents handles
548
- * everything from agents.json.
549
- */
550
412
  export async function installDomainAgents(projectDir, frameworkDir, options) {
551
- // No-op: domain content is now defined inline in agents.json (persona field).
552
413
  return { specialists: 0 };
553
414
  }
554
415
 
555
- /**
556
- * Check which agents SHOULD be installed but are NOT.
557
- *
558
- * @param {string} projectDir
559
- * @param {string} frameworkDir
560
- * @param {string[]} projectTags
561
- * @returns {{ missing: Array<{ name: string, stacks: string[] }>, installed: number, expected: number }}
562
- */
563
416
  export function checkMissingAgents(projectDir, frameworkDir, projectTags = []) {
564
417
  const targetDir = join(projectDir, '.claude', 'agents');
565
418
  const installedFiles = new Set();
@@ -594,26 +447,8 @@ export function checkMissingAgents(projectDir, frameworkDir, projectTags = []) {
594
447
  }
595
448
 
596
449
  /**
597
- * Drift check between agents.json (source of truth) and the generated
598
- * .claude/agents/morph-*.md files. Consumed by `morph-spec doctor`.
599
- *
600
- * - missing: eligible persona with no .md installed → run setup-infra --reinstall
601
- * - stale: .md exists but differs from renderAgentMd() output (hand-edited
602
- * or generated by an older CLI) → regenerate. `staleFields` names
603
- * what differs per file: the frontmatter keys, and `body`.
604
- * - invalid: persona whose agents.json entry cannot be rendered (an invalid
605
- * `maxTurns` or `surfaces`) — reported with the reason instead of throwing, so
606
- * the doctor prints the cause and not "failed to compute"
607
- * - orphans: morph-*.md files with no matching agents.json entry
608
- *
609
- * @param {string} projectDir
610
- * @param {string} frameworkDir - directory containing agents.json
611
- * @param {string[]} projectTags
612
- * @param {object} [options]
613
- * @param {string} [options.mcpRegistryDir=frameworkDir] - where `mcp/registry.json`
614
- * lives. It must be the directory the INSTALLER read: an installed project's
615
- * `.morph/framework/` carries agents.json but no `mcp/`, so rendering against
616
- * it drops every `mcp__*` grant and flags a pristine install as stale.
450
+ * `mcpRegistryDir` must be where the INSTALLER read the registry: the installed
451
+ * `.morph/framework/` has no `mcp/`, and rendering against it flags a clean install as stale.
617
452
  * @returns {{ missing: string[], stale: string[], staleFields: Object<string, string[]>,
618
453
  * invalid: Array<{ file: string, reason: string }>, orphans: string[], expected: number }}
619
454
  */
@@ -672,10 +507,6 @@ export function checkAgentsDrift(projectDir, frameworkDir, projectTags = [], opt
672
507
  return { missing, stale, staleFields, invalid, orphans, expected };
673
508
  }
674
509
 
675
- /**
676
- * Split a generated .md into its frontmatter map and the rest.
677
- * @returns {{ fields: Map<string, string>, body: string }}
678
- */
679
510
  function splitAgentMd(content) {
680
511
  const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
681
512
  if (!match) return { fields: new Map(), body: content };
@@ -687,11 +518,6 @@ function splitAgentMd(content) {
687
518
  return { fields, body: match[2] };
688
519
  }
689
520
 
690
- /**
691
- * Name what differs between a .md on disk and its render: the frontmatter
692
- * keys whose value (or presence) differs, in render order, then `body`.
693
- * A file with no parseable frontmatter differs in everything → `frontmatter`.
694
- */
695
521
  function diffAgentMd(onDisk, rendered) {
696
522
  const disk = splitAgentMd(onDisk);
697
523
  const want = splitAgentMd(rendered);
@@ -699,16 +525,10 @@ function diffAgentMd(onDisk, rendered) {
699
525
  const keys = [...new Set([...want.fields.keys(), ...disk.fields.keys()])];
700
526
  const fields = keys.filter(k => disk.fields.get(k) !== want.fields.get(k));
701
527
  if (disk.body !== want.body) fields.push('body');
702
- // Bytes differ but no key/value does (e.g. a stray blank line between the
703
- // fences): still drift, and it lives in the frontmatter.
528
+ // Bytes differ but no key/value does (a stray blank line between the fences).
704
529
  return fields.length > 0 ? fields : ['frontmatter'];
705
530
  }
706
531
 
707
- /**
708
- * Compatibility export — kept so existing tests/CLI code that imports
709
- * formatStandardRef or parseSkillFile / collectSkillFiles still work,
710
- * but they are no-ops (will be removed in v5.1).
711
- */
712
532
  export function formatStandardRef(standard) {
713
533
  if (typeof standard === 'string') return `- ${standard}`;
714
534
  const id = standard.id || String(standard);