@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,73 +1,33 @@
1
- /**
2
- * handoff-generator.js — compressed session-handoff document (fleet).
3
- *
4
- * A fleet runs one Claude Code instance per feature worktree. What travels
5
- * between sessions must be a HANDOFF, not an export: a compact document that
6
- * points at the feature's own files instead of copying their contents, so a
7
- * fresh instance starts focused instead of inheriting a wall of stale context.
8
- *
9
- * Deterministic by construction — everything here is derived from feature.json,
10
- * tasks.json, decisions.md and the worktree env manifest. The document is
11
- * therefore REGENERABLE (never hand-edited, never merged): `morph-spec handoff`
12
- * rebuilds it, and setup/create/advance/approve regenerate it at the moments a
13
- * new session is most likely to pick the feature up.
14
- *
15
- * Every section is fail-open: a missing tasks.json or a corrupt decisions.md
16
- * degrades that section, never the document.
17
- */
1
+ // Derived only from committed sources, so it is regenerated, never hand-edited or merged.
2
+ // Every section is fail-open.
18
3
 
19
4
  import { existsSync, readFileSync, writeFileSync } from 'fs';
20
5
  import { join } from 'path';
21
6
  import { ensureCommonExclude } from '../../../framework/hooks/shared/git-exclude.js';
22
7
  import { derivePhaseForFeature, getPendingGates } from '../../../framework/hooks/shared/state-reader.js';
8
+ import { sliceProgress, readySlices } from '../../../framework/hooks/shared/slice-progress.js';
23
9
 
24
- const PHASE_DIRS = {
25
- proposal: '0-proposal',
26
- uiux: '1-design',
27
- plan: '2-plan',
28
- implement: '3-implement',
29
- review: '4-review',
30
- };
31
-
32
- /** Ordered pointer candidates — only existing paths make it into the doc. */
33
10
  const POINTER_CANDIDATES = [
34
- ['0-proposal/proposal.md', 'proposta: problema, escopo e riscos'],
35
- // Behaviour before solution — the same order morph-plan writes them in.
36
- ['2-plan/usecases', 'use cases: comportamento e pós-condições testáveis (pasta)'],
37
- ['2-plan/spec.md', 'especificação técnica'],
38
- ['2-plan/tasks.json', 'DAG de tasks (status vivo)'],
39
- ['decisions.md', 'decisões registradas'],
40
- ['3-implement/recap.md', 'recap da implementação'],
41
- ['4-review/review-report.md', 'relatório do Gate 3'],
11
+ ['2-plan/plan.md', 'plano: problema, cenários com prova, decisões e fatias'],
12
+ ['2-plan/checks.json', 'cenários e fatias extraídos pelo plan-check'],
13
+ ['1-design/design-system.md', 'design system da feature'],
14
+ ['3-implement/reports', 'relatórios das fatias (**Arquivos:**/**Standards:**): o registro da implementação'],
15
+ ['4-review/verifier.md', 'verificador do Gate 3: cada cenário provado'],
16
+ ['4-review/reviewer.md', 'revisor do Gate 3: escopo, arquitetura, regressão'],
17
+ ['4-review/security.md', 'segurança do Gate 3 (só com caminho sensível)'],
42
18
  ];
43
19
 
44
20
  function readJson(path) {
45
21
  return JSON.parse(readFileSync(path, 'utf8').replace(/^/, ''));
46
22
  }
47
23
 
