@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,32 +1,6 @@
1
- /**
2
- * Reuse Validator — símbolo novo cujo nome já existe no codebase.
3
- *
4
- * A terceira camada do ciclo de reuso:
5
- *
6
- * PROSA (morph-plan, personas) ──► ARTEFATO (`reuse` em tasks.json)
7
- * ──► COBRANÇA (este arquivo)
8
- *
9
- * Prosa sozinha não segura — `morph-brainstorming/SKILL.md:39` já manda olhar
10
- * o que existe, e o ProspectPRO redefiniu `SimpleBuilding` em cinco arquivos da
11
- * MESMA pasta assim mesmo: só a verificação em código segura o ciclo.
12
- *
13
- * Regras, na ordem em que reduzem ruído:
14
- *
15
- * 1. Só tipo (class/record/interface/enum/struct/type) — não função nem
16
- * variável. É a categoria que o grafo casa com confiança.
17
- * 2. Só MESMA LINGUAGEM. Contrato espelhado entre C# e TS (110 casos no
18
- * ProspectPRO) é o escopo F da proposta: exige gerar tipos do contrato,
19
- * não se resolve por aviso, e fica FORA daqui de propósito.
20
- * 3. Mesma feature = achado (`error`); feature diferente = aviso
21
- * (`warning`), porque VSA prescreve DTO homônimo entre slices.
22
- * 4. Veredito `CRIAR` declarado no plano (tasks.json `reuse`) rebaixa o
23
- * achado a aviso: a decisão foi tomada no Gate 2, com justificativa em
24
- * `why`. O validador cobra a decisão AUSENTE, não a decisão tomada.
25
- *
26
- * Sem grafo o validador não roda (ADR-002: ausência de grafo nunca bloqueia).
27
- *
28
- * @module reuse-validator
29
- */
1
+ // Só tipos, só mesma linguagem (contrato espelhado C#↔TS não se resolve por aviso).
2
+ // Mesma feature = error; outra feature = warning (VSA admite DTO homônimo entre slices).
3
+ // Veredito CRIAR no plano rebaixa a warning: cobra-se a decisão AUSENTE, não a tomada.
30
4
 
31
5
  import { existsSync, readFileSync } from 'fs';
32
6
  import { execFileSync } from 'child_process';
@@ -34,34 +8,23 @@ import { join, isAbsolute } from 'path';
34
8
 
35
9
  import { loadGraph } from '../../graph/graph-loader.js';
36
10
  import { languageOf } from '../../graph/sanitize.js';
37
- import { getAbsoluteOutputPath } from '../../../core/paths/output-schema.js';
38
11
  import {
39
12
  featureOf,
40
13
  isIdiomaticLocalName,
41
14
  isScannableFile,
42
15
  scanDeclarations,
43
16
  } from './symbol-scanner.js';
17
+ import { readPlanChecks } from '../../plan/checks.js';
44
18
 
45
- /** Quantos locais citar por achado antes de resumir com "(+N)". */
46
19
  const MAX_CITED = 4;
47
20
 
