@polymorphism-tech/morph-spec 9.0.0 → 9.2.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 (345) hide show
  1. package/CLAUDE.md +8 -9
  2. package/README.md +38 -36
  3. package/bin/morph-spec.js +11 -63
  4. package/bin/validate.js +1 -39
  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 +2 -1
  9. package/framework/MORPH.md +3 -3
  10. package/framework/agents.json +19 -11
  11. package/framework/commands/morph-apply.md +1 -1
  12. package/framework/commands/morph-status.md +1 -1
  13. package/framework/docs/CLI.md +22 -31
  14. package/framework/evals/review-checklist.md +12 -2
  15. package/framework/hooks/README.md +8 -44
  16. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
  17. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
  18. package/framework/hooks/claude-code/core/chain.js +4 -65
  19. package/framework/hooks/claude-code/core/comment-guard.js +2 -16
  20. package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
  21. package/framework/hooks/claude-code/core/gate-guard.js +7 -67
  22. package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
  23. package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
  24. package/framework/hooks/claude-code/core/heavy-release.js +2 -13
  25. package/framework/hooks/claude-code/core/loop-detect.js +2 -31
  26. package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
  27. package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
  28. package/framework/hooks/claude-code/core/state-sync.js +12 -68
  29. package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
  30. package/framework/hooks/claude-code/core/validate-completion.js +11 -57
  31. package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
  32. package/framework/hooks/claude-code/core/worktree-context.js +10 -110
  33. package/framework/hooks/claude-code/core/worktree-create.js +6 -70
  34. package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
  35. package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
  36. package/framework/hooks/claude-code/statusline.sh +11 -11
  37. package/framework/hooks/shared/activity-logger.js +9 -89
  38. package/framework/hooks/shared/agent-reap.js +2 -17
  39. package/framework/hooks/shared/build-check.js +6 -75
  40. package/framework/hooks/shared/claim-registry.js +9 -119
  41. package/framework/hooks/shared/command-tokens.js +6 -51
  42. package/framework/hooks/shared/comment-rules.js +5 -20
  43. package/framework/hooks/shared/comment-scanner.js +6 -25
  44. package/framework/hooks/shared/comment-stripper.js +11 -85
  45. package/framework/hooks/shared/e2e-check.js +0 -0
  46. package/framework/hooks/shared/errata.js +2 -15
  47. package/framework/hooks/shared/feature-locator.js +11 -95
  48. package/framework/hooks/shared/frontend-locations.js +1 -3
  49. package/framework/hooks/shared/gate-question.js +1 -7
  50. package/framework/hooks/shared/git-exclude.js +3 -20
  51. package/framework/hooks/shared/git-paths.js +8 -104
  52. package/framework/hooks/shared/graph-freshness.js +22 -166
  53. package/framework/hooks/shared/graph-refresh.js +21 -149
  54. package/framework/hooks/shared/heavy-command.js +4 -12
  55. package/framework/hooks/shared/hook-response.js +8 -83
  56. package/framework/hooks/shared/morph-cli.js +8 -52
  57. package/framework/hooks/shared/payload-utils.js +10 -132
  58. package/framework/hooks/shared/phase-utils.js +1 -42
  59. package/framework/hooks/shared/redact-secrets.js +4 -15
  60. package/framework/hooks/shared/reparse-scan.js +6 -66
  61. package/framework/hooks/shared/slice-progress.js +4 -23
  62. package/framework/hooks/shared/source-files.js +4 -17
  63. package/framework/hooks/shared/state-reader.js +13 -183
  64. package/framework/hooks/shared/state-writer.js +8 -52
  65. package/framework/hooks/shared/stdin-reader.js +1 -11
  66. package/framework/hooks/shared/telemetry-logger.js +7 -67
  67. package/framework/hooks/shared/transcript-usage.js +7 -29
  68. package/framework/hooks/shared/worktree-root.js +7 -29
  69. package/framework/rules/csharp-standards.md +1 -1
  70. package/framework/rules/frontend-standards.md +1 -0
  71. package/framework/rules/hangfire-standards.md +1 -1
  72. package/framework/rules/neon-standards.md +1 -1
  73. package/framework/rules/nextjs-standards.md +1 -1
  74. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  75. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  76. package/framework/skills/core/morph-apply/SKILL.md +4 -4
  77. package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
  78. package/framework/skills/core/morph-machine/SKILL.md +1 -1
  79. package/framework/skills/core/morph-media/SKILL.md +1 -1
  80. package/framework/skills/core/morph-plan/SKILL.md +9 -7
  81. package/framework/skills/core/morph-review/SKILL.md +9 -6
  82. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  83. package/framework/skills/core/morph-uiux/SKILL.md +29 -12
  84. package/framework/standards/STANDARDS.json +36 -14
  85. package/framework/standards/STANDARDS.meta.json +16 -2
  86. package/framework/standards/ai-agents/agent-archetypes.md +2 -2
  87. package/framework/standards/ai-agents/agent-session.md +1 -1
  88. package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
  89. package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
  90. package/framework/standards/ai-agents/guardrails.md +1 -1
  91. package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
  92. package/framework/standards/ai-agents/observability-patterns.md +1 -1
  93. package/framework/standards/ai-agents/production.md +1 -1
  94. package/framework/standards/ai-agents/prompt-sources.md +2 -2
  95. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  96. package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
  97. package/framework/standards/ai-agents/setup.md +2 -2
  98. package/framework/standards/ai-agents/sweet-spot.md +2 -2
  99. package/framework/standards/ai-agents/tool-adapters.md +0 -2
  100. package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
  101. package/framework/standards/ai-agents/workflows.md +1 -1
  102. package/framework/standards/architecture/code-comments.md +1 -1
  103. package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
  104. package/framework/standards/backend/api/validation.md +1 -1
  105. package/framework/standards/backend/dotnet/testing.md +10 -4
  106. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
  107. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
  108. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
  109. package/framework/standards/data/nosql/cache/redis.md +1 -1
  110. package/framework/standards/data/nosql/cosmos-db.md +3 -3
  111. package/framework/standards/frontend/admin-app/forms.md +1 -1
  112. package/framework/standards/frontend/design-system/art-direction.md +88 -0
  113. package/framework/standards/frontend/design-system/premium-finish.md +77 -2
  114. package/framework/standards/frontend/nextjs/testing.md +10 -6
  115. package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
  116. package/framework/templates/.idea/morph-templates.xml +92 -0
  117. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  118. package/framework/templates/CLAUDE.md.core.template +3 -3
  119. package/framework/templates/IDE-SNIPPETS.md +262 -262
  120. package/framework/templates/REGISTRY.json +9 -9
  121. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  122. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  123. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  124. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  125. package/framework/templates/docs/plan.md +3 -1
  126. package/framework/templates/docs/ui-mockups.md +4 -0
  127. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  128. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  129. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  130. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  131. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  132. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  133. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  134. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  135. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  136. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  137. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  138. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  139. package/framework/templates/state.template.json +17 -17
  140. package/framework/templates/ui/design-system.css +226 -226
  141. package/package.json +1 -1
  142. package/src/commands/context/context-refresh.js +0 -16
  143. package/src/commands/dashboard/dashboard.js +2 -8
  144. package/src/commands/e2e/e2e.js +17 -104
  145. package/src/commands/graph/affected.js +0 -14
  146. package/src/commands/graph/build.js +11 -50
  147. package/src/commands/graph/explain.js +0 -16
  148. package/src/commands/graph/status.js +1 -12
  149. package/src/commands/mcp/mcp-setup.js +4 -62
  150. package/src/commands/observability/cost.js +5 -57
  151. package/src/commands/observability/telemetry.js +1 -14
  152. package/src/commands/project/branch.js +4 -45
  153. package/src/commands/project/claim.js +4 -48
  154. package/src/commands/project/doctor.js +48 -302
  155. package/src/commands/project/env.js +1 -27
  156. package/src/commands/project/fleet.js +3 -32
  157. package/src/commands/project/init.js +4 -68
  158. package/src/commands/project/install-plugin-cmd.js +0 -1
  159. package/src/commands/project/standards.js +1 -62
  160. package/src/commands/project/status.js +0 -53
  161. package/src/commands/project/update.js +31 -239
  162. package/src/commands/project/worktree.js +108 -882
  163. package/src/commands/scaffold/scaffold.js +0 -25
  164. package/src/commands/state/advance.js +14 -85
  165. package/src/commands/state/approve.js +15 -97
  166. package/src/commands/state/archive.js +12 -64
  167. package/src/commands/state/auto-approval.js +2 -5
  168. package/src/commands/state/create.js +22 -109
  169. package/src/commands/state/delete.js +11 -63
  170. package/src/commands/state/finish.js +98 -475
  171. package/src/commands/state/gate-check.js +22 -132
  172. package/src/commands/state/gate-decision.js +3 -39
  173. package/src/commands/state/handoff.js +3 -30
  174. package/src/commands/state/list.js +14 -84
  175. package/src/commands/state/report-check.js +8 -58
  176. package/src/commands/state/retype.js +4 -26
  177. package/src/commands/validation/index.js +0 -3
  178. package/src/commands/validation/mutate.js +11 -11
  179. package/src/commands/validation/plan-check.js +4 -2
  180. package/src/commands/validation/tests-audit.js +7 -0
  181. package/src/commands/validation/validate-feature.js +10 -97
  182. package/src/commands/validation/verify.js +30 -113
  183. package/src/core/paths/output-schema.js +2 -101
  184. package/src/core/state/feature-identity.js +1 -25
  185. package/src/core/state/gate-order.js +0 -27
  186. package/src/core/state/lifecycle-audit.js +8 -100
  187. package/src/core/state/state-manager.js +22 -304
  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 +0 -1
  191. package/src/core/work-types.js +1 -36
  192. package/src/dashboard/app/App.jsx +1 -8
  193. package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
  194. package/src/dashboard/app/components/CommandPalette.jsx +0 -7
  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 +1 -5
  207. package/src/dashboard/app/lib/project.js +0 -12
  208. package/src/dashboard/app/main.jsx +13 -13
  209. package/src/dashboard/app/pages/AgentMap.jsx +1 -8
  210. package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
  211. package/src/dashboard/app/pages/Features.jsx +1 -8
  212. package/src/dashboard/app/pages/Guide.jsx +0 -1
  213. package/src/dashboard/app/pages/HookInspector.jsx +2 -23
  214. package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
  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 +10 -69
  220. package/src/dashboard/scanner/agents-scanner.js +1 -23
  221. package/src/dashboard/scanner/docs-scanner.js +0 -6
  222. package/src/dashboard/scanner/feature-status.js +2 -5
  223. package/src/dashboard/scanner/features-scanner.js +10 -66
  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 +2 -6
  231. package/src/dashboard/vite-plugin-morph-data.js +5 -19
  232. package/src/dashboard/vite.config.js +24 -24
  233. package/src/lib/ai/ai-checks.js +7 -52
  234. package/src/lib/ai/ai-project-scan.js +14 -108
  235. package/src/lib/ai/code-audit.js +7 -63
  236. package/src/lib/ai/pin-drift.js +8 -51
  237. package/src/lib/ai/registry-audit.js +11 -90
  238. package/src/lib/ci/ci-verify.js +4 -19
  239. package/src/lib/ci/github-ci.js +9 -37
  240. package/src/lib/ci/workflow-scaffold.js +14 -10
  241. package/src/lib/detectors/claude-config-detector.js +13 -74
  242. package/src/lib/detectors/index.js +4 -50
  243. package/src/lib/e2e/applicability.js +37 -0
  244. package/src/lib/e2e/compose-env.js +5 -66
  245. package/src/lib/e2e/stack-teardown.js +6 -40
  246. package/src/lib/generators/architecture-scanner.js +18 -156
  247. package/src/lib/generators/handoff-generator.js +9 -53
  248. package/src/lib/generators/scaffold-generator.js +5 -101
  249. package/src/lib/generators/settings-generator.js +4 -37
  250. package/src/lib/git/branch-classifier.js +11 -84
  251. package/src/lib/git/default-branch.js +14 -169
  252. package/src/lib/git/evidence-staleness.js +1 -0
  253. package/src/lib/git/gh.js +2 -17
  254. package/src/lib/git/pending-prs.js +6 -37
  255. package/src/lib/git/pr-files.js +3 -28
  256. package/src/lib/git/pr-index.js +3 -34
  257. package/src/lib/git/tree-fingerprint.js +3 -15
  258. package/src/lib/git/tree-state.js +6 -33
  259. package/src/lib/graph/god-nodes.js +2 -22
  260. package/src/lib/graph/graph-health.js +3 -24
  261. package/src/lib/graph/graph-loader.js +5 -56
  262. package/src/lib/graph/graphify-runtime.js +7 -37
  263. package/src/lib/graph/graphifyignore.js +8 -74
  264. package/src/lib/graph/sanitize.js +16 -136
  265. package/src/lib/heavy/cleanup.js +1 -1
  266. package/src/lib/installers/effects.js +8 -93
  267. package/src/lib/installers/mcp-installer.js +9 -169
  268. package/src/lib/installers/memory-installer.js +4 -27
  269. package/src/lib/installers/templates-reconcile.js +5 -60
  270. package/src/lib/mutation/adapters.js +6 -17
  271. package/src/lib/mutation/dotnet-introspection.js +20 -106
  272. package/src/lib/mutation/runner.js +23 -75
  273. package/src/lib/mutation/subset.js +1 -4
  274. package/src/lib/mutation/trx.js +0 -1
  275. package/src/lib/plan/artifact-vocabulary.js +5 -25
  276. package/src/lib/plan/checks.js +34 -33
  277. package/src/lib/plan/plan-parser.js +7 -8
  278. package/src/lib/projects/registry.js +7 -44
  279. package/src/lib/review-reports.js +11 -26
  280. package/src/lib/risk-detector.js +41 -173
  281. package/src/lib/specs/adr-index.js +6 -34
  282. package/src/lib/specs/frontmatter.js +2 -15
  283. package/src/lib/specs/gfm-table.js +1 -7
  284. package/src/lib/specs/revocations.js +1 -14
  285. package/src/lib/specs/screen-checklist.js +47 -124
  286. package/src/lib/stack/dotnet-build-ignores.js +2 -6
  287. package/src/lib/stack/stack-profile.js +1 -28
  288. package/src/lib/stack/stack-scan.js +1 -8
  289. package/src/lib/stack-filter.js +4 -27
  290. package/src/lib/standards/registry.js +1 -7
  291. package/src/lib/standards/sections.js +2 -13
  292. package/src/lib/standards/verified-against.js +19 -191
  293. package/src/lib/tasks/eval-node.js +21 -169
  294. package/src/lib/tasks/linux-runner.js +18 -142
  295. package/src/lib/tasks/test-nodes.js +43 -240
  296. package/src/lib/tasks/test-runner.js +43 -341
  297. package/src/lib/telemetry/metrics.js +7 -36
  298. package/src/lib/telemetry/telemetry-reader.js +13 -125
  299. package/src/lib/telemetry/verify-event.js +2 -30
  300. package/src/lib/tests-audit/audit.js +96 -0
  301. package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
  302. package/src/lib/validators/code-budget/index.js +0 -7
  303. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
  304. package/src/lib/validators/contracts/index.js +0 -6
  305. package/src/lib/validators/nextjs/index.js +0 -5
  306. package/src/lib/validators/nextjs/next-component-validator.js +4 -81
  307. package/src/lib/validators/packages/index.js +0 -6
  308. package/src/lib/validators/packages/package-validator.js +6 -56
  309. package/src/lib/validators/reuse/index.js +0 -8
  310. package/src/lib/validators/reuse/reuse-validator.js +11 -101
  311. package/src/lib/validators/reuse/symbol-scanner.js +6 -62
  312. package/src/lib/validators/traceability-check.js +5 -63
  313. package/src/lib/validators/validation-runner.js +21 -158
  314. package/src/lib/validators/verify-runner.js +85 -376
  315. package/src/lib/work-type-detector.js +5 -67
  316. package/src/lib/worktree/docker-audit.js +17 -136
  317. package/src/lib/worktree/env-allocator.js +10 -98
  318. package/src/lib/worktree/feature-truth.js +12 -115
  319. package/src/lib/worktree/finish-backup.js +32 -242
  320. package/src/lib/worktree/fleet-scanner.js +13 -95
  321. package/src/lib/worktree/ignored-triage.js +15 -91
  322. package/src/lib/worktree/vscode-settings.js +2 -3
  323. package/src/lib/worktree/worktree-audit.js +21 -213
  324. package/src/lib/worktree/worktree-include.js +3 -5
  325. package/src/scripts/global-install.js +3 -9
  326. package/src/scripts/install-plugin.js +1 -13
  327. package/src/scripts/setup-infra.js +17 -147
  328. package/src/utils/agents-installer.js +29 -276
  329. package/src/utils/banner.js +4 -25
  330. package/src/utils/claude-md-injector.js +1 -37
  331. package/src/utils/claude-settings-manager.js +1 -52
  332. package/src/utils/file-copier.js +11 -95
  333. package/src/utils/hooks-installer.js +21 -298
  334. package/src/utils/skills-installer.js +4 -101
  335. package/src/utils/version-checker.js +4 -83
  336. package/framework/commands/morph-proposal.md +0 -9
  337. package/framework/hooks/git/commit-msg/conventional-commits.sh +0 -33
  338. package/framework/hooks/git/pre-commit/agents.sh +0 -25
  339. package/framework/hooks/git/pre-commit/orchestrator.sh +0 -64
  340. package/framework/hooks/git/pre-push/run-tests.sh +0 -44
  341. package/src/commands/utils/index.js +0 -3
  342. package/src/lib/detectors/design-system-detector.js +0 -188
  343. package/src/lib/generators/design-system-generator.js +0 -508
  344. package/src/lib/specs/old-format.js +0 -68
  345. package/src/utils/agent-id-mapper.js +0 -11