48
- function taskSummary(featureDir) {
49
- try {
50
- const tasksPath = join(featureDir, '2-plan', 'tasks.json');
51
- if (!existsSync(tasksPath)) return null;
52
- const data = readJson(tasksPath);
53
- if (!Array.isArray(data.tasks)) return null;
54
- const done = data.tasks.filter((t) => t.status === 'done').length;
55
- const doneIds = new Set(data.tasks.filter((t) => t.status === 'done').map((t) => t.id));
56
- const ready = data.tasks.filter(
57
- (t) => t.status === 'pending' && Array.isArray(t.dependencies) && t.dependencies.every((d) => doneIds.has(d)),
58
- );
59
- return { done, total: data.tasks.length, ready };
60
- } catch {
61
- return null;
62
- }
63
- }
64
-
65
24
  function decisionTitles(featureDir, limit = 5) {
66
25
  try {
67
- const path = join(featureDir, 'decisions.md');
26
+ const path = join(featureDir, '2-plan', 'checks.json');
68
27
  if (!existsSync(path)) return [];
69
- const titles = [...readFileSync(path, 'utf8').matchAll(/^##\s+(.+)$/gm)].map((m) => m[1].trim());
70
- return titles.slice(-limit);
28
+ const adrs = readJson(path).adrs;
29
+ if (!Array.isArray(adrs)) return [];
30
+ return adrs.map((a) => `${a.id} — ${a.title}`).slice(-limit);
71
31
  } catch {
72
32
  return [];
73
33
  }
@@ -86,53 +46,34 @@ function readWorktreeEnv(cwd) {
86
46
  }
87
47
  }
88
48
 
89
- function nextAction(feature, phase, tasks, featureName) {
49
+ function nextAction(feature, phase, slices, featureName) {
90
50
  const gates = feature.approvals || feature.approvalGates || {};
91
51
  const approved = (g) => gates[g]?.approved === true;
92
52
 
93
53
  switch (phase) {
94
- case 'proposal':
95
- // An approved Gate 1 with no plan artifact yet means "plan work is open":
96
- // the derived phase only flips when 2-plan/spec.md lands (derivePhase is
97
- // artifact-first). Pointing back at the proposal here would be wrong.
98
- return approved('proposal')
99
- ? `Gate 1 aprovado — produza \`2-plan/spec.md\` + \`tasks.json\` (a fase avança com o primeiro artefato; Gate 2 em seguida).`
100
- : `Complete \`0-proposal/proposal.md\` e aprove o Gate 1 (\`morph-spec advance ${featureName}\`). Skill: /morph-proposal ${featureName}.`;
54
+ case 'plan':
55
+ return `Escreva \`2-plan/plan.md\`, rode \`morph-spec plan-check ${featureName}\` até zero erro e aprove o gate do plano (\`morph-spec advance ${featureName}\`). Skill: /morph-plan ${featureName}.`;
101
56
  case 'uiux':
102
57
  return approved('uiux')
103
- ? `Gate de uiux aprovado — produza \`2-plan/spec.md\` + \`tasks.json\` (Gate 2 em seguida).`
58
+ ? `Gate de uiux aprovado — implemente as fatias do plano via /morph-apply ${featureName}.`
104
59
  : `Complete \`1-design/design-system.md\` e aprove o gate de uiux (\`morph-spec advance ${featureName}\`).`;
105
- case 'plan':
106
- return `Produza \`2-plan/spec.md\` + \`tasks.json\` e aprove o Gate 2 (\`morph-spec advance ${featureName}\`).`;
107
60
  case 'implement': {
108
- const ready = tasks?.ready?.map((t) => `${t.id} "${t.title}"`).join(', ');
61
+ const ready = readySlices(slices).map((s) => `${s.id} "${s.title}"`).join(', ');
109
62
  return ready
110
- ? `Execute as tasks ready — ${ready} — via /morph-apply ${featureName} (task fecha com verify verde e critérios provados).`
111
- : `Todas as tasks concluídas ou bloqueadas — rode \`morph-spec advance ${featureName}\` para escancarar a review, ou destrave dependências. Skill: /morph-apply ${featureName}.`;
63
+ ? `Implemente as fatias prontas — ${ready} — via /morph-apply ${featureName} (cada fatia grava 3-implement/reports/{id}.md).`
64
+ : `Todas as fatias têm relatório final — rode \`morph-spec verify ${featureName}\` e siga para a review. Skill: /morph-apply ${featureName}.`;
112
65
  }
113
66
  case 'review':
114
67
  return approved('review')
115
68
  ? `Gate 3 aprovado — feche: \`morph-spec finish ${featureName} --pr\` (abre PR) ou \`--merge\` (merge local na default). Arquiva + integra num passo.`
116
- : `Produza \`4-review/review-report.md\` (avaliador independente) e aprove o Gate 3; depois \`morph-spec finish ${featureName} --pr|--merge\`.`;
69
+ : `Rode /morph-review ${featureName} (verificador ∥ revisor, segurança se exigida) e aprove o Gate 3; depois \`morph-spec finish ${featureName} --pr|--merge\`.`;
117
70
  default:
118
71
  return `Rode \`morph-spec status ${featureName}\` para orientar o próximo passo.`;
119
72
  }
120
73
  }
121
74
 
122
- /**
123
- * Build the handoff document for a feature, resolved against `cwd` (primary
124
- * root or worktree — wherever the feature's `.morph/features/{f}` lives).
125
- *
126
- * @param {string} featureName
127
- * @param {string} [cwd]
128
- * @param {{ worktree?: {path:string, branch:string}|null }} [opts] - override
129
- * the `worktree` bookkeeping instead of reading `feature.worktree`. Needed
130
- * because that field is ROOT-OWNED and stripped from every branch-local
131
- * copy at birth (create.js) — a handoff built FROM the worktree's own copy
132
- * would otherwise always say "no worktree registered", even while sitting
133
- * inside one. Pass `undefined` (the default) to fall back to `feature.worktree`.
134
- * @returns {{ markdown: string, meta: { generatedAt: string, phase: string, tasksDone: number, tasksTotal: number } } | null}
135
- */
75
+ // `opts.worktree` overrides `feature.worktree`: that field is root-owned and stripped from branch-local
76
+ // copies, so a handoff built inside a worktree would otherwise say "no worktree registered".
136
77
  export function buildHandoff(featureName, cwd = process.cwd(), opts = {}) {
137
78
  const featureDir = join(cwd, '.morph', 'features', featureName);
138
79
  if (!existsSync(join(featureDir, 'feature.json'))) return null;
@@ -144,15 +85,15 @@ export function buildHandoff(featureName, cwd = process.cwd(), opts = {}) {
144
85
  return null;
145
86
  }
146
87
 
147
- let phase = 'proposal';
88
+ let phase = 'plan';
148
89
  let pendingGates = [];
149
90
  try {
150
- phase = derivePhaseForFeature(featureName, cwd) || 'proposal';
91
+ phase = derivePhaseForFeature(featureName, cwd) || 'plan';
151
92
  pendingGates = getPendingGates(featureName, cwd) || [];
152
- } catch { /* fail-open — proposal defaults keep the doc useful */ }
93
+ } catch {}
153
94
 
154
95
  const generatedAt = new Date().toISOString();
155
- const tasks = taskSummary(featureDir);
96
+ const slices = sliceProgress(featureDir);
156
97
  const env = readWorktreeEnv(cwd);
157
98
  const decisions = decisionTitles(featureDir);
158
99
  const worktree = opts.worktree !== undefined ? opts.worktree : (feature.worktree || null);
@@ -187,9 +128,9 @@ export function buildHandoff(featureName, cwd = process.cwd(), opts = {}) {
187
128
  '## Estado',
188
129
  '',
189
130
  `- Gates: ${gateLine}`,
190
- tasks
191
- ? `- Tasks: ${tasks.done}/${tasks.total} done · ready agora: ${tasks.ready.length ? tasks.ready.map((t) => `**${t.id}** "${t.title}"`).join(', ') : 'nenhuma'}`
192
- : '- Tasks: sem tasks.json ainda (fase de planejamento não concluída)',
131
+ slices
132
+ ? `- Fatias: ${slices.done}/${slices.total} feitas${slices.partial ? ` · ${slices.partial} parcial(is)` : ''} · prontas agora: ${readySlices(slices).map((s) => `**${s.id}** "${s.title}"`).join(', ') || 'nenhuma'}`
133
+ : '- Fatias: sem checks.json ainda (rode plan-check)',
193
134
  pendingGates.length ? `- Gates pendentes: ${pendingGates.join(', ')}` : null,
194
135
  '',
195
136
  '## Leia nesta ordem (ponteiros — nada copiado)',
@@ -200,7 +141,7 @@ export function buildHandoff(featureName, cwd = process.cwd(), opts = {}) {
200
141
  '',
201
142
  '## Próxima ação',
202
143
  '',
203
- nextAction(feature, phase, tasks, featureName),
144
+ nextAction(feature, phase, slices, featureName),
204
145
  '',
205
146
  '## Retomar',
206
147
  '',
@@ -214,8 +155,8 @@ export function buildHandoff(featureName, cwd = process.cwd(), opts = {}) {
214
155
  meta: {
215
156
  generatedAt,
216
157
  phase,
217
- tasksDone: tasks?.done ?? 0,
218
- tasksTotal: tasks?.total ?? 0,
158
+ slicesDone: slices?.done ?? 0,
159
+ slicesTotal: slices?.total ?? 0,
219
160
  },
220
161
  };
221
162
  }
@@ -224,29 +165,13 @@ export function buildHandoff(featureName, cwd = process.cwd(), opts = {}) {
224
165
  const HANDOFF_EXCLUDE_MARKER = '# morph-spec: derived handoff docs (managed — see handoff-generator)';
225
166
  const HANDOFF_EXCLUDE_PATTERN = '/.morph/**/handoff.md';
226
167
 
227
- /**
228
- * The handoff is DERIVED from committed sources, so it is never committed
229
- * itself — regenerate > synchronize, the same principle as the materialized
230
- * state.json. Left visible, every `worktree setup`/`advance` would drop an
231
- * untracked file into the very `git status` the finish gates read. The
232
- * common-dir `.git/info/exclude` hides it in every worktree of the repo
233
- * (including the primary root); `**` also covers a feature folder after
234
- * `archive` moves it. Idempotent and fail-open — a handoff that shows up as
235
- * untracked is noise, never data loss.
236
- */
168
+ // Never committed: left visible it lands in the `git status` the finish gates read. The common-dir
169
+ // exclude hides it in every worktree; `**` also covers a feature folder after `archive` moves it.
237
170
  function ensureHandoffExcluded(cwd) {
238
171
  ensureCommonExclude(cwd, [HANDOFF_EXCLUDE_PATTERN], HANDOFF_EXCLUDE_MARKER);
239
172
  }
240
173
 
241
- /**
242
- * Write `.morph/features/{f}/handoff.md`. Returns the written path, or null
243
- * when the feature does not exist (nothing is created).
244
- *
245
- * @param {string} featureName
246
- * @param {string} [cwd]
247
- * @param {{ worktree?: {path:string, branch:string}|null }} [opts] - see buildHandoff
248
- * @returns {string|null}
249
- */
174
+ /** The written path, or null when the feature does not exist (nothing is created). */
250
175
  export function writeHandoff(featureName, cwd = process.cwd(), opts = {}) {
251
176
  const result = buildHandoff(featureName, cwd, opts);
252
177
  if (!result) return null;
@@ -1,22 +1,8 @@
1
- /**
2
- * Scaffold Generator - Generates VSA feature slice (.NET) and feature page (Next.js) files.
3
- *
4
- * Uses existing Handlebars templates from framework/templates/ to generate
5
- * complete feature structures with one command.
6
- *
7
- * @module scaffold-generator
8
- */
9
-
10
1
  import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'fs';
11
2
  import { join, dirname, basename } from 'path';
12
3
  import { renderTemplate } from '../../core/templates/template-renderer.js';
13
4
  import { resolveFrameworkTemplatePath } from '../../core/templates/template-registry.js';
14
5
 
15
- /**
16
- * Convert kebab-case to PascalCase.
17
- * @param {string} str
18
- * @returns {string}
19
- */
20
6
  function toPascalCase(str) {
21
7
  return str
22
8
  .split('-')
@@ -24,11 +10,6 @@ function toPascalCase(str) {
24
10
  .join('');
25
11
  }
26
12
 
27
- /**
28
- * Convert kebab-case to camelCase.
29
- * @param {string} str
30
- * @returns {string}
31
- */
32
13
  function toCamelCase(str) {
33
14
  return str
34
15
  .split('-')
@@ -36,16 +17,8 @@ function toCamelCase(str) {
36
17
  .join('');
37
18
  }
38
19
 
39
- /**
40
- * Normalize a namespace to kebab-case so Handlebars {{pascalCase}} reconstructs it correctly.
41
- * Handles: PascalCase ("MyBookStore" → "my-book-store"), dot-separated ("My.App" → "my-app"),
42
- * and already kebab-case pass-through.
43
- * @param {string} ns
44
- * @returns {string}
45
- */
20
+ /** Kebab-case so Handlebars {{pascalCase}} reconstructs it: "MyBookStore" → "my-book-store", "My.App" → "my-app". */
46
21
  export function normalizeNamespaceToKebab(ns) {
47
- // Split on dots first (My.BookStore → ["My", "BookStore"])
48
- // Then split PascalCase on uppercase boundaries (BookStore → Book-Store)
49
22
  return ns
50
23
  .split('.')
51
24
  .map(segment =>
@@ -57,15 +30,8 @@ export function normalizeNamespaceToKebab(ns) {
57
30
  .join('-');
58
31
  }
59
32
 
60
- // ─────────────────────────────────────────────────────────────────────────────
61
- // .NET VSA Scaffold
62
- // ─────────────────────────────────────────────────────────────────────────────
63
-
64
- // Each entry pairs a REGISTRY.json template `id` with the generator-specific
65
- // output shaping (class-name suffix, target directory). REGISTRY owns the
66
- // template path; the generator owns where the rendered file lands.
33
+ // REGISTRY.json owns the template path; the generator owns where the rendered file lands.
67
34
 
68
- /** .NET slice-level files (endpoint/handler/request/response/validator). */
69
35
  const DOTNET_SLICE_TEMPLATES = [
70
36
  { templateId: 'dotnet-feature-endpoint', suffix: 'Endpoint' },
71
37
  { templateId: 'dotnet-feature-handler', suffix: 'Handler' },
@@ -74,23 +40,11 @@ const DOTNET_SLICE_TEMPLATES = [
74
40
  { templateId: 'dotnet-feature-validator', suffix: 'Validator' },
75
41
  ];
76
42
 
77
- /** .NET feature-level files (entity, errors). */
78
43
  const DOTNET_FEATURE_TEMPLATES = [
79
44
  { templateId: 'dotnet-feature-entity', suffix: '', dir: 'entities' },
80
45
  { templateId: 'dotnet-feature-errors', suffix: 'Errors', dir: 'feature' },
81
46
  ];
82
47
 
83
- /**
84
- * Scaffold a .NET VSA feature slice.
85
- *
86
- * @param {Object} options
87
- * @param {string} options.feature - Entity/feature name in kebab-case (e.g., "book")
88
- * @param {string} options.sliceName - Slice name in kebab-case (e.g., "create-book")
89
- * @param {string} options.namespace - Root namespace (e.g., "MyApp")
90
- * @param {string} [options.outputDir] - Base output directory (default: cwd)
91
- * @param {boolean} [options.dryRun] - Preview without writing
92
- * @returns {{ files: Array<{path: string, content: string, skipped: boolean}> }}
93
- */
94
48
  export function scaffoldDotnetSlice(options) {
95
49
  const { feature, sliceName, namespace, outputDir = process.cwd(), dryRun = false } = options;
96
50
 
@@ -107,7 +61,6 @@ export function scaffoldDotnetSlice(options) {
107
61
 
108
62
  const files = [];
109
63
 
110
- // 1. Render slice-level templates
111
64
  for (const tmpl of DOTNET_SLICE_TEMPLATES) {
112
65
  const templatePath = resolveFrameworkTemplatePath(tmpl.templateId);
113
66
  const outputPath = join(
@@ -128,7 +81,6 @@ export function scaffoldDotnetSlice(options) {
128
81
  files.push({ path: outputPath, content, skipped: false });
129
82
  }
130
83
 
131
- // 2. Render feature-level templates (skip if already exist)
132
84
  for (const tmpl of DOTNET_FEATURE_TEMPLATES) {
133
85
  const templatePath = resolveFrameworkTemplatePath(tmpl.templateId);
134
86
  let outputPath;
@@ -157,12 +109,6 @@ export function scaffoldDotnetSlice(options) {
157
109
  return { files };
158
110
  }
159
111
 
160
- // ─────────────────────────────────────────────────────────────────────────────
161
- // Next.js Scaffold
162
- // ─────────────────────────────────────────────────────────────────────────────
163
-
164
- // Each entry pairs a REGISTRY.json template `id` with where the rendered file
165
- // lands. REGISTRY owns the template path; the generator owns the output path.
166
112
  const NEXTJS_TEMPLATES = [
167
113
  {
168
114
  templateId: 'nextjs-page',
@@ -182,15 +128,6 @@ const NEXTJS_TEMPLATES = [
182
128
  },
183
129
  ];
184
130
 
185
- /**
186
- * Scaffold a Next.js feature page with components and hooks.
187
- *
188
- * @param {Object} options
189
- * @param {string} options.featureName - Feature name in kebab-case (e.g., "book")
190
- * @param {string} [options.outputDir] - Base output directory (default: cwd)
191
- * @param {boolean} [options.dryRun] - Preview without writing
192
- * @returns {{ files: Array<{path: string, content: string, skipped: boolean}> }}
193
- */
194
131
  export function scaffoldNextjsFeature(options) {
195
132
  const { featureName, outputDir = process.cwd(), dryRun = false } = options;
196
133
 
@@ -226,33 +163,15 @@ export function scaffoldNextjsFeature(options) {
226
163
  return { files };
227
164
  }
228
165
 
229
- // ─────────────────────────────────────────────────────────────────────────────
230
- // Namespace Detection
231
- // ─────────────────────────────────────────────────────────────────────────────
232
-
233
- /**
234
- * Detect the .NET root namespace from .csproj or config.json.
235
- *
236
- * Search order:
237
- * 1. *.csproj in dir (extract <RootNamespace>)
238
- * 2. *.csproj in dir (use filename without extension)
239
- * 3. *.csproj one level deep
240
- * 4. .morph/config.json → project.name
241
- *
242
- * @param {string} dir - Directory to search in
243
- * @returns {string|null} Detected namespace or null
244
- */
166
+ /** <RootNamespace> or the name of a .csproj here or one level deep, else config.json `project.name`. */
245
167
  export function detectNamespace(dir) {
246
- // 1. Search for .csproj in current dir
247
168
  const csprojInDir = findCsproj(dir);
248
169
  if (csprojInDir) {
249
170
  const ns = extractNamespaceFromCsproj(csprojInDir);
250
171
  if (ns) return ns;
251
- // Fallback: use filename
252
172
  return basename(csprojInDir, '.csproj');
253
173
  }
254
174
 
255
- // 2. Search one level deep
256
175
  try {
257
176
  const entries = readdirSync(dir, { withFileTypes: true });
258
177
  for (const entry of entries) {
@@ -265,29 +184,19 @@ export function detectNamespace(dir) {
265
184
  }
266
185
  }
267
186
  }
268
- } catch {
269
- // ignore read errors
270
- }
187
+ } catch {}
271
188
 
272
- // 3. Fall back to config.json
273
189
  const configPath = join(dir, '.morph', 'config.json');
274
190
  if (existsSync(configPath)) {
275
191
  try {
276
192
  const config = JSON.parse(readFileSync(configPath, 'utf-8'));
277
193
  if (config.project?.name) return config.project.name;
278
- } catch {
279
- // ignore parse errors
280
- }
194
+ } catch {}
281
195
  }
282
196
 
283
197
  return null;
284
198
  }
285
199
 
286
- /**
287
- * Find the first .csproj file in a directory.
288
- * @param {string} dir
289
- * @returns {string|null}
290
- */
291
200
  function findCsproj(dir) {
292
201
  try {
293
202
  const entries = readdirSync(dir);
@@ -298,11 +207,6 @@ function findCsproj(dir) {
298
207
  }
299
208
  }
300
209
 
301
- /**
302
- * Extract <RootNamespace> from a .csproj file.
303
- * @param {string} csprojPath
304
- * @returns {string|null}
305
- */
306
210
  function extractNamespaceFromCsproj(csprojPath) {
307
211
  try {
308
212
  const content = readFileSync(csprojPath, 'utf-8');
@@ -1,10 +1,4 @@
1
- /**
2
- * Settings Generator
3
- *
4
- * Generates .claude/settings.local.json for projects based on detected
5
- * stack, MCPs, and morph-spec configuration. Uses _morph_managed marker
6
- * to track managed entries for safe reset.
7
- */
1
+ // `_morph_managed` marks the managed entries, so a reset never touches the user's.
8
2
 
9
3
  import { join } from 'path';
10
4
  import { existsSync } from 'fs';
@@ -12,44 +6,22 @@ import { readFile, writeFile, mkdir } from 'fs/promises';
12
6
 
13
7
  const MORPH_MANAGED_KEY = '_morph_managed';
14
8
 
15
- /**
16
- * Generate and merge settings for a project
17
- * @param {string} targetPath - Project root directory
18
- * @param {Object} detectedConfig - Output from detectClaudeConfig()
19
- * @param {Object} morphConfig - morph-spec config (from .morph/config/config.json)
20
- * @returns {Promise<Object>} Generated settings summary
21
- */
22
9
  export async function generateSettings(targetPath, detectedConfig, morphConfig = {}) {
23
10
  const claudeDir = join(targetPath, '.claude');
24
11
  const settingsPath = join(claudeDir, 'settings.local.json');
25
12
 
26
- // Read existing settings
27
- const existing = await readSettingsSafe(settingsPath);
28
-
29
- // Build stack-based permissions
30
- const stack = morphConfig?.project?.architecture || 'unknown';
31
- const permissions = buildPermissions(stack, detectedConfig);
32
-
33
- // Build generated settings
13
+ const existing = withoutLegacyPermissions(await readSettingsSafe(settingsPath));
34
14
  const generated = {};
35
15
 
36
- if (permissions.length > 0) {
37
- generated.permissions = permissions;
38
- }
39
-
40
- // Track what we manage
41
16
  const managed = {
42
- permissions_added: permissions,
43
17
  mcps_detected: detectedConfig.mcpServers.map(s => s.name),
44
- plugins_detected: detectedConfig.plugins.map(p => p.name),
18
+ plugins_detected: detectedConfig.plugins.map(p => (typeof p === 'string' ? p : p?.name)),
45
19
  generated_at: new Date().toISOString()
46
20
  };
47
21
 
48
- // Merge without overwriting user config
49
22
  const merged = mergeSettings(existing, generated);
50
23
  merged[MORPH_MANAGED_KEY] = managed;
51
24
 
52
- // Ensure directory exists
53
25
  if (!existsSync(claudeDir)) {
54
26
  await mkdir(claudeDir, { recursive: true });
55
27
  }
@@ -58,50 +30,11 @@ export async function generateSettings(targetPath, detectedConfig, morphConfig =
58
30
 
59
31
  return {
60
32
  settingsPath,
61
- permissionsAdded: permissions.length,
62
33
  managed
63
34
  };
64
35
  }
65
36
 
66
- /**
67
- * Build permission rules based on detected stack
68
- * @param {string} stack - Detected project stack
69
- * @param {Object} detectedConfig - Detected Claude config
70
- * @returns {Array<string>} Permission patterns
71
- */
72
- export function buildPermissions(stack, detectedConfig = {}) {
73
- const permissions = [];
74
-
75
- // Stack-based permissions
76
- const stackPermissions = {
77
- nodejs: ['Bash(node:*)', 'Bash(npm:*)', 'Bash(npx:*)'],
78
- typescript: ['Bash(node:*)', 'Bash(npm:*)', 'Bash(npx:*)', 'Bash(tsc:*)'],
79
- nextjs: ['Bash(node:*)', 'Bash(npm:*)', 'Bash(npx:*)'],
80
- react: ['Bash(node:*)', 'Bash(npm:*)', 'Bash(npx:*)'],
81
- vue: ['Bash(node:*)', 'Bash(npm:*)', 'Bash(npx:*)'],
82
- dotnet: ['Bash(dotnet:*)', 'Bash(nuget:*)'],
83
- python: ['Bash(python:*)', 'Bash(pip:*)', 'Bash(pytest:*)'],
84
- go: ['Bash(go:*)']
85
- };
86
-
87
- if (stackPermissions[stack]) {
88
- permissions.push(...stackPermissions[stack]);
89
- }
90
-
91
- // morph-spec CLI always needs node/npx
92
- if (!permissions.includes('Bash(npx:*)')) {
93
- permissions.push('Bash(npx:*)');
94
- }
95
-
96
- return permissions;
97
- }
98
-
99
- /**
100
- * Deep merge settings without overwriting user entries
101
- * @param {Object} existing - Current settings.local.json
102
- * @param {Object} generated - Generated settings to merge
103
- * @returns {Object} Merged settings
104
- */
37
+ /** Never overwrites user entries: arrays are unioned, objects merged, only new keys added. */
105
38
  export function mergeSettings(existing, generated) {
106
39
  const result = { ...existing };
107
40
 
@@ -109,17 +42,14 @@ export function mergeSettings(existing, generated) {
109
42
  if (key === MORPH_MANAGED_KEY) continue;
110
43
 
111
44
  if (Array.isArray(value) && Array.isArray(result[key])) {
112
- // Merge arrays without duplicates
113
45
  const set = new Set(result[key]);
114
46
  for (const item of value) {
115
47
  set.add(item);
116
48
  }
117
49
  result[key] = [...set];
118
50
  } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
119
- // Recursively merge objects
120
51
  result[key] = mergeSettings(result[key] || {}, value);
121
52
  } else if (!(key in result)) {
122
- // Only add new keys, never overwrite existing
123
53
  result[key] = value;
124
54
  }
125
55
  }
@@ -127,51 +57,6 @@ export function mergeSettings(existing, generated) {
127
57
  return result;
128
58
  }
129
59
 
130
- /**
131
- * Remove only morph-spec managed entries from settings
132
- * @param {string} settingsPath - Path to settings.local.json
133
- * @returns {Promise<Object>} Summary of what was removed
134
- */
135
- export async function resetMorphSettings(settingsPath) {
136
- if (!existsSync(settingsPath)) {
137
- return { removed: false, reason: 'file not found' };
138
- }
139
-
140
- const settings = await readSettingsSafe(settingsPath);
141
- const managed = settings[MORPH_MANAGED_KEY];
142
-
143
- if (!managed) {
144
- return { removed: false, reason: 'no _morph_managed section found' };
145
- }
146
-
147
- // Remove managed permissions
148
- if (managed.permissions_added && Array.isArray(settings.permissions)) {
149
- settings.permissions = settings.permissions.filter(
150
- p => !managed.permissions_added.includes(p)
151
- );
152
- if (settings.permissions.length === 0) {
153
- delete settings.permissions;
154
- }
155
- }
156
-
157
- // Remove the managed marker itself
158
- delete settings[MORPH_MANAGED_KEY];
159
-
160
- // Write back
161
- await writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
162
-
163
- return {
164
- removed: true,
165
- permissionsRemoved: managed.permissions_added || [],
166
- previouslyGenerated: managed.generated_at
167
- };
168
- }
169
-
170
- /**
171
- * Save integrations.json with detected config
172
- * @param {string} targetPath - Project root directory
173
- * @param {Object} detectedConfig - Output from detectClaudeConfig()
174
- */
175
60
  export async function saveIntegrations(targetPath, detectedConfig) {
176
61
  const integrationsPath = join(targetPath, '.morph', 'config', 'integrations.json');
177
62
  const integrationsDir = join(targetPath, '.morph', 'config');
@@ -194,9 +79,13 @@ export async function saveIntegrations(targetPath, detectedConfig) {
194
79
  return integrationsPath;
195
80
  }
196
81
 
197
- // ============================================================================
198
- // Helpers
199
- // ============================================================================
82
+ // Claude Code reads only `permissions: { allow, deny }`. Old generators wrote wildcard rules as a bare
83
+ // array that never applied; converting it to { allow } would silently enable them.
84
+ function withoutLegacyPermissions(settings) {
85
+ if (!Array.isArray(settings.permissions)) return settings;
86
+ const { permissions, ...rest } = settings;
87
+ return rest;
88
+ }
200
89
 
201
90
  async function readSettingsSafe(settingsPath) {
202
91
  try {