@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
@@ -11,35 +11,17 @@ import { readMorphConfig } from '../../lib/tasks/test-runner.js';
11
11
  import { readPlanChecks } from '../../lib/plan/checks.js';
12
12
  import { assertOwnerTree, ownerTreeRefusalLines } from '../../lib/worktree/feature-truth.js';
13
13
 
14
- /**
15
- * `--signals` on `gate-check` is NOT a general override like `gate-decision`'s
16
- * — everything else here is read straight off disk (workType, sliceCount, sha,
17
- * diffstat, verification), and letting a caller override those would let a
18
- * careless/hostile `--signals` silently defeat the policy this command exists
19
- * to make deterministic (e.g. overriding `workType` away from "hotfix" would
20
- * suppress risk-detector's forced Gate-3 pause). Only the signals that are
21
- * genuinely NOT derivable from disk today are accepted here:
22
- * - standardsViolations / designSystemDivergence: still an LLM's
23
- * natural-language read (see the R4 report — no deterministic detector
24
- * exists in JS yet).
25
- */
14
+ // Only signals NOT derivable from disk may be overridden: overriding a disk fact (e.g. `workType`
15
+ // away from hotfix) would silently defeat the policy this command makes deterministic.
26
16
  const OVERRIDABLE_SIGNAL_KEYS = ['standardsViolations', 'designSystemDivergence'];
27
17
 
28
- /**
29
- * Os standards que fazem uma feature ser "de mídia", como os ids de
30
- * STANDARDS.json. Fato do plano congelado: ou uma fatia pinou o standard em
31
- * `standards:`, ou não pinou.
32
- */
33
18
  export const MEDIA_STANDARD_IDS = [
34
19
  'ai-agents-modalities-image-gen',
35
20
  'ai-agents-media-video',
36
21
  'ai-agents-media-pipeline',
37
22
  ];
38
23
 