48
- /**
49
- * Índice "o que já existe": nome em minúsculas -> ocorrências reais do grafo.
50
- *
51
- * Só nós com `source_file` preenchido entram — nó com `source_file: ""` é stub
52
- * não resolvido (BCL, NuGet, pacote externo), e apontar `Task` do .NET como
53
- * duplicação seria ruído garantido. É o mesmo critério do `buildIndex`.
54
- *
55
- * @param {{nodes: object[]}} graph
56
- * @returns {Map<string, Array<{name: string, file: string, language: string, feature: string}>>}
57
- */
58
21
  export function buildExistingIndex(graph) {
59
22
  const index = new Map();
60
23
 
61
24
  for (const node of graph?.nodes ?? []) {
25
+ // `source_file` vazio é stub não resolvido (BCL, NuGet): `Task` do .NET não é duplicação.
62
26
  if (!node.source_file) continue;
63
- // `_callable_class` é a marca de tipo do graphify; nós de ARQUIVO também a
64
- // carregam, e se distinguem pelo label com extensão.
27
+ // Nós de ARQUIVO também têm `_callable_class`; distinguem-se pelo label com extensão.
65
28
  if (node._callable_class !== true) continue;
66
29
  if (/\.[a-z0-9]+$/i.test(node.label)) continue;
67
30
  if (!isScannableFile(node.source_file)) continue;
@@ -81,23 +44,10 @@ export function buildExistingIndex(graph) {
81
44
  return index;
82
45
  }
83
46
 
84
- /**
85
- * Casa as declarações desta feature contra o que já existe.
86
- *
87
- * Puro e sem I/O — é aqui que a regra mora, e é isto que o teste roda contra o
88
- * grafo real do ProspectPRO.
89
- *
90
- * @param {Array<{name: string, kind: string, line: number, file: string}>} declarations
91
- * @param {Map<string, Array<{name: string, file: string, language: string, feature: string}>>} existing
92
- * @param {{verdicts?: Map<string, {verdict: string, target?: string}>}} [opts]
93
- * @returns {Array<object>} issues, uma por símbolo duplicado
94
- */
95
47
  export function findDuplicateSymbols(declarations, existing, opts = {}) {
96
48
  const verdicts = opts.verdicts ?? new Map();
97
49
  const issues = [];
98
50
 
99
- // Uma declaração por (nome, arquivo); o mesmo nome em N arquivos da feature
100
- // vira UM achado com N locais, não N achados.
101
51
  const byName = new Map();
102
52
  for (const decl of declarations) {
103
53
  const key = decl.name.toLowerCase();
@@ -112,17 +62,10 @@ export function findDuplicateSymbols(declarations, existing, opts = {}) {
112
62
  const declaredFiles = new Set(decls.map((d) => d.file));
113
63
  const language = languageOf(decls[0].file);
114
64
 
115
- // Onde mais esse nome vive: no grafo (código que já existia) e nos OUTROS
116
- // arquivos desta mesma leva (código novo que o grafo ainda não viu — ele
117
- // só é refeito depois do commit, e é exatamente o caso SimpleBuilding).
65
+ // A leva também conta: o grafo só é refeito depois do commit e ainda não viu o código novo.
118
66
  const fromGraph = (existing.get(key) ?? []).filter(
119
67
  (e) => e.language === language && !declaredFiles.has(e.file),
120
68
  );
121
- // O filtro de linguagem vale AQUI TAMBÉM, não só contra o grafo: uma leva
122
- // que toca `CatalogContracts.cs` e `catalog.types.ts` declara `CatalogItem`
123
- // nos dois, na mesma feature `Catalog/` — e sem este filtro isso vira erro,
124
- // que é precisamente o contrato espelhado C#↔TS que a proposta separa no
125
- // escopo F por não se resolver com aviso. Medido no MORPH_GHLBrain.
126
69
  const fromBatch = decls
127
70
  .slice(1)
128
71
  .filter((d) => languageOf(d.file) === language)
@@ -154,7 +97,7 @@ export function findDuplicateSymbols(declarations, existing, opts = {}) {
154
97
  ? `Reuse ou estenda o tipo existente em vez de redeclarar. Confirme com ` +
155
98
  `\`morph-spec graph explain ${decls[0].name}\` (sem grafo: ` +
156
99
  `\`grep -rn "${decls[0].name}"\`) e, se a criação for mesmo necessária, ` +
157
- 'registre o veredito CRIAR com a justificativa em "why" no tasks[].reuse da task.'
100
+ 'registre o veredito CRIAR com a justificativa em "why" na tabela ## Reuso do plan.md.'
158
101
  : `VSA tolera DTO homônimo entre slices — confirme que são conceitos ` +
159
102
  `diferentes e não a mesma coisa escrita duas vezes.`,
160
103
  });
@@ -166,67 +109,28 @@ export function findDuplicateSymbols(declarations, existing, opts = {}) {
166
109
  });
167
110
  }
168
111
 
169
- /**
170
- * Vereditos de reuso carimbados no plano: `tasks[].reuse[]`.
171
- *
172
- * O veredito mora só no `tasks.json`: dado estruturado, lido aqui e no Gate 2.
173
- *
174
- * @param {string} projectPath
175
- * @param {string} featureName
176
- * @returns {Map<string, {verdict: string, target?: string}>}
177
- */
178
112
  export function readReuseVerdicts(projectPath, featureName) {
179
113
  const verdicts = new Map();
180
114
  if (!featureName) return verdicts;
181
-
182
- try {
183
- const tasksPath = getAbsoluteOutputPath(projectPath, featureName, 'tasks');
184
- if (!existsSync(tasksPath)) return verdicts;
185
- const doc = JSON.parse(readFileSync(tasksPath, 'utf8').replace(/^/, ''));
186
- for (const task of doc.tasks ?? []) {
187
- for (const entry of task.reuse ?? []) {
188
- if (!entry?.concept) continue;
189
- verdicts.set(String(entry.concept).toLowerCase(), {
190
- verdict: String(entry.verdict || '').toUpperCase(),
191
- target: entry.target,
192
- });
193
- }
194
- }
195
- } catch {
196
- // Plano ilegível não é problema deste validador — o tasks-json-guard já
197
- // cobra o schema, e aqui a ausência de veredito só torna a cobrança mais
198
- // rígida, nunca mais frouxa.
115
+ // Sem extração, nenhum veredito: a cobrança só fica mais rígida, nunca mais frouxa.
116
+ for (const entry of readPlanChecks(projectPath, featureName).checks?.reuse ?? []) {
117
+ if (!entry?.concept) continue;
118
+ verdicts.set(String(entry.concept).toLowerCase(), {
119
+ verdict: String(entry.verdict || '').toUpperCase(),
120
+ target: entry.target,
121
+ });
199
122
  }
200
123
  return verdicts;
201
124
  }
202
125
 
203
- /**
204
- * Arquivos que esta feature criou ou alterou.
205
- *
206
- * Duas fontes, unidas, porque cada uma é cega no momento em que a outra
207
- * enxerga: `outputs` do `tasks.json` é o contrato do framework (morph-implement
208
- * §8 exige listar TODO arquivo produzido) e sobrevive ao commit; `git status`
209
- * pega o que ainda não foi registrado numa task. Sem git, sem drama.
210
- *
211
- * @param {string} projectPath
212
- * @param {string} featureName
213
- * @returns {string[]} paths relativos ao projeto, com barra normal
214
- */
126
+ // União de duas fontes cegas em momentos diferentes: `arquivos:` das fatias sobrevivem
127
+ // ao commit; `git status` pega o que o plano não previu.
215
128
  export function collectChangedFiles(projectPath, featureName) {
216
129
  const files = new Set();
217
-
218
- try {
219
- const tasksPath = getAbsoluteOutputPath(projectPath, featureName, 'tasks');
220
- if (existsSync(tasksPath)) {
221
- const doc = JSON.parse(readFileSync(tasksPath, 'utf8').replace(/^/, ''));
222
- for (const task of doc.tasks ?? []) {
223
- for (const out of task.outputs ?? []) {
224
- if (typeof out === 'string') files.add(out.replace(/\\/g, '/'));
225
- }
226
- }
130
+ for (const slice of readPlanChecks(projectPath, featureName).checks?.slices ?? []) {
131
+ for (const file of slice.files ?? []) {
132
+ if (typeof file === 'string') files.add(file.replace(/\\/g, '/'));
227
133
  }
228
- } catch {
229
- // tasks.json ausente/ilegível: sobra o git.
230
134
  }
231
135
 
232
136
  try {
@@ -238,32 +142,17 @@ export function collectChangedFiles(projectPath, featureName) {
238
142
  });
239
143
  for (const line of out.split(/\r?\n/)) {
240
144
  if (!line.trim()) continue;
241
- // "XY path" e, em rename, "XY antigo -> novo": só o destino interessa.
242
- // Os paths vêm relativos à RAIZ do repositório — que no morph-spec é a
243
- // própria árvore da feature (cada worktree é a sua raiz). Se algum dia
244
- // não coincidir com `projectPath`, o `existsSync` adiante descarta o
245
- // path: perde-se cobertura, nunca se acusa um arquivo que não existe.
145
+ // Rename vem como "XY antigo -> novo": só o destino interessa.
246
146
  const path = line.slice(3).split(' -> ').pop().replace(/^"|"$/g, '');
247
147
  if (path) files.add(path.replace(/\\/g, '/'));
248
148
  }
249
149
  } catch {
250
- // Fora de um repo git, ou git indisponível: o tasks.json já respondeu.
150
+ // Fora de um repo git, ou git indisponível: o plano já respondeu.
251
151
  }
252
152
 
253
153
  return [...files];
254
154
  }
255
155
 
256
- /**
257
- * Validador plugado no `validation-runner` (e por ele no nó `validators` do
258
- * `morph-spec verify`).
259
- *
260
- * @param {string} projectPath
261
- * @param {string} featureName
262
- * @param {object} [options]
263
- * @param {string[]} [options.changedFiles] - override da coleta (testes)
264
- * @param {object} [options._graph] - grafo já carregado (testes)
265
- * @returns {Promise<{errors: number, warnings: number, issues: object[]}>}
266
- */
267
156
  export async function validateReuse(projectPath, featureName = null, options = {}) {
268
157
  const result = { errors: 0, warnings: 0, issues: [] };
269
158
 
@@ -273,11 +162,9 @@ export async function validateReuse(projectPath, featureName = null, options = {
273
162
  })();
274
163
 
275
164
  if (!graph) {
276
- // ADR-002: projeto sem grafo (sem Python, ou grafo ainda não construído)
277
- // segue em frente. Um validador que reprovasse aqui transformaria uma
278
- // dependência opcional em pré-requisito.
165
+ // Sem grafo segue em frente: reprovar faria de uma dependência opcional um pré-requisito.
279
166
  result._skipReason = 'sem grafo de codebase — `morph-spec graph build` habilita esta verificação';
280
- // "Could not check", unlike "nothing to check": verify reports it aloud.
167
+ // "Não pude checar", diferente de "nada a checar": o verify relata em voz alta.
281
168
  result._skipUnavailable = true;
282
169
  return result;
283
170
  }
@@ -1,74 +1,32 @@
1
- /**
2
- * Extração determinística de declarações de tipo a partir do TEXTO do arquivo.
3
- *
4
- * Por que não ler as declarações do grafo: o grafo só é reconstruído depois de
5
- * um commit (o refresh em background de framework/hooks/shared/graph-refresh.js),
6
- * então o código que acabou de ser escrito — o caso que este validador existe
7
- * para pegar — ainda NÃO está nele. O grafo responde "o que já existia"; o
8
- * disco responde "o que esta feature acabou de declarar". Inverter os dois
9
- * papéis produziria um validador cego justamente na janela em que ele precisa
10
- * morder — ver graph-loader.js `getFreshness`.
11
- *
12
- * A varredura é por linha, com regex ancorada no início da declaração: uma
13
- * declaração citada dentro de uma string ou de um comentário de bloco pode
14
- * passar. O custo de errar é um aviso a mais, nunca um bloqueio silencioso.
15
- *
16
- * @module reuse-symbol-scanner
17
- */
1
+ // Lê declarações do disco, não do grafo: o grafo só é refeito depois de um commit,
2
+ // então o código recém-escrito (o que este validador existe para pegar) ainda não está nele.
18
3
 
19
4
  import { isTestPath, languageOf } from '../../graph/sanitize.js';
20
5
  import { isProjectSource } from '../../../../framework/hooks/shared/source-files.js';
21
6
 
22
- /**
23
- * Nomes que a convenção usa como tipo LOCAL de arquivo, não como conceito do
24
- * domínio. Todo componente React declara o seu `Props`; dois arquivos com
25
- * `interface Props` não são o mesmo conceito escrito duas vezes — são dois
26
- * conceitos diferentes com o nome que a convenção manda usar.
27
- *
28
- * A lista é curta de propósito e só admite nome genérico de UMA palavra: assim
29
- * que há qualificador de domínio (`BuildingFiltersProps`, `FilterOption`) o
30
- * nome volta a ser um conceito e a regra vale.
31
- */
7
+ // Tipo LOCAL de arquivo por convenção (todo componente tem seu `Props`), não conceito do
8
+ // domínio. Só nome genérico de UMA palavra: com qualificador (`FilterOption`) a regra vale.
32
9
  const IDIOMATIC_LOCAL_NAMES = new Set([
33
10
  'props', 'state', 'row', 'item', 'column', 'context', 'params', 'options',
34
11
  ]);
35
12
 
36
- /** Declaração de tipo em C#. `partial` é capturado para ser excluído depois. */
37
13
  const CSHARP_DECL =
38
14
  /^\s*(?:\[[^\]]*\]\s*)*(?:(?:public|internal|private|protected|sealed|abstract|static|readonly|new|file|partial)\s+)*(class|record|interface|enum|struct)\s+([A-Za-z_]\w*)/;
39
15
 
40
- /** Declaração de tipo em TS/JS. */
41
16
  const WEB_DECL =
42
17
  /^\s*(?:export\s+(?:default\s+)?)?(?:declare\s+)?(?:abstract\s+)?(interface|type|class|enum)\s+([A-Za-z_$][\w$]*)/;
43
18
 
44
- /** Linha que é claramente comentário — não vale a pena parsear. */
45
19
  const COMMENT_LINE = /^\s*(?:\/\/|\/\*|\*|<!--)/;
46
20
 
47
- /**
48
- * @param {string} sourceFile
49
- * @returns {boolean} arquivo de teste, pelo mesmo predicado do grafo
50
- */
51
21
  export function isTestFile(sourceFile) {
52
22
  return isTestPath(String(sourceFile || '').replace(/\\/g, '/'));
53
23
  }
54
24
 
55
- /**
56
- * @param {string} sourceFile
57
- * @returns {boolean} o arquivo entra na varredura de reuso
58
- */
59
25
  export function isScannableFile(sourceFile) {
60
26
  return isProjectSource(sourceFile) && !isTestFile(sourceFile);
61
27
  }
62
28
 
63
- /**
64
- * "Feature" de um path, com a mesma heurística do baseline
65
- * (docs/specs/code-reuse-gap/measure-duplication.mjs): o segmento após
66
- * `features/`/`Features/` quando existe, senão a pasta do arquivo. É o que
67
- * separa o achado indefensável (mesma feature) do aviso tolerado por VSA.
68
- *
69
- * @param {string} sourceFile
70
- * @returns {string}
71
- */
29
+ /** Segmento após `features/`/`Features/`, senão a pasta do arquivo. */
72
30
  export function featureOf(sourceFile) {
73
31
  const f = String(sourceFile || '').replace(/\\/g, '/');
74
32
  const m = /(?:^|\/)(?:features|Features)\/([^/]+)/.exec(f);
@@ -76,25 +34,11 @@ export function featureOf(sourceFile) {
76
34
  return f.split('/').slice(0, -1).join('/').toLowerCase();
77
35
  }
78
36
 
79
- /**
80
- * @param {string} name
81
- * @returns {boolean} nome genérico que a convenção usa como tipo local
82
- */
83
37
  export function isIdiomaticLocalName(name) {
84
38
  return IDIOMATIC_LOCAL_NAMES.has(String(name).toLowerCase());
85
39
  }
86
40
 
87
- /**
88
- * Declarações de tipo de um arquivo.
89
- *
90
- * `partial` (C#) é reconhecido e DESCARTADO: declarar a mesma classe em dois
91
- * arquivos é o propósito da palavra-chave, e sinalizá-la seria um falso
92
- * positivo garantido em qualquer projeto .NET com código gerado.
93
- *
94
- * @param {string} content - conteúdo do arquivo
95
- * @param {string} sourceFile - path, usado só para decidir a linguagem
96
- * @returns {Array<{name: string, kind: string, line: number}>}
97
- */
41
+ // `partial` (C#) é descartado: declarar a mesma classe em dois arquivos é o propósito da palavra-chave.
98
42
  export function scanDeclarations(content, sourceFile) {
99
43
  const language = languageOf(sourceFile);
100
44
  const re = language === 'dotnet' ? CSHARP_DECL : WEB_DECL;
@@ -1,89 +1,31 @@
1
- /**
2
- * traceability-check.js — advisory, feature-scope-only check: do the files
3
- * cited in recap.md's "**Files:**" bullet lists have an owning task in
4
- * tasks.json's `outputs`?
5
- *
6
- * This is the DETERMINISTIC half of a two-layer fix. The NLH half lives in
7
- * `framework/skills/core/morph-implement/SKILL.md` §7 ("outputs DEVE listar
8
- * TODO arquivo criado ou modificado pela task") and targets the root cause: a
9
- * file shipped without EVER being declared anywhere (not in outputs, not in
10
- * recap.md — the real-world case that motivated this module, see the finding
11
- * below). This module cannot catch THAT class of drift — a file absent from
12
- * both documents is invisible to a check that only compares the two documents
13
- * against each other. What it DOES catch is a narrower, complementary class:
14
- * recap.md and tasks.json DISAGREEING — an author cited a file in the recap's
15
- * prose but forgot (or a later task re-touched it without) declaring it in any
16
- * task's `outputs`. That disagreement is real signal even though it is not the
17
- * same defect the SKILL.md rule targets.
18
- *
19
- * Motivating evidence: run 2026-08-12T133732Z-vet-visit-followup-reminder —
20
- * `ApplicationDbContextFactory.cs` shipped in production with no owning task
21
- * in `outputs` AND no mention in recap.md (out of this module's reach by
22
- * construction). Separately, `src/VetClinic.Api/appsettings.json` and
23
- * `Properties/launchSettings.json` WERE cited in T1's recap `**Files:**` list
24
- * but never appear in any task's `outputs` — exactly the disagreement this
25
- * module flags.
26
- *
27
- * Advisory, never blocking: the returned `status` is only ever 'pass' | 'warn'
28
- * | 'skip' — never 'fail'. `runVerify()` keeps this result OUTSIDE `nodes` on
29
- * purpose so it can never flip the overall build/tests/validators/e2e status
30
- * computed from `nodes` alone (see verify-runner.js).
31
- *
32
- * Fail-open throughout: no recap.md, no tasks.json, or a parse error all
33
- * degrade to `{ status: 'skip' }` rather than throwing or warning about the
34
- * absence itself — this is rastreabilidade tooling, not a completeness gate.
35
- *
36
- * @module traceability-check
37
- */
1
+ // Advisory: status is only 'pass' | 'warn' | 'skip', and runVerify() keeps it outside
2
+ // `nodes` so it never flips the verify rollup.
38
3
 
39
4
  import { existsSync, readFileSync } from 'fs';
40
- import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
5
+ import { join } from 'path';
6
+ import { readPlanChecks } from '../plan/checks.js';
7
+ import { sliceProgress, sliceReportPath } from '../../../framework/hooks/shared/slice-progress.js';
41
8
 
42
- /** Matches a `**Files:**` marker followed by one or more `- ` bullet lines. */
43
- const FILES_BLOCK_RE = /\*\*Files:\*\*[ \t]*\r?\n((?:^[ \t]*-[ \t].*\r?\n?)+)/gm;
9
+ const FILES_BLOCK_RE = /\*\*Arquivos:\*\*([^\r\n]*)\r?\n?((?:^[ \t]*-[ \t].*\r?\n?)*)/gm;
44
10
 
45
- /** Backtick-quoted spans within a bullet line. */
46
11
  const BACKTICK_RE = /`([^`]+)`/g;
47
12
 
48
- /**
49
- * Extract the set of file paths cited under every "**Files:**" block in a
50
- * recap.md's text, applying a conservative decomposition of the natural
51
- * language shorthand real recaps use:
52
- *
53
- * - A line with exactly one backtick span: that span is a candidate,
54
- * whether or not it contains a `/` (a legitimate top-level file, e.g.
55
- * `` `VetClinic.sln` ``, has no slash).
56
- * - A line with multiple comma-separated backtick spans (e.g.
57
- * `` `a/b.json`, `c.json`, `d/e.json` ``): only the FIRST span and any
58
- * LATER span that itself contains `/` are candidates. A later span with
59
- * no `/` is ambiguous shorthand (a bare filename implicitly relative to
60
- * the first span's directory, or a suffix like `` `.Designer.cs` ``) and
61
- * is skipped rather than guessed — false negatives are the acceptable
62
- * failure mode for advisory tooling, false positives are not.
63
- * - Any span containing `{`/`}` (brace-expansion shorthand, e.g.
64
- * `` `Foo/{A,B,C}.cs` ``) is skipped entirely — decomposing it correctly
65
- * would require guessing at delimiters this format doesn't standardize.
66
- * - Any span ending in `/` is a directory reference, not a file — skipped.
67
- *
68
- * @param {string} recapText
69
- * @returns {string[]} de-duplicated, normalized (forward-slash) candidate paths
70
- */
71
- export function extractCitedFiles(recapText) {
13
+ // A later span without `/` is ambiguous shorthand and is skipped, not guessed: false
14
+ // negatives are acceptable for advisory tooling, false positives are not.
15
+ export function extractCitedFiles(reportText) {
72
16
  const cited = new Set();
73
- if (!recapText) return [];
17
+ if (!reportText) return [];
74
18
 
75
19
  FILES_BLOCK_RE.lastIndex = 0;
76
20
  let blockMatch;
77
- while ((blockMatch = FILES_BLOCK_RE.exec(recapText)) !== null) {
78
- const block = blockMatch[1];
79
- const lines = block.split(/\r?\n/).filter((l) => /^[ \t]*-[ \t]/.test(l));
80
-
81
- for (const line of lines) {
21
+ while ((blockMatch = FILES_BLOCK_RE.exec(reportText)) !== null) {
22
+ const bullets = blockMatch[2].split(/\r?\n/).filter((l) => /^[ \t]*-[ \t]/.test(l));
23
+ for (const line of [blockMatch[1], ...bullets]) {
82
24
  const spans = [...line.matchAll(BACKTICK_RE)].map((m) => m[1].trim()).filter(Boolean);
83
25
  spans.forEach((span, idx) => {
84
- if (idx > 0 && !span.includes('/')) return; // ambiguous shorthand continuation
85
- if (span.includes('{') || span.includes('}')) return; // brace-expansion, too ambiguous to decompose
86
- if (span.endsWith('/')) return; // directory reference, not a file
26
+ if (idx > 0 && !span.includes('/')) return;
27
+ if (span.includes('{') || span.includes('}')) return;
28
+ if (span.endsWith('/')) return;
87
29
  cited.add(normalizePath(span));
88
30
  });
89
31
  }
@@ -92,114 +34,70 @@ export function extractCitedFiles(recapText) {
92
34
  return [...cited];
93
35
  }
94
36
 
95
- /**
96
- * Backslashes to forward slashes, strip a leading `./`.
97
- *
98
- * Exported because `src/lib/tasks/test-scope.js` normalises the SAME thing —
99
- * a task's `outputs` entry — before deciding which tests a task-scoped verify
100
- * runs. One normaliser for `outputs`, so the traceability check and the test
101
- * recorte never disagree about which file a task delivered.
102
- */
103
37
  export function normalizePath(p) {
104
38
  return String(p).replace(/\\/g, '/').replace(/^\.\//, '');
105
39
  }
106
40
 
107
- /**
108
- * Union of `outputs` across every `done` task in tasks.json, normalized.
109
- *
110
- * @param {string} projectPath
111
- * @param {string} featureName
112
- * @returns {string[]|null} null when tasks.json is missing/unreadable/invalid
113
- * (the caller treats null as "cannot judge coverage" and skips, not warns)
114
- */
115
- export function collectDoneOutputs(projectPath, featureName) {
116
- let tasksPath;
117
- try {
118
- tasksPath = getAbsoluteOutputPath(projectPath, featureName, 'tasks');
119
- } catch {
120
- return null;
121
- }
122
- if (!existsSync(tasksPath)) return null;
123
-
124
- let data;
125
- try {
126
- data = JSON.parse(readFileSync(tasksPath, 'utf8'));
127
- } catch {
128
- return null;
129
- }
130
-
131
- const tasks = Array.isArray(data?.tasks) ? data.tasks : [];
132
- const outputs = [];
133
- for (const task of tasks) {
134
- if (task?.status !== 'done') continue;
135
- for (const output of task.outputs || []) {
136
- if (typeof output === 'string' && output.trim()) outputs.push(normalizePath(output));
137
- }
138
- }
139
- return outputs;
41
+ /** null when the plan has no readable extraction. */
42
+ export function collectSliceFiles(projectPath, featureName) {
43
+ const checks = readPlanChecks(projectPath, featureName).checks;
44
+ if (!checks) return null;
45
+ return checks.slices.flatMap((slice) => (slice.files ?? [])
46
+ .filter((f) => typeof f === 'string' && f.trim())
47
+ .map(normalizePath));
140
48
  }
141
49
 
142
- /**
143
- * A cited file is covered if some done task's `outputs` names it exactly, or
144
- * names a directory (an entry ending in `/`) that the cited file falls under.
145
- *
146
- * @param {string} file
147
- * @param {string[]} outputs
148
- * @returns {boolean}
149
- */
150
- function isCovered(file, outputs) {
151
- for (const output of outputs) {
152
- if (output === file) return true;
153
- if (output.endsWith('/') && file.startsWith(output)) return true;
50
+ function isCovered(file, declared) {
51
+ for (const entry of declared) {
52
+ if (entry === file) return true;
53
+ if (entry.endsWith('/') && file.startsWith(entry)) return true;
154
54
  }
155
55
  return false;
156
56
  }
157
57
 
158
- /**
159
- * Run the recap-vs-outputs traceability check for a feature.
160
- *
161
- * @param {string} projectPath
162
- * @param {string} featureName
163
- * @returns {{ status: 'pass'|'warn'|'skip', warnings: string[], reason: string|null, citedCount: number }}
164
- */
165
- export function checkOutputsTraceability(projectPath, featureName) {
166
- let recapPath;
58
+ function readOrNull(path) {
167
59
  try {
168
- recapPath = getAbsoluteOutputPath(projectPath, featureName, 'recap');
60
+ return existsSync(path) ? readFileSync(path, 'utf8') : null;
169
61
  } catch {
170
- return { status: 'skip', warnings: [], reason: 'unknown feature', citedCount: 0 };
62
+ return null;
171
63
  }
64
+ }
172
65
 
173
- if (!existsSync(recapPath)) {
174
- return { status: 'skip', warnings: [], reason: 'no recap.md', citedCount: 0 };
66
+ export function checkOutputsTraceability(projectPath, featureName) {
67
+ const featureDir = join(projectPath, '.morph', 'features', featureName);
68
+ const progress = sliceProgress(featureDir);
69
+ const reports = (progress?.slices ?? [])
70
+ .map((slice) => ({ id: slice.id, text: readOrNull(sliceReportPath(featureDir, slice.id)) }))
71
+ .filter((r) => r.text !== null);
72
+ if (reports.length === 0) {
73
+ return { status: 'skip', warnings: [], reason: 'no slice reports', citedCount: 0 };
175
74
  }
176
75
 
177
- let recapText;
178
- try {
179
- recapText = readFileSync(recapPath, 'utf8');
180
- } catch {
181
- return { status: 'skip', warnings: [], reason: 'recap.md unreadable', citedCount: 0 };
76
+ const citedBy = new Map();
77
+ for (const { id, text } of reports) {
78
+ for (const file of extractCitedFiles(text)) {
79
+ if (!citedBy.has(file)) citedBy.set(file, []);
80
+ citedBy.get(file).push(id);
81
+ }
182
82
  }
183
-
184
- const cited = extractCitedFiles(recapText);
185
- if (cited.length === 0) {
186
- return { status: 'skip', warnings: [], reason: 'no Files: entries found in recap.md', citedCount: 0 };
83
+ if (citedBy.size === 0) {
84
+ return { status: 'skip', warnings: [], reason: 'no Arquivos: entries found in the slice reports', citedCount: 0 };
187
85
  }
188
86
 
189
- const outputs = collectDoneOutputs(projectPath, featureName);
190
- if (outputs === null) {
191
- return { status: 'skip', warnings: [], reason: 'no tasks.json', citedCount: cited.length };
87
+ const declared = collectSliceFiles(projectPath, featureName);
88
+ if (declared === null) {
89
+ return { status: 'skip', warnings: [], reason: 'no checks.json', citedCount: citedBy.size };
192
90
  }
193
91
 
194
- const warnings = cited
195
- .filter((file) => !isCovered(file, outputs))
92
+ const warnings = [...citedBy.keys()]
93
+ .filter((file) => !isCovered(file, declared))
196
94
  .sort()
197
- .map((file) => `${file} — cited in recap.md but not listed in any done task's outputs`);
95
+ .map((file) => `${file} — cited in reports/${citedBy.get(file).join(', ')} but declared in no slice's arquivos:`);
198
96
 
199
97
  return {
200
98
  status: warnings.length > 0 ? 'warn' : 'pass',
201
99
  warnings,
202
100
  reason: null,
203
- citedCount: cited.length,
101
+ citedCount: citedBy.size,
204
102
  };
205
103
  }