@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,20 +1,5 @@
1
- /**
2
- * Work Type Detector — deterministic keyword classifier for `text`/`declaredScope`.
3
- *
4
- * Mirrors risk-detector.js's style: pure, deterministic, no I/O. This module
5
- * is the TESTABLE MIRROR of the natural-language classification rules that
6
- * live in MORPH.md — behavior described here must stay in sync with those
7
- * rules, but this file is what `create`/`retype` call at runtime.
8
- *
9
- * Precedence: hotfix keywords win outright (a production incident never
10
- * ties down against a bug or chore signal). Otherwise bug vs. chore is
11
- * decided by keyword-match count, nudged by two weak signals:
12
- * - estimatedFiles <= 2 -> +1 toward bug (small, surgical diffs)
13
- * - declaredScope <= 6 words -> +1 toward chore (a narrowly-scoped ask)
14
- * A genuine tie between bug and chore is surfaced to the caller (LLM/HCOP
15
- * router) rather than guessed at. Zero keyword matches -> no opinion; the
16
- * caller defaults to `feature`.
17
- */
1
+ // Testable mirror of the classification rules in MORPH.md: the two must stay in sync.
2
+ // Hotfix wins outright; a bug/chore tie is returned to the caller, never guessed.
18
3
 
19
4
  // Plural/inflected forms are listed EXPLICITLY: matching is whole-word, so
20
5
  // "incidentes" is not "incidente". Recall lives here, in a list that is visible
@@ -37,38 +22,8 @@ const CHORE_KEYWORDS = [
37
22
  'limpeza', 'docs', 'documentação', 'lint', 'formatação',
38
23
  ];
39
24
 