@@ -1,42 +1,12 @@
1
- /**
2
- * feature-truth.js — where a feature's authoritative `feature.json` actually
3
- * lives, and how to read it from a place that isn't the feature's own tree.
4
- *
5
- * `create --worktree` writes a COMPLETE feature.json into the primary root
6
- * (status, approvalGates, tasks, gateDecisions) and only then copies it into
7
- * the worktree. From that instant the branch-committed copy (`morph/{f}`) is
8
- * the truth and the root's copy never changes again — the root sees the
9
- * feature exactly as it was at birth, forever, unless someone reads through.
10
- *
11
- * `branchToReadFrom`/`branchView` are moved verbatim from fleet-scanner.js —
12
- * they already solved this (comments B3/B3b there), fleet just never shared
13
- * them. `resolveFeatureTree` is the missing piece for CONSUMERS OTHER than
14
- * fleet: "which tree/branch actually owns this feature's state", used by
15
- * `list`, `handoff`, and the mutator guard (assertOwnerTree).
16
- *
17
- * Fail-open throughout: no repo, no branch, no blob, unparseable JSON never
18
- * throws — callers get null/false and fall back to reading the disk.
19
- */
1
+ // After `create --worktree`, the root keeps the feature.json frozen at birth; the
2
+ // committed copy on `morph/{f}` is the truth. Fail-open: null means "read the disk".
20
3
 
