@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
@@ -56,28 +56,26 @@ SEP = f" {GRAY}│{R} "
56
56
 
57
57
  # ── MORPH framework constants ────────────────────────────────────────────────
58
58
 
59
- # Mirrors derivePhase() in src/core/state/state-manager.js — the first phase
60
- # (scanning backwards) whose CANONICAL artifact exists wins. An empty folder
61
- # is not enough.
59
+ # Mirrors derivePhaseFrom() in framework/hooks/shared/state-reader.js — the
60
+ # first phase (scanning backwards) whose CANONICAL artifact exists wins. An
61
+ # empty folder is not enough; a canonical ending in '/' is any .md under it.
62
62
  PHASE_MAP = [
63
- ('4-review', 'review', 'review-report.md'),
64
- ('3-implement', 'implement', 'recap.md'),
65
- ('2-plan', 'plan', 'spec.md'),
63
+ ('4-review', 'review', 'verifier.md'),
64
+ ('3-implement', 'implement', 'reports/'),
66
65
  ('1-design', 'uiux', 'design-system.md'),
67
- ('0-proposal', 'proposal', 'proposal.md'),
66
+ ('2-plan', 'plan', 'plan.md'),
68
67
  ]
69
- PHASE_INDEX = {'proposal': 0, 'uiux': 1, 'plan': 2, 'implement': 3, 'review': 4}
68
+ PHASE_INDEX = {'plan': 0, 'uiux': 1, 'implement': 2, 'review': 3}
70
69
  PHASE_LABELS = {
71
- 'proposal': 'proposal',
72
- 'uiux': 'UI/UX',
73
70
  'plan': 'planning',
71
+ 'uiux': 'UI/UX',
74
72
  'implement': 'implement',
75
73
  'review': 'review',
76
74
  }
77
- PIPELINE_TOTAL = 5
75
+ PIPELINE_TOTAL = 4
78
76
 
79
77
  # Gates that can block; `implement` has none. Mirrors feature.schema.json.
80
- GATE_PHASES = ('proposal', 'uiux', 'plan', 'review')
78
+ GATE_PHASES = ('plan', 'uiux', 'review')
81
79
 
82
80
 
83
81
  # ── Auto-compact threshold ───────────────────────────────────────────────────
@@ -358,49 +356,65 @@ def get_worktree_label(payload, cwd):
358
356
  return None
359
357
 
360
358
 
361
- def _gate_implied_phase(gates):
362
- """Phase implied purely by approved gates (mirrors state-manager.js)."""
359
+ def _gate_implied_phase(gates, design_started):
360
+ """Phase implied purely by approved gates (mirrors state-reader.js)."""
363
361
  def approved(gate):
364
362
  return (gates.get(gate) or {}).get('approved') is True
365
363
  if approved('review'):
366
364
  return 'review'
367
365
  if approved('plan'):
368
- return 'implement'
369
- if approved('uiux'):
370
- return 'plan'
371
- return 'proposal'
366
+ return 'uiux' if design_started and not approved('uiux') else 'implement'
367
+ return 'plan'
368
+
369
+
370
+ def _has_artifact(folder, canonical):
371
+ if not canonical.endswith('/'):
372
+ return (folder / canonical).exists()
373
+ try:
374
+ return any(p.is_file() for p in (folder / canonical).glob('*.md'))
375
+ except OSError:
376
+ return False
372
377
 
373
378
 
374
379
  def derive_phase(feature_dir, gates):
375
380
  """Phase from the filesystem, bumped by approved gates."""
376
- artifact_phase = 'proposal'
381
+ artifact_phase = 'plan'
377
382
  for folder, phase, canonical in PHASE_MAP:
378
- if (feature_dir / folder / canonical).exists():
383
+ if _has_artifact(feature_dir / folder, canonical):
379
384
  artifact_phase = phase
380
385
  break
