@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,17 +1,4 @@
1
- /**
2
- * fleet-scanner.js — aggregate view of every active feature across the fleet.
3
- *
4
- * A fleet is N Claude Code instances, one per feature worktree, plus whatever
5
- * is active in the primary root. Each instance only sees its own cwd; the
6
- * operator loses track of "what is each session doing, what is waiting on me".
7
- * This scanner answers that from the primary root's vantage point: one entry
8
- * per active feature, worktree or root-resident, with the operational facts
9
- * (phase, pending gates, task counts, port block, handoff freshness, last
10
- * commit, dirtiness) resolved per entry.
11
- *
12
- * Read-only and fail-open per FIELD: a corrupt feature.json or a git hiccup
13
- * nulls that field, never the scan.
14
- */
1
+ // Fail-open per FIELD: a corrupt feature.json or a git hiccup nulls that field, never the scan.
15
2
 
16
3
  import { execSync, execFileSync } from 'child_process';
17
4
  import { existsSync, readdirSync, readFileSync } from 'fs';
@@ -24,52 +11,31 @@ import {
24
11
  getFeature,
25
12
  derivePhaseForFeature,
26
13
  derivePhaseFrom,
14
+ fileSetArtifactProbe,
27
15
  getPendingGates,
28
16
  pendingGatesFrom,
29
17
  } from '../../../framework/hooks/shared/state-reader.js';
30
18
  import { listClaims } from '../../../framework/hooks/shared/claim-registry.js';
31
19
  import { buildPrIndex } from '../git/pr-index.js';
32
20
  import { pendingPrFeatures } from '../git/pending-prs.js';
21
+ import { sliceProgress, sliceProgressFrom } from '../../../framework/hooks/shared/slice-progress.js';
33
22
 
34
- function countFrom(raw) {
35
- const data = JSON.parse(raw);
36
- if (!Array.isArray(data.tasks)) return null;
37
- return { done: data.tasks.filter((t) => t.status === 'done').length, total: data.tasks.length };
38
- }
23
+ const counts = (progress) => (progress ? { done: progress.done, total: progress.total } : null);
39
24
 
