@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,36 +1,17 @@
1
- /**
2
- * The index of the ADRs that still hold across the project:
3
- * `.morph/specs/adrs/`, one file per ADR, committed next to the use-case acervo.
4
- *
5
- * Every feature writes ADRs into its own `decisions.md`, and most of them only
6
- * matter inside that feature. The index keeps the few that set a project-wide
7
- * rule, so brainstorming and plan read one folder instead of every archived
8
- * `decisions.md` — those stay as history, not as instruction.
9
- *
10
- * One file per ADR (`{feature}--ADR-{nnn}.md`), like the use-case acervo:
11
- * parallel features that index different ADRs never touch the same file, so
12
- * their archive commits merge cleanly — a single shared page conflicted on the
13
- * second merge.
14
- *
15
- * Maintained by finish/archive alone, inside the archive commit:
16
- * - enters: an ADR of the closing feature whose block carries `**Alcance:** projeto`;
17
- * - leaves: an ADR the closing feature's `spec.md` revokes in full (file deleted);
18
- * - narrows: one it revokes in part keeps its file, with `scope:` where it still holds.
19
- * Nothing to add or revoke → nothing is touched (nor created).
20
- */
1
+ // One file per ADR under `.morph/specs/adrs/`, so parallel features that index different ADRs merge cleanly.
21
2
 
22
3
  import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'fs';
23
4
  import { basename, join } from 'path';
5
+ import { readFrontmatterField, setFrontmatterField } from './frontmatter.js';
6
+ import { parseRevocations } from './revocations.js';
7
+ import { parsePlan } from '../plan/plan-parser.js';
24
8
  import { OUTPUT_SCHEMA } from '../../core/paths/output-schema.js';
25
- import { readFrontmatterField, setFrontmatterField } from './usecase-promoter.js';
26
9
 
27
10
  /** Project-relative path of the index folder (git pathspec-ready). */
28
11
  export const ADR_INDEX_PATHSPEC = join('.morph', 'specs', 'adrs');
29
12
 