40
- /**
41
- * @returns {string[]} the subset of `keywords` present in `haystack` as WHOLE
42
- * words (or whole phrases), never as a fragment of a larger word.
43
- *
44
- * Substring matching routed real requests to the wrong pipeline: `p0` is two
45
- * characters with outright hotfix precedence, so "endpoint group0" matched it
46
- * and skipped Gates 1 and 2 by policy — no human, and not even the LLM router,
47
- * since a substring hit reports `confidence: 'high'`. Same shape for `fix`
48
- * inside "prefix"/"fixtures" and `bug` inside "debug".
49
- *
50
- * The failure directions are asymmetric, which is what settles the trade-off: a
51
- * false POSITIVE silently skips approval gates, while a false negative usually
52
- * only lowers confidence — and low confidence defers to the LLM router (exit 2).
53
- * Matching strictly is therefore the safe error, and provably so for the vector
54
- * that motivated it: whole-word matches are a strict SUBSET of substring matches
55
- * per keyword, so narrowing can never invent a hotfix hit that substring didn't
56
- * already have. No misroute INTO the only gate-skipping pipeline can be created
57
- * here, only removed.
58
- *
59
- * Being precise about the limit of that argument, since overclaiming in a
60
- * comment is what this file is fixing: the asymmetry holds per KEYWORD, not per
61
- * CLASSIFICATION. Dropping a match can still shift the bug-vs-chore balance —
62
- * losing a bug hit may promote a chore from `tie` to `high`, and chore also
63
- * skips Gates 1 and 2. Whole-word matching DID cost recall that substring had:
64
- * "bugs" contains "bug" but is not the word "bug". The answer is to list the
65
- * forms (below), not to match loosely — recall belongs in the vocabulary, where
66
- * it is explicit and testable, not in a matcher that fires on fragments.
67
- *
68
- * `\b` is not usable here: JS word boundaries are ASCII-only, so `\bnão\b`
69
- * would find a boundary between `n` and `ã` and misbehave on half of these
70
- * pt-BR keywords. Unicode letter/number lookarounds are the portable form.
71
- */
25
+ // Whole words only: a false positive silently skips approval gates (`p0` in "group0", `fix` in
26
+ // "prefix"). `\b` is ASCII-only in JS and breaks on pt-BR letters, hence the Unicode lookarounds.
72
27
  function matchKeywords(haystack, keywords) {
73
28
  return keywords.filter((kw) => {
74
29
  const escaped = kw.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
@@ -81,18 +36,6 @@ function wordCount(str) {
81
36
  return trimmed.length === 0 ? 0 : trimmed.split(/\s+/).length;
82
37
  }
83
38
 
84
- /**
85
- * @param {object} input
86
- * @param {string} [input.text]
87
- * @param {string} [input.declaredScope]
88
- * @param {number} [input.estimatedFiles]
89
- * @returns {{
90
- * workType: string|null,
91
- * confidence: 'high'|'tie'|'none',
92
- * matched: { hotfix: string[], bug: string[], chore: string[], feature: string[] },
93
- * candidates: string[],
94
- * }}
95
- */
96
39
  export function detectWorkType({ text = '', declaredScope = '', estimatedFiles } = {}) {
97
40
  const haystack = `${text} ${declaredScope}`.toLowerCase();
98
41
 
@@ -107,7 +50,6 @@ export function detectWorkType({ text = '', declaredScope = '', estimatedFiles }
107
50
  feature: [], // no keywords of its own — the catch-all default
108
51
  };
109
52
 
110
- // Hard precedence: any hotfix signal wins outright, never ties down.
111
53
  if (hotfixMatched.length > 0) {
112
54
  return { workType: 'hotfix', confidence: 'high', matched, candidates: ['hotfix'] };
113
55
  }
@@ -119,11 +61,7 @@ export function detectWorkType({ text = '', declaredScope = '', estimatedFiles }
119
61
  let bugScore = bugMatched.length;
120
62
  let choreScore = choreMatched.length;
121
63
 
122
- // The weak nudges act ONLY as tie-breakers — apply them only when the raw
123
- // keyword counts are already equal. Otherwise a clean single-keyword match
124
- // on one type would be dragged into a tie by the other type's weak signal
125
- // (e.g. a lone "bug" keyword + a short declaredScope), routing an
126
- // unambiguous request to the LLM and risking a gate-skipping misroute.
64
+ // Weak nudges only break ties: applied otherwise, they drag a clean single-keyword match into a tie.
127
65
  if (bugMatched.length === choreMatched.length) {
128
66
  if (typeof estimatedFiles === 'number' && estimatedFiles <= 2) {
129
67
  bugScore += 1;
@@ -1,41 +1,8 @@
1
- /**
2
- * docker-audit.js — the part of "which worktrees exist" that the filesystem
3
- * cannot answer: the docker stacks still running for them.
4
- *
5
- * `worktree-audit.js` reconciles git's registry, the disk and morph's features.
6
- * None of the three can see a CONTAINER. A stack survives everything they look
7
- * at: `finish` removes the worktree and the branch, the directory disappears,
8
- * git forgets the registration — and `morph-{feature}` keeps running, holding
9
- * the feature's whole port block (and its named volumes) against the next
10
- * worktree that lands on the same block. The failure surfaces later, somewhere
11
- * else, as `port is already allocated` during an `e2e up` that has nothing to
12
- * do with the stack that is actually holding the port.
13
- *
14
- * Two findings, both `severity: 'warn'` — reported, never a failure of
15
- * `doctor --worktrees` (`doctor.js` treats `info`/`warn` as non-problems):
16
- *
17
- * • docker-stack-orphan — a `morph-*` compose project of THIS repository
18
- * running for a feature this tree does not know
19
- * • docker-port-held — a port from a feature's allocated block published
20
- * by a container belonging to a DIFFERENT compose project
21
- *
22
- * This module does not go into `doctor --full`, on purpose (D10): every check
23
- * there must be satisfiable in the framework repo itself, and a docker check in
24
- * a repo with no compose file could only be permanently yellow. A check that
25
- * can never pass where it runs teaches nothing.
26
- *
27
- * Nothing here mutates anything — not the registry it reads (see
28
- * `allocatedFeatures`), and certainly not docker. The `fix` strings name the
29
- * destructive command; a human runs it.
30
- *
31
- * @module lib/worktree/docker-audit
32
- */
33
-
1
+ // Stays out of `doctor --full`: a docker check in a repo with no compose file
2
+ // could only be permanently yellow. Read-only; the `fix` strings are for a human.
34
3
  import { execFileSync } from 'child_process';
35
4
  import { resolve } from 'path';
36
5
 
37
- // src/ → framework/hooks/shared/ is the allowed direction (hooks are installed
38
- // standalone into target projects, where src/ does not exist).
39
6
  import {
40
7
  composeProjectName,
41
8
  defaultExec,
@@ -44,19 +11,9 @@ import {
44
11
  } from '../../../framework/hooks/shared/e2e-check.js';
45
12
  import { allocateEnvBlock, allocatedFeatures, portLayout, resolveFleetConfig } from './env-allocator.js';
46
13
 
47
- /**
48
- * The prefix `composeProjectName()` stamps on every stack morph brings up,
49
- * derived from the function itself rather than retyped — the string `morph-`
50
- * living in two places is how the two stop agreeing.
51
- */
52
14
  const PROJECT_PROBE = 'probe';
53
15
  const MORPH_PROJECT_PREFIX = composeProjectName(PROJECT_PROBE).slice(0, -PROJECT_PROBE.length);
54
16
 
55
- /**
56
- * Same fail-open resolution the other callers use: the COMMON git dir is the
57
- * one location every linked worktree shares, and it is where the port-block
58
- * registry lives.
59
- */
60
17
  function resolveGitCommonDir(cwd) {
61
18
  try {
62
19
  const common = execFileSync('git', ['rev-parse', '--git-common-dir'], {
@@ -68,32 +25,12 @@ function resolveGitCommonDir(cwd) {
68
25
  }
69
26
  }
70
27
 
71
- /**
72
- * Path comparison that matches the host's own rules. Windows paths differ in
73
- * drive-letter case between what git reports and what docker echoes back;
74
- * treating those as different paths would make every stack look foreign.
75
- *
76
- * Exported as the ONE copy of that rule: `doctor.js` compares a declared test
77
- * node `cwd` with a discovered `package.json` folder through it (#87 F5), and
78
- * `test-scope.js` takes only the CASE of a canonical path through it (#87 N3).
79
- */
28
+ // On Windows git and docker report the drive letter in different case.
80
29
  export const samePathCase = process.platform === 'win32' ? (s) => s.toLowerCase() : (s) => s;
81
30
  const norm = (p) => samePathCase(resolve(String(p)).replace(/\\/g, '/'));
82
31
 
83
- /**
84
- * Is this compose project OURS to talk about?
85
- *
86
- * The `morph-` prefix is global to the host, not to a repository: two different
87
- * repos that use morph-spec and both have a feature named `triagem` produce the
88
- * SAME compose project name. Without this test, `doctor` in repo A would report
89
- * repo B's live stack as an orphan and hand the reader a `down -v` — volumes
90
- * included — for somebody else's running database. The compose file's path is
91
- * what ties a stack to a repository, and it keeps pointing inside the root even
92
- * after the worktree directory it named is gone.
93
- *
94
- * Unattributable (no config file in the listing) counts as NOT ours: an
95
- * unknown owner is not evidence of ownership.
96
- */
32
+ // The `morph-` prefix is host-global: two repos with the same feature name share
33
+ // a project name, so ownership comes from the compose file path. No path = not ours.
97
34
  function belongsToRoot(entry, rootNorm) {
98
35
  const configFiles = String(entry?.ConfigFiles ?? '').split(',').map((s) => s.trim()).filter(Boolean);
99
36
  if (configFiles.length === 0) return false;
@@ -106,14 +43,6 @@ function belongsToRoot(entry, rootNorm) {
106
43
  });
107
44
  }
108
45
 
109
- /**
110
- * Host ports a `docker ps`/`docker compose ps` entry publishes.
111
- * Both output shapes are accepted rather than pinning a docker version:
112
- * `Ports` as the human string (`0.0.0.0:4002->5432/tcp`) and `Publishers` as
113
- * the structured array compose emits.
114
- *
115
- * @returns {Set<number>}
116
- */
117
46
  function publishedPorts(entry) {
118
47
  const ports = new Set();
119
48
  for (const pub of Array.isArray(entry?.Publishers) ? entry.Publishers : []) {
@@ -128,7 +57,6 @@ function publishedPorts(entry) {
128
57
  return ports;
129
58
  }
130
59
 
131
- /** The compose project a container belongs to, from its labels. `null` = unknown. */
132
60
  function composeProjectOf(entry) {
133
61
  const labels = String(entry?.Labels ?? '');
134
62
  for (const part of labels.split(',')) {
@@ -139,83 +67,43 @@ function composeProjectOf(entry) {
139
67
  return null;
140
68
  }
141
69
 
142
- /** Readable container name for a `docker ps` entry (Names may be a list). */
143
70
  function containerNameOf(entry) {
144
71
  const names = String(entry?.Names ?? entry?.Name ?? '').split(',').map((s) => s.trim()).filter(Boolean);
145
72
  return names[0] || String(entry?.ID ?? 'container desconhecido');
146
73
  }
147
74
 
148
- /**
149
- * Parse a docker JSON listing that may be an array or NDJSON.
150
- * `null` means the output could not be read — DISTINCT from `[]`, which means
151
- * docker answered and there was nothing running. A caller that conflated the
152
- * two would turn "could not ask" into "nothing to report", or worse, into a
153
- * finding.
154
- *
155
- * @returns {Array<object>|null}
156
- */
75
+ // `null` = unreadable, distinct from `[]` = docker answered with nothing running.
157
76
  function parseDockerJson(raw) {
158
77
  const text = String(raw ?? '').trim();
159
78
  if (!text) return [];
160
79
  const parsed = parseComposePs(text);
161
80
  if (parsed.length > 0) return parsed;
162
- // parseComposePs (the ONE docker-JSON parser in this codebase) collapses two
163
- // very different answers into `[]`: "docker listed nothing" and "this is not
164
- // JSON". Only a literal empty array is the first one; everything else that
165
- // parsed to nothing is garbage, and garbage must not read as "all clear".
81
+ // parseComposePs returns `[]` for non-JSON too; only a literal `[]` is "nothing".
166
82
  return /^\[\s*\]$/.test(text) ? [] : null;
167
83
  }
168
84
 
169
85
  /**
170
- * Audit the docker stacks around this repository.
171
- *
172
- * Returns `[]` — asking docker nothing at all — in three situations, and the
173
- * silence is the point: this module is loaded by `doctor --worktrees` in every
174
- * repo, including ones that never containerize anything.
175
- *
176
- * 1. no feature ever allocated a port block (no registry): there is no
177
- * isolation contract to compare a running stack against;
178
- * 2. the docker CLI is not usable here;
179
- * 3. any docker call exits non-zero or answers with something that is not
180
- * JSON — fail-open, the standing rule of this repo's diagnostics.
181
- *
182
- * `[]` here reads as "nothing to report", not as "unknown": with no docker or
183
- * no blocks, nothing CAN be colliding. That is why this is not the `null`-is-
184
- * UNKNOWN shape `pr-index.js` uses — there the question stays open, here the
185
- * absence is itself the answer.
186
- *
187
- * @param {string} root - primary root of the repository
188
- * @param {object} [opts]
189
- * @param {(file: string, args: string[], o?: object) => {code: number, output: string}} [opts.exec]
190
- * injectable executor, exactly like `e2e-check.js` — the CI runners have no
191
- * guaranteed daemon, so every path here must be testable without docker
192
- * @param {Iterable<string>} [opts.features] - feature names this tree knows.
193
- * Passed IN rather than re-derived: `auditWorktrees` already read
194
- * `.morph/features/` and `git worktree list`, and two scans of the same fact
195
- * are two chances to disagree about whether a stack is orphaned
196
- * @param {string|null} [opts.gitCommonDir]
197
- * @returns {Array<{type: string, severity: string, detail: string, fix: string, feature?: string}>}
86
+ * `[]` means "nothing to report", not unknown: with no port block allocated or
87
+ * no docker, nothing can collide. Docker errors are fail-open.
88
+ * `features` is passed in so the audit and `auditWorktrees` share one scan.
198
89
  */
199
90
  export function auditDockerStacks(root, { exec = defaultExec, features = [], gitCommonDir } = {}) {
200
91
  const commonDir = gitCommonDir === undefined ? resolveGitCommonDir(root) : gitCommonDir;
201
92
  if (!commonDir) return [];
202
93
 
203
- // (1) No port block was ever allocated → no fleet isolation to protect.
204
94
  const allocated = allocatedFeatures(commonDir);
205
95
  if (allocated.length === 0) return [];
206
96
 
207
- // (2) No docker → nothing can be holding anything.
208
97
  if (!isDockerAvailable(root, exec)) return [];
209
98
 
210
99
  const known = new Set(features);
211
100
  const rootNorm = norm(root);
212
101
  const issues = [];
213
102
 
214
- // ---- docker-stack-orphan ----------------------------------------------
215
103
  const lsResult = exec('docker', ['compose', 'ls', '--format', 'json'], { cwd: root });
216
- if (lsResult.code !== 0) return []; // (3) fail-open
104
+ if (lsResult.code !== 0) return [];
217
105
  const projects = parseDockerJson(lsResult.output);
218
- if (projects === null) return []; // (3) unreadable answer is not a finding
106
+ if (projects === null) return [];
219
107
 
220
108
  const knownProjects = new Set([...known].map((f) => composeProjectName(f)));
221
109
  for (const entry of projects) {
@@ -230,35 +118,27 @@ export function auditDockerStacks(root, { exec = defaultExec, features = [], git
230
118
  severity: 'warn',
231
119
  feature: slug || null,
232
120
  detail: `stack docker "${name}" está de pé (${String(entry?.Status ?? 'estado desconhecido')}) e não corresponde a nenhuma feature desta árvore — sobra de outra sessão, segurando o bloco de portas e os volumes de "${slug}"`,
233
- // NEVER `morph-spec e2e down {slug}`: that command resolves the feature's
234
- // compose file, and this stack's feature no longer exists here. The
235
- // project name is the only handle that still works.
121
+ // Not `e2e down {slug}`: the feature's compose file no longer resolves.
236
122
  fix: `docker compose -p ${name} down -v`,
237
123
  });
238
124
  }
239
125
 
240
- // ---- docker-port-held --------------------------------------------------
241
126
  const psResult = exec('docker', ['ps', '--format', 'json'], { cwd: root });
242
- if (psResult.code !== 0) return []; // (3) fail-open
127
+ if (psResult.code !== 0) return [];
243
128
  const containers = parseDockerJson(psResult.output);
244
129
  if (containers === null) return [];
245
130
 
246
131
  const { basePort, blockSize } = resolveFleetConfig(root);
247
132
  for (const feature of allocated) {
248
- // A block allocated by a feature this tree does not know belongs to another
249
- // worktree of the repo; its collisions are that worktree's to report.
250
133
  if (!known.has(feature)) continue;
251
134
 
252
135
  let layout;
253
136
  try {
254
- // `allocateEnvBlock` for a feature that is ALREADY in the registry takes
255
- // the early-return path and writes nothing. The expected ports are
256
- // DERIVED from portLayout — the same function that writes worktree.env
257
- // and the compose env — never recomputed here.
137
+ // Feature is already in the registry, so allocateEnvBlock writes nothing.
258
138
  const block = allocateEnvBlock(feature, { gitCommonDir: commonDir, basePort, blockSize });
259
139
  layout = portLayout(block, { feature });
260
140
  } catch {
261
- continue; // block out of range / corrupt entry — nothing to compare
141
+ continue;
262
142
  }
263
143
 
264
144
  const ownProject = layout.COMPOSE_PROJECT_NAME;
@@ -270,7 +150,7 @@ export function auditDockerStacks(root, { exec = defaultExec, features = [], git
270
150
 
271
151
  for (const container of containers) {
272
152
  const project = composeProjectOf(container);
273
- if (project === ownProject) continue; // the feature's own stack, as intended
153
+ if (project === ownProject) continue;
274
154
  for (const port of publishedPorts(container)) {
275
155
  const key = expected.get(port);
276
156
  if (!key) continue;
@@ -1,34 +1,14 @@
1
- /**
2
- * env-allocator.js — deterministic per-worktree port-block allocation (fleet).
3
- *
4
- * Two parallel features that both start `next dev` (or `dotnet run`) collide on
5
- * the same default port, and the failure is invisible until one server kills
6
- * the other. Worktrees isolate files and branches, not runtime — this module
7
- * closes that gap by giving every feature worktree its own stable block of
8
- * ports.
9
- *
10
- * The registry lives at `<git-common-dir>/morph-spec/env-registry.json` — under
11
- * `.git/` on purpose, for the same reasons as `finish-backup/`: it can never
12
- * dirty the `git status` the worktree-finish gates read, it survives
13
- * `git clean -fdx`, and the COMMON dir is the one location every linked
14
- * worktree of the repo shares.
15
- *
16
- * Allocation is deterministic (first free index), idempotent per feature, and
17
- * released only when `morph-spec finish` actually removed the worktree. Writes
18
- * are tmp+rename so a crash never leaves a torn registry; a corrupt registry
19
- * is treated as empty (fail-open — the cost is a re-allocation, never a crash).
20
- */
1
+ // The registry lives under the COMMON git dir: shared by every linked worktree,
2
+ // invisible to the `git status` the finish gates read, and survives `git clean -fdx`.
21
3
 
22
4
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'fs';
23
5
  import { join } from 'path';
24
- // src/ → hooks/shared/ is the allowed direction (hooks are installed standalone).
25
6
  import { composeProjectName } from '../../../framework/hooks/shared/e2e-check.js';
26
7
 
27
8
  const DEFAULT_BASE_PORT = 4000;
28
9
  const DEFAULT_BLOCK_SIZE = 10;
29
10
  const MAX_PORT = 65535;
30
11
 
31
- /** Registry file path for a repo's common git dir. */
32
12
  export function registryPath(gitCommonDir) {
33
13
  return join(gitCommonDir, 'morph-spec', 'env-registry.json');
34
14
  }
@@ -40,7 +20,7 @@ function readRegistry(gitCommonDir) {
40
20
  if (parsed && typeof parsed.allocations === 'object' && parsed.allocations !== null) {
41
21
  return parsed;
42
22
  }
43
- } catch { /* fail-open: corrupt or absent registry restarts empty */ }
23
+ } catch { /* fail-open: a re-allocation, never a crash */ }
44
24
  return { version: 1, allocations: {} };
45
25
  }
46
26
 
@@ -52,13 +32,6 @@ function writeRegistry(gitCommonDir, registry) {
52
32
  renameSync(tmp, path);
53
33
  }
54
34
 
55
- /**
56
- * Read fleet overrides from the project's committed config.json.
57
- * Fail-open: missing/malformed config (or a Windows BOM) → defaults.
58
- *
59
- * @param {string} rootPath - primary root of the project
60
- * @returns {{ basePort: number, blockSize: number }}
61
- */
62
35
  export function resolveFleetConfig(rootPath) {
63
36
  const defaults = { basePort: DEFAULT_BASE_PORT, blockSize: DEFAULT_BLOCK_SIZE };
64
37
  try {
@@ -74,15 +47,7 @@ export function resolveFleetConfig(rootPath) {
74
47
  }
75
48
  }
76
49
 
77
- /**
78
- * Allocate (or return the existing) port block for a feature.
79
- * First-free-index policy: released slots are reused, so long-lived repos
80
- * don't creep toward the port ceiling.
81
- *
82
- * @param {string} feature
83
- * @param {{ gitCommonDir: string, basePort?: number, blockSize?: number }} opts
84
- * @returns {{ index: number, base: number, range: [number, number], created: boolean }}
85
- */
50
+ /** First free index wins, so released slots are reused instead of creeping toward the port ceiling. */
86
51
  export function allocateEnvBlock(feature, { gitCommonDir, basePort = DEFAULT_BASE_PORT, blockSize = DEFAULT_BLOCK_SIZE }) {
87
52
  const registry = readRegistry(gitCommonDir);
88
53
 
@@ -110,23 +75,7 @@ export function allocateEnvBlock(feature, { gitCommonDir, basePort = DEFAULT_BAS
110
75
  return { index, base, range: [base, base + blockSize - 1], created: true };
111
76
  }
112
77
 
113
- /**
114
- * The features that ALREADY own a block — read-only, allocating nothing.
115
- *
116
- * `allocateEnvBlock` is the only sanctioned way to get a block, and it WRITES
117
- * when the feature has none. A diagnostic that has to ask "who owns a block?"
118
- * must not answer by handing one out: `doctor` is read-only, and an audit that
119
- * mutates the registry it audits invents the very state it then reports. So
120
- * this reader exists to bound the question, and the block itself still comes
121
- * from `allocateEnvBlock` (which, for a feature listed here, takes the
122
- * early-return path and writes nothing).
123
- *
124
- * An absent or corrupt registry yields `[]` — "nobody allocated anything",
125
- * which for every caller is the same as "nothing to compare against".
126
- *
127
- * @param {string} gitCommonDir
128
- * @returns {string[]} feature names with an allocated block
129
- */
78
+ /** Read-only on purpose: `allocateEnvBlock` writes, and an audit that allocates invents the state it reports. */
130
79
  export function allocatedFeatures(gitCommonDir) {
131
80
  const registry = readRegistry(gitCommonDir);
132
81
  return Object.entries(registry.allocations)
@@ -134,13 +83,6 @@ export function allocatedFeatures(gitCommonDir) {
134
83
  .map(([feature]) => feature);
135
84
  }
136
85
 
137
- /**
138
- * Release a feature's block. Returns false when nothing was allocated.
139
- *
140
- * @param {string} feature
141
- * @param {{ gitCommonDir: string }} opts
142
- * @returns {boolean}
143
- */
144
86
  export function releaseEnvBlock(feature, { gitCommonDir }) {
145
87
  const registry = readRegistry(gitCommonDir);
146
88
  if (!registry.allocations[feature]) return false;
@@ -149,27 +91,12 @@ export function releaseEnvBlock(feature, { gitCommonDir }) {
149
91
  return true;
150
92
  }
151
93
 
152
- /**
153
- * Fixed offsets inside a block. The block has 10 ports; four are named, the
154
- * rest (+4..+9) are the project's to use. A new offset added here MUST also be
155
- * published by the compose template (`${VAR:-default}:container`), or it is a
156
- * port nobody listens on.
157
- */
94
+ // A new offset here must also be published by the compose template, or it is a port nobody listens on.
158
95
  export const PORT_OFFSETS = Object.freeze({ web: 0, api: 1, postgres: 2, redis: 3 });
159
96
 
160
97
  /**
161
- * The ONE port-layout contract. Two writers derive from it — the
162
- * `.morph/worktree.env` manifest (`buildEnvFileContent`) and the env handed to
163
- * `docker compose` (`src/lib/e2e/compose-env.js`) — so a worktree's file and
164
- * its running stack can never describe different worlds.
165
- *
166
- * `COMPOSE_PROJECT_NAME` is part of the layout on purpose: a hand-run
167
- * `docker compose up` inside the worktree must land in the isolated project,
168
- * not in the root's. Every value is a string (env-safe); key order is stable.
169
- *
170
- * @param {{ base: number, range: [number, number] }} block
171
- * @param {{ stack?: string, feature?: string, aliases?: Record<string, string> }} [opts] - aliases: resolvePortAliases()
172
- * @returns {Record<string,string>}
98
+ * The single layout both `worktree.env` and the compose env derive from. `COMPOSE_PROJECT_NAME`
99
+ * is in it so a hand-run `docker compose up` in the worktree lands in the isolated project.
173
100
  */
174
101
  export function portLayout(block, { stack = '', feature = '', aliases = {} } = {}) {
175
102
  const env = {};
@@ -194,9 +121,6 @@ const RESERVED_ENV_NAMES = new Set([
194
121
  'MORPH_FEATURE',
195
122
  ]);
196
123
 
197
- // e2e.portEnvNames: extra env names for the same four ports, so a compose file reading
198
- // ${PG_PORT} is fed by the block, not by its own default. An entry that cannot be
199
- // honoured is dropped and named in `problems`.
200
124
  export function resolvePortAliases(config) {
201
125
  const declared = config?.e2e?.portEnvNames;
202
126
  const aliases = {};
@@ -217,20 +141,8 @@ export function resolvePortAliases(config) {
217
141
  }
218
142
 
219
143
  /**
220
- * Render the `.morph/worktree.env` contents for a block: a header, the
221
- * feature, and exactly the `portLayout()` pairs — nothing else, so the file
222
- * cannot drift from what compose receives.
223
- *
224
- * `morphFeature` is for a task tree born from `morph/<f>`: `MORPH_FEATURE`
225
- * names the feature it works for, while the block and `COMPOSE_PROJECT_NAME`
226
- * stay keyed by `feature` (`task/<n>`) — keying them by `<f>` would hand the
227
- * sub-agent the feature's own ports and compose project.
228
- *
229
- * @param {string} feature - the env-registry key: `<feature>` or `task/<name>`
230
- * @param {{ base: number, range: [number, number] }} block
231
- * @param {string} [stack] - config.json `project.stack` (e.g. "nextjs+dotnet")
232
- * @param {{ morphFeature?: string|null, aliases?: Record<string, string> }} [opts]
233
- * @returns {string}
144
+ * `morphFeature` names the feature a `task/<n>` tree works for; the block and compose
145
+ * project stay keyed by `feature`, or the sub-agent would get the feature's own ports.
234
146
  */
235
147
  export function buildEnvFileContent(feature, block, stack = '', { morphFeature = null, aliases = {} } = {}) {
236
148
  const lines = [