@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,31 +1,12 @@
1
1
  import chalk from 'chalk';
2
- import { mkdirSync } from 'fs';
2
+ import { existsSync, mkdirSync, readFileSync, unlinkSync } from 'fs';
3
3
  import { join } from 'path';
4
4
  import { getFeature, setApprovalGate, updateFeature } from '../../core/state/state-manager.js';
5
5
  import { WORK_TYPES, isValidWorkType, POLICY_APPROVER_PREFIX } from '../../core/work-types.js';
6
+ import { renderPlanSkeleton } from '../../lib/plan/plan-template.js';
6
7
 
7
- /**
8
- * `morph-spec retype <feature> <newType>` — mid-flight work-type change with
9
- * asymmetric gate rules that preserve GENUINE human/auto approvals and only
10
- * ever undo policy stamps:
11
- *
12
- * - Blocked outright once Gate 3 (review) is approved — the feature already
13
- * shipped its pipeline, retyping it retroactively would be revisionist.
14
- * - Downgrade (fewer skipped gates → more skipped gates, e.g. feature/bug
15
- * → chore/hotfix) is blocked once Gate 2 (plan) carries a genuine
16
- * (non-policy) approval — the plan was already accepted under the old
17
- * type's scrutiny; retyping around it would erase that judgment call.
18
- * - Upgrade (more skipped gates → fewer, e.g. chore/hotfix → feature/bug)
19
- * is always allowed before review: it only ever ADDS scrutiny.
20
- *
21
- * Applying a retype resets exactly the gates that were stamped
22
- * `policy:{oldType}` and the NEW type no longer skips (so the human is asked
23
- * again), stamps `policy:{newType}` on any of the new type's skippedGates
24
- * that aren't already approved (a prior genuine approval is left standing —
25
- * that's the "preserves human approvals" half of the contract), scaffolds
26
- * any phase folders the new type needs that don't exist yet, and appends an
27
- * audit entry to `workTypeHistory`.
28
- */
8
+ // Retype only ever undoes `policy:` stamps: blocked once review is approved; a downgrade
9
+ // is blocked once the plan carries a genuine approval; an upgrade only adds scrutiny.
29
10
  export async function retypeCommand(featureName, newType, options = {}) {
30
11
  try {
31
12
  if (!isValidWorkType(newType)) {
@@ -61,15 +42,14 @@ export async function retypeCommand(featureName, newType, options = {}) {
61
42
  const genuinelyApproved = planGate?.approved === true
62
43
  && !String(planGate.approvedBy || '').startsWith(POLICY_APPROVER_PREFIX);
63
44
  if (genuinelyApproved) {
64
- console.error(chalk.red(`\n❌ Gate 2 (plan) já aprovado por "${planGate.approvedBy}"; downgrade bloqueado — o plano já foi aceito\n`));
45
+ console.error(chalk.red(`\n❌ Gate do plano já aprovado por "${planGate.approvedBy}"; downgrade bloqueado — o plano já foi aceito\n`));
65
46
  process.exit(1);
66
47
  }
67
48
  }
68
49
 
69
50
  const now = new Date().toISOString();
70
51
 
71
- // Reset gates that were stamped policy:{oldType} and the new type no
72
- // longer skips — a genuine (non-policy) approval is never touched.
52
+ // A genuine (non-policy) approval is never reset.
73
53
  const resetGates = [];
74
54
  for (const [gate, data] of Object.entries(feature.approvalGates || {})) {
75
55
  const isPolicyStamped = String(data?.approvedBy || '').startsWith(POLICY_APPROVER_PREFIX);
@@ -79,8 +59,7 @@ export async function retypeCommand(featureName, newType, options = {}) {
79
59
  }
80
60
  }
81
61
 
82
- // Stamp the new type's skipped gates — but only the ones not already
83
- // approved (preserves a prior genuine approval untouched).
62
+ // A gate already approved keeps its genuine approval.
84
63
  const stampedGates = [];
85
64
  for (const gate of newWorkType.skippedGates) {
86
65
  if (!feature.approvalGates?.[gate]?.approved) {
@@ -93,6 +72,11 @@ export async function retypeCommand(featureName, newType, options = {}) {
93
72
  }
94
73
 
95
74
  const featureDir = join(process.cwd(), '.morph', 'features', featureName);
75
+ const planPath = join(featureDir, '2-plan', 'plan.md');
76
+ if (stampedGates.includes('plan') && existsSync(planPath)
77
+ && readFileSync(planPath, 'utf8') === renderPlanSkeleton(featureName)) {
78
+ unlinkSync(planPath);
79
+ }
96
80
  for (const folder of newWorkType.scaffold) {
97
81
  mkdirSync(join(featureDir, folder), { recursive: true });
98
82
  }
@@ -1,5 +1,2 @@
1
- /**
2
- * Validation Commands
3
- */
4
1
  export { validateFeatureCommand } from './validate-feature.js';
5
2
  export { verifyCommand } from './verify.js';
@@ -1,9 +1,9 @@
1
1
  import chalk from 'chalk';
2
2
  import { execFileSync } from 'node:child_process';
3
- import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
4
4
  import { join, resolve } from 'node:path';
5
- import { resolveTestBuildTargets } from '../../lib/tasks/test-runner.js';
6
- import { dotnetTestMode } from '../../lib/tasks/dotnet-test-introspection.js';
5
+ import { discoverDotnetBuildTargets } from '../../lib/tasks/test-runner.js';
6
+ import { dotnetTestMode } from '../../lib/mutation/dotnet-introspection.js';
7
7
  import { dotnetBuildCommands } from '../../lib/mutation/commands.js';
8
8
  import { resolveTestNodes } from '../../lib/tasks/test-nodes.js';
9
9
  import { changedPathsAgainst, detectDefaultBranch, getHeadSha } from '../../lib/git/default-branch.js';
@@ -12,7 +12,8 @@ import { readHeavyConfig } from '../../../framework/hooks/shared/heavy-config.js
12
12
  import { resolveGitCommonDir } from '../../../framework/hooks/shared/git-paths.js';
13
13
  import { BACKSLASH_TOKEN, ManifestError } from '../../lib/mutation/manifest.js';
14
14
  import { groupByNode, loadPlan } from '../../lib/mutation/plan.js';
15
- import { dotnetSubset, jsSubset, testProjectDirsOf } from '../../lib/mutation/subset.js';
15
+ import { dotnetSubset, jsSubset, mutatedTypesOf, testFilesCiting, testProjectDirsOf } from '../../lib/mutation/subset.js';
16
+ import { isFinished } from '../../dashboard/scanner/feature-status.js';
16
17
  import { dotnetAdapter, genericAdapter, spawnExec } from '../../lib/mutation/adapters.js';
17
18
  import { runMutation } from '../../lib/mutation/runner.js';
18
19
  import { renderSection, summarize } from '../../lib/mutation/report.js';
@@ -22,11 +23,8 @@ const EXIT = { ok: 0, failed: 1, usage: 2, busy: 3, unsafe: 4 };
22
23
  const KEEP_RUNS = 2;
23
24
  const DEFAULT_JOBS = 2;
24
25
 
25
- /**
26
- * One heavy slot per worker: the first waits like any heavy node, the others
27
- * are taken only if free right now. The run uses as many workers as it got,
28
- * so a busy machine degrades to sequential instead of queueing.
29
- */
26
+ // One heavy slot per worker: the first waits, the others are taken only if free now,
27
+ // so a busy machine degrades to sequential instead of queueing.
30
28
  export function workerGate({ root, feature, jobs }) {
31
29
  const config = readHeavyConfig(root);
32
30
  const gates = [createHeavyGate({ projectPath: root, label: 'mutate', feature, config })];
@@ -97,23 +95,72 @@ export function restoreLatest(root, out = console) {
97
95
  return restored;
98
96
  }
99
97
 
98
+ function testDirsOf(node, root, { projectsOnly = false } = {}) {
99
+ const cwd = resolve(root, node.cwd || '.');
100
+ const targets = discoverDotnetBuildTargets(node, cwd);
101
+ return testProjectDirsOf(root, projectsOnly ? targets.filter((t) => !/\.slnx?$/i.test(t)) : targets, cwd);
102
+ }
103
+
100
104
  function subsetFor({ node, group, root, options }) {
101
105
  if (options.subset === false) return { usable: false, reason: '--no-subset' };
102
106
  const mutatedFiles = group.regions.map((r) => r.file);
103
107
  if (node.runner === 'dotnet') {
104
108
  const base = detectDefaultBranch(root);
105
109
  const changed = base ? changedPathsAgainst(base, root) : [];
106
- return dotnetSubset({ root, testProjectDirs: testProjectDirsOf(root, resolveTestBuildTargets(node)), mutatedFiles, changed });
110
+ return dotnetSubset({ root, testProjectDirs: testDirsOf(node, root), mutatedFiles, changed });
107
111
  }
108
112
  return jsSubset({ runner: node.runner, command: node.command, root, mutatedFiles });
109
113
  }
110
114
 
111
- export async function mutateCommand(feature, options = {}) {
115
+ function e2eManualProtocol(feature, node) {
116
+ const f = feature ?? '<feature>';
117
+ const round = `eval "$(morph-spec env ${f})" && morph-spec e2e up ${f} && morph-spec heavy run -- ${node.command}`;
118
+ return 'mutate só recompila localmente, e a suíte que alcança esse código testa o container do compose: o mutante sairia sobrevivente em falso. ' +
119
+ `Prove à mão pelo nó "${node.id}", cada passo numa linha só e encadeado por \`&&\` (nunca \`;\`: um passo que falha não pode deixar a suíte rodar sobre o código original; ` +
120
+ 'e o env da feature se perde entre chamadas de Bash): ' +
121
+ `(1) base verde antes do 1º mutante: \`${round}\`; ` +
122
+ `(2) por mutante: edite a linha && \`${round}\`; ` +
123
+ `(3) restaure, confirme com \`git diff --quiet -- <arquivo>\` e suba a stack de novo (\`eval "$(morph-spec env ${f})" && morph-spec e2e up ${f}\`). ` +
124
+ 'Registre cada mutante em 4-review (expect, resultado, teste que o matou).';
125
+ }
126
+
127
+ // A .cs file belongs to every dotnet suite, by runner, not by project reference: a unit suite
128
+ // "owns" code only an e2e suite executes. With an e2e node declared, a unit suite with no test
129
+ // naming a type of the mutated file is not evidence, so the run is refused.
130
+ function uncitedUnderE2e({ root, groups, nodes }) {
131
+ const e2e = nodes.filter((n) => n.e2e);
132
+ if (e2e.length === 0) return [];
133
+ const e2eDirs = new Set(e2e.filter((n) => n.runner === 'dotnet').flatMap((n) => testDirsOf(n, root)));
134
+ const uncited = [];
135
+ for (const { node, regions } of groups.filter((g) => g.node.runner === 'dotnet')) {
136
+ const direct = new Set(testDirsOf(node, root, { projectsOnly: true }));
137
+ const dirs = testDirsOf(node, root).filter((d) => direct.has(d) || !e2eDirs.has(d));
138
+ for (const region of regions) {
139
+ const types = mutatedTypesOf(root, [region.file]);
140
+ if (testFilesCiting({ root, testProjectDirs: dirs, types, exclude: [region.file] }).length === 0) uncited.push({ region, node, types, e2e });
141
+ }
142
+ }
143
+ return uncited;
144
+ }
145
+
146
+ function soleActiveFeature(root) {
147
+ const dir = join(root, '.morph', 'features');
148
+ let names;
149
+ try { names = readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name); } catch { return null; }
150
+ const active = names.filter((name) => {
151
+ try { return !isFinished(JSON.parse(readFileSync(join(dir, name, 'feature.json'), 'utf8'))); } catch { return false; }
152
+ });
153
+ return active.length === 1 ? active[0] : null;
154
+ }
155
+
156
+ export async function mutateCommand(featureArg, options = {}) {
112
157
  const root = resolve(options.cwd ?? process.cwd());
113
158
  if (options.restore) { restoreLatest(root); return null; }
114
159
 
115
- const planPath = resolve(root, options.plan ?? join('.morph', 'features', feature ?? '', '4-review', 'mutants.json'));
116
- if (!existsSync(planPath)) return fail(`manifesto não encontrado: ${planPath}\nEscreva-o com a ferramenta Write, se você a tem; só com Bash, heredoc de delimitador entre aspas simples, com a barra invertida do código como ${BACKSLASH_TOKEN} — formato em .morph/framework/CLI.md, seção mutate.`, EXIT.usage);
160
+ const feature = featureArg ?? soleActiveFeature(root);
161
+ if (!feature && !options.plan) return fail('sem feature: passe <feature> (nenhuma, ou mais de uma, em andamento) ou o manifesto por --plan.', EXIT.usage);
162
+ const planPath = resolve(root, options.plan ?? join('.morph', 'features', feature, '4-review', 'mutants.json'));
163
+ if (!existsSync(planPath)) return fail(`manifesto não encontrado: ${planPath}\nEscreva-o com a ferramenta Write, se você a tem; só com Bash, heredoc de delimitador entre aspas simples, com a barra invertida do código como ${BACKSLASH_TOKEN} — formato em .morph/framework/CLI.md, seção "### mutate".`, EXIT.usage);
117
164
 
118
165
  let regions;
119
166
  try {
@@ -125,16 +172,26 @@ export async function mutateCommand(feature, options = {}) {
125
172
  }
126
173
 
127
174
  const { nodes } = resolveTestNodes(root);
128
- const { groups, orphans } = groupByNode(regions, nodes, root);
175
+ const { groups, orphans, stackOnly } = groupByNode(regions, nodes, root);
129
176
  if (orphans.length > 0) {
130
177
  return fail(`nenhuma suíte declarada roda: ${orphans.map((o) => `${o.id} (${o.file})`).join(', ')}\nDeclare o nó em project.tests[] (.morph/config/config.json).`, EXIT.usage);
131
178
  }
179
+ if (stackOnly.length > 0) {
180
+ const named = stackOnly.map(({ region, node }) => `${region.id} (${region.file}) → nó "${node.id}"`).join(', ');
181
+ return fail(`só uma suíte e2e: true cobre: ${named}\nProve a guarda por um teste de unidade, ou: ${e2eManualProtocol(feature, stackOnly[0].node)}`, EXIT.usage);
182
+ }
132
183
  const mtp = groups.find((g) => g.node.runner === 'dotnet' && dotnetTestMode(resolve(root, g.node.cwd || '.')) === 'mtp');
133
184
  if (mtp) return fail(`a suíte "${mtp.node.id}" roda no Microsoft.Testing.Platform (global.json): o TRX do VSTest, de onde o veredito e o motivo saem, não existe nela. Suporte a MTP ainda não há — rode a mutação com a suíte inteira à mão.`, EXIT.usage);
134
185
  const uncompilable = groups.find((g) => g.node.runner === 'dotnet' && dotnetBuildCommands(g.node, root).length === 0);
135
- if (uncompilable) return fail(`a suíte "${uncompilable.node.id}" não aponta nenhum .csproj (solution, ou nada): sem saber o que compilar, todo mutante rodaria sobre o binário antigo e sairia "sobrevivente". Declare o campo build no nó (config.json, project.tests[]).`, EXIT.usage);
186
+ if (uncompilable) return fail(`a suíte "${uncompilable.node.id}" não deixa descobrir o que compilar: o comando não nomeia .sln/.slnx/.csproj e o diretório alvo não tem uma única solution (ou um único projeto). Sem isso, todo mutante rodaria sobre o binário antigo e sairia "sobrevivente". Nomeie a solution no comando ou declare o campo build no nó (config.json, project.tests[]).`, EXIT.usage);
136
187
  const preDecided = groups.find((g) => g.node.status);
137
188
  if (preDecided) return fail(`a suíte "${preDecided.node.id}" já vem decidida como ${preDecided.node.status}: ${preDecided.node.reason ?? ''}`, EXIT.usage);
189
+ const uncited = uncitedUnderE2e({ root, groups, nodes });
190
+ if (uncited.length > 0) {
191
+ const named = uncited.map(({ region, node, types }) => `${region.id} (${region.file}; tipos: ${types.join(', ') || 'nenhum legível'}) → nenhum teste da suíte "${node.id}" os cita`).join('\n');
192
+ const e2eIds = uncited[0].e2e.map((n) => `"${n.id}"`).join(', ');
193
+ return fail(`${named}\nNó e2e: true declarado: ${e2eIds}. ${e2eManualProtocol(feature, uncited[0].e2e[0])}`, EXIT.usage);
194
+ }
138
195
 
139
196
  if (!options.allowDirty) {
140
197
  const dirty = gitOut(root, ['status', '--porcelain', '--', ...new Set(regions.map((r) => r.file))]).trim();
@@ -184,15 +241,18 @@ export async function mutateCommand(feature, options = {}) {
184
241
  const order = new Map(regions.map((r, i) => [r.id, i]));
185
242
  merged.mutants.sort((a, b) => order.get(a.id) - order.get(b.id));
186
243
  const section = renderSection(merged, { head: git.head(), date: new Date().toISOString().slice(0, 10), subset: subsets[0] ?? null });
187
- const outPath = resolve(root, options.out ?? join('.morph', 'features', feature ?? '_', '4-review', 'mutation-section.md'));
188
- mkdirSync(join(outPath, '..'), { recursive: true });
189
- writeFileSync(outPath, section);
244
+ const outRel = options.out ?? (feature ? join('.morph', 'features', feature, '4-review', 'mutation-section.md') : null);
245
+ const outPath = outRel ? resolve(root, outRel) : null;
246
+ if (outPath) {
247
+ mkdirSync(join(outPath, '..'), { recursive: true });
248
+ writeFileSync(outPath, section);
249
+ }
190
250
 
191
251
  const tally = summarize(merged);
192
252
  if (options.json) console.log(JSON.stringify({ ...merged, tally, section: outPath }, null, 2));
193
253
  else {
194
254
  console.log(section);
195
- console.log(chalk.gray(`seção gravada em ${outPath}`));
255
+ console.log(chalk.gray(outPath ? `seção gravada em ${outPath}` : 'seção não gravada: sem feature nem --out'));
196
256
  console.log(`${chalk.green(`${tally.killed} mortos`)} · ${tally.survived ? chalk.yellow(`${tally.survived} sobreviveram`) : '0 sobreviveram'} · ${tally['build-error']} não compilaram · ${tally.inconclusive} inconclusivos`);
197
257
  }
198
258
  if (!merged.restore.ok || ['mismatch', 'unlocated'].includes(merged.binary.proof) || !merged.restore.treeClean) process.exitCode = EXIT.unsafe;
@@ -0,0 +1,81 @@
1
+ import chalk from 'chalk';
2
+ import { existsSync, readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { atomicWriteJson } from '../../core/state/state-manager.js';
5
+ import { isGateApproved } from '../../../framework/hooks/shared/state-reader.js';
6
+ import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
7
+ import { parsePlan } from '../../lib/plan/plan-parser.js';
8
+ import { buildChecks, knownPersonas, parallelGroups, planHash, validatePlan } from '../../lib/plan/checks.js';
9
+ import { knownSurfaces } from '../../lib/specs/screen-checklist.js';
10
+ import { revocationWarnings } from '../../lib/specs/adr-index.js';
11
+ import { assertOwnerTree, ownerTreeRefusalLines } from '../../lib/worktree/feature-truth.js';
12
+ import { seedDotnetBuildIgnores } from '../../lib/stack/dotnet-build-ignores.js';
13
+ import { reportDotnetIgnores } from '../state/advance.js';
14
+
15
+ function readJson(path) {
16
+ try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return null; }
17
+ }
18
+
19
+ /** Parse and judge `2-plan/plan.md` of the project at `cwd`; with no error, write `2-plan/checks.json`. */
20
+ export function runPlanCheck(featureName, cwd = process.cwd()) {
21
+ const planPath = getAbsoluteOutputPath(cwd, featureName, 'plan');
22
+ const checksPath = getAbsoluteOutputPath(cwd, featureName, 'checks');
23
+ const base = { feature: featureName, plan: planPath, checksPath };
24
+ if (!existsSync(planPath)) return { ...base, ok: false, exit: 2, error: 'plan-not-found', errors: [], warnings: [], groups: [] };
25
+
26
+ const text = readFileSync(planPath, 'utf8');
27
+ const plan = parsePlan(text);
28
+ const { errors, warnings } = validatePlan(plan, { personas: knownPersonas(cwd), surfaces: knownSurfaces(cwd) });
29
+ warnings.push(...revocationWarnings(cwd, plan));
30
+ const groups = errors.length === 0 ? parallelGroups(plan.slices) : [];
31
+ const hash = planHash(text);
32
+ const result = { ...base, planHash: hash, errors, warnings, groups, written: false };
33
+ if (errors.length > 0) return { ...result, ok: false, exit: 1 };
34
+
35
+ const approved = isGateApproved(featureName, 'plan', cwd);
36
+ const previous = existsSync(checksPath) ? readJson(checksPath) : null;
37
+ if (approved && previous && previous.planHash !== hash) {
38
+ return { ...result, ok: false, exit: 1, error: 'frozen', previousHash: previous.planHash ?? null };
39
+ }
40
+
41
+ atomicWriteJson(checksPath, buildChecks(featureName, text, plan));
42
+ return { ...result, ok: true, exit: 0, written: true };
43
+ }
44
+
45
+ export function planCheckCommand(featureName, options = {}) {
46
+ const owner = assertOwnerTree(featureName, process.cwd());
47
+ if (owner.blocked) {
48
+ for (const { tone, text } of ownerTreeRefusalLines(featureName, owner.tree, owner.branch, `morph-spec plan-check ${featureName}`)) {
49
+ console.error(chalk[tone](text));
50
+ }
51
+ process.exit(1);
52
+ }
53
+
54
+ const result = runPlanCheck(featureName);
55
+ const gitignoreAdded = result.written ? seedDotnetBuildIgnores(process.cwd(), { featureName }) : [];
56
+ if (options.json) {
57
+ if (gitignoreAdded.length) result.gitignoreAdded = gitignoreAdded;
58
+ console.log(JSON.stringify(result, null, 2));
59
+ process.exit(result.exit);
60
+ }
61
+
62
+ if (result.error === 'plan-not-found') {
63
+ console.error(chalk.red(`plan-check: ${result.plan} não existe — o create escreve o esqueleto em 2-plan/plan.md (chore/hotfix: você o cria); gramática completa: morph-spec plan-check --help`));
64
+ process.exit(2);
65
+ }
66
+ for (const e of result.errors) console.log(chalk.red(` ✗ ${e}`));
67
+ for (const w of result.warnings) console.log(chalk.yellow(` ⚠ ${w}`));
68
+
69
+ if (result.error === 'frozen') {
70
+ console.log(chalk.red(`✗ plan-check: o gate plan de ${featureName} está aprovado e o plan.md mudou fora da errata — checks.json congelado.`));
71
+ console.log(chalk.gray(' Fato errado sobre o código: acrescente em ## Errata (não muda o planHash).'));
72
+ console.log(chalk.gray(` Mudança real de escopo: morph-spec unapprove ${featureName} plan, rode plan-check e re-apresente o gate.`));
73
+ } else if (!result.ok) {
74
+ console.log(chalk.red(`✗ plan-check: ${result.errors.length} erro(s) em ${result.plan} — checks.json não foi gravado`));
75
+ } else {
76
+ console.log(chalk.green(`✓ plan-check: ${join('2-plan', 'checks.json')} gravado (${result.planHash.slice(0, 12)})`));
77
+ result.groups.forEach((g, i) => console.log(chalk.gray(` onda ${i + 1}: ${g.join(', ')}${g.length > 1 ? ' (paralelo)' : ''}`)));
78
+ reportDotnetIgnores(gitignoreAdded);
79
+ }
80
+ process.exit(result.exit);
81
+ }