21
4
  import { execFileSync } from 'child_process';
22
5
  import { existsSync, readFileSync } from 'fs';
23
6
  import { join } from 'path';
24
7
  import { isGitRepo, resolvePrimaryRoot, findWorktreeForFeature, buildBranchName } from '../../commands/project/worktree.js';
25
8
 
26
- /**
27
- * Which branch holds this feature's truth, when the working tree at `path` does
28
- * NOT — or `null` when the working tree IS the feature's own checkout.
29
- *
30
- * The ONE place this condition lives. `tasks` (B3) and `phase`/`pendingGates`
31
- * (B3b) are the same defect read through different files, so they must agree on
32
- * when to look at the branch; two drifting copies of this test would resurrect
33
- * the bug on one field while the other stayed fixed.
34
- *
35
- * A worktree entry is the feature's checkout by construction. A root entry is
36
- * only the feature's checkout when the root happens to be ON `morph/{f}`.
37
- *
38
- * @returns {string|null} branch name to read from, or null for "read the disk"
39
- */
9
+ /** Branch holding the feature's truth, or `null` when this tree IS its checkout (read the disk). */
40
10
  export function branchToReadFrom(root, feature, location, branch) {
41
11
  if (location === 'worktree' || !root || !feature) return null;
42
12
  const featureBranch = branch || `morph/${feature}`;
@@ -45,24 +15,12 @@ export function branchToReadFrom(root, feature, location, branch) {
45
15
  .toString().trim();
46
16
  return current === featureBranch ? null : featureBranch;
47
17
  } catch {
48
- return null; // no HEAD to compare against — trust the disk
18
+ return null;
49
19
  }
50
20
  }