381
- implied = _gate_implied_phase(gates)
386
+ implied = _gate_implied_phase(gates, (feature_dir / '1-design').exists())
382
387
  if PHASE_INDEX[implied] > PHASE_INDEX[artifact_phase]:
383
388
  return implied
384
389
  return artifact_phase
385
390
 
386
391
 
387
- def count_tasks(feature_dir, feature_json):
388
- """(done, total) from 2-plan/tasks.json — the declared source of truth.
392
+ PARTIAL_MARKER = re.compile(r'^[\s#>*_]*PARCIAL\b')
389
393
 
390
- `skipped` counts as resolved: it will never be worked on, and leaving it
391
- out would pin the bar below 100%. Falls back to feature.json's aggregate
392
- counters, which the schema marks as informational.
394
+
395
+ def count_slices(feature_dir):
396
+ """(done, total) of the slices in 2-plan/checks.json.
397
+
398
+ Same rule as framework/hooks/shared/slice-progress.js: a slice is done when
399
+ 3-implement/reports/{id}.md exists and its first line is not PARCIAL.
393
400
  """
394
401
  try:
395
- raw = (feature_dir / '2-plan' / 'tasks.json').read_text(encoding='utf-8')
396
- tasks = json.loads(raw).get('tasks')
397
- if isinstance(tasks, list) and tasks:
398
- done = sum(1 for task in tasks if task.get('status') in ('done', 'skipped'))
399
- return done, len(tasks)
402
+ slices = json.loads((feature_dir / '2-plan' / 'checks.json').read_text(encoding='utf-8')).get('slices')
400
403
  except Exception:
401
- pass
402
- aggregate = feature_json.get('tasks') or {}
403
- return safe_int(aggregate.get('completed')), safe_int(aggregate.get('total'))
404
+ return 0, 0
405
+ if not isinstance(slices, list):
406
+ return 0, 0
407
+ ids = [s.get('id') for s in slices if isinstance(s, dict) and isinstance(s.get('id'), str)]
408
+ done = 0
409
+ for slice_id in ids:
410
+ try:
411
+ text = (feature_dir / '3-implement' / 'reports' / f'{slice_id}.md').read_text(encoding='utf-8')
412
+ except Exception:
413
+ continue
414
+ first = next((line for line in text.splitlines() if line.strip()), '')
415
+ if not PARTIAL_MARKER.match(first):
416
+ done += 1
417
+ return done, len(ids)
404
418
 
405
419
 
406
420
  def get_active_features(cwd):
@@ -428,15 +442,15 @@ def get_active_features(cwd):
428
442
  feature_dir = path.parent
429
443
  gates = data.get('approvalGates') or {}
430
444
  phase = derive_phase(feature_dir, gates)
431
- done, total = count_tasks(feature_dir, data)
445
+ done, total = count_slices(feature_dir)
432
446
  pending = None
433
447
  if phase in GATE_PHASES and (gates.get(phase) or {}).get('approved') is not True:
434
448
  pending = phase
435
449
  result.append({
436
450
  'name': feature_dir.name,
437
451
  'phase': phase,
438
- 'tasks_done': done,
439
- 'tasks_total': total,
452
+ 'slices_done': done,
453
+ 'slices_total': total,
440
454
  'pending': pending,
441
455
  })
442
456
  return result
@@ -484,7 +498,7 @@ def render_morph_line(feature, hook_name, columns):
484
498
  """Line 0 — the active MORPH feature.
485
499
 
486
500
  Goes through join_to_width like the status line: a feature name plus phase,
487
- task bar, gate and hook is the longest thing this script prints, and a
501
+ slice bar, gate and hook is the longest thing this script prints, and a
488
502
  wrapping line 0 would defeat the whole point of the single-row layout.