40
- /**
41
- * Task progress for one feature.
42
- *
43
- * DIVERGENCE from the bug report, which blamed "a cached state.json/feature.json
44
- * snapshot": this never read either — it always read `2-plan/tasks.json`
45
- * directly. The real mechanism is which COPY of that file is on disk. Since
46
- * v8.4 every feature is born on `morph/{f}` and its tasks.json is COMMITTED
47
- * there; the primary root's working tree only ever holds the copy of the
48
- * CHECKED-OUT branch. An operator sitting on the default therefore read the
49
- * pre-work copy — the reported `2/2` for a feature whose branch already had
50
- * T1..T3 all done.
51
- *
52
- * @param {string} featureDir - the feature dir inside `path`
53
- * @param {{ root: string, feature: string, featureBranch: string|null }} ctx
54
- */
55
- function taskCounts(featureDir, { root, feature, featureBranch } = {}) {
25
+ // A root-resident feature's reports live on `morph/{f}`; the root tree only has the pre-work copy.
26
+ function sliceCounts(featureDir, { root, feature, featureBranch } = {}) {
56
27
  if (featureBranch && root && feature) {
57
- try {
58
- const raw = execFileSync(
59
- 'git',
60
- ['show', `${featureBranch}:.morph/features/${feature}/2-plan/tasks.json`],
61
- { cwd: root, stdio: 'pipe' },
62
- ).toString();
63
- const counted = countFrom(raw);
64
- if (counted) return counted;
65
- } catch { /* no such branch/blob — fall through to the working tree */ }
66
- }
67
-
68
- try {
69
- return countFrom(readFileSync(join(featureDir, '2-plan', 'tasks.json'), 'utf8'));
70
- } catch {
71
- return null;
28
+ const show = (rel) => {
29
+ try {
30
+ return execFileSync('git', ['show', `${featureBranch}:.morph/features/${feature}/${rel}`], { cwd: root, stdio: 'pipe' }).toString();
31
+ } catch {
32
+ return null;
33
+ }
34
+ };
35
+ const onBranch = sliceProgressFrom(show('2-plan/checks.json'), (id) => show(`3-implement/reports/${id}.md`));
36
+ if (onBranch) return counts(onBranch);
72
37
  }
38
+ return counts(sliceProgress(featureDir));
73
39
  }
74
40
 
75
41
  function handoffInfo(featureDir) {
@@ -101,20 +67,7 @@ function dirtyCount(worktreePath) {
101
67
  }
102
68
  }
103
69
 
104
- /**
105
- * One `git ls-remote --heads origin` for the WHOLE scan → Map<branch, sha>.
106
- *
107
- * The fleet used to read only local state, so a feature whose branch (and PR)
108
- * already lived on origin still showed up as "pending, nothing pushed" — that
109
- * reading is what made a second session redo finished work. Resolved once per
110
- * scan, never per entry.
111
- *
112
- * Returns `null` when there is no origin, or the probe fails/times out: the
113
- * entries then carry `remote: null` ("unknown"), never a false `pushed: false`.
114
- *
115
- * @param {string} root
116
- * @returns {Map<string,string>|null}
117
- */
70
+ // null = unknown (no origin, or the probe failed): entries get `remote: null`, never a false `pushed: false`.
118
71
  function remoteHeads(root) {
119
72
  if (!hasOriginRemote(root)) return null;
120
73
  try {
@@ -132,28 +85,11 @@ function remoteHeads(root) {
132
85
  }
133
86
  }
134
87
 
135
- /**
136
- * One `gh pr list` for the whole scan → Map<headRefName, {number, state}>.
137
- *
138
- * Delegates to the shared `buildPrIndex` so `fleet`, `branch prune` and
139
- * `doctor --worktrees` cannot answer "does this branch have a PR" differently
140
- * — the destructive two read it to decide what NOT to delete.
141
- *
142
- * Fail-open: `null` (no `gh`, not authenticated, not GitHub, timeout) just
143
- * leaves the PR half of the column empty while `pushed` still works.
144
- *
145
- * @param {string} root
146
- * @returns {Map<string,{number:number,state:string}>|null}
147
- */
148
88
  function remotePrs(root) {
149
89
  return buildPrIndex(root, { limit: 100 });
150
90
  }
151
91
 
152
- /**
153
- * The remote fact for one feature. Worktree entries know their real branch
154
- * (which may carry a `--fresh` date suffix); root-resident features fall back
155
- * to the canonical `morph/{feature}` every feature is born on.
156
- */
92
+ // A worktree's real branch may carry a `--fresh` date suffix; only root entries fall back to `morph/{f}`.
157
93
  function buildRemote(feature, branch, heads, prs) {
158
94
  if (!heads) return null;
159
95
  const remoteBranch = branch || `morph/${feature}`;
@@ -164,23 +100,17 @@ function buildRemote(feature, branch, heads, prs) {
164
100
  function buildEntry(feature, { location, path, branch, root, allocations, blockSize, heads, prs, claims }) {
165
101
  let workType = null;
166
102
  let status = 'unknown';
167
- let phase = 'proposal';
103
+ let phase = 'plan';
168
104
  let pendingGates = [];
169
105
 
170
- // Resolved once and shared with taskCounts — see branchToReadFrom.
171
106
  const featureBranch = branchToReadFrom(root, feature, location, branch);
172
107
 
173
108
  try {
174
- // B3b: phase and gates suffered the same branch-staleness B3 fixed for
175
- // tasks. They read `.morph/features/{f}/` through the working tree, so a
176
- // root parked on the default reported the PRE-WORK phase for a feature
177
- // whose branch was already at implement — and re-asked for a gate the
178
- // feature branch had already approved.
179
109
  const view = featureBranch ? branchView(root, feature, featureBranch) : null;
180
110
  if (view) {
181
111
  workType = view.fj?.workType || null;
182
112
  status = view.fj?.status || 'unknown';
183
- phase = derivePhaseFrom(view.fj, (rel) => view.files.has(rel));
113
+ phase = derivePhaseFrom(view.fj, fileSetArtifactProbe(view.files));
184
114
  pendingGates = pendingGatesFrom(view.fj);
185
115
  } else {
186
116
  const fj = getFeature(feature, path);
@@ -189,7 +119,7 @@ function buildEntry(feature, { location, path, branch, root, allocations, blockS
189
119
  phase = derivePhaseForFeature(feature, path);
190
120
  pendingGates = getPendingGates(feature, path);
191
121
  }
192
- } catch { /* field-level fail-open */ }
122
+ } catch { /* fail-open */ }
193
123
 
194
124
  const featureDir = join(path, '.morph', 'features', feature);
195
125
  const alloc = allocations[feature];
@@ -202,7 +132,7 @@ function buildEntry(feature, { location, path, branch, root, allocations, blockS
202
132
  location,
203
133
  path,
204
134
  branch,
205
- tasks: taskCounts(featureDir, { root, feature, featureBranch }),
135
+ slices: sliceCounts(featureDir, { root, feature, featureBranch }),
206
136
  env: alloc && Number.isInteger(alloc.base)
207
137
  ? { base: alloc.base, range: [alloc.base, alloc.base + blockSize - 1] }
208
138
  : null,
@@ -210,20 +140,11 @@ function buildEntry(feature, { location, path, branch, root, allocations, blockS
210
140
  lastCommit: location === 'worktree' && branch ? lastCommitRelative(root, branch) : null,
211
141
  dirty: location === 'worktree' ? dirtyCount(path) : null,
212
142
  remote: buildRemote(feature, branch, heads, prs),
213
- // Advisory ownership (B1): which session is on this feature right now.
214
- // `live` is what matters for the operator — a stale claim is reported as
215
- // stale rather than hidden, so "nobody is here" and "somebody died here"
216
- // stay distinguishable.
143
+ // A stale claim is reported, not hidden: "nobody is here" ≠ "somebody died here".
217
144
  claim: claims?.[feature] || null,
218
145
  };
219
146
  }
220
147
 
221
- /**
222
- * Scan the fleet from anywhere inside the repo.
223
- *
224
- * @param {string} [cwd]
225
- * @returns {{ root: string, entries: object[] }}
226
- */
227
148
  export function scanFleet(cwd = process.cwd()) {
228
149
  if (!isGitRepo(cwd)) {
229
150
  return { root: resolve(cwd).replace(/\\/g, '/'), entries: [] };
@@ -239,13 +160,12 @@ export function scanFleet(cwd = process.cwd()) {
239
160
  try {
240
161
  const reg = JSON.parse(readFileSync(registryPath(gitCommonDir), 'utf8'));
241
162
  if (reg && typeof reg.allocations === 'object' && reg.allocations !== null) allocations = reg.allocations;
242
- } catch { /* no env registry yet — env stays null per entry */ }
163
+ } catch { /* env stays null per entry */ }
243
164
  for (const c of listClaims(gitCommonDir)) claims[c.feature] = c;
244
165
  }
245
- } catch { /* no common dir — env and claim both stay null per entry */ }
166
+ } catch { /* env and claim stay null per entry */ }
246
167
  const { blockSize } = resolveFleetConfig(root);
247
168
 
248
- // Remote awareness, resolved ONCE for the whole scan (see remoteHeads/remotePrs).
249
169
  const heads = remoteHeads(root);
250
170
  const prs = heads ? remotePrs(root) : null;
251
171
 
@@ -254,7 +174,7 @@ export function scanFleet(cwd = process.cwd()) {
254
174
 
255
175
  for (const e of parseWorktreeList(root)) {
256
176
  const m = e.branch && e.branch.match(/^refs\/heads\/morph\/(.+)$/);
257
- if (!m) continue; // primary root entry and non-morph worktrees
177
+ if (!m) continue;
258
178
  inWorktree.add(m[1]);
259
179
  entries.push(buildEntry(m[1], {
260
180
  location: 'worktree',
@@ -269,7 +189,6 @@ export function scanFleet(cwd = process.cwd()) {
269
189
  }));
270
190
  }
271
191
 
272
- // Root-resident features (no live worktree): whatever has a feature.json.
273
192
  try {
274
193
  const featuresDir = join(root, '.morph', 'features');
275
194
  if (existsSync(featuresDir)) {
@@ -285,23 +204,9 @@ export function scanFleet(cwd = process.cwd()) {
285
204
  }
286
205
  } catch { /* fail-open */ }
287
206
 
288
- // Features that exist ONLY as an open PR: no registered worktree, no
289
- // `.morph/features/*` folder in this tree. This is exactly what
290
- // `finish --pr` leaves behind — and without this pass `fleet` would say
291
- // "nothing in flight" about work that is under review on GitHub.
292
- //
293
- // `prs` was attempted above whenever `heads` is truthy (`remotePrs` runs —
294
- // see `const prs = heads ? remotePrs(root) : null;` — regardless of
295
- // whether that `gh` call itself succeeded). Gate on the SAME condition
296
- // `remotePrs` used, not on `prs !== null`: `remotePrs`/`buildPrIndex` can
297
- // fail (timeout, not authenticated, malformed JSON) and legitimately
298
- // return `null` even after really invoking `gh`. Passing that `null`
299
- // through lets `pendingPrFeatures` short-circuit on its own documented
300
- // contract (explicit `null` index = known unknown, never a fallback build)
301
- // instead of retrying `gh` a second time in the same scan. Only when
302
- // `remotePrs` was never attempted at all (`heads` falsy) does
303
- // `pendingPrFeatures` get no index, so it makes the first (and only) `gh`
304
- // call itself.
207
+ // Features that exist only as an open PR (what `finish --pr` leaves). Gate on `heads`, not
208
+ // `prs !== null`: a `gh` that already ran and failed passes an explicit `null` index, so
209
+ // `pendingPrFeatures` does not call `gh` a second time.
305
210
  try {
306
211
  const seen = new Set(entries.map((e) => e.feature));
307
212
  const pendOpts = heads ? { index: prs } : {};
@@ -316,7 +221,7 @@ export function scanFleet(cwd = process.cwd()) {
316
221
  location: 'pr-pending',
317
222
  path: null,
318
223
  branch: p.branch,
319
- tasks: null,
224
+ slices: null,
320
225
  env: null,
321
226
  handoff: { exists: false, generatedAt: null },
322
227
  lastCommit: null,
@@ -325,7 +230,7 @@ export function scanFleet(cwd = process.cwd()) {
325
230
  claim: null,
326
231
  });
327
232
  }
328
- } catch { /* fail-open — a pendency not listed must never take down the scan */ }
233
+ } catch { /* fail-open */ }
329
234
 
330
235
  return { root, entries };
331
236
  }
@@ -1,77 +1,24 @@
1
- /**
2
- * ignored-triage.js — decide, per gitignored leftover, whether removing a
3
- * worktree would DESTROY something or merely delete a copy.
4
- *
5
- * WHY THIS EXISTS: `git worktree remove` deletes gitignored files outright and
6
- * the clean-check that gates it (`git status --porcelain`) omits them by
7
- * design, so a user's `.env` dies silently while the command reports success.
8
- * The first fix for that refused the removal whenever ANY unrecognised
9
- * gitignored path was present — correct, but blind: in the observed session a
10
- * worktree carried `.env` and `.env.e2e` that were BYTE-IDENTICAL to the
11
- * primary root's, alongside a `keys/key-*.xml` that existed nowhere else. The
12
- * refusal treated them alike, and the only escape it offered
13
- * (`--discard-ignored`) destroyed the unique one together with the replicas.
14
- *
15
- * The triage below replaces "recognised vs not" with the question that actually
16
- * decides whether bytes are lost:
17
- *
18
- * REPLICABLE — the primary root has the same relative path with identical
19
- * content. Deleting the worktree's copy loses nothing; the
20
- * original stays where it always was.
21
- * DISPOSABLE — a well-known build artifact or cache (`bin/`, `obj/`,
22
- * `node_modules/`, `.next/`, `TestResults/`, `graphify-out/`…,
23
- * see DISPOSABLE_DIR_NAMES). Reproducible from source, which is a
24
- * STRONGER guarantee than backed up.
25
- * EXCLUSIVE — everything else: no counterpart in the root, or a counterpart
26
- * whose content differs. These are the only bytes at stake, and
27
- * the caller copies them to `finish-backup` before proceeding.
28
- *
29
- * Fail-CLOSED on every uncertainty: an unreadable file, a hash that cannot be
30
- * computed, a directory too large to compare, a counterpart that is a link
31
- * rather than a real file — all classify as EXCLUSIVE. Being wrong in that
32
- * direction costs a backup copy; being wrong the other way costs the file.
33
- */
1
+ // `git worktree remove` deletes gitignored files and `git status` never shows them.
2
+ // Every uncertainty classifies as EXCLUSIVE (backed up): being wrong that way costs a
3
+ // copy, the other way costs the file.
34
4
 
35
5
  import { createHash } from 'crypto';
36
6
  import { existsSync, lstatSync, readdirSync, readFileSync } from 'fs';
37
7
  import { join } from 'path';
38
8
 
39
- /**
40
- * Well-known, regenerable build artifacts and caches. Deliberately narrow — do
41
- * NOT add names a project might use for hand-written output (`dist/`,
42
- * `build/`, `coverage/`); anything unlisted still reaches the EXCLUSIVE bucket,
43
- * where it is backed up rather than assumed disposable. Each name here is a
44
- * directory its own tool rewrites from scratch: `.next/` (next build/dev),
45
- * `.turbo/`, `.pytest_cache/`, `TestResults/` (VSTest), `graphify-out/`
46
- * (`morph-spec graph build`). `.next/` is also the one that makes a backup
47
- * weigh hundreds of megabytes; `graphify-out/` is seeded into every worktree
48
- * and is past the replica size cap (18 MB in a real project), so without its
49
- * entry every `finish` would back it up.
50
- *
51
- * `.auth/` (Playwright storage state) belongs here for a reason worth stating:
52
- * it holds a captured session, so it LOOKS like precious local state — but it
53
- * is regenerated by the auth setup project on the next run, and it expires on
54
- * its own. Keeping it out of the backup avoids copying credentials around.
55
- */
9
+ // Only dirs their own tool rewrites from scratch. Never add `dist/`, `build/` or `coverage/`:
10
+ // projects hand-write those. `.auth/` (Playwright session) is regenerated by the auth setup,
11
+ // and keeping it out of the backup avoids copying credentials around.
56
12
  const DISPOSABLE_DIR_NAMES = new Set([
57
13
  'bin', 'obj', 'node_modules', '__pycache__', 'test-results', '.auth',
58
14
  '.next', '.turbo', '.pytest_cache', 'TestResults', 'graphify-out',
59
15
  ]);
60
16
  const DISPOSABLE_FILE_RE = /\.tsbuildinfo$/;
61
17
 
62
- /**
63
- * Comparing a directory recursively is only worth doing while it stays small:
64
- * the point is to spare `.env`-sized replicas from a needless refusal, not to
65
- * checksum a data set. Past either cap the answer becomes EXCLUSIVE (the safe
66
- * direction), and the caller backs it up.
67
- */
68
18
  const REPLICABLE_MAX_FILES = 500;
69
19
  const REPLICABLE_MAX_BYTES = 8 * 1024 * 1024;
70
20
 
71
- /**
72
- * @param {string} p - forward-slash relative path
73
- * @returns {boolean}
74
- */
21
+ /** @param {string} p - forward-slash relative path */
75
22
  export function isDisposableBuildArtifact(p) {
76
23
  if (DISPOSABLE_FILE_RE.test(p)) return true;
77
24
  return p.split('/').filter(Boolean).some((seg) => DISPOSABLE_DIR_NAMES.has(seg));
@@ -85,11 +32,7 @@ function sha256OfFile(path) {
85
32
  }
86
33
  }
87
34
 
88
- /**
89
- * Real file (not a link, not a directory)? A reparse point must never be read
90
- * THROUGH — following one is the exact failure mode the whole teardown path
91
- * exists to prevent — so a link on either side makes the pair incomparable.
92
- */
35
+ // lstat, never stat: reading THROUGH a junction is the failure the teardown path exists to prevent.
93
36
  function isRealFile(path) {
94
37
  try {
95
38
  return lstatSync(path).isFile();
@@ -106,11 +49,7 @@ function isRealDir(path) {
106
49
  }
107
50
  }
108
51
 
109
- /**
110
- * Every real file under `dir`, relative to it. Returns null when the walk hits
111
- * a link, an unreadable entry, or either cap — "cannot enumerate" and "too big
112
- * to compare" both mean "do not claim this is replicable".
113
- */
52
+ // null on a link, an unreadable entry or either cap: none of them may be claimed replicable.
114
53
  function walkFiles(dir, budget) {
115
54
  const out = [];
116
55
  const stack = [''];
@@ -125,9 +64,9 @@ function walkFiles(dir, budget) {
125
64
  for (const e of entries) {
126
65
  const childRel = rel ? `${rel}/${e.name}` : e.name;
127
66
  const childAbs = join(dir, childRel);
128
- if (e.isSymbolicLink()) return null; // never compare through a link
67
+ if (e.isSymbolicLink()) return null;
129
68
  if (e.isDirectory()) { stack.push(childRel); continue; }
130
- if (!e.isFile()) return null; // socket/fifo/unknown — incomparable
69
+ if (!e.isFile()) return null;
131
70
  let size;
132
71
  try { size = lstatSync(childAbs).size; } catch { return null; }
133
72
  budget.files += 1;
@@ -140,14 +79,8 @@ function walkFiles(dir, budget) {
140
79
  }
141
80
 
142
81
  /**
143
- * Does `root/rel` hold exactly the same content as `worktree/rel`?
144
- *
145
- * Files compare by SHA-256; directories compare by having the SAME set of
146
- * files, each with an identical hash — a root copy that is merely a SUPERSET
147
- * is not a replica of the worktree's, because the worktree's own extra files
148
- * would still be lost.
149
- *
150
- * @returns {boolean} false on ANY uncertainty
82
+ * False on any uncertainty. A directory needs the SAME file set: a root superset is
83
+ * not a replica, because the worktree's own extra files would still be lost.
151
84
  */
152
85
  export function isReplicatedInRoot(worktreePath, rootPath, rel) {
153
86
  const src = join(worktreePath, rel);
@@ -178,19 +111,10 @@ export function isReplicatedInRoot(worktreePath, rootPath, rel) {
178
111
  return true;
179
112
  }
180
113
 
181
- return false; // a link, or something that is neither — incomparable
114
+ return false;
182
115
  }
183
116
 
184
- /**
185
- * Split gitignored worktree paths into the three buckets above.
186
- *
187
- * ORDER IS DELIBERATE: disposable is checked FIRST, so a `bin/` that happens to
188
- * be byte-identical to the root's is discarded rather than hashed, and a
189
- * `node_modules` never enters the comparison budget at all.
190
- *
191
- * @param {{ worktreePath: string, rootPath: string, paths: string[] }} args
192
- * @returns {{ replicable: string[], disposable: string[], exclusive: string[] }}
193
- */
117
+ // Disposable is checked first so `node_modules` never enters the hashing budget.
194
118
  export function triageIgnoredPaths({ worktreePath, rootPath, paths }) {
195
119
  const replicable = [];
196
120
  const disposable = [];
@@ -63,9 +63,8 @@ function snippetFor(wanted, present) {
63
63
  }
64
64
 
65
65
  /**
66
- * Read-only: what `.vscode/settings.json` would need. `write` carries the full new text;
67
- * `suggest` carries the snippet to paste, because rewriting would drop comments, trailing
68
- * commas or a tracked file's cleanliness.
66
+ * Read-only. `write` carries the full new text; `suggest` carries the snippet to paste, because
67
+ * rewriting would drop comments, trailing commas or a tracked file's cleanliness.
69
68
  */
70
69
  export function planEditorSettings(root, { config = readProjectConfig(root), env = process.env } = {}) {
71
70
  const file = join(root, SETTINGS_REL);