@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,264 +0,0 @@
1
- /**
2
- * Lifecycle of the project's acervo: a use case stops being the current
3
- * behaviour when a later feature supersedes it.
4
- *
5
- * status: superseded
6
- * superseded_by: UC-589
7
- * scope: "vitrine pública, fora de `.morph-admin-shell`" ← only when partial
8
- *
9
- * Three roles live here:
10
- * - the ONE rule for that frontmatter (`supersessionProblems`) — the check
11
- * reads it, and the writer below only ever produces what it accepts;
12
- * - the close-time writer (`applySpecRevocations`): what the feature's
13
- * `spec.md` declared it revokes lands on `.morph/specs/usecases/` and on
14
- * the ADR index, fail-open like the promotion it follows;
15
- * - the plan-time check (`checkLifecycle`), shared by `dag` and `usecase
16
- * check`, run while `spec.md` can still be fixed.
17
- */
18
-
19
- import { readFileSync, writeFileSync } from 'fs';
20
- import { join } from 'path';
21
- import { OUTPUT_SCHEMA } from '../../core/paths/output-schema.js';
22
- import {
23
- SPECS_USECASES_PATHSPEC,
24
- hasFrontmatter,
25
- readFrontmatterField,
26
- removeFrontmatterField,
27
- setFrontmatterField,
28
- } from './usecase-promoter.js';
29
- import { SUPERSEDED, UC_ID_RE, useCaseFilesById } from './acervo-format.js';
30
- import { parseRevocations } from './revocations.js';
31
- import { ADR_INDEX_PATHSPEC, readAdrIndex, updateAdrIndex } from './adr-index.js';
32
-
33
- export { SUPERSEDED };
34
-
35
- const UC = OUTPUT_SCHEMA.usecases;
36
- const ACERVO_LABEL = SPECS_USECASES_PATHSPEC.replace(/\\/g, '/');
37
- const ADR_INDEX_LABEL = `${ADR_INDEX_PATHSPEC.replace(/\\/g, '/')}/`;
38
-
39
- /** @returns {{ status: string|null, supersededBy: string|null, scope: string|null }} */
40
- export function lifecycleOf(content) {
41
- return {
42
- status: readFrontmatterField(content, 'status'),
43
- supersededBy: readFrontmatterField(content, 'superseded_by'),
44
- scope: readFrontmatterField(content, 'scope'),
45
- };
46
- }
47
-
48
- /**
49
- * The rule for a use case's supersession frontmatter.
50
- *
51
- * @param {string} content
52
- * @param {string} id - the use case's own id
53
- * @returns {string[]} one line per problem; `[]` = well formed
54
- */
55
- export function supersessionProblems(content, id) {
56
- const { status, supersededBy, scope } = lifecycleOf(content);
57
- const problems = [];
58
- if (status === SUPERSEDED) {
59
- if (!supersededBy) problems.push('status: superseded sem superseded_by');
60
- else if (!UC_ID_RE.test(supersededBy)) problems.push(`superseded_by "${supersededBy}" não é um UC-{nn}`);
61
- else if (supersededBy === id) problems.push('superseded_by aponta para o próprio use case');
62
- } else if (supersededBy !== null || scope !== null) {
63
- problems.push(`${supersededBy !== null ? 'superseded_by' : 'scope'} sem status: superseded`);
64
- }
65
- if (scope !== null && !String(scope).trim()) problems.push('scope vazio');
66
- return problems;
67
- }
68
-
69
- function readOrNull(path) {
70
- try { return readFileSync(path, 'utf8'); } catch { return null; }
71
- }
72
-
73
- const specPathOf = (featureDir) => join(featureDir, OUTPUT_SCHEMA.spec.phaseDir, OUTPUT_SCHEMA.spec.filename);
74
-
75
- /** A finished feature cannot be re-closed: the fix is the next feature's revocation row. */
76
- const FIX_NEXT_TIME = 'corrija a linha na seção de revogações do spec.md — a feature já arquivada não é reprocessada, ' +
77
- 'então a revogação corrigida entra no spec.md da próxima feature';
78
-
79
- /**
80
- * Mark the acervo use cases the entries revoke. Runs after the promotion, so
81
- * the acervo already holds every substitute this feature brought — except the
82
- * ones the promotion refused, which only LOOK present (the file under that id
83
- * belongs to another feature).
84
- *
85
- * @returns {{ superseded: Array<{id: string, file: string, supersededBy: string, scope?: string}>,
86
- * problems: Array<{id: string, reason: string, message: string}> }}
87
- */
88
- function supersedeUseCases(cwd, entries, refusedIds) {
89
- const superseded = [];
90
- const problems = [];
91
- if (!entries.length) return { superseded, problems };
92
-
93
- const dir = join(cwd, SPECS_USECASES_PATHSPEC);
94
- const files = useCaseFilesById(dir);
95
- const problem = (id, reason, message) => problems.push({ id, reason, message });
96
-
97
- for (const e of entries) {
98
- const targets = files.get(e.id) ?? [];
99
- if (targets.length === 0) {
100
- problem(e.id, 'not-in-acervo', `${e.id} não está no acervo (${ACERVO_LABEL}/) — nada foi marcado; ${FIX_NEXT_TIME}`);
101
- continue;
102
- }
103
- if (targets.length > 1) {
104
- problem(e.id, 'duplicate-in-acervo',
105
- `${e.id} tem ${targets.length} arquivos no acervo (${targets.join(', ')}) — nenhum foi marcado; o acervo precisa de um arquivo por UC`);
106
- continue;
107
- }
108
- if (refusedIds.has(e.substitute)) {
109
- problem(e.id, 'substitute-refused',
110
- `o substituto ${e.substitute} de ${e.id} não foi promovido (colisão de numeração) — ${e.id} não foi marcado`);
111
- continue;
112
- }
113
- if (!files.has(e.substitute)) {
114
- problem(e.id, 'substitute-not-found',
115
- `o substituto ${e.substitute} de ${e.id} não está no acervo — ${e.id} não foi marcado; ${FIX_NEXT_TIME}`);
116
- continue;
117
- }
118
-
119
- const file = targets[0];
120
- try {
121
- const path = join(dir, file);
122
- const before = readFileSync(path, 'utf8');
123
- if (!hasFrontmatter(before)) {
124
- problem(e.id, 'no-frontmatter', `${ACERVO_LABEL}/${file} não tem frontmatter — o status não foi aplicado`);
125
- continue;
126
- }
127
- let next = setFrontmatterField(before, 'status', SUPERSEDED);
128
- next = setFrontmatterField(next, 'superseded_by', e.substitute);
129
- // JSON-quoted: free text with `: ` or a leading backtick breaks a bare YAML scalar.
130
- next = e.scope
131
- ? setFrontmatterField(next, 'scope', JSON.stringify(e.scope))
132
- : removeFrontmatterField(next, 'scope');
133
- if (next !== before) writeFileSync(path, next, 'utf8');
134
- superseded.push({ id: e.id, file, supersededBy: e.substitute, ...(e.scope ? { scope: e.scope } : {}) });
135
- } catch {
136
- problem(e.id, 'unwritable', `${ACERVO_LABEL}/${file} não pôde ser lido/gravado — o status não foi aplicado`);
137
- }
138
- }
139
- return { superseded, problems };
140
- }
141
-
142
- /**
143
- * Apply, at close time, what the feature's `spec.md` declared it revokes.
144
- * Fail-open: nothing here may refuse the close; what did not apply comes back
145
- * in `problems`, next to the row's own parse problems.
146
- *
147
- * @param {string} cwd - project root that owns `.morph/specs/`
148
- * @param {string} featureDir - the feature folder (already moved to the archive)
149
- * @param {{ refused?: Array<{ id: string }> }} [promotion] - the promotion's result, run just before
150
- * @returns {{ superseded: Array<{id: string, file: string, supersededBy: string, scope?: string}>,
151
- * adrIndex: ReturnType<typeof updateAdrIndex>|null,
152
- * problems: Array<{id: string|null, reason: string, message: string}> }}
153
- */
154
- export function applySpecRevocations(cwd, featureDir, promotion = {}) {
155
- const out = { superseded: [], adrIndex: null, problems: [] };
156
-
157
- const spec = readOrNull(specPathOf(featureDir));
158
- const parsed = spec === null ? { entries: [], problems: [] } : parseRevocations(spec);
159
- out.problems.push(...parsed.problems);
160
-
161
- try {
162
- const refusedIds = new Set((promotion.refused ?? []).map((r) => r.id));
163
- const uc = supersedeUseCases(cwd, parsed.entries.filter((e) => e.kind === 'uc'), refusedIds);
164
- out.superseded = uc.superseded;
165
- out.problems.push(...uc.problems);
166
- } catch { /* fail-open */ }
167
-
168
- try {
169
- out.adrIndex = updateAdrIndex(cwd, featureDir, parsed.entries.filter((e) => e.kind === 'adr'));
170
- out.problems.push(...out.adrIndex.problems);
171
- } catch { /* fail-open */ }
172
-
173
- return out;
174
- }
175
-
176
- /**
177
- * Plan-time check of a feature against the acervo's lifecycle.
178
- *
179
- * Warnings only, never errors: a superseded use case is a question for the
180
- * plan, and the revocation rows are re-reported at close time anyway.
181
- *
182
- * @param {{ cwd: string, featureDir: string, tasksData?: object|null }} input
183
- * @returns {{ warnings: string[] }}
184
- */
185
- export function checkLifecycle({ cwd, featureDir, tasksData = null }) {
186
- const warnings = [];
187
- const acervoDir = join(cwd, SPECS_USECASES_PATHSPEC);
188
- const ownDir = join(featureDir, UC.phaseDir, UC.dirname);
189
- const acervo = useCaseFilesById(acervoDir);
190
- const own = useCaseFilesById(ownDir);
191
-
192
- const ownLife = new Map();
193
- for (const [id, files] of own) {
194
- const file = files[files.length - 1];
195
- const content = readOrNull(join(ownDir, file));
196
- if (content === null) continue;
197
- ownLife.set(id, lifecycleOf(content));
198
- for (const p of supersessionProblems(content, id)) warnings.push(`2-plan/usecases/${file}: ${p}`);
199
- }
200
-
201
- // The acervo can hold hundreds of use cases: read only the ones this plan touches.
202
- const acervoLife = new Map();
203
- const acervoOf = (id) => {
204
- if (acervoLife.has(id)) return acervoLife.get(id);
205
- const files = acervo.get(id) ?? [];
206
- if (files.length > 1) warnings.push(`${ACERVO_LABEL}/: ${id} tem ${files.length} arquivos (${files.join(', ')}) — o acervo precisa de um arquivo por UC`);
207
- const file = files[files.length - 1];
208
- const content = file ? readOrNull(join(acervoDir, file)) : null;
209
- const life = content === null ? null : lifecycleOf(content);
210
- if (content !== null) {
211
- for (const p of supersessionProblems(content, id)) warnings.push(`${ACERVO_LABEL}/${file}: ${p}`);
212
- }
213
- acervoLife.set(id, life);
214
- return life;
215
- };
216
-
217
- const spec = readOrNull(specPathOf(featureDir));
218
- const rev = spec === null ? { entries: [], problems: [] } : parseRevocations(spec);
219
- for (const p of rev.problems) warnings.push(`spec.md, revogações: ${p.message}`);
220
-
221
- const revokedHere = new Map();
222
- let indexedAdrs = null;
223
- for (const e of rev.entries) {
224
- if (e.kind === 'adr') {
225
- indexedAdrs ??= new Set(readAdrIndex(cwd).map((r) => r.ref));
226
- if (!indexedAdrs.has(e.id)) {
227
- warnings.push(`spec.md, revogações: ${e.id} não está no índice de ADRs ativos (${ADR_INDEX_LABEL}) — o finish não terá o que retirar`);
228
- }
229
- continue;
230
- }
231
- revokedHere.set(e.id, e);
232
- if (!acervo.has(e.id)) {
233
- warnings.push(`spec.md, revogações: ${e.id} não está no acervo (${ACERVO_LABEL}/) — o finish não terá o que marcar`);
234
- } else {
235
- acervoOf(e.id);
236
- }
237
- if (!acervo.has(e.substitute) && !own.has(e.substitute)) {
238
- warnings.push(`spec.md, revogações: o substituto ${e.substitute} de ${e.id} não existe no acervo nem em 2-plan/usecases/ — o finish não aplicará a linha`);
239
- }
240
- }
241
-
242
- const tasks = Array.isArray(tasksData?.tasks) ? tasksData.tasks : [];
243
- for (const task of tasks) {
244
- const id = task?.usecase;
245
- if (!id) continue;
246
- const inAcervo = acervoOf(id);
247
- const inOwn = ownLife.get(id);
248
- const here = revokedHere.get(id);
249
-
250
- let where = null;
251
- if (inAcervo?.status === SUPERSEDED) where = { src: 'está superseded no acervo', ...inAcervo };
252
- else if (inOwn?.status === SUPERSEDED) where = { src: 'está superseded na cópia da feature', ...inOwn };
253
- else if (here) where = { src: 'é revogado por esta feature (spec.md)', supersededBy: here.substitute, scope: here.scope };
254
- if (!where) continue;
255
-
256
- const by = where.supersededBy;
257
- const detail = [by ? `substituto: ${by}` : 'sem substituto declarado', where.scope ? `ainda vale em: ${where.scope}` : null]
258
- .filter(Boolean).join('; ');
259
- const advice = where.scope ? 'confira que a task realiza a parte que ainda vale' : by ? `cite ${by}` : 'confira o acervo';
260
- warnings.push(`${task.id}.usecase → ${id} ${where.src} (${detail}) — ${advice}`);
261
- }
262
-
263
- return { warnings };
264
- }
@@ -1,200 +0,0 @@
1
- /**
2
- * Lexical heuristic: does a task's `doneCriteria` describe an ARTIFACT (a
3
- * file, a grep, a binary) instead of behaviour observable at runtime?
4
- *
5
- * A literal `doneCriteria` travels verbatim into the test that verifies it
6
- * (`morph-implement` copies it, never paraphrases). A use-case post-condition
7
- * shaped like "nenhum arquivo em X contém Y" — even a literal `grep` command —
8
- * becomes a test that reads file text instead of exercising behaviour. This
9
- * is a WARNING, never an error: `dag`'s exit code never changes because of
10
- * it, the same way `checkUseCases`'s coverage/literalness findings do not.
11
- *
12
- * Runs over EVERY task, unlike `usecase-check.js#checkUseCases` (which
13
- * short-circuits when the feature has no use cases): a task's own
14
- * `doneCriteria` can describe an artifact with no use case in sight, and
15
- * that must still be caught. This is why it lives in its own file — it is a
16
- * standalone lexical pass, not wiring between use cases and tasks.
17
- *
18
- * Two ways a match is silenced:
19
- * - a task WITHOUT a use case can justify itself inline, with
20
- * `Por que artefato: …` anywhere in its own `doneCriteria`. A task WITH a
21
- * use case never can — it justifies in the use case, otherwise the marker
22
- * would silence every post-condition it carries at once;
23
- * - a task carrying a use-case post-condition literally is silenced only
24
- * if THAT post-condition was justified in the use case document (the
25
- * `> [!note] Por que artefato:` callout `usecase-check.js` parses).
26
- * Justified text is removed from the task's `doneCriteria` before scanning —
27
- * not skipped as a whole task — so a justified P1 never silences an
28
- * unjustified P2 carried in the same `doneCriteria`. The scan runs on the
29
- * whitespace-collapsed text with its CASE PRESERVED: an uppercase format
30
- * acronym (`arquivo em CSV`) is the only thing that tells an export from a
31
- * directory (`arquivo em src`), and lowercasing first would erase it.
32
- */
33
-
34
- import { ARTIFACT_MARKER_RE, loadUseCases, normalize } from './usecase-check.js';
35
-
36
- /**
37
- * Case-insensitive spelling of `word` inside a regex that must stay
38
- * case-SENSITIVE elsewhere — Node 18 has no inline `(?i:…)` modifier.
39
- */
40
- const anyCase = (word) => [...word].map((c) => {
41
- const lo = c.toLowerCase();
42
- const up = c.toUpperCase();
43
- return lo === up ? c : `[${lo}${up}]`;
44
- }).join('');
45
-
46
- /**
47
- * What follows "arquivo em" in a behavioural export criterion ("exporta o
48
- * arquivo em CSV", "salva o arquivo em formato aberto"): an uppercase file
49
- * format acronym or the word `formato`. Case-sensitive on purpose — `src`,
50
- * `docs` or `lib` after "arquivo em" are places, and places are artifacts.
51
- */
52
- const FORMAT_TOKEN = String.raw`\s+(?:[A-Z]{2,5}(?![\p{L}\d_])|${anyCase('formato')}(?![\p{L}]))`;
53
-
54
- /**
55
- * Vocabulary of artifact-shaped phrasing. `term` is the label surfaced in
56
- * warnings and cited by the `morph-plan` skill prose — keep it stable, a
57
- * later parity test imports it verbatim.
58
- *
59
- * Unicode lookarounds, never `\b`: in JS, `\b` does not treat accented
60
- * letters (é, á, ó…) as word characters, so it would false-boundary inside
61
- * "não" or "binário".
62
- */
63
- export const ARTIFACT_VOCABULARY = [
64
- { term: 'grep', re: /(?<![\p{L}\d_])grep(?![\p{L}\d_])/iu },
65
- { term: 'contém a string', re: /cont[ée]m\s+as?\s+strings?/iu },
66
- { term: 'o binário', re: /(?<![\p{L}])o\s+bin[áa]rio(?![\p{L}])/iu },
67
- { term: 'nenhum arquivo', re: /(?<![\p{L}])nenhum\s+arquivo(?![\p{L}])/iu },
68
- {
69
- term: 'arquivo/pasta/diretório em',
70
- re: new RegExp(
71
- String.raw`(?<![\p{L}])(?:${anyCase('arquivo')}|${anyCase('pasta')}|${anyCase('diret')}[óoÓO]${anyCase('rio')})` +
72
- String.raw`[sS]?\s+${anyCase('em')}(?![\p{L}])(?!${FORMAT_TOKEN})`,
73
- 'u',
74
- ),
75
- },
76
- { term: 'glob **/', re: /\*\*\/|\/\*\*/ },
77
- ];
78
-
79
- /**
80
- * Vocabulary terms matched in `text`, in vocabulary order, each at most once.
81
- *
82
- * @param {string} text
83
- * @returns {string[]}
84
- */
85
- export function findArtifactTerms(text) {
86
- const s = String(text ?? '');
87
- const found = [];
88
- for (const { term, re } of ARTIFACT_VOCABULARY) {
89
- if (re.test(s)) found.push(term);
90
- }
91
- return found;
92
- }
93
-
94
- /** Whitespace-collapsed, case PRESERVED — `normalize` without the lowercasing. */
95
- function collapse(text) {
96
- return String(text).replace(/\s+/g, ' ').trim();
97
- }
98
-
99
- /**
100
- * Remove every case-insensitive occurrence of `needle` (already normalized)
101
- * from `text`, keeping the case of what remains. Relies on `text` and its
102
- * lowercase form being index-aligned — the caller guarantees it.
103
- */
104
- function cut(text, needle) {
105
- if (!needle) return text;
106
- const lower = text.toLowerCase();
107
- let out = '';
108
- let from = 0;
109
- let at;
110
- while ((at = lower.indexOf(needle, from)) !== -1) {
111
- out += `${text.slice(from, at)} `;
112
- from = at + needle.length;
113
- }
114
- return out + text.slice(from);
115
- }
116
-
117
- /**
118
- * Flag every task whose `doneCriteria` describes an artifact rather than
119
- * behaviour, after removing use-case text the author already justified.
120
- *
121
- * Fail-open: a missing `usecasesDir` still checks every task (it just has no
122
- * justifications or post-conditions to consult); a malformed `tasksData`
123
- * (no `tasks` array) or a non-string `doneCriteria` never throws.
124
- *
125
- * @param {Object} tasksData - parsed tasks.json
126
- * @param {string} usecasesDir - absolute path to `2-plan/usecases`
127
- * @returns {{ warnings: string[], flagged: Array<{ task: string, usecase: string|null, labels: string[], terms: string[] }>, checked: true }}
128
- */
129
- export function checkArtifactCriteria(tasksData, usecasesDir) {
130
- const warnings = [];
131
- const flagged = [];
132
- const tasks = Array.isArray(tasksData?.tasks) ? tasksData.tasks : [];
133
-
134
- let byId = new Map();
135
- if (typeof usecasesDir === 'string') {
136
- try { byId = loadUseCases(usecasesDir); } catch { byId = new Map(); }
137
- }
138
-
139
- for (const task of tasks) {
140
- if (typeof task?.doneCriteria !== 'string') continue;
141
-
142
- let D = collapse(task.doneCriteria);
143
- if (!D) continue;
144
- // `cut` needs `D` index-aligned with its lowercase form. The rare letter
145
- // whose lowercase is longer (`İ`) breaks that; fall back to the lowercase
146
- // text, which only costs the format-acronym exception for that task.
147
- if (D.toLowerCase().length !== D.length) D = D.toLowerCase();
148
-
149
- // Inline justification is for tasks WITHOUT a use case (morph-plan: "Task
150
- // sem `usecase` justifica no próprio doneCriteria"). On a use-case task it
151
- // would silence every post-condition carried, justified or not.
152
- if (!task.usecase && ARTIFACT_MARKER_RE.test(D)) continue;
153
-
154
- const uc = task.usecase ? byId.get(task.usecase) : null;
155
- const unjustifiedLabels = [];
156
-
157
- if (uc) {
158
- // Remove justified post-condition text first, so its own vocabulary
159
- // hits (if any) never surface — and so it cannot mask what remains.
160
- // A wrapped post-condition is removed in both forms a plan legitimately
161
- // carries: the whole block, or only its first line (what `checkUseCases`
162
- // treats as THE post-condition). Longest first.
163
- for (const pc of uc.postConditions) {
164
- if (!uc.artifactJustified.has(pc.label)) continue;
165
- for (const text of [uc.artifactJustified.get(pc.label), pc.text]) {
166
- D = cut(D, normalize(text));
167
- }
168
- }
169
- // Among what is left, name the unjustified post-conditions actually
170
- // carried into this doneCriteria AND that themselves read as artifact —
171
- // judged on the whole block, since a wrapped post-condition can carry
172
- // its vocabulary on the continuation line.
173
- const carried = D.toLowerCase();
174
- for (const pc of uc.postConditions) {
175
- if (uc.artifactJustified.has(pc.label)) continue;
176
- const needle = normalize(pc.text);
177
- const block = uc.fullText.get(pc.label) ?? pc.text;
178
- if (needle && carried.includes(needle) && findArtifactTerms(block).length > 0) {
179
- unjustifiedLabels.push(pc.label);
180
- }
181
- }
182
- }
183
-
184
- const terms = findArtifactTerms(D);
185
- if (terms.length === 0) continue;
186
-
187
- const quoted = terms.join(', ');
188
- const message = unjustifiedLabels.length > 0
189
- ? `${task.id}.doneCriteria descreve artefato, não comportamento ("${quoted}") — ` +
190
- `reescreva ${task.usecase} ${unjustifiedLabels.join('/')} como efeito observável em runtime, ` +
191
- `ou justifique com "> [!note] Por que artefato:" na linha abaixo dela`
192
- : `${task.id}.doneCriteria descreve artefato, não comportamento ("${quoted}") — ` +
193
- `reescreva como efeito observável em runtime, ou acrescente "Por que artefato: …" ao próprio critério`;
194
-
195
- warnings.push(message);
196
- flagged.push({ task: task.id, usecase: task.usecase || null, labels: unjustifiedLabels, terms });
197
- }
198
-
199
- return { warnings, flagged, checked: true };
200
- }