@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,132 +0,0 @@
1
- /**
2
- * mermaid-renderer.js — pure renderer: tasks.json DAG -> Mermaid flowchart TD.
3
- *
4
- * Backs `morph-spec dag <feature> --mermaid` (S8-T1), so the plan's DAG can be
5
- * visualized at Gate 2 (IndyDevDan rec. 2: draw the workflow in Mermaid before
6
- * automating it).
7
- *
8
- * STRICT Mermaid rules enforced here (see project CLAUDE.md — violating these
9
- * breaks rendering in Obsidian):
10
- * - every node label wrapped in double quotes, no exceptions
11
- * - labels never start with a numeric-with-dot prefix ("1. ", "2. ...") —
12
- * Obsidian's markdown-list parser chokes on it even inside quotes
13
- * - `&` inside labels -> `e`
14
- * - line breaks inside labels -> `<br/>`, never a literal `\n`
15
- * - double quotes inside titles are stripped so they can't break out of the
16
- * surrounding label quotes
17
- *
18
- * Pure module: no fs, no Date.now()/Math.random(). Tasks are sorted by
19
- * numeric id so output is stable regardless of input order.
20
- */
21
-
22
- const MAX_TITLE_LENGTH = 40;
23
-
24
- const VALID_STATUS_CLASSES = ['done', 'in_progress', 'blocked', 'skipped', 'pending'];
25
-
26
- // Web-safe fill/stroke pairs, one per task status.
27
- const CLASS_DEFS = [
28
- 'classDef done fill:#c6f6d5,stroke:#2f855a,color:#22543d',
29
- 'classDef in_progress fill:#bee3f8,stroke:#2b6cb0,color:#2a4365',
30
- 'classDef blocked fill:#feb2b2,stroke:#c53030,color:#63171b',
31
- 'classDef skipped fill:#e2e8f0,stroke:#718096,color:#2d3748',
32
- 'classDef pending fill:#edf2f7,stroke:#a0aec0,color:#2d3748',
33
- ];
34
-
35
- function numericId(id) {
36
- const match = /^T(\d+)$/.exec(id ?? '');
37
- return match ? parseInt(match[1], 10) : Number.POSITIVE_INFINITY;
38
- }
39
-
40
- /**
41
- * Sanitize arbitrary text for embedding inside a double-quoted Mermaid node
42
- * label, per the project's strict Mermaid rules.
43
- *
44
- * - double quotes stripped (they would close the label's own quotes)
45
- * - `&` -> `e`
46
- * - truncated to MAX_TITLE_LENGTH chars + `…`
47
- * - line breaks -> `<br/>`
48
- *
49
- * Ordering matters: truncation runs on the raw text BEFORE newlines expand to
50
- * `<br/>`. Newline->`<br/>` is the only length-changing substitution (1 char ->
51
- * 5); truncating after it could slice a tag mid-way ("...<b…"), so we truncate
52
- * first — every emitted `<br/>` is then guaranteed whole. The char-for-char
53
- * substitutions (`"`->`'`, `&`->`e`) don't move the truncation boundary.
54
- */
55
- export function sanitizeLabel(text) {
56
- let out = String(text ?? '');
57
- out = out.replace(/"/g, "'");
58
- out = out.replace(/&/g, 'e');
59
- if (out.length > MAX_TITLE_LENGTH) {
60
- out = `${out.slice(0, MAX_TITLE_LENGTH)}…`;
61
- }
62
- out = out.replace(/\r\n|\r|\n/g, '<br/>');
63
- return out;
64
- }
65
-
66
- /**
67
- * Same fallback as groupOf() in dispatch-planner.js / groupTasks() in
68
- * tasks-json-parser.js — tasks without a `group` field are treated as
69
- * 'backend'.
70
- */
71
- function groupOf(task) {
72
- return task.group || 'backend';
73
- }
74
-
75
- function statusClass(status) {
76
- return VALID_STATUS_CLASSES.includes(status) ? status : 'pending';
77
- }
78
-
79
- /**
80
- * Render a parsed tasks.json document as a Mermaid `flowchart TD`.
81
- *
82
- * @param {{ tasks?: Array<{ id: string, title: string, dependencies?: string[], effort?: string, group?: string, status?: string }> }} tasksData
83
- * @returns {string}
84
- */
85
- export function renderMermaidFlowchart(tasksData) {
86
- const tasks = Array.isArray(tasksData?.tasks) ? tasksData.tasks : [];
87
-
88
- if (tasks.length === 0) {
89
- return ['flowchart TD', ' empty["sem tasks"]'].join('\n');
90
- }
91
-
92
- const sorted = [...tasks].sort((a, b) => numericId(a.id) - numericId(b.id));
93
- const knownIds = new Set(sorted.map(t => t.id));
94
-
95
- // Group tasks by `group`, preserving the deterministic sorted-by-id order
96
- // in which each group is first encountered.
97
- const groups = new Map();
98
- for (const task of sorted) {
99
- const g = groupOf(task);
100
- if (!groups.has(g)) groups.set(g, []);
101
- groups.get(g).push(task);
102
- }
103
-
104
- const lines = ['flowchart TD'];
105
-
106
- for (const [group, groupTasks] of groups) {
107
- lines.push(` subgraph ${group}["${sanitizeLabel(group)}"]`);
108
- for (const task of groupTasks) {
109
- const label = `${task.id} · ${sanitizeLabel(task.title)} (${task.effort})`;
110
- lines.push(` ${task.id}["${label}"]`);
111
- }
112
- lines.push(' end');
113
- }
114
-
115
- // Edges — one per dependency. Dependencies pointing at a non-existent id
116
- // are silently skipped (never throw on a dangling reference).
117
- for (const task of sorted) {
118
- const deps = Array.isArray(task.dependencies) ? task.dependencies : [];
119
- for (const dep of deps) {
120
- if (!knownIds.has(dep)) continue;
121
- lines.push(` ${dep} --> ${task.id}`);
122
- }
123
- }
124
-
125
- lines.push(...CLASS_DEFS);
126
-
127
- for (const task of sorted) {
128
- lines.push(` class ${task.id} ${statusClass(task.status)}`);
129
- }
130
-
131
- return lines.join('\n');
132
- }
@@ -1,30 +0,0 @@
1
- /**
2
- * Task classifier — identifies test/validation and infrastructure tasks by title.
3
- */
4
-
5
- const TEST_TASK_PATTERN = /\b(tests?|testing|specs?|coverage|e2e|unit|integration|integra[cç][aã]o|valida[cç][aã]o|cobertura|testes?)\b/i;
6
-
7
- /**
8
- * Returns true if the task title indicates a testing or validation task.
9
- *
10
- * @param {string} taskTitle
11
- * @returns {boolean}
12
- */
13
- export function isTestTask(taskTitle) {
14
- if (!taskTitle) return false;
15
- return TEST_TASK_PATTERN.test(taskTitle);
16
- }
17
-
18
- const INFRA_TASK_PATTERN = /\b(infrastructure|infra|cleanup|migration|migra[cç][aã]o|config|setup|ci\/cd|devops|deploy|docker|packages?|dependencies|tags)\b/i;
19
-
20
- /**
21
- * Returns true if the task title indicates an infrastructure/cleanup/migration task.
22
- * Used to skip contract validation and suppress breaking change warnings.
23
- *
24
- * @param {string} taskTitle
25
- * @returns {boolean}
26
- */
27
- export function isInfrastructureTask(taskTitle) {
28
- if (!taskTitle) return false;
29
- return INFRA_TASK_PATTERN.test(taskTitle);
30
- }
@@ -1,209 +0,0 @@
1
- /**
2
- * Task Parser — Shared utilities for reading, parsing, and grouping tasks.
3
- *
4
- * Extracted from bin/task-manager.cjs and dispatch-agents.js so all
5
- * consumers share the same parsing logic without duplication.
6
- */
7
-
8
- import { readFile } from 'fs/promises';
9
- import { readFileSync, existsSync } from 'fs';
10
- import { join } from 'path';
11
-
12
- // ============================================================================
13
- // TASK_CATEGORY_TO_GROUP — maps task categories to parallel groups
14
- // ============================================================================
15
-
16
- const TASK_CATEGORY_TO_GROUP = {
17
- 'domain': 'backend',
18
- 'domain-bc': 'backend',
19
- 'infrastructure': 'backend',
20
- 'application': 'backend',
21
- 'presentation': 'frontend',
22
- 'tests': 'tests',
23
- 'infra': 'infra',
24
- 'docs': 'docs',
25
- };
26
-
27
- // ============================================================================
28
- // parseTaskList — structured task parsing with error reporting
29
- // ============================================================================
30
-
31
- /**
32
- * Parse tasks.md content into structured task objects with error reporting.
33
- *
34
- * @param {string} content - Raw tasks.md content
35
- * @returns {{ tasks: ParsedTask[], errors: TaskParseError[], groups: Object<string, string[]> }}
36
- */
37
- export function parseTaskList(content) {
38
- const tasks = [];
39
- const errors = [];
40
- const groups = {};
41
-
42
- const taskHeadingRe = /^###\s+(T\d{3,})\s*[—–:\-]\s*(.+)$/gm;
43
- const groupRe = /(?:\*\*Group\*\*\s*:\s*`?([a-z-]+)`?|- \*\*Group:\*\*\s*`?([a-z-]+)`?)/i;
44
- const categoryRe = /(?:\*\*category\*\*\s*:\s*`?([a-z-]+)`?|category:\s*([a-z-]+))/i;
45
-
46
- let headingMatch;
47
- const taskPositions = [];
48
-
49
- while ((headingMatch = taskHeadingRe.exec(content)) !== null) {
50
- taskPositions.push({ id: headingMatch[1], title: headingMatch[2].trim(), pos: headingMatch.index });
51
- }
52
-
53
- for (let i = 0; i < taskPositions.length; i++) {
54
- const { id, title, pos } = taskPositions[i];
55
- const end = i + 1 < taskPositions.length ? taskPositions[i + 1].pos : content.length;
56
- const block = content.slice(pos, end);
57
-
58
- // Try group field first (v4), fall back to category field (v3)
59
- const grpMatch = groupRe.exec(block);
60
- const catMatch = categoryRe.exec(block);
61
- let group;
62
- if (grpMatch) {
63
- group = (grpMatch[1] || grpMatch[2]).toLowerCase();
64
- } else if (catMatch) {
65
- const category = (catMatch[1] || catMatch[2]).toLowerCase();
66
- group = TASK_CATEGORY_TO_GROUP[category] || 'backend';
67
- } else {
68
- group = 'backend';
69
- }
70
-
71
- tasks.push({ id, title, group });
72
-
73
- if (!groups[group]) groups[group] = [];
74
- groups[group].push(id);
75
- }
76
-
77
- return { tasks, errors, groups };
78
- }
79
-
80
- // ============================================================================
81
- // parseAndGroupTasks — drop-in replacement for dispatch-agents.js
82
- // ============================================================================
83
-
84
- /**
85
- * Parse tasks.md and return a map of { group -> taskId[] }.
86
- * Gracefully returns {} if the file doesn't exist or is unparseable.
87
- *
88
- * @param {string} tasksPath - Absolute path to tasks.md
89
- * @returns {Object<string, string[]>}
90
- */
91
- export function parseAndGroupTasks(tasksPath) {
92
- if (!existsSync(tasksPath)) return {};
93
-
94
- try {
95
- const content = readFileSync(tasksPath, 'utf8');
96
- return parseTaskList(content).groups;
97
- } catch {
98
- return {};
99
- }
100
- }
101
-
102
- // ============================================================================
103
- // parseTasksMd
104
- // ============================================================================
105
-
106
- /**
107
- * Parse tasks.md to extract task stubs for the v3 state format.
108
- * Accepts headings in either format:
109
- * ### T001 — Task title (em-dash / en-dash / hyphen)
110
- * ### T001: Task title (colon)
111
- *
112
- * @param {string} featureName
113
- * @param {string} [cwd] - Project root (defaults to process.cwd())
114
- * @returns {Promise<Array<{id, title, status, dependencies, files, checkpoint}>>}
115
- */
116
- export async function parseTasksMd(featureName, cwd = process.cwd()) {
117
- const tasksPath = join(cwd, `.morph/features/${featureName}/2-plan/tasks.md`);
118
- let content = '';
119
- try {
120
- content = await readFile(tasksPath, 'utf-8');
121
- } catch {
122
- return [];
123
- }
124
-
125
- // Phase 1: collect heading positions
126
- const headingRe = /^###\s+(T\d+)\s*[—–:\-]\s*(.+)$/gm;
127
- const depRe = /\*\*Dependenc(?:y|ies):\*\*\s*(.+)/i;
128
- const positions = [];
129
- let match;
130
- while ((match = headingRe.exec(content)) !== null) {
131
- positions.push({ id: match[1], title: match[2].trim(), pos: match.index });
132
- }
133
-
134
- // Phase 2: extract body and parse dependencies
135
- const tasks = [];
136
- for (let i = 0; i < positions.length; i++) {
137
- const { id, title, pos } = positions[i];
138
- const end = i + 1 < positions.length ? positions[i + 1].pos : content.length;
139
- const block = content.slice(pos, end);
140
-
141
- let dependencies = [];
142
- const depMatch = depRe.exec(block);
143
- if (depMatch) {
144
- const val = depMatch[1].trim();
145
- if (val.toLowerCase() !== 'none') {
146
- dependencies = val.split(/[,\s]+/).filter(s => /^T\d+$/.test(s));
147
- }
148
- }
149
-
150
- tasks.push({ id, title, status: 'pending', dependencies, files: [], checkpoint: null });
151
- }
152
- return tasks;
153
- }
154
-
155
- // ============================================================================
156
- // ensureTaskList
157
- // ============================================================================
158
-
159
- /**
160
- * Ensure feature.taskList exists (array of individual task objects).
161
- *
162
- * In v3 state, feature.tasks is a counter object {total, completed, …}.
163
- * Individual task objects live in feature.taskList.
164
- *
165
- * @param {Object} feature - Feature state object (mutated in place)
166
- * @param {string} featureName
167
- * @param {string} [cwd]
168
- * @returns {Promise<Array>} The task list
169
- */
170
- export async function ensureTaskList(feature, featureName, cwd = process.cwd()) {
171
- if (Array.isArray(feature.tasks)) {
172
- // v2 format: tasks IS the array
173
- return feature.tasks;
174
- }
175
- // v3 format: use taskList or build from tasks.md
176
- if (!feature.taskList || feature.taskList.length === 0) {
177
- feature.taskList = await parseTasksMd(featureName, cwd);
178
- }
179
- return feature.taskList;
180
- }
181
-
182
- // ============================================================================
183
- // syncCounters
184
- // ============================================================================
185
-
186
- /**
187
- * After modifying taskList, sync the counter fields back to feature.tasks.
188
- * tasks.total is derived from the actual taskList length (not trusted from state).
189
- *
190
- * @deprecated Prefer reading directly from feature.taskList. Counter fields
191
- * (feature.tasks) duplicate native Claude Code task tracking. This function
192
- * is kept for backward compatibility but will be removed in a future version.
193
- *
194
- * @param {Object} feature - Feature state object (mutated in place)
195
- */
196
- export function syncCounters(feature) {
197
- if (Array.isArray(feature.tasks)) return; // v2 format — nothing to sync
198
- // Safety: ensure feature.tasks is an object (may be undefined after migration or corruption)
199
- if (!feature.tasks || typeof feature.tasks !== 'object') {
200
- feature.tasks = { total: 0, completed: 0, inProgress: 0, pending: 0 };
201
- }
202
- const list = feature.taskList || [];
203
- // Exclude expanded tasks — they are replaced by their sub-tasks
204
- const active = list.filter(t => t.status !== 'expanded');
205
- feature.tasks.total = active.length;
206
- feature.tasks.completed = active.filter(t => t.status === 'completed').length;
207
- feature.tasks.inProgress = active.filter(t => t.status === 'in_progress').length;
208
- feature.tasks.pending = active.filter(t => t.status === 'pending').length;
209
- }
@@ -1,296 +0,0 @@
1
- /**
2
- * tasks-json-parser.js — read, validate, query, mutate tasks.json
3
- *
4
- * Replaces the prior Markdown-based tasks.md parser for new features.
5
- * Legacy features that still use tasks.md are handled by task-parser.js.
6
- *
7
- * Uses a minimal hand-rolled JSON Schema validator (no AJV dep) — sufficient
8
- * for the controlled schema in framework/schemas/tasks.schema.json.
9
- */
10
-
11
- import fs from 'fs-extra';
12
- import { readFileSync, existsSync } from 'fs';
13
- import { join, dirname } from 'path';
14
- import { fileURLToPath } from 'url';
15
- import { UC_ID_RE } from '../specs/acervo-format.js';
16
-
17
- const __dirname = dirname(fileURLToPath(import.meta.url));
18
- const SCHEMA_PATH = join(__dirname, '..', '..', '..', 'framework', 'schemas', 'tasks.schema.json');
19
-
20
- let cachedSchema = null;
21
- export function getSchema() {
22
- if (cachedSchema) return cachedSchema;
23
- cachedSchema = JSON.parse(readFileSync(SCHEMA_PATH, 'utf8'));
24
- return cachedSchema;
25
- }
26
-
27
- const VALID_STATUS = ['pending', 'in_progress', 'done', 'skipped', 'blocked'];
28
- const VALID_EFFORT = ['S', 'M', 'L', 'XL'];
29
- const VALID_GROUP = ['backend', 'frontend', 'tests', 'infra', 'docs'];
30
- /** Veredito de reuso por conceito — ver framework/schemas/tasks.schema.json. */
31
- const VALID_REUSE_VERDICT = ['REUSAR', 'ESTENDER', 'CRIAR'];
32
-
33
- /**
34
- * Validate a tasks.json structure.
35
- * @returns {{ valid: boolean, errors: string[] }}
36
- */
37
- export function validateTasksJson(data) {
38
- const errors = [];
39
- if (!data || typeof data !== 'object') {
40
- errors.push('tasks.json must be an object');
41
- return { valid: false, errors };
42
- }
43
- if (data.version !== '1.0') errors.push('version must be "1.0"');
44
- if (typeof data.feature !== 'string' || !data.feature) {
45
- errors.push('feature must be a non-empty string');
46
- }
47
- if (!Array.isArray(data.tasks)) {
48
- errors.push('tasks must be an array');
49
- return { valid: false, errors };
50
- }
51
-
52
- const ids = new Set();
53
- for (const [i, task] of data.tasks.entries()) {
54
- const ctx = `tasks[${i}]`;
55
- if (!task.id || !/^T\d+$/.test(task.id)) {
56
- errors.push(`${ctx}.id must match /^T\\d+$/`);
57
- } else if (ids.has(task.id)) {
58
- errors.push(`${ctx}.id duplicate: ${task.id}`);
59
- }
60
- ids.add(task.id);
61
-
62
- if (typeof task.title !== 'string' || !task.title) errors.push(`${ctx}.title required`);
63
- if (typeof task.description !== 'string' || !task.description) errors.push(`${ctx}.description required`);
64
- if (!Array.isArray(task.dependencies)) {
65
- errors.push(`${ctx}.dependencies must be array`);
66
- } else {
67
- for (const dep of task.dependencies) {
68
- if (typeof dep !== 'string' || !/^T\d+$/.test(dep)) {
69
- errors.push(`${ctx}.dependencies[${dep}] must match /^T\\d+$/`);
70
- }
71
- }
72
- }
73
- if (!VALID_EFFORT.includes(task.effort)) {
74
- errors.push(`${ctx}.effort must be one of ${VALID_EFFORT.join(', ')}`);
75
- }
76
- if (typeof task.doneCriteria !== 'string' || !task.doneCriteria) {
77
- errors.push(`${ctx}.doneCriteria required`);
78
- }
79
- if (!VALID_STATUS.includes(task.status)) {
80
- errors.push(`${ctx}.status must be one of ${VALID_STATUS.join(', ')}`);
81
- }
82
- if (task.group !== undefined && !VALID_GROUP.includes(task.group)) {
83
- errors.push(`${ctx}.group must be one of ${VALID_GROUP.join(', ')}`);
84
- }
85
- // Optional: a task with no observable behaviour references no use case.
86
- // When present it must be the bare ID — `morph-spec dag` is the gate that
87
- // catches "UC-07-listar.md" or "UC7" before the plan reaches Gate 2.
88
- if (task.usecase !== undefined && (typeof task.usecase !== 'string' || !UC_ID_RE.test(task.usecase))) {
89
- errors.push(`${ctx}.usecase must match /^UC-\\d+$/ (the ID alone, e.g. "UC-07")`);
90
- }
91
-
92
- // Optional: morph-plan may stamp a persona + standards per task (R2 token
93
- // cut — `morph-spec standards --find` replaces reading the whole
94
- // STANDARDS.json). Both are part of the plan contract and frozen after
95
- // Gate 2 by protect-spec-files (any key outside MUTABLE_SPEC_FIELDS is
96
- // blocked by construction — see that hook's top comment). Type-check
97
- // only here: no cross-check against agents.json, since stack-filtered
98
- // installs carry fewer personas than the full set and would false-fail.
99
- if (task.agent !== undefined) {
100
- if (typeof task.agent !== 'string' || !task.agent.trim()) {
101
- errors.push(`${ctx}.agent must be a non-empty string (persona id, e.g. "dotnet-senior")`);
102
- } else if (task.agent.startsWith('morph-')) {
103
- errors.push(`${ctx}.agent must be the bare persona id — no "morph-" prefix (got "${task.agent}")`);
104
- }
105
- }
106
- // Type-check e enum apenas: quem cobra o veredito contra o código é o
107
- // reuse-validator, no `morph-spec verify`.
108
- if (task.reuse !== undefined) {
109
- if (!Array.isArray(task.reuse)) {
110
- errors.push(`${ctx}.reuse must be an array of { concept, verdict, target?, why? }`);
111
- } else {
112
- task.reuse.forEach((entry, j) => {
113
- if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
114
- errors.push(`${ctx}.reuse[${j}] must be an object { concept, verdict, target?, why? }`);
115
- return;
116
- }
117
- if (typeof entry.concept !== 'string' || !entry.concept.trim()) {
118
- errors.push(`${ctx}.reuse[${j}].concept must be a non-empty string`);
119
- }
120
- if (!VALID_REUSE_VERDICT.includes(entry.verdict)) {
121
- errors.push(`${ctx}.reuse[${j}].verdict must be one of ${VALID_REUSE_VERDICT.join(', ')}`);
122
- }
123
- if (entry.target !== undefined && typeof entry.target !== 'string') {
124
- errors.push(`${ctx}.reuse[${j}].target must be a string path`);
125
- }
126
- if (entry.why !== undefined && typeof entry.why !== 'string') {
127
- errors.push(`${ctx}.reuse[${j}].why must be a string`);
128
- }
129
- });
130
- }
131
- }
132
- if (task.standards !== undefined) {
133
- if (!Array.isArray(task.standards)) {
134
- errors.push(`${ctx}.standards must be an array of paths`);
135
- } else {
136
- task.standards.forEach((s, i) => {
137
- if (typeof s !== 'string' || !s.trim()) {
138
- errors.push(`${ctx}.standards[${i}] must be a non-empty string path`);
139
- }
140
- });
141
- }
142
- }
143
- }
144
-
145
- return { valid: errors.length === 0, errors };
146
- }
147
-
148
- /**
149
- * Read and parse tasks.json from disk. Throws on validation errors.
150
- * @param {string} filePath - Absolute path to tasks.json
151
- * @returns {Promise<object>}
152
- */
153
- export async function parseTasksJson(filePath) {
154
- const data = await fs.readJson(filePath);
155
- const { valid, errors } = validateTasksJson(data);
156
- if (!valid) {
157
- throw new Error(`Invalid tasks.json:\n ${errors.join('\n ')}`);
158
- }
159
- return data;
160
- }
161
-
162
- /**
163
- * Lenient variant for read-only analysis commands (e.g. `morph-spec dag`):
164
- * a missing `version` field is defaulted to "1.0" instead of failing
165
- * validation — legacy tasks.json predating the field is still analyzable.
166
- * All other schema errors still throw exactly like `parseTasksJson`.
167
- *
168
- * @param {string} filePath - Absolute path to tasks.json
169
- * @returns {Promise<{ data: object, versionDefaulted: boolean }>}
170
- */
171
- export async function parseTasksJsonLenient(filePath) {
172
- const data = await fs.readJson(filePath);
173
- const versionDefaulted = data.version === undefined;
174
- if (versionDefaulted) data.version = '1.0';
175
-
176
- const { valid, errors } = validateTasksJson(data);
177
- if (!valid) {
178
- throw new Error(`Invalid tasks.json:\n ${errors.join('\n ')}`);
179
- }
180
- return { data, versionDefaulted };
181
- }
182
-
183
- /**
184
- * Synchronous variant — used by hooks.
185
- */
186
- export function parseTasksJsonSync(filePath) {
187
- if (!existsSync(filePath)) {
188
- throw new Error(`tasks.json not found: ${filePath}`);
189
- }
190
- const data = JSON.parse(readFileSync(filePath, 'utf8'));
191
- const { valid, errors } = validateTasksJson(data);
192
- if (!valid) {
193
- throw new Error(`Invalid tasks.json:\n ${errors.join('\n ')}`);
194
- }
195
- return data;
196
- }
197
-
198
- /**
199
- * Teto de `outputs` por task: a task inteira tem de caber num dispatch. Medido
200
- * em campo (chat-demo-simulacao): tasks de 12 a 30 outputs levaram 6 de 10
201
- * sub-agents ao teto de turnos, e cada retomada custou um contexto frio.
202
- */
203
- export const MAX_TASK_OUTPUTS = 8;
204
-
205
- /**
206
- * A régua única de "task grande demais", lida pelo `dag` e pelo `gate-check`.
207
- * Fora de `validateTasksJson` de propósito: `outputs` segue mutável depois do
208
- * Gate 2, e o teto num write de status travaria feature em voo.
209
- *
210
- * @returns {{ id: string, outputs: number }[]}
211
- */
212
- export function oversizedTasks(data) {
213
- const tasks = Array.isArray(data?.tasks) ? data.tasks : [];
214
- return tasks
215
- .filter(t => Array.isArray(t?.outputs) && t.outputs.length > MAX_TASK_OUTPUTS)
216
- .map(t => ({ id: t.id, outputs: t.outputs.length }));
217
- }
218
-
219
- /**
220
- * @returns {object[]} tasks with status === 'pending'
221
- */
222
- export function getPendingTasks(data) {
223
- return data.tasks.filter(t => t.status === 'pending');
224
- }
225
-
226
- /**
227
- * Tasks ready to run: pending AND all dependencies are 'done'.
228
- */
229
- export function getReadyTasks(data) {
230
- const doneIds = new Set(data.tasks.filter(t => t.status === 'done').map(t => t.id));
231
- return data.tasks.filter(t =>
232
- t.status === 'pending' && t.dependencies.every(dep => doneIds.has(dep))
233
- );
234
- }
235
-
236
- /**
237
- * Mutate `data` to set task status. Returns `data` for chaining.
238
- */
239
- export function updateTaskStatus(data, taskId, newStatus) {
240
- if (!VALID_STATUS.includes(newStatus)) {
241
- throw new Error(`Invalid status: ${newStatus}`);
242
- }
243
- const task = data.tasks.find(t => t.id === taskId);
244
- if (!task) throw new Error(`Task not found: ${taskId}`);
245
- task.status = newStatus;
246
- return data;
247
- }
248
-
249
- /**
250
- * Write tasks.json to disk. Validates before writing.
251
- */
252
- export async function writeTasksJson(filePath, data) {
253
- const { valid, errors } = validateTasksJson(data);
254
- if (!valid) {
255
- throw new Error(`Cannot write invalid tasks.json:\n ${errors.join('\n ')}`);
256
- }
257
- await fs.writeJson(filePath, data, { spaces: 2 });
258
- }
259
-
260
- /**
261
- * Group tasks by `group` field, falling back to 'backend' when absent.
262
- * Returns `{ [group]: taskId[] }` shape matching the legacy parseAndGroupTasks output.
263
- */
264
- export function groupTasks(data) {
265
- const groups = {};
266
- for (const task of data.tasks) {
267
- const group = task.group || 'backend';
268
- if (!groups[group]) groups[group] = [];
269
- groups[group].push(task.id);
270
- }
271
- return groups;
272
- }
273
-
274
- /**
275
- * Convert tasks.json to the legacy taskList shape used in state.json.
276
- * Lets the rest of the codebase keep using feature.taskList while migration
277
- * is in progress.
278
- *
279
- * BOUNDARY — this is the ONE place `done` becomes `completed`, and it is a
280
- * one-way translation OUT of the tasks.json vocabulary. In tasks.json the
281
- * terminal status is `done` (tasks.schema.json's enum has no `completed`);
282
- * `completed` belongs to the legacy taskList and to Claude Code's native
283
- * session tasks. Leaking `completed` back into a tasks.json write is rejected
284
- * by `protect-spec-files` — see framework/rules/morph-task-tracking.md, which
285
- * spells the split out for the author.
286
- */
287
- export function toTaskList(data) {
288
- return data.tasks.map(t => ({
289
- id: t.id,
290
- title: t.title,
291
- status: t.status === 'done' ? 'completed' : t.status,
292
- dependencies: [...t.dependencies],
293
- files: t.outputs || [],
294
- checkpoint: null,
295
- }));
296
- }