489
503
  """
490
504
  parts = [(0, f"{CYAN}{BOLD}► {feature['name']}{R}")]
@@ -497,12 +511,12 @@ def render_morph_line(feature, hook_name, columns):
497
511
  else:
498
512
  parts.append((1, f"{CYAN}{label} {position + 1}/{PIPELINE_TOTAL}{R}"))
499
513
 
500
- if feature['tasks_total'] > 0:
501
- pct = feature['tasks_done'] / feature['tasks_total'] * 100
514
+ if feature['slices_total'] > 0:
515
+ pct = feature['slices_done'] / feature['slices_total'] * 100
502
516
  parts.append((
503
517
  3,
504
518
  f"{GREEN}{progress_bar(pct, 6)} "
505
- f"{feature['tasks_done']}/{feature['tasks_total']} tasks{R}"
519
+ f"{feature['slices_done']}/{feature['slices_total']} fatias{R}"
506
520
  ))
507
521
 
508
522
  if feature['pending']:
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -1,28 +1,5 @@
1
- /**
2
- * Activity Logger — Session-scoped hook event log
3
- *
4
- * Writes hook activity to .morph/logs/activity.jsonl for live consumption
5
- * (statusline, `morph-spec fleet`, etc).
6
- *
7
- * Design constraints:
8
- * - Synchronous only (hooks run in process, no async overhead)
9
- * - Fail-silent (never throws — hooks must not be blocked by the logger)
10
- * - ≤5ms per call (no retries, no network)
11
- * - APPEND-ONLY: one `appendFileSync` line per event, O(1) per call. The
12
- * previous format did a read-modify-write of the WHOLE JSON doc per call
13
- * (logHookActivity/logEvent both read, mutated, and rewrote the entire
14
- * file) — O(n²) across a session once tool calls number in the hundreds,
15
- * with 6 hooks each calling this 3-5x per Write. Same discipline as
16
- * telemetry-logger.js's events.jsonl.
17
- * - Windows-safe paths via path.join
18
- *
19
- * Exported names/signatures are unchanged from the previous doc-based
20
- * implementation on purpose — hooks outside this file's ownership
21
- * (gate-guard.js, tasks-json-guard.js, enforce-phase-writes.js,
22
- * loop-detect.js, protect-spec-files.js, validate-completion.js) call
23
- * logHookActivity/logEvent and must keep working untouched. Only the
24
- * on-disk format changed.
25
- */
1
+ // Append-only on purpose: rewriting a whole JSON doc per event is O(n²) across
2
+ // a session. Fail-silent: never blocks a hook.
26
3
 
27
4
  import {
28
5
  appendFileSync, readFileSync, mkdirSync, existsSync, statSync, renameSync, writeFileSync,
@@ -32,10 +9,7 @@ import { join } from 'path';
32
9
  const LOGS_DIR = '.morph/logs';
33
10
  export const ACTIVITY_FILE = join(LOGS_DIR, 'activity.jsonl');
34
11
 
35
- // SessionStart (worktree-context.js) rotates the log once it crosses this
36
- // size — the archive/rotation call site resetActivity()/archiveSession()
37
- // were written for but never got until now.
38
- export const ROTATE_THRESHOLD_BYTES = 2 * 1024 * 1024; // 2 MB
12
+ export const ROTATE_THRESHOLD_BYTES = 2 * 1024 * 1024;
39
13
 
40
14
  function getActivityPath(projectPath) {
41
15
  return join(projectPath || process.cwd(), ACTIVITY_FILE);
@@ -49,20 +23,17 @@ function ensureLogsDir(projectPath) {
49
23
  }
50
24
 
51
25
  function nowTs() {
52
- return new Date().toTimeString().slice(0, 8); // HH:MM:SS — matches the previous format
26
+ return new Date().toTimeString().slice(0, 8);
53
27
  }
54
28
 
55
- /** Append one JSON line. Fail-silent, O(1) regardless of file size. */
56
29
  function appendLine(projectPath, line) {
57
30
  try {
58
31
  ensureLogsDir(projectPath);
59
32
  appendFileSync(getActivityPath(projectPath), JSON.stringify(line) + '\n', 'utf-8');
60
33
  } catch {
61
- // Fail-silent — never block hooks
62
34
  }
63
35
  }
64
36
 
65
- /** Read every parseable line back. A corrupt/truncated line is skipped, not fatal. */
66
37
  function readLines(projectPath) {
67
38
  try {
68
39
  const activityPath = getActivityPath(projectPath);
@@ -75,7 +46,7 @@ function readLines(projectPath) {
75
46
  try {
76
47
  lines.push(JSON.parse(trimmed));
77
48
  } catch {
78
- // skip a single corrupt line — don't lose the whole stream
49
+ // a process killed mid-write leaves a truncated line
79
50
  }
80
51
  }
81
52
  return lines;
@@ -84,38 +55,15 @@ function readLines(projectPath) {
84
55
  }
85
56
  }
86
57
 
87
- /**
88
- * Log a hook firing event to the session activity log.
89
- *
90
- * @param {string} name - Hook name (e.g. 'dispatch.js', 'enrich-prompt')
91
- * @param {string} event - Claude Code event (e.g. 'PostToolUse', 'UserPromptSubmit')
92
- * @param {string} result - Outcome summary (e.g. 'ok', 'blocked', 'checkpoint_saved')
93
- * @param {string} [projectPath] - Project root (defaults to cwd)
94
- */
95
58
  export function logHookActivity(name, event, result, projectPath) {
96
59
  appendLine(projectPath, { kind: 'hook', name, event, result, ts: nowTs() });
97
60
  }
98
61
 
99
- /**
100
- * Log a structured event to the session activity log.
101
- *
102
- * @param {string} type - Event type (e.g., 'task_start', 'loop_detected')
103
- * @param {Object} data - Event data payload
104
- * @param {string} [projectPath] - Project root (defaults to cwd)
105
- */
106
62
  export function logEvent(type, data, projectPath) {
107
63
  appendLine(projectPath, { kind: 'event', type, data, ts: nowTs() });
108
64
  }
109
65
 
110
- /**
111
- * Read the current activity log, reconstructed into the aggregate shape
112
- * older consumers expect: `{ sessionId, feature, phase, hooks, events }`.
113
- * `sessionId`/`feature`/`phase` come from the synthetic 'session' header
114
- * line `resetActivity()` writes, when present.
115
- *
116
- * @param {string} [projectPath]
117
- * @returns {{ sessionId: string, feature: string, phase: string, hooks: Array, events: Array }|null}
118
- */
66
+ /** @returns {{ sessionId: string, feature: string, phase: string, hooks: Array, events: Array }|null} */
119
67
  export function readActivity(projectPath) {
120
68
  const lines = readLines(projectPath);
121
69
  if (lines.length === 0) return null;
@@ -132,17 +80,8 @@ export function readActivity(projectPath) {
132
80
  };
133
81
  }
134
82
 
135
- /**
136
- * Reset the activity log for a new session — the one operation in this file
137
- * that is NOT append-only (a session boundary, not a log entry). Truncates
138
- * the file and writes a 'session' header line so `readActivity()` can
139
- * recover sessionId/feature/phase.
140
- *
141
- * @param {string} sessionId - ISO timestamp of new session
142
- * @param {string} feature - Active feature name (empty string if none)
143
- * @param {string} phase - Current phase (empty string if none)
144
- * @param {string} [projectPath]
145
- */
83
+ // The one write that truncates: the 'session' header is what `readActivity()`
84
+ // recovers sessionId/feature/phase from.
146
85
  export function resetActivity(sessionId, feature, phase, projectPath) {
147
86
  try {
148
87
  ensureLogsDir(projectPath);
@@ -154,18 +93,9 @@ export function resetActivity(sessionId, feature, phase, projectPath) {
154
93
  };
155
94
  writeFileSync(getActivityPath(projectPath), JSON.stringify(session) + '\n', 'utf-8');
156
95
  } catch {
157
- // Fail-silent
158
96
  }
159
97
  }
160
98
 
161
- /**
162
- * Archive the current activity log to sessions/ for historical logging.
163
- * Only archives when there is at least one hook or event line — a bare (or
164
- * absent) log is not worth preserving. `renameSync` keeps this O(1) even at
165
- * the multi-MB sizes rotation is meant to catch (no read+rewrite of content).
166
- *
167
- * @param {string} [projectPath] - Project root (defaults to cwd)
168
- */
169
99
  export function archiveSession(projectPath) {
170
100
  try {
171
101
  const activityPath = getActivityPath(projectPath);
@@ -183,20 +113,10 @@ export function archiveSession(projectPath) {
183
113
  const archivePath = join(sessionsDir, `${stamp}.jsonl`);
184
114
  renameSync(activityPath, archivePath);
185
115
  } catch {
186
- // Fail-silent
187
116
  }
188
117
  }
189
118
 
190
- /**
191
- * Rotate the activity log when it crosses `thresholdBytes` — the real call
192
- * site for archiveSession() (previously written but never invoked anywhere).
193
- * Called from worktree-context.js on SessionStart, the only reliable
194
- * "between sessions" checkpoint the harness gets.
195
- *
196
- * @param {string} [projectPath] - Project root (defaults to cwd)
197
- * @param {number} [thresholdBytes] - Rotate once the log is at least this large
198
- * @returns {boolean} true if a rotation was performed
199
- */
119
+ /** @returns {boolean} true if a rotation was performed */
200
120
  export function rotateActivityLogIfLarge(projectPath, thresholdBytes = ROTATE_THRESHOLD_BYTES) {
201
121
  try {
202
122
  const activityPath = getActivityPath(projectPath);
@@ -1,15 +1,5 @@
1
- /**
2
- * agent-reap.js — dispara `morph-spec worktree reap` em background no SessionStart.
3
- *
4
- * O hook de SessionStart tem 5 s de timeout e o reaper mede git por árvore: por isso o
5
- * disparo só olha o disco (nenhum subprocesso) e solta este arquivo como worker DESTACADO
6
- * (`--run <raiz>`), que chama o CLI. Só a raiz: o worker nasce com `cwd` no git common dir
7
- * e nunca segura o diretório de uma worktree.
8
- *
9
- * Fail-open em tudo. `MORPH_WORKTREE_REAP=0` desliga.
10
- *
11
- * @module agent-reap
12
- */
1
+ // O SessionStart tem 5 s: o disparo só olha o disco e solta um worker DESTACADO
2
+ // com `cwd` no git common dir, que nunca segura o diretório de uma worktree.
13
3
 
14
4
  import { spawn } from 'child_process';
15
5
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'fs';
@@ -29,7 +19,6 @@ function anyEntry(dir, pattern) {
29
19
  try { return readdirSync(dir).some((name) => pattern.test(name)); } catch { return false; }
30
20
  }
31
21
 
32
- /** Uma worktree de agent registrada, ou uma branch `morph-task/agent-*` (solta ou empacotada). */
33
22
  export function hasAgentLeftovers(common) {
34
23
  if (anyEntry(join(common, 'worktrees'), ADMIN_DIR)) return true;
35
24
  if (anyEntry(join(common, 'refs', 'heads', 'morph-task'), AGENT_BRANCH_FILE)) return true;
@@ -49,10 +38,6 @@ function recentlyKicked(marker, now) {
49
38
  }
50
39
  }
51
40
 
52
- /**
53
- * @param {string} dir - diretório do projeto da sessão
54
- * @returns {{ kicked: boolean, reason?: string, pid?: number }}
55
- */
56
41
  export function kickAgentReap(dir, { spawnFn = spawn, env = process.env, now = Date.now } = {}) {
57
42
  if (env.MORPH_WORKTREE_REAP === '0') return { kicked: false, reason: 'disabled' };
58
43
  const common = resolveGitCommonDir(dir);
@@ -1,12 +1,3 @@
1
- /**
2
- * Build/Typecheck verification utility.
3
- *
4
- * Stack-aware: determines the correct build command(s) based on project stack.
5
- * Used by the Stop hook and `morph-spec verify` to verify code health.
6
- *
7
- * Pure functions — no I/O except the actual build execution.
8
- */
9
-
10
1
  import { execSync } from 'child_process';
11
2
  import { existsSync } from 'fs';
12
3
  import { join } from 'path';
@@ -14,7 +5,7 @@ import { join } from 'path';
14
5
  const DOTNET_BUILD_CMD = 'dotnet build --no-restore --nologo -v q';
15
6
  const TSC_BUILD_CMD = 'npx tsc --noEmit';
16
7
 
17
- function isDotnetStack(s) {
8
+ export function isDotnetStack(s) {
18
9
  return s.includes('dotnet') || s.includes('aspnet');
19
10
  }
20
11
 
@@ -22,25 +13,8 @@ function isFrontendStack(s) {
22
13
  return s.includes('nextjs') || s.includes('node') || s.includes('react') || s.includes('vue');
23
14
  }
24
15
 
25
- /**
26
- * Determine the build command for a SINGLE (non-monorepo) stack string.
27
- *
28
- * Kept for backward compatibility with single-stack projects and existing
29
- * callers. A monorepo stack that mixes a frontend token with "dotnet" (e.g.
30
- * the detector's own `${frontendStack}+dotnet` format, or a hyphenated
31
- * `dotnet-nextjs`) should resolve through `getBuildSteps()` instead: this
32
- * function alone can only ever return ONE command, and — because it checks
33
- * `dotnet` before the frontend tokens — a compound stack always resolves to
34
- * `dotnet build` even when the frontend is what actually needs checking (or
35
- * when there's no root-level `.sln`/`.csproj`, e.g. in a monorepo where the
36
- * backend lives in a subfolder). `getBuildSteps()` runs BOTH sides, each
37
- * from its own working directory, instead of one command winning by
38
- * substring match order.
39
- *
40
- * @param {string} stack - Project stack (e.g., 'dotnet-nextjs', 'nextjs')
41
- * @param {boolean} [hasTsConfig] - Whether tsconfig.json exists (for TS projects)
42
- * @returns {string|null} Build command or null if no check available
43
- */
16
+ // Single stack only: a compound stack resolves to `dotnet build` alone. Use
17
+ // `getBuildSteps()` for those.
44
18
  export function getBuildCommand(stack, hasTsConfig = false) {
45
19
  if (!stack) return null;
46
20
  const s = stack.toLowerCase();
@@ -56,38 +30,21 @@ export function getBuildCommand(stack, hasTsConfig = false) {
56
30
  return null;
57
31
  }
58
32
 
59
- /**
60
- * Resolve the build step(s) to run for a project, monorepo-aware.
61
- *
62
- * A monorepo stack mixes a frontend token and "dotnet" (see
63
- * `detectProject()`'s own `${frontendStack}+dotnet` format) — in that case
64
- * BOTH sides are checked, each from its own working directory
65
- * (`frontendPath` / `backendPath`, as stored in
66
- * `.morph/config/config.json → project`), instead of one command silently
67
- * winning — or running from the wrong cwd — by substring match order.
68
- * Single-stack projects (no `frontendPath`/`backendPath`, or a stack that
69
- * only matches one side) resolve to exactly one step, from `cwd`, matching
70
- * `getBuildCommand()`'s existing single-stack behavior.
71
- *
72
- * @param {string} stack
73
- * @param {{ cwd: string, frontendPath?: string, backendPath?: string }} opts
74
- * @returns {Array<{ command: string, cwd: string }>}
75
- */
76
- export function getBuildSteps(stack, { cwd, frontendPath, backendPath } = {}) {
33
+ // A compound stack checks BOTH sides, each from its own directory.
34
+ // `backendTarget` names the solution when the backend directory holds none.
35
+ export function getBuildSteps(stack, { cwd, frontendPath, backendPath, backendTarget } = {}) {
77
36
  if (!stack || !cwd) return [];
78
37
  const s = stack.toLowerCase();
79
38
  const steps = [];
80
39
 
81
40
  if (isDotnetStack(s)) {
82
- steps.push({ command: DOTNET_BUILD_CMD, cwd: backendPath ? join(cwd, backendPath) : cwd });
41
+ const command = backendTarget ? DOTNET_BUILD_CMD.replace(/^dotnet build/, `dotnet build "${backendTarget}"`) : DOTNET_BUILD_CMD;
42
+ steps.push({ command, cwd: backendPath ? join(cwd, backendPath) : cwd });
83
43
  }
84
44
 
85
45
  if (isFrontendStack(s)) {
86
46
  const frontendDir = frontendPath ? join(cwd, frontendPath) : cwd;
87
- // node_modules guard : a fresh worktree before
88
- // `morph-spec worktree link` has tsconfig.json (versioned) but no toolchain — running
89
- // `npx tsc` there fetches an unrelated "tsc" package and fails noisily. No toolchain →
90
- // no tsc step; the real typecheck runs wherever node_modules exists (root/CI).
47
+ // Without node_modules, `npx tsc` fetches an unrelated "tsc" package.
91
48
  if (
92
49
  existsSync(join(frontendDir, 'tsconfig.json')) &&
93
50
  existsSync(join(frontendDir, 'node_modules'))
@@ -99,41 +56,19 @@ export function getBuildSteps(stack, { cwd, frontendPath, backendPath } = {}) {
99
56
  return steps;
100
57
  }
101
58
 
102
- /**
103
- * Parse build command result into a structured object.
104
- *
105
- * @param {number} exitCode - Process exit code (0 = success)
106
- * @param {string} output - stdout+stderr from build
107
- * @returns {{ status: 'pass'|'fail', output: string }}
108
- */
109
59
  export function parseBuildResult(exitCode, output) {
110
60
  const trimmed = (output || '').trim();
111
61
  if (exitCode === 0) {
112
62
  return { status: 'pass', output: '' };
113
63
  }
114
- // Truncate to last 20 lines
115
64
  const lines = trimmed.split('\n');
116
65
  const truncated = lines.slice(-20).join('\n');
117
66
  return { status: 'fail', output: truncated };
118
67
  }
119
68
 
120
- /**
121
- * Run build check for a project — monorepo-aware (see `getBuildSteps()`).
122
- *
123
- * For a compound stack (e.g. "nextjs+dotnet"), runs each resolved step in
124
- * its own working directory and fails if any step fails; output from all
125
- * failing steps is concatenated (prefixed by the command that produced it)
126
- * so a frontend typecheck failure and a backend build failure are never
127
- * silently masked by one another.
128
- *
129
- * @param {string} cwd - Project root directory
130
- * @param {string} stack - Project stack string
131
- * @param {number} [timeout=30000] - Timeout in ms
132
- * @param {{ frontendPath?: string, backendPath?: string }} [paths] - Monorepo sub-paths, from config.json → project
133
- * @returns {{ status: 'pass'|'fail'|'skip', output: string }}
134
- */
69
+ /** @returns {{ status: 'pass'|'fail'|'skip', output: string }} */
135
70
  export function runBuildCheck(cwd, stack, timeout = 30000, paths = {}) {
136
- const steps = getBuildSteps(stack, { cwd, frontendPath: paths.frontendPath, backendPath: paths.backendPath });
71
+ const steps = getBuildSteps(stack, { cwd, frontendPath: paths.frontendPath, backendPath: paths.backendPath, backendTarget: paths.backendTarget });
137
72
  if (steps.length === 0) return { status: 'skip', output: '' };
138
73
 
139
74
  const failures = [];