39
- /**
40
- * O plano escreve o standard pelo id ou pelo caminho (`ai-agents/media-video.md`,
41
- * com ou sem `framework/` ou `standards/` na frente): as duas grafias viram o id.
42
- */
24
+ // O plano escreve o standard pelo id ou pelo caminho (com ou sem `framework/`/`standards/`).
43
25
  export function standardIdOf(value) {
44
26
  return String(value ?? '').trim().replace(/\\/g, '/')
45
27
  .replace(/^(\.morph\/)?(framework\/)?(standards\/)?/, '')
@@ -47,41 +29,17 @@ export function standardIdOf(value) {
47
29
  .replace(/\//g, '-');
48
30
  }
49
31
 
50
- /**
51
- * Uma feature "gera mídia" quando alguma fatia extraída pinou um dos
52
- * standards de mídia. Predicado exportado porque decide se um guardrail arma:
53
- * um que sempre devolve `false` desarma a pausa de mídia da review e a rota opus em
54
- * silêncio.
55
- *
56
- * @param {{ standards?: string[] }[]|null|undefined} slices
57
- */
32
+ // Decide se um guardrail arma: um predicado que sempre devolve `false` desarma em silêncio a
33
+ // pausa de mídia da review e a rota opus.
58
34
  export function featureGeneratesMedia(slices) {
59
35
  if (!Array.isArray(slices)) return false;
60
36
  return slices.some((s) => Array.isArray(s?.standards)
61
37
  && s.standards.some((id) => MEDIA_STANDARD_IDS.includes(standardIdOf(id))));
62
38
  }
63
39
 
64
- /**
65
- * "Esta feature mexeu em prompt ou em modelo?" — em duas metades, porque uma só
66
- * não distingue as duas situações que pedem ações diferentes.
67
- *
68
- * `declared: false` é "o projeto não declara caminho algum de prompt/modelo" —
69
- * a regra é inerte por construção e a razão registrada diz isso. `declared:
70
- * true` com `touched: []` é "declara, e nada foi tocado" — a regra rodou e não
71
- * viu nada. Colapsar os dois num array vazio faria um projeto que nunca
72
- * configurou `watch[]` parecer um projeto que foi verificado.
73
- *
74
- * O predicado NÃO é invenção deste framework: o repositório de referência já
75
- * roda exatamente ele em produção, disparando publicação por `paths:` sobre os
76
- * mesmos globs e decidindo o escopo com `git diff --name-only`.
77
- *
78
- * COLETA, NUNCA DECIDE. Nada aqui julga se o que foi tocado deveria pausar um
79
- * gate — política escrita no coletor cria duas fontes para o mesmo fato.
80
- *
81
- * @param {string} cwd
82
- * @param {string|null} defaultBranch
83
- * @returns {{declared: boolean, watch: string[], touched: string[]}}
84
- */
40
+ // `declared: false` (nenhum caminho declarado: regra inerte) e `touched: []` (declarou, nada
41
+ // tocado) não podem colapsar num array vazio. COLETA, NUNCA DECIDE: política no coletor cria
42
+ // duas fontes para o mesmo fato.
85
43
  function collectPromptModel(cwd, defaultBranch) {
86
44
  const watch = readMorphConfig(cwd)?.project?.evals?.watch;
87
45
  const globs = Array.isArray(watch) ? watch.filter((g) => typeof g === 'string' && g.trim()) : [];
@@ -96,12 +54,7 @@ function collectPromptModel(cwd, defaultBranch) {
96
54
  };
97
55
  }
98
56
 
99
- /**
100
- * Quais relatórios o ship exige: a regra única que gate-check, approve e
101
- * `report-check --before-eval` leem. Coleta do git, nunca decide.
102
- *
103
- * @returns {{ required: string[], security: { required: boolean, reasons: string[] } }}
104
- */
57
+ /** A regra única de quais relatórios o ship exige, lida por gate-check, approve e report-check. */
105
58
  export function collectReviewRequirement(cwd, feature) {
106
59
  let changed = [];
107
60
  try { changed = changedPathsAgainst(detectDefaultBranch(cwd), cwd); } catch { changed = []; }
@@ -109,48 +62,21 @@ export function collectReviewRequirement(cwd, feature) {
109
62
  return { required: requiredReviewRoles({ workType: feature?.workType, securityRequired: security.required }), security };
110
63
  }
111
64
 
112
- /** O sinal `reviewReports` do ship, lido do disco. */
113
65
  export function collectReviewSignal(cwd, featureName, feature, planChecks = readPlanChecks(cwd, featureName)) {
114
66
  const { required } = collectReviewRequirement(cwd, feature);
115
67
  const checkIds = Array.isArray(planChecks.checks?.checks) ? planChecks.checks.checks.map((c) => c.id) : null;
116
68
  return reviewSignal(reviewReports(cwd, featureName), { required, checkIds });
117
69
  }
118
70
 
119
- /**
120
- * Why an automatic signature may not close the plan gate — the same rule that
121
- * makes gate-check pause it, read here by approve and advance.
122
- *
123
- * @returns {string|null}
124
- */
71
+ /** Why an automatic signature may not close the plan gate: the rule that makes gate-check pause it. */
125
72
  export function planAutoApprovalBlock(cwd, featureName) {
126
73
  const reason = planChecksPause(readPlanChecks(cwd, featureName).status);
127
74
  return reason ? reason.replace('{feature}', featureName) : null;
128
75
  }
129
76
 
130
- /**
131
- * Collect every signal `shouldPauseGate()` (risk-detector.js) is documented
132
- * to consume, straight off disk — the deterministic call site risk-detector
133
- * never had (it shipped fully tested with zero callers; see the R4 report).
134
- *
135
- * `verificationStatus` feeds a real pause rule (verify vermelho pausa —
136
- * política adicionada em risk-detector.js no fecho do R4, qualquer gate).
137
- * `verificationStale` também alimenta uma regra própria, mas só no ship
138
- * (`gate === 'review'`) — sha do stamp divergindo do HEAD atual. `gate` em si
139
- * é o que permite risk-detector.js diferenciar as duas. `e2eStatus`/`e2eReason`
140
- * alimentam a regra da dispensa de e2e (também só no ship): um nó e2e que
141
- * PULOU não reprova o rollup, logo `verificationStatus` sai 'pass' e a razão
142
- * literal é o único fato que distingue dispensa legítima de ausência de prova.
143
- * `planChecks` (só no gate do plano) é o estado dos checks extraídos contra o
144
- * plano; `sliceCount` e as `standards` das fatias saem da mesma extração.
145
- * `verification` completo, `e2eSkipClass`, diffstat e sha seguem como contexto
146
- * de auditoria persistido, sem regra própria.
147
- *
148
- * @param {string} featureName
149
- * @param {object} feature
150
- * @param {string} cwd
151
- * @param {string} gateName
152
- * @returns {object}
153
- */
77
+ // Collects only; risk-detector.js decides. `e2eStatus`/`e2eReason` exist because a skipped e2e
78
+ // node does not fail the rollup: the literal reason is the only fact separating a legitimate
79
+ // waiver from missing proof.
154
80
  export function collectSignals(featureName, feature, cwd, gateName) {
155
81
  const planChecks = readPlanChecks(cwd, featureName);
156
82
  const slices = planChecks.checks?.slices ?? null;
@@ -159,9 +85,7 @@ export function collectSignals(featureName, feature, cwd, gateName) {
159
85
  const verification = feature.verification || null;
160
86
 
161
87
  const signals = {
162
- // Furo 2: disk-derived, never in OVERRIDABLE_SIGNAL_KEYS — a caller
163
- // cannot spoof which gate risk-detector.js thinks it is deciding, same
164
- // anti-spoof rationale as workType (hotfix's forced pause).
88
+ // Disk-derived, never overridable: a caller cannot spoof which gate is being decided.
165
89
  gate: gateName,
166
90
  workType: feature.workType || 'feature',
167
91
  sliceCount: slices ? slices.length : null,
@@ -171,8 +95,7 @@ export function collectSignals(featureName, feature, cwd, gateName) {
171
95
 
172
96
  if (gateName === 'plan') signals.planChecks = planChecks.status;
173
97
 
174
- // Só onde o checklist decide algo: no fecho do design e no ship. Fato de
175
- // disco, fora de OVERRIDABLE_SIGNAL_KEYS.
98
+ // Só onde o checklist decide algo: no fecho do design e no ship.
176
99
  if (gateName === 'uiux' || gateName === 'review') {
177
100
  signals.screenChecklist = collectScreenChecklist(cwd, featureName, gateName);
178
101
  }
@@ -185,37 +108,19 @@ export function collectSignals(featureName, feature, cwd, gateName) {
185
108
  };
186
109
  }
187
110
 
188
- // verificationStatus alimenta a regra "verify vermelho pausa"; o resto do
189
- // stamp é auditoria. verificationStale é fail-open na direção "not stale":
190
- // stamp com sha null (checkout sem git) ou stamp ausente lê como NOT stale.
111
+ // verificationStale é fail-open: stamp sem sha (checkout sem git) ou ausente lê como NOT stale.
191
112
  signals.verification = verification;
192
113
  if (verification?.status) signals.verificationStatus = verification.status;
193
114
  signals.verificationStale = treeMovedSince(cwd, verification?.sha, sha);
194
115
 
195
- // O nó e2e, separado do rollup de propósito: um `skip` NÃO reprova
196
- // `verification.status`, então sem estas duas chaves o gate lê 'pass' tanto
197
- // quando o e2e rodou e passou quanto quando ele nunca rodou. `null` aqui é
198
- // DESCONHECIDO (stamp ausente, ou anterior ao nó e2e), nunca 'skip'.
199
- // `e2eSkipClass` é DERIVADO pela fonte única (classifyE2eSkip, em
200
- // risk-detector.js) e persistido só como auditoria — quem decide continua
201
- // sendo shouldPauseGate, aqui não há política.
202
- //
203
- // Nenhuma das três entra em OVERRIDABLE_SIGNAL_KEYS: descrevem O QUE
204
- // ACONTECEU no disco, como `gate`/`workType`, não uma opinião que o chamador
205
- // possa afirmar. Um `--signals '{"e2eStatus":"pass"}'` compraria o Gate 3.
116
+ // Separado do rollup: um `skip` NÃO reprova `verification.status`. `null` é DESCONHECIDO, nunca
117
+ // 'skip'. Fora de OVERRIDABLE_SIGNAL_KEYS: um `--signals '{"e2eStatus":"pass"}'` compraria o ship.
206
118
  signals.e2eStatus = verification?.nodes?.e2e ?? null;
207
119
  signals.e2eReason = verification?.e2eReason ?? null;
208
120
  signals.e2eSkipClass = signals.e2eStatus === 'skip' ? classifyE2eSkip(signals.e2eReason) : null;
209
121
 
210
- // O nó de eval, separado do rollup pela mesma razão que o e2e: um eval que
211
- // PULOU não avermelha `verification.status`, então o gate
212
- // leria 'pass' tanto para "o eval rodou e passou" quanto para "o eval nunca
213
- // rodou". `null` aqui é "este projeto não declara nó de eval" — DISTINTO de
214
- // 'skip', que é "declara, e esta corrida não o executou".
122
+ // Mesma razão do e2e. `null` = o projeto não declara nó de eval, DISTINTO de 'skip'.
215
123
  signals.evalsStatus = verification?.nodes?.evals ?? null;
216
- // O que a feature TOCOU, contra os globs que o projeto declarou como
217
- // prompt/modelo. Fato de disco: `declared` vem do config commitado, `touched`
218
- // do git. Nenhuma política mora aqui — quem decide é shouldPauseGate.
219
124
  signals.promptModel = collectPromptModel(cwd, defaultBranch);
220
125
 
221
126
  signals.diffstat = getDiffStat(defaultBranch, cwd);
@@ -224,20 +129,8 @@ export function collectSignals(featureName, feature, cwd, gateName) {
224
129
  return signals;
225
130
  }
226
131
 
227
- /**
228
- * `morph-spec gate-check <feature> <gate> [--signals <json>] [--trust <mode>]
229
- * [--json]` — the deterministic, disk-derived counterpart to `gate-decision`:
230
- * collects the signals `shouldPauseGate()` (risk-detector.js) already knows
231
- * how to score, calls it, and persists the result through the SAME write path
232
- * as `gate-decision` ({@link persistGateDecision}) so the two commands can
233
- * never produce a differently-shaped `gateDecisions.{gate}` entry.
234
- *
235
- * Golden rule: this command does NOT change gate policy. It is a faithful
236
- * caller of risk-detector.js's existing, already-tested rules — nothing here
237
- * adds a new pause condition. Where the skills' natural-language risk tables
238
- * (morph-plan/morph-review/morph-apply) diverge from risk-detector.js,
239
- * risk-detector.js wins.
240
- */
132
+ // Never changes gate policy: a faithful caller of risk-detector.js, which wins wherever the
133
+ // skills' risk tables diverge. Persists through the same path as `gate-decision`.
241
134
  export async function gateCheckCommand(featureName, gateName, options = {}) {
242
135
  try {
243
136
  if (!VALID_GATES.includes(gateName)) {
@@ -252,10 +145,7 @@ export async function gateCheckCommand(featureName, gateName, options = {}) {
252
145
  process.exit(1);
253
146
  }
254
147
 
255
- // Owner-tree guard: gate-check reads workType/checks/verdict/git off cwd
256
- // AND writes the resulting decision through persistGateDecision — both
257
- // halves are wrong from a tree that isn't this feature's own, so refuse
258
- // rather than compute a decision from stale reads and then fail to save it.
148
+ // Both the reads and the persisted decision are wrong from a tree that doesn't own the feature.
259
149
  const owner = assertOwnerTree(featureName, process.cwd());
260
150
  if (owner.blocked) {
261
151
  for (const { tone, text } of ownerTreeRefusalLines(featureName, owner.tree, owner.branch, `morph-spec gate-check ${featureName} ${gateName}`)) {
@@ -15,11 +15,7 @@ function parsePauseValue(raw) {
15
15
  process.exit(1);
16
16
  }
17
17
 
18
- /**
19
- * trust from the committed config (BOM-tolerant), defaulting to "auto".
20
- * Shared by `gate-decision` (recorded as metadata) and `gate-check`
21
- * (drives shouldPauseGate's decision directly).
22
- */
18
+ /** `trust` from the committed config (BOM-tolerant), defaulting to "auto". */
23
19
  export function readTrustFromConfig() {
24
20
  try {
25
21
  const path = join(process.cwd(), '.morph', 'config', 'config.json');
@@ -32,22 +28,8 @@ export function readTrustFromConfig() {
32
28
  }
33
29
 
34
30
  /**
35
- * Write the canonical `gateDecisions.{gate}` entry to a feature's
36
- * authoritative feature.json — the ONE write path both `gate-decision` (a
37
- * caller-supplied pause/reason/signals, e.g. an LLM's natural-language risk
38
- * read, or a manual override) and `gate-check` (a `shouldPauseGate()`-derived
39
- * pause/reason/signals) feed into, so the two commands can never drift into
40
- * two different persisted shapes for the same field. Does NOT validate
41
- * `gateName` — callers that need a friendly CLI error check `VALID_GATES`
42
- * themselves before calling this.
43
- *
44
- * @param {string} featureName
45
- * @param {string} gateName
46
- * @param {{ pause: boolean, reason?: string, signals?: object, trust?: string, cwd?: string, source: 'manual'|'gate-check' }} decision
47
- * @returns {Promise<{ pause: boolean, reason: string, signals: object, trust: string, decidedAt: string, mockupsHash?: string|null }>}
48
- * the exact entry written (decidedAt is server-side, generated here). A uiux
49
- * decision also carries the hash of the mockup it saw: it answers that
50
- * mockup's pause, not the next one's.
31
+ * The ONE write path of `gateDecisions.{gate}`, shared by `gate-decision` and `gate-check`.
32
+ * Does not validate `gateName`. A uiux decision records the `mockupsHash` it saw.
51
33
  */
52
34
  export async function persistGateDecision(featureName, gateName, { pause, reason, signals = {}, trust, cwd = process.cwd(), source } = {}) {
53
35
  const entry = {
@@ -64,24 +46,6 @@ export async function persistGateDecision(featureName, gateName, { pause, reason
64
46
  return entry;
65
47
  }
66
48
 
67
- /**
68
- * `morph-spec gate-decision <feature> <gate> --pause <bool> [--reason <txt>]
69
- * [--signals <json>] [--trust <mode>]` — record the trust decision for a gate
70
- * under `gateDecisions` in the authoritative feature.json.
71
- *
72
- * The timestamp (`decidedAt`) is
73
- * stamped server-side and the write is atomic (state-manager). This replaces
74
- * the manual Edit of feature.json from the skills — which collided with the
75
- * state-sync hook rewriting the file between the agent's Read and Edit
76
- * ("file modified since read"). The canonical shape is documented in
77
- * framework/schemas/feature.schema.json and consumed by gate-guard.js.
78
- *
79
- * The manual escape hatch: `gate-check` (src/commands/state/gate-check.js) is
80
- * the default, disk-derived path via `shouldPauseGate()`; this command stays
81
- * for a caller that already knows the decision (an LLM's natural-language
82
- * risk read a script can't see, or a deliberate override) — both write
83
- * through {@link persistGateDecision}, so the persisted shape never diverges.
84
- */
85
49
  export async function gateDecisionCommand(featureName, gateName, options = {}) {
86
50
  try {
87
51
  if (!VALID_GATES.includes(gateName)) {
@@ -1,34 +1,9 @@
1
- /**
2
- * `morph-spec handoff <feature>` — generate the compressed session-handoff
3
- * document (`.morph/features/{f}/handoff.md`).
4
- *
5
- * The fleet workflow hands a feature between Claude Code instances (a new
6
- * worktree session, a resumed session after a phase transition). The handoff
7
- * is the sanctioned way to carry context across: derived, regenerable, and
8
- * pointer-based (see handoff-generator.js). This command is also called
9
- * in-process by `worktree setup`, `create --worktree`, `advance` and
10
- * `approve` — the CLI surface exists for the LLM/user to refresh the
11
- * document on demand.
12
- *
13
- * WORKTREE-AWARE: `create --worktree` writes a COMPLETE feature.json into the
14
- * primary root and the branch-committed copy (`morph/{f}`) becomes the truth
15
- * from then on. Building the handoff from `process.cwd()` unconditionally
16
- * used to mean: run this from the root while the worktree is alive, and you
17
- * either got "Feature not found" (root's copy was already sheded) or a
18
- * document silently built from the stale birth skeleton, written INTO the
19
- * root (a second, always-defasada copy of a document that's supposed to be
20
- * regenerated, not duplicated). This resolves the owning tree first
21
- * (`resolveFeatureTree`) and writes THERE, unless `--here` forces the cwd.
22
- */
23
-
24
1
  import chalk from 'chalk';
25
2
  import { buildHandoff, writeHandoff } from '../../lib/generators/handoff-generator.js';
26
3
  import { resolveFeatureTree } from '../../lib/worktree/feature-truth.js';
27
4
 
28
- /**
29
- * @param {string} featureName
30
- * @param {{ json?: boolean, here?: boolean }} [options]
31
- */
5
+ // With a live worktree the root copy is a stale birth skeleton: build and write in the
6
+ // owning tree (`resolveFeatureTree`) unless `--here` forces the cwd.
32
7
  export function handoffCommand(featureName, options = {}) {
33
8
  const cwd = process.cwd();
34
9
 
@@ -44,9 +19,7 @@ export function handoffCommand(featureName, options = {}) {
44
19
  }
45
20
 
46
21
  if (owner.source === 'branch-only') {
47
- // The truth exists, committed on morph/{feature}, but is not materialized
48
- // into ANY tree right now. A handoff points at files on disk — pointing
49
- // at files that exist nowhere is worse than recusing outright.
22
+ // The handoff points at files on disk; a truth materialized in no tree is refused.
50
23
  const hint = `morph-spec worktree setup ${featureName} (ou: git checkout ${owner.branch})`;
51
24
  if (options.json) {
52
25
  console.log(JSON.stringify({ error: 'feature-not-in-tree', feature: featureName, branch: owner.branch, hint }));
@@ -10,32 +10,8 @@ import { morphBranches, currentBranch, branchView } from '../../lib/worktree/fea
10
10
  import { buildPrIndex } from '../../lib/git/pr-index.js';
11
11
  import { pendingPrFeatures, prForFeature } from '../../lib/git/pending-prs.js';
12
12
 
13
- /**
14
- * `morph-spec list [--all] [--json] [--no-branch]` — the feature audit board.
15
- *
16
- * One row per feature: slug × workType × status × phase × gates (G1/Gd/G3) ×
17
- * description, plus a flags column for anomalies (missing description, gate
18
- * order, dangling worktree pointer). Complements `delete`: see what's dead
19
- * before removing it. Replaces the ad-hoc "loop over every feature.json in
20
- * python" audit.
21
- *
22
- * Read-only by construction — reads feature.json straight off disk (never
23
- * `loadState`, which mutates), so listing can never resurrect or prune state.
24
- * Always exits 0 (fail-open — an empty board is an answer).
25
- *
26
- * WORKTREE TRUTH: `create --worktree` writes a COMPLETE feature.json into the
27
- * primary root, then the branch-committed copy (`morph/{f}`) becomes the
28
- * truth and the root's copy never changes again. By default this command
29
- * reads that branch when it disagrees with the disk — same mechanism fleet
30
- * already uses (`branchToReadFrom`/`branchView`, moved to feature-truth.js so
31
- * both consumers share the one place this condition lives). `--no-branch`
32
- * turns that off for a faster, disk-only read.
33
- *
34
- * NOT AN OBJECTIVE: this command only enumerates what exists IN THIS TREE.
35
- * A feature that lives solely on a branch with no local `.morph/features/`
36
- * copy in any tree never appears here — aggregating across trees is
37
- * `morph-spec fleet`'s job, not this one's.
38
- */
13
+ // Read-only: feature.json straight off disk, never `loadState` (which mutates). By default the
14
+ // branch `morph/{f}` wins over a stale root copy; `--no-branch` reads only the disk.
39
15
 
40
16
  const G = ['plan', 'uiux', 'review'];
41
17
 
@@ -59,34 +35,23 @@ function readFeaturesIn(baseDir, subdir, location) {
59
35
  return rows;
60
36
  }
61
37
 
62
- /** The feature's `worktree` pointer, normalized with a computed `exists`. */
63
38
  function readWorktreePointer(fj) {
64
39
  const w = fj?.worktree;
65
40
  if (!w?.path) return null;
66
41
  return { path: w.path, branch: w.branch || null, exists: existsSync(w.path) };
67
42
  }
68
43
 
69
- /**
70
- * Build a row's flags/gates/identity shape from a `feature.json` object, given
71
- * an already-resolved phase and provenance. Shared by the disk read
72
- * (`buildRow`) and the branch read (`resolveBranchTruth`) so the two never
73
- * drift on what counts as an anomaly.
74
- */
44
+ // Shared by the disk and branch reads so the two never drift on what counts as an anomaly.
75
45
  function shapeRow(name, fj, location, phase, { corruptDisk = false, worktree = null, source = 'disk', branch = null } = {}) {
76
46
  const gates = {};
77
47
  for (const g of G) gates[g] = fj.approvalGates?.[g]?.approved === true;
78
48
 
79
- // Gate 3 approved WITHOUT the independent review reports (`approve --self-assessed`).
80
- // A recorded degradation is only worth recording if it is visible — this is
81
- // the surface that makes it so (D2).
49
+ // Review approved via `--self-assessed`: a recorded degradation is only worth it if visible.
82
50
  const selfAssessed = fj.approvalGates?.review?.selfAssessed === true;
83
51
 
84
52
  const flags = [];
85
53
  if (corruptDisk) flags.push('corrupt');
86
54
  if (selfAssessed) flags.push('self-assessed-g3');
87
- // Identity + gate-order anomalies come from the shared rules
88
- // (core/state/feature-identity.js, core/state/gate-order.js) — same source
89
- // doctor reports from.
90
55
  for (const field of missingIdentity(fj)) flags.push(`missing-${field}`);
91
56
  for (const v of gateOrderViolations(fj.approvalGates)) flags.push(`gate-order:${v.gate}`);
92
57
  if (worktree && !worktree.exists) flags.push('worktree-missing');
@@ -104,10 +69,7 @@ function shapeRow(name, fj, location, phase, { corruptDisk = false, worktree = n
104
69
  location,
105
70
  createdAt: fj.createdAt || null,
106
71
  updatedAt: fj.updatedAt || null,
107
- // Provenance — added for worktree-truth awareness. `worktree` is always
108
- // read from the LOCAL disk copy (root-owned bookkeeping, stripped from
109
- // branch-committed copies at birth) regardless of where `source` reads
110
- // status/phase/gates from.
72
+ // `worktree` always comes from the local disk copy, whatever `source` reads the rest from.
111
73
  source,
112
74
  branch,
113
75
  worktree,
@@ -124,14 +86,7 @@ function buildRow(name, fj, fdir, location) {
124
86
  });
125
87
  }
126
88
 
127
- /**
128
- * Refresh active rows from the branch that owns their truth, when the disk
129
- * copy in THIS tree is not that owner. Never throws — a git hiccup or a
130
- * corrupt blob just leaves the disk-read row in place.
131
- *
132
- * @param {string} cwd
133
- * @param {object[]} rows - mutated in place (active rows only)
134
- */
89
+ // Never throws: a git hiccup or a corrupt blob leaves the disk-read row in place.
135
90
  function resolveBranchTruth(cwd, rows) {
136
91
  if (!isGitRepo(cwd)) return;
137
92
  let head;
@@ -160,15 +115,8 @@ function resolveBranchTruth(cwd, rows) {
160
115
  }
161
116
  }
162
117
 
163
- /**
164
- * Flag an active row whose feature ALSO has an archived copy in this tree —
165
- * the signature left behind when a feature is merged and archived on its
166
- * branch (or via GitHub) without `morph-spec finish` ever running here:
167
- * the untracked root skeleton from `create` survives `git pull` (it was
168
- * never tracked, so pull has nothing to remove) while `.morph/archive/{f}`
169
- * arrives for real. Flagging beats suppressing the row — a read-only board
170
- * that resurrects or prunes nothing must not silently hide the anomaly either.
171
- */
118
+ // The untracked root skeleton survives a `git pull` that brings `.morph/archive/{f}`. Flagged,
119
+ // never hidden: a read-only board must not silently suppress the anomaly.
172
120
  function flagArchivedElsewhere(cwd, rows) {
173
121
  for (const row of rows) {
174
122
  if (row.location !== 'active') continue;
@@ -178,13 +126,8 @@ function flagArchivedElsewhere(cwd, rows) {
178
126
  }
179
127
  }
180
128
 
181
- /**
182
- * A linha de uma feature que existe SÓ como PR aberto — sem pasta ativa nem
183
- * arquivada nesta árvore. É o estado que o `finish --pr` deixa: o scaffold do
184
- * root foi apagado pelo shed e o archive viaja no diff do PR, chegando na
185
- * default apenas no merge. Sem esta linha o trabalho fica sem dono no quadro,
186
- * e uma branch sem dono é candidata a poda.
187
- */
129
+ // O estado que o `finish --pr` deixa: sem esta linha o trabalho fica sem dono no quadro, e
130
+ // uma branch sem dono é candidata a poda.
188
131
  function pendingRow(p) {
189
132
  return {
190
133
  feature: p.feature,
@@ -232,15 +175,8 @@ export function listCommand(options = {}) {
232
175
  // Commander's `--no-branch` sets options.branch = false; anything else (undefined/true) reads the branch.
233
176
  if (options.branch !== false) resolveBranchTruth(cwd, rows);
234
177
 
235
- // Estado do PR — uma consulta ao `gh` por invocação, compartilhada pela
236
- // coluna e pela síntese. `--no-branch` (o modo offline rápido) pula as duas.
237
- //
238
- // `prIndex` continua `null` quando a pergunta não pôde ser feita (sem `gh`,
239
- // sem auth, remoto não-GitHub, ou `--no-branch`). É o mesmo sinal que
240
- // `--json` já expõe como `pr: null` por linha — a tabela texto usa o mesmo
241
- // fato para decidir se a coluna PR sequer aparece (achado #2): índice `null`
242
- // degrada para o comportamento de antes, sem coluna e sem `-` que afirme
243
- // ausência.
178
+ // Uma consulta ao `gh` por invocação. `prIndex` `null` = a pergunta não pôde ser feita: sem
179
+ // coluna PR, e sem `-` que afirme "sem PR" para o que é "não sabemos".
244
180
  let prIndex = null;
245
181
  if (options.branch !== false) {
246
182
  prIndex = buildPrIndex(cwd);
@@ -249,8 +185,7 @@ export function listCommand(options = {}) {
249
185
  row.pr = pr ? { number: pr.number, state: pr.state, url: pr.url, ageDays: null } : null;
250
186
  }
251
187
  const known = new Set(rows.map((r) => r.feature));
252
- // `null` propaga desconhecido: nenhuma linha sintetizada, e nenhuma
253
- // afirmação de que não há pendência.
188
+ // `null` propaga desconhecido: nenhuma linha sintetizada.
254
189
  for (const p of pendingPrFeatures(cwd, { index: prIndex }) || []) {
255
190
  if (!known.has(p.feature)) rows.push(pendingRow(p));
256
191
  }
@@ -271,13 +206,8 @@ export function listCommand(options = {}) {
271
206
  }
272
207
 
273
208
  const col = (s, w) => String(s ?? '-').padEnd(w);
274
- // A coluna PR só aparece quando o índice foi construído com sucesso (achado
275
- // #2) — um índice `null` não vira uma coluna cheia de `-`, que afirmaria
276
- // "sem PR" para algo que na verdade é "não sabemos".
277
209
  const showPr = Boolean(prIndex);
278
- // `pr-pending` é estado normal de trabalho em voo, não inconsistência —
279
- // não deve marcar `⚠` nem contar no rodapé (achado #6). Continua em
280
- // `flags` no `--json`: é metadado real, só não é anomalia.
210
+ // `pr-pending` é trabalho em voo, não anomalia: segue em `flags` no `--json`, sem `⚠`.
281
211
  const warnFlags = (r) => r.flags.filter((f) => f !== 'pr-pending');
282
212
  console.log(chalk.white.bold(
283
213
  col('FEATURE', 22) + col('TYPE', 9) + col('STATUS', 13) + col('PHASE', 17) + col('WHERE', 22) + (showPr ? col('PR', 12) : '')