51
21
 
52
- /**
53
- * The feature's committed state on its own branch: the feature.json blob plus
54
- * the set of files under its feature dir.
55
- *
56
- * `git ls-tree -z` is deliberate: without it git QUOTES paths containing
57
- * non-ASCII bytes (`"a\303\247/…"`), which would silently fail the prefix strip
58
- * and leave the artifact set empty — a predicate that is always false looks
59
- * exactly like "this feature has no artifacts".
60
- *
61
- * Fail-open in layers: no blob → null (caller reads the disk); no tree → the
62
- * gates alone still beat a stale working tree.
63
- *
64
- * @returns {{ fj: Object, files: Set<string> }|null}
65
- */
22
+ // `ls-tree -z` is load-bearing: without it git quotes non-ASCII paths, the prefix
23
+ // strip fails and the file set comes back silently empty.
66
24
  export function branchView(root, feature, featureBranch) {
67
25
  const prefix = `.morph/features/${feature}/`;
68
26
  let fj;
@@ -83,12 +41,11 @@ export function branchView(root, feature, featureBranch) {
83
41
  const p = raw.trim();
84
42
  if (p.startsWith(prefix)) files.add(p.slice(prefix.length));
85
43
  }
86
- } catch { /* no tree — gates alone still beat the stale working tree */ }
44
+ } catch { /* the gates alone still beat a stale working tree */ }
87
45
 