30
- const ADR_HEADING_RE = /^(#{2,6})\s+(ADR-\d+)\b[\s:—–-]*(.*?)\s*$/;
31
- const ANY_HEADING_RE = /^(#{1,6})\s/;
32
- /** `**Alcance:** projeto` — also `**Alcance**: projeto`. */
33
- const PROJECT_SCOPE_RE = /^\s*\*\*Alcance:?\*\*:?\s*projeto\b/i;
13
+ const PLAN = OUTPUT_SCHEMA.plan;
14
+ const INDEX_LABEL = '.morph/specs/adrs/';
34
15
  const INDEX_FILE_RE = /^([a-z0-9][a-z0-9-]*)--(ADR-\d+)\.md$/;
35
16
 
36
17
  /** `admin/ADR-001` ↔ `admin--ADR-001.md`. */
@@ -40,61 +21,26 @@ const refOf = (name) => {
40
21
  return m ? `${m[1]}/${m[2]}` : null;
41
22
  };
42
23
 
43
- /**
44
- * The ADRs of one `decisions.md` marked as project-wide, each with its block.
45
- *
46
- * @param {string} content
47
- * @returns {Array<{ adr: string, title: string, body: string }>}
48
- */
49
- export function projectAdrs(content) {
50
- const out = [];
51
- let current = null; // { adr, title, level, marked, lines }
52
- const close = () => {
53
- if (current?.marked) out.push({ adr: current.adr, title: current.title, body: current.lines.join('\n').trim() });
54
- current = null;
55
- };
56
-
57
- for (const line of String(content ?? '').split(/\r?\n/)) {
58
- const adr = ADR_HEADING_RE.exec(line);
59
- if (adr) {
60
- close();
61
- current = { adr: adr[2], title: adr[3], level: adr[1].length, marked: false, lines: [] };
62
- continue;
63
- }
64
- const heading = ANY_HEADING_RE.exec(line);
65
- if (current && heading && heading[1].length <= current.level) { close(); continue; }
66
- if (!current) continue;
67
- if (PROJECT_SCOPE_RE.test(line)) current.marked = true;
68
- current.lines.push(line);
69
- }
70
- close();
71
- return out;
72
- }
73
-
74
24
  function renderAdrFile({ ref, title, body }, feature) {
75
- const decisions = `.morph/archive/${feature}/${OUTPUT_SCHEMA.decisions.phaseDir}/${OUTPUT_SCHEMA.decisions.filename}`;
25
+ const source = `.morph/archive/${feature}/${PLAN.phaseDir}/${PLAN.filename}`;
76
26
  return [
77
27
  '---',
78
28
  `adr: ${ref}`,
79
29
  `title: ${JSON.stringify(title)}`,
80
- `source: ${decisions}`,
30
+ `source: ${source}`,
81
31
  '---',
82
32
  '',
83
33
  `# ${ref}: ${title}`,
84
34
  '',
85
- '> Mantido pelo `morph-spec finish` — não edite à mão. Vale no projeto até que o `spec.md` de uma',
86
- '> feature o revogue: por inteiro, e este arquivo sai; em parte, e `scope:` diz onde ele ainda vale.',
35
+ '> Mantido pelo `morph-spec finish` — não edite à mão. Vale no projeto até que o `plan.md` de uma',
36
+ '> feature o revogue em `### Conflitos com o acervo`: por inteiro, e este arquivo sai; em parte, e `scope:` diz onde ele ainda vale.',
87
37
  '',
88
38
  body,
89
39
  '',
90
40
  ].join('\n');
91
41
  }
92
42
 
93
- /**
94
- * The ADRs the index holds; `[]` when there is none or it cannot be read.
95
- *
96
- * @returns {Array<{ ref: string, file: string, title: string|null, scope: string|null }>}
97
- */
43
+ /** `[]` when there is no index or it cannot be read. */
98
44
  export function readAdrIndex(cwd) {
99
45
  const dir = join(cwd, ADR_INDEX_PATHSPEC);
100
46
  try {
@@ -103,7 +49,7 @@ export function readAdrIndex(cwd) {
103
49
  const ref = refOf(file);
104
50
  if (!ref) return [];
105
51
  let content = '';
106
- try { content = readFileSync(join(dir, file), 'utf8'); } catch { /* listed, unreadable */ }
52
+ try { content = readFileSync(join(dir, file), 'utf8'); } catch {}
107
53
  return [{ ref, file, title: readFrontmatterField(content, 'title'), scope: readFrontmatterField(content, 'scope') }];
108
54
  });
109
55
  } catch {
@@ -111,30 +57,21 @@ export function readAdrIndex(cwd) {
111
57
  }
112
58
  }
113
59
 
114
- /**
115
- * Apply a closing feature to the index: its project-wide ADRs enter, and the
116
- * ADR revocations its `spec.md` declared delete a file (total) or narrow it
117
- * (parcial).
118
- *
119
- * @param {string} cwd
120
- * @param {string} featureDir - the feature folder (already archived, at close time)
121
- * @param {Array<{ id: string, change: 'total'|'parcial', scope: string|null }>} revocations - ADR entries of `parseRevocations`
122
- * @returns {{ changed: boolean, added: string[], revoked: string[], narrowed: string[],
123
- * problems: Array<{ id: string, reason: string, message: string }> }}
124
- */
125
- export function updateAdrIndex(cwd, featureDir, revocations = []) {
60
+ /** An unreadable or absent plan touches nothing. */
61
+ export function updateAdrIndex(cwd, featureDir) {
126
62
  const result = { changed: false, added: [], revoked: [], narrowed: [], problems: [] };
127
63
  const feature = basename(featureDir);
128
64
  const dir = join(cwd, ADR_INDEX_PATHSPEC);
129
65
 
130
- let marked = [];
66
+ let plan = null;
131
67
  try {
132
- const decisions = join(featureDir, OUTPUT_SCHEMA.decisions.phaseDir, OUTPUT_SCHEMA.decisions.filename);
133
- if (existsSync(decisions)) marked = projectAdrs(readFileSync(decisions, 'utf8'));
134
- } catch { /* unreadable decisions.md — nothing enters */ }
68
+ const planPath = join(featureDir, PLAN.phaseDir, PLAN.filename);
69
+ if (existsSync(planPath)) plan = parsePlan(readFileSync(planPath, 'utf8'));
70
+ } catch {}
71
+ if (!plan) return result;
135
72
 
136
- for (const { adr, title, body } of marked) {
137
- const ref = `${feature}/${adr}`;
73
+ for (const { id, title, body } of plan.adrs.filter((a) => a.scope === 'projeto')) {
74
+ const ref = `${feature}/${id}`;
138
75
  const path = join(dir, adrFileName(ref));
139
76
  const next = renderAdrFile({ ref, title, body }, feature);
140
77
  const existed = existsSync(path);
@@ -145,13 +82,15 @@ export function updateAdrIndex(cwd, featureDir, revocations = []) {
145
82
  if (!existed) result.added.push(ref);
146
83
  }
147
84
 
85
+ const { entries: revocations, problems } = parseRevocations(plan.conflicts?.text);
86
+ result.problems.push(...problems);
148
87
  for (const rev of revocations) {
149
88
  const path = join(dir, adrFileName(rev.id));
150
89
  if (!existsSync(path)) {
151
90
  result.problems.push({
152
91
  id: rev.id,
153
92
  reason: 'adr-not-indexed',
154
- message: `${rev.id} não está no índice de ADRs ativos (${ADR_INDEX_PATHSPEC.replace(/\\/g, '/')}/) — nada a retirar; o decisions.md arquivado segue como história`,
93
+ message: `${rev.id} não está no índice de ADRs ativos (${INDEX_LABEL}) — nada a retirar; o plan.md arquivado segue como história`,
155
94
  });
156
95
  continue;
157
96
  }
@@ -167,3 +106,17 @@ export function updateAdrIndex(cwd, featureDir, revocations = []) {
167
106
 
168
107
  return result;
169
108
  }
109
+
110
+ /** What the finish would fail to apply from the revocation table, reported while the plan can still change. */
111
+ export function revocationWarnings(cwd, plan) {
112
+ const { entries, problems } = parseRevocations(plan.conflicts?.text);
113
+ const warnings = problems.map((p) => `### Conflitos com o acervo: ${p.message}`);
114
+ if (entries.length === 0) return warnings;
115
+ const indexed = new Set(readAdrIndex(cwd).map((a) => a.ref));
116
+ for (const e of entries) {
117
+ if (!indexed.has(e.id)) {
118
+ warnings.push(`### Conflitos com o acervo: ${e.id} não está no índice de ADRs ativos (${INDEX_LABEL}) — o finish não terá o que retirar`);
119
+ }
120
+ }
121
+ return warnings;
122
+ }
@@ -0,0 +1,18 @@
1
+ import { readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { templateSourcePath, writeIfAbsent } from '../../core/templates/template-seed.js';
4
+ import { renderTemplateString } from '../../core/templates/template-renderer.js';
5
+ import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
6
+
7
+ export function seedDesignArtifacts(featureName, cwd = process.cwd()) {
8
+ const mockupsTemplate = readFileSync(templateSourcePath('ui-mockups', cwd), 'utf8');
9
+ const mockups = writeIfAbsent(
10
+ getAbsoluteOutputPath(cwd, featureName, 'uiMockups'),
11
+ renderTemplateString(mockupsTemplate, { FEATURE_NAME: featureName, DATE: new Date().toISOString().slice(0, 10) }),
12
+ );
13
+ const designSystem = writeIfAbsent(
14
+ join(cwd, '.morph', 'context', 'design-system.md'),
15
+ readFileSync(templateSourcePath('context-design-system', cwd), 'utf8'),
16
+ );
17
+ return { mockups, designSystem };
18
+ }
@@ -0,0 +1,31 @@
1
+ const FRONTMATTER_RE = /^(?---\r?\n)([\s\S]*?)(\r?\n---(?:\r?\n|$))/;
2
+
3
+ /** Preserves every other line, the EOL style and the body; no frontmatter block → unchanged. `value` is written as-is. */
4
+ export function setFrontmatterField(content, key, value) {
5
+ const m = FRONTMATTER_RE.exec(content);
6
+ if (!m || m.index !== 0) return content;
7
+
8
+ const [full, open, body, close] = m;
9
+ const eol = full.includes('\r\n') ? '\r\n' : '\n';
10
+ const fieldLine = new RegExp(`^${key}:[ \\t]*.*$`, 'm');
11
+ // Function replacer: a value carrying `$&` or `$1` is text, not a pattern.
12
+ const next = fieldLine.test(body)
13
+ ? body.replace(fieldLine, () => `${key}: ${value}`)
14
+ : `${body}${eol}${key}: ${value}`;
15
+
16
+ return open + next + close + content.slice(full.length);
17
+ }
18
+
19
+ /** `null` when absent; double-quoted values read as JSON strings, single-quoted as YAML (`''` is a quote). */
20
+ export function readFrontmatterField(content, key) {
21
+ const m = FRONTMATTER_RE.exec(content);
22
+ if (!m || m.index !== 0) return null;
23
+ const line = new RegExp(`^${key}:[ \\t]*(.+?)[ \\t]*$`, 'm').exec(m[2]);
24
+ if (!line) return null;
25
+ const raw = line[1].trim();
26
+ if (raw.length >= 2 && raw.startsWith('"') && raw.endsWith('"')) {
27
+ try { return JSON.parse(raw); } catch { return raw; }
28
+ }
29
+ if (raw.length >= 2 && raw.startsWith("'") && raw.endsWith("'")) return raw.slice(1, -1).replace(/''/g, "'");
30
+ return raw;
31
+ }
@@ -0,0 +1,11 @@
1
+ /** A markdown table separator row: `|---|:--:|`, with or without the outer pipes. */
2
+ export const SEPARATOR_ROW_RE = /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?\s*$/;
3
+
4
+ /** A line that belongs to a table: it carries an unescaped pipe (GFM does not require the leading one). */
5
+ export const isTableLine = (line) => /(?<!\\)\|/.test(line);
6
+
7
+ /** Splits on unescaped pipes, outer pipes optional; `\|` inside a cell is a literal pipe. */
8
+ export function tableCells(row) {
9
+ const inner = row.trim().replace(/^\|/, '').replace(/(?<!\\)\|$/, '');
10
+ return inner.split(/(?<!\\)\|/).map((c) => c.replace(/\\\|/g, '|').trim());
11
+ }
@@ -1,156 +1,85 @@
1
- /**
2
- * The plan's declaration of what a feature supersedes in the project's acervo:
3
- * the "UCs e ADRs que esta feature altera ou revoga" section of `spec.md`.
4
- *
5
- * ## UCs e ADRs que esta feature altera ou revoga
6
- *
7
- * | Id | Mudança | Substituto | Escopo em que o antigo ainda vale |
8
- * |---|---|---|---|
9
- * | UC-{nn} | parcial | UC-{nn} | {onde o antigo ainda vale} |
10
- * | {feature}/ADR-{nnn} | total | — | — |
11
- *
12
- * Columns are positional, so the header wording is free. Parsing is pure: the
13
- * finish applies the entries (`acervo-lifecycle.js`), and `dag`/`usecase
14
- * check` report the problems while `spec.md` can still be edited — it freezes
15
- * at Gate 2, so a bad row found at close time can no longer be fixed there.
16
- *
17
- * Nothing ambiguous is applied: a row with any problem, and every row of an id
18
- * declared twice, stays out of `entries`. A table inside a code fence is an
19
- * example, never a declaration.
20
- */
1
+ // plan-check reports the problems while the plan can still change, so nothing ambiguous reaches the finish.
21
2
 
22
- import { SEPARATOR_ROW_RE, UC_ID_RE, isTableLine, tableCells } from './acervo-format.js';
23
-
24
- export const REVOCATIONS_TITLE = 'UCs e ADRs que esta feature altera ou revoga';
3
+ import { SEPARATOR_ROW_RE, isTableLine, tableCells } from './gfm-table.js';
25
4
 
26
5
  /** ADR numbers restart in every feature, so only `{feature}/ADR-{nnn}` is unambiguous. */
27
6
  export const ADR_REF_RE = /^[a-z0-9][a-z0-9-]*\/ADR-\d+$/;
28
7
 
29
- const HEADING_RE = /^(#{1,6})\s+(.+?)\s*#*\s*$/;
30
8
  const FENCE_RE = /^\s*(```|~~~)/;
31
- const ID_TOKEN_RE = /\b(?:UC|ADR)-\d+\b/;
32
- /** A list item: the shape a revocation takes when the author skips the table. */
9
+ const REF_TOKEN_RE = /\b[a-z0-9][a-z0-9-]*\/ADR-\d+\b/;
33
10
  const LIST_ITEM_RE = /^\s*(?:[-*+]|\d+[.)])\s/;
34
-
35
11
  const CHANGES = new Map([['total', 'total'], ['parcial', 'parcial'], ['partial', 'parcial']]);
36
12
 
37
- /** `—`, `–`, `-` and an empty cell all mean "none". */
38
13
  const isNone = (cell) => /^(?:—|–|-)?$/.test(cell);
39
-
40
- /** Id-like cells tolerate the typography an author reaches for: `code` and **bold**. */
41
14
  const bare = (cell) => cell.replace(/^[`*]+|[`*]+$/g, '').trim();
42
15
 
43
- /**
44
- * The problems of one data row; `[]` = the row is applicable.
45
- * @returns {{ entry: object|null, problems: Array<{ id: string|null, reason: string, message: string }> }}
46
- */
47
16
  function readRow(line) {
48
17
  const problems = [];
49
18
  const problem = (id, reason, message) => problems.push({ id, reason, message });
50
19
 
51
20
  const row = tableCells(line);
52
- if (row.length < 4) {
53
- problem(null, 'malformed-row', `linha com ${row.length} coluna(s), esperado 4 (Id | Mudança | Substituto | Escopo): ${line.trim()}`);
21
+ if (row.length < 3) {
22
+ problem(null, 'malformed-row', `linha com ${row.length} coluna(s), esperado 3 (ADR | Mudança | Escopo): ${line.trim()}`);
54
23
  return { entry: null, problems };
55
24
  }
56
25
  const id = bare(row[0]);
57
- const changeCell = bare(row[1]).toLowerCase();
58
- const substitute = isNone(bare(row[2])) ? null : bare(row[2]);
59
- const scope = isNone(row[3]) ? null : row[3];
26
+ const change = CHANGES.get(bare(row[1]).toLowerCase());
27
+ const scope = isNone(row[2]) ? null : row[2];
60
28
 
61
- const kind = UC_ID_RE.test(id) ? 'uc' : ADR_REF_RE.test(id) ? 'adr' : null;
62
- if (!kind) {
63
- problem(id || null, 'malformed-id', `"${id}" não é um UC-{nn} nem um {feature}/ADR-{nnn}`);
29
+ if (!ADR_REF_RE.test(id)) {
30
+ problem(id || null, 'malformed-id', `"${id}" não é um {feature}/ADR-{nnn}`);
64
31
  return { entry: null, problems };
65
32
  }
66
-
67
- const change = CHANGES.get(changeCell);
68
33
  if (!change) problem(id, 'malformed-change', `${id}: mudança "${bare(row[1])}" — use total ou parcial`);
69
-
70
- if (kind === 'uc' && !substitute) {
71
- problem(id, 'missing-substitute', `${id}: UC revogado precisa do substituto (UC-{nn}) — é o superseded_by do acervo`);
72
- } else if (substitute && !(kind === 'uc' ? UC_ID_RE : ADR_REF_RE).test(substitute)) {
73
- problem(id, 'malformed-substitute',
74
- `${id}: substituto "${substitute}" — esperado ${kind === 'uc' ? 'UC-{nn}' : '{feature}/ADR-{nnn}'}`);
75
- } else if (substitute === id) {
76
- problem(id, 'self-substitute', `${id}: o substituto é o próprio ${id}`);
77
- }
78
-
79
34
  if (change === 'parcial' && !scope) {
80
35
  problem(id, 'missing-scope', `${id}: revogação parcial precisa dizer onde o antigo ainda vale (coluna Escopo)`);
81
36
  } else if (change === 'total' && scope) {
82
37
  problem(id, 'scope-on-total', `${id}: revogação total com escopo "${scope}" — se o antigo ainda vale em algum lugar, a mudança é parcial`);
83
38
  }
84
39
 
85
- return { entry: problems.length ? { id } : { kind, id, change, substitute, scope }, problems };
40
+ return { entry: problems.length ? { id } : { id, change, scope }, problems };
86
41
  }
87
42
 
88
- /**
89
- * @param {string} content - the whole `spec.md`
90
- * @returns {{
91
- * declared: boolean,
92
- * entries: Array<{ kind: 'uc'|'adr', id: string, change: 'total'|'parcial', substitute: string|null, scope: string|null }>,
93
- * problems: Array<{ id: string|null, reason: string, message: string }>
94
- * }}
95
- * `declared: false` — no section at all (a plan written before it existed).
96
- */
97
- export function parseRevocations(content) {
98
- const lines = String(content ?? '').split(/\r?\n/);
99
- const title = REVOCATIONS_TITLE.toLowerCase();
100
-
101
- let start = -1;
102
- let level = 0;
103
- for (let i = 0; i < lines.length; i++) {
104
- const h = HEADING_RE.exec(lines[i]);
105
- if (h && h[2].toLowerCase().startsWith(title)) { start = i + 1; level = h[1].length; break; }
106
- }
107
- if (start === -1) return { declared: false, entries: [], problems: [] };
108
-
109
- let end = lines.length;
110
- for (let i = start; i < lines.length; i++) {
111
- const h = HEADING_RE.exec(lines[i]);
112
- if (h && h[1].length <= level) { end = i; break; }
113
- }
114
-
43
+ export function parseRevocations(conflicts) {
115
44
  const problems = [];
116
- const rows = []; // { id, entry } in order, applicable or not
45
+ const rows = [];
117
46
  let fenced = false;
118
- let separatorSeen = false;
119
- let tableLines = 0;
47
+ let table = null;
120
48
 
121
- for (const line of lines.slice(start, end)) {
122
- if (FENCE_RE.test(line)) { fenced = !fenced; continue; }
49
+ for (const line of String(conflicts ?? '').split(/\r?\n/)) {
50
+ if (FENCE_RE.test(line)) { fenced = !fenced; table = null; continue; }
123
51
  if (fenced) continue;
124
52
 
125
53
  if (!isTableLine(line)) {
126
- const stray = LIST_ITEM_RE.test(line) ? ID_TOKEN_RE.exec(line) : null;
54
+ table = null;
55
+ const stray = LIST_ITEM_RE.test(line) ? REF_TOKEN_RE.exec(line) : null;
127
56
  if (stray) {
128
57
  problems.push({ id: stray[0], reason: 'outside-table',
129
- message: `${stray[0]} aparece fora da tabela — só as linhas da tabela são aplicadas no finish` });
58
+ message: `${stray[0]} aparece fora da tabela ADR | Mudança | Escopo — só as linhas da tabela são aplicadas no finish` });
130
59
  }
131
60
  continue;
132
61
  }
133
- tableLines++;
134
- if (SEPARATOR_ROW_RE.test(line)) { separatorSeen = true; continue; }
135
- if (!separatorSeen) continue; // the header row: its wording is free
136
-
62
+ if (!table) {
63
+ table = { revocations: bare(tableCells(line)[0] ?? '').toLowerCase() === 'adr', separatorSeen: false };
64
+ continue;
65
+ }
66
+ if (!table.revocations) continue;
67
+ if (!table.separatorSeen) {
68
+ if (SEPARATOR_ROW_RE.test(line)) { table.separatorSeen = true; continue; }
69
+ problems.push({ id: null, reason: 'unreadable-table',
70
+ message: 'tabela de revogação ilegível — falta a linha separadora (|---|---|---|) depois do cabeçalho; nenhuma linha foi lida' });
71
+ table.revocations = false;
72
+ continue;
73
+ }
137
74
  const { entry, problems: rowProblems } = readRow(line);
138
75
  problems.push(...rowProblems);
139
76
  if (entry) rows.push(entry);
140
77
  }
141
78
 
142
- if (tableLines > 0 && !separatorSeen) {
143
- problems.push({ id: null, reason: 'unreadable-table',
144
- message: 'tabela ilegível — falta a linha separadora (|---|---|---|---) depois do cabeçalho; nenhuma linha foi lida' });
145
- }
146
-
147
- // An id declared twice is ambiguous as a whole: none of its rows applies.
148
79
  const count = new Map();
149
80
  for (const r of rows) count.set(r.id, (count.get(r.id) ?? 0) + 1);
150
81
  for (const [id, n] of count) {
151
82
  if (n > 1) problems.push({ id, reason: 'duplicate-id', message: `${id} aparece em ${n} linhas — nenhuma foi aplicada; deixe uma só` });
152
83
  }
153
- const entries = rows.filter((r) => r.kind && count.get(r.id) === 1);
154
-
155
- return { declared: true, entries, problems };
84
+ return { entries: rows.filter((r) => r.change && count.get(r.id) === 1), problems };
156
85
  }