88
46
  return { fj, files };
89
47
  }
90
48
 
91
- /** All local `morph/*` branches, as bare feature names — one spawn, a pre-filter. */
92
49
  export function morphBranches(root) {
93
50
  try {
94
51
  const out = execFileSync('git', ['for-each-ref', '--format=%(refname:short)', 'refs/heads/morph/'], {
@@ -105,7 +62,7 @@ export function morphBranches(root) {
105
62
  }
106
63
  }
107
64
 
108
- /** The tree's current branch, or null (detached HEAD / no repo / no commits yet). */
65
+ /** null on detached HEAD, no repo or no commits yet. */
109
66
  export function currentBranch(cwd) {
110
67
  try {
111
68
  const out = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd, stdio: 'pipe' }).toString().trim();
@@ -115,7 +72,6 @@ export function currentBranch(cwd) {
115
72
  }
116
73
  }
117
74
 
118
- /** Read a feature's local feature.json from `dir`, or null if missing/corrupt. */
119
75
  function readLocalFeatureJson(dir, feature) {
120
76
  const p = join(dir, '.morph', 'features', feature, 'feature.json');
121
77
  if (!existsSync(p)) return null;
@@ -127,35 +83,9 @@ function readLocalFeatureJson(dir, feature) {
127
83
  }
128
84
 
129
85
  /**
130
- * Resolve which TREE (or, failing that, which BRANCH) actually owns a
131
- * feature's state — "follow the pointer" for any consumer, not just fleet.
132
- *
133
- * Six-step search, each step justified by what it's immune to:
134
- *
135
- * 1. cwd holds the feature AND cwd is that feature's registered worktree
136
- * (git's own record, via findWorktreeForFeature) → running inside the
137
- * owner never counts as a jump.
138
- * 2. `findWorktreeForFeature` finds a live worktree elsewhere → the tree the
139
- * GIT REGISTRY says is the owner, not the feature.json pointer — immune
140
- * to a dangling/stale `feature.worktree` field.
141
- * 3. the local feature.json's own `worktree.path` field, if it still exists
142
- * on disk → covers a worktree living outside the conventional path or a
143
- * registry `git worktree list` can't see for some reason.
144
- * 4. cwd holds the feature with no worktree registered anywhere → the common
145
- * case (no `--worktree`, or the worktree was already torn down).
146
- * 5. no local copy, but `morph/{feature}` has the file committed → the truth
147
- * exists, just not materialized into any tree right now.
148
- * 6. nothing anywhere.
149
- *
150
- * @param {string} feature
151
- * @param {string} [cwd]
152
- * @returns {{
153
- * source: 'cwd'|'registry'|'pointer'|'branch-only'|'none',
154
- * tree: string|null,
155
- * branch: string|null,
156
- * worktree: {path:string, branch:string}|null,
157
- * pointerDangling: boolean,
158
- * }}
86
+ * Order: git's worktree registry, then the feature.json pointer, then cwd, then the branch.
87
+ * The registry comes first so a stale `feature.worktree` pointer never wins.
88
+ * @returns {{ source: 'cwd'|'registry'|'pointer'|'branch-only'|'none', tree: string|null, branch: string|null, worktree: {path:string, branch:string}|null, pointerDangling: boolean }}
159
89
  */
160
90
  export function resolveFeatureTree(feature, cwd = process.cwd()) {
161
91
  const localFj = readLocalFeatureJson(cwd, feature);
@@ -165,8 +95,6 @@ export function resolveFeatureTree(feature, cwd = process.cwd()) {
165
95
  const inGit = isGitRepo(cwd);
166
96
  const root = inGit ? resolvePrimaryRoot(cwd) : null;
167
97
 
168
- // Step 1 + 2: git's own worktree registry is the source of truth for "is
169
- // there a live worktree for this feature, and where".
170
98
  if (inGit && root) {
171
99
  const registered = findWorktreeForFeature(feature, root);
172
100
  if (registered && existsSync(registered.path)) {
@@ -178,19 +106,15 @@ export function resolveFeatureTree(feature, cwd = process.cwd()) {
178
106
  }
179
107
  }
180
108
 
181
- // Step 3: the local pointer names a worktree the registry didn't surface
182
- // (unconventional location, or registry unreadable) but that still exists.
183
109
  if (pointerPath && !pointerDangling) {
184
110
  const branch = localFj?.worktree?.branch || buildBranchName(feature);
185
111
  return { source: 'pointer', tree: pointerPath, branch, worktree: { path: pointerPath, branch }, pointerDangling };
186
112
  }
187
113
 
188
- // Step 4: this tree holds the feature and nothing else claims to own it.
189
114
  if (localFj) {
190
115
  return { source: 'cwd', tree: cwd, branch: null, worktree: null, pointerDangling };
191
116
  }
192
117
 
193
- // Step 5: no local copy anywhere in this tree, but the branch has one.
194
118
  if (inGit && root) {
195
119
  const branch = buildBranchName(feature);
196
120
  const view = branchView(root, feature, branch);
@@ -199,43 +123,16 @@ export function resolveFeatureTree(feature, cwd = process.cwd()) {
199
123
  }
200
124
  }
201
125
 
202
- // Step 6.
203
126
  return { source: 'none', tree: null, branch: null, worktree: null, pointerDangling: false };
204
127
  }
205
128
 
206
- /**
207
- * Should a mutator (approve/advance/score/gate-decision/gate-check) refuse to
208
- * write this feature's `feature.json` from `cwd`?
209
- *
210
- * Blocked ONLY when a live worktree owns the feature and it is NOT cwd —
211
- * `resolveFeatureTree` sources `'registry'`/`'pointer'`. `'cwd'` (cwd IS the
212
- * owner, or no worktree exists at all) always passes. This is what stops
213
- * `morph-spec approve <f> plan` run from the root, with the worktree still
214
- * alive, from silently stamping the gate onto the root's birth-instant
215
- * skeleton — branch-drift CREATED by a command, not merely left behind by one.
216
- *
217
- * @param {string} feature
218
- * @param {string} [cwd]
219
- * @returns {{ blocked: boolean, tree: string|null, branch: string|null }}
220
- */
129
+ /** Blocked only when a live worktree other than cwd owns the feature, so the root skeleton is never stamped. */
221
130
  export function assertOwnerTree(feature, cwd = process.cwd()) {
222
131
  const owner = resolveFeatureTree(feature, cwd);
223
132
  const blocked = owner.source === 'registry' || owner.source === 'pointer';
224
133
  return { blocked, tree: owner.tree, branch: owner.branch };
225
134
  }
226
135
 
227
- /**
228
- * The refusal text every mutator prints on an owner-tree block — one place so
229
- * `approve`/`advance`/`score`/`gate-decision`/`gate-check` can never drift
230
- * into five different wordings for the same defect. `commandLine` is the
231
- * exact command the caller should re-run inside the owner tree.
232
- *
233
- * @param {string} feature
234
- * @param {string} tree
235
- * @param {string|null} branch
236
- * @param {string} commandLine
237
- * @returns {Array<{tone: 'red'|'gray'|'cyan'|'yellow', text: string}>}
238
- */
239
136
  export function ownerTreeRefusalLines(feature, tree, branch, commandLine) {
240
137
  return [
241
138
  { tone: 'red', text: `\n❌ A feature "${feature}" não pode ser mutada A PARTIR DESTA ÁRVORE.` },