@polymorphism-tech/morph-spec 9.0.0 → 9.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (345) hide show
  1. package/CLAUDE.md +8 -9
  2. package/README.md +38 -36
  3. package/bin/morph-spec.js +11 -63
  4. package/bin/validate.js +1 -39
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +1 -1
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/CLAUDE.md +2 -1
  9. package/framework/MORPH.md +3 -3
  10. package/framework/agents.json +19 -11
  11. package/framework/commands/morph-apply.md +1 -1
  12. package/framework/commands/morph-status.md +1 -1
  13. package/framework/docs/CLI.md +22 -31
  14. package/framework/evals/review-checklist.md +12 -2
  15. package/framework/hooks/README.md +8 -44
  16. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
  17. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
  18. package/framework/hooks/claude-code/core/chain.js +4 -65
  19. package/framework/hooks/claude-code/core/comment-guard.js +2 -16
  20. package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
  21. package/framework/hooks/claude-code/core/gate-guard.js +7 -67
  22. package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
  23. package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
  24. package/framework/hooks/claude-code/core/heavy-release.js +2 -13
  25. package/framework/hooks/claude-code/core/loop-detect.js +2 -31
  26. package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
  27. package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
  28. package/framework/hooks/claude-code/core/state-sync.js +12 -68
  29. package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
  30. package/framework/hooks/claude-code/core/validate-completion.js +11 -57
  31. package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
  32. package/framework/hooks/claude-code/core/worktree-context.js +10 -110
  33. package/framework/hooks/claude-code/core/worktree-create.js +6 -70
  34. package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
  35. package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
  36. package/framework/hooks/claude-code/statusline.sh +11 -11
  37. package/framework/hooks/shared/activity-logger.js +9 -89
  38. package/framework/hooks/shared/agent-reap.js +2 -17
  39. package/framework/hooks/shared/build-check.js +6 -75
  40. package/framework/hooks/shared/claim-registry.js +9 -119
  41. package/framework/hooks/shared/command-tokens.js +6 -51
  42. package/framework/hooks/shared/comment-rules.js +5 -20
  43. package/framework/hooks/shared/comment-scanner.js +6 -25
  44. package/framework/hooks/shared/comment-stripper.js +11 -85
  45. package/framework/hooks/shared/e2e-check.js +0 -0
  46. package/framework/hooks/shared/errata.js +2 -15
  47. package/framework/hooks/shared/feature-locator.js +11 -95
  48. package/framework/hooks/shared/frontend-locations.js +1 -3
  49. package/framework/hooks/shared/gate-question.js +1 -7
  50. package/framework/hooks/shared/git-exclude.js +3 -20
  51. package/framework/hooks/shared/git-paths.js +8 -104
  52. package/framework/hooks/shared/graph-freshness.js +22 -166
  53. package/framework/hooks/shared/graph-refresh.js +21 -149
  54. package/framework/hooks/shared/heavy-command.js +4 -12
  55. package/framework/hooks/shared/hook-response.js +8 -83
  56. package/framework/hooks/shared/morph-cli.js +8 -52
  57. package/framework/hooks/shared/payload-utils.js +10 -132
  58. package/framework/hooks/shared/phase-utils.js +1 -42
  59. package/framework/hooks/shared/redact-secrets.js +4 -15
  60. package/framework/hooks/shared/reparse-scan.js +6 -66
  61. package/framework/hooks/shared/slice-progress.js +4 -23
  62. package/framework/hooks/shared/source-files.js +4 -17
  63. package/framework/hooks/shared/state-reader.js +13 -183
  64. package/framework/hooks/shared/state-writer.js +8 -52
  65. package/framework/hooks/shared/stdin-reader.js +1 -11
  66. package/framework/hooks/shared/telemetry-logger.js +7 -67
  67. package/framework/hooks/shared/transcript-usage.js +7 -29
  68. package/framework/hooks/shared/worktree-root.js +7 -29
  69. package/framework/rules/csharp-standards.md +1 -1
  70. package/framework/rules/frontend-standards.md +1 -0
  71. package/framework/rules/hangfire-standards.md +1 -1
  72. package/framework/rules/neon-standards.md +1 -1
  73. package/framework/rules/nextjs-standards.md +1 -1
  74. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  75. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  76. package/framework/skills/core/morph-apply/SKILL.md +4 -4
  77. package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
  78. package/framework/skills/core/morph-machine/SKILL.md +1 -1
  79. package/framework/skills/core/morph-media/SKILL.md +1 -1
  80. package/framework/skills/core/morph-plan/SKILL.md +9 -7
  81. package/framework/skills/core/morph-review/SKILL.md +9 -6
  82. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  83. package/framework/skills/core/morph-uiux/SKILL.md +29 -12
  84. package/framework/standards/STANDARDS.json +36 -14
  85. package/framework/standards/STANDARDS.meta.json +16 -2
  86. package/framework/standards/ai-agents/agent-archetypes.md +2 -2
  87. package/framework/standards/ai-agents/agent-session.md +1 -1
  88. package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
  89. package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
  90. package/framework/standards/ai-agents/guardrails.md +1 -1
  91. package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
  92. package/framework/standards/ai-agents/observability-patterns.md +1 -1
  93. package/framework/standards/ai-agents/production.md +1 -1
  94. package/framework/standards/ai-agents/prompt-sources.md +2 -2
  95. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  96. package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
  97. package/framework/standards/ai-agents/setup.md +2 -2
  98. package/framework/standards/ai-agents/sweet-spot.md +2 -2
  99. package/framework/standards/ai-agents/tool-adapters.md +0 -2
  100. package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
  101. package/framework/standards/ai-agents/workflows.md +1 -1
  102. package/framework/standards/architecture/code-comments.md +1 -1
  103. package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
  104. package/framework/standards/backend/api/validation.md +1 -1
  105. package/framework/standards/backend/dotnet/testing.md +10 -4
  106. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
  107. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
  108. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
  109. package/framework/standards/data/nosql/cache/redis.md +1 -1
  110. package/framework/standards/data/nosql/cosmos-db.md +3 -3
  111. package/framework/standards/frontend/admin-app/forms.md +1 -1
  112. package/framework/standards/frontend/design-system/art-direction.md +88 -0
  113. package/framework/standards/frontend/design-system/premium-finish.md +77 -2
  114. package/framework/standards/frontend/nextjs/testing.md +10 -6
  115. package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
  116. package/framework/templates/.idea/morph-templates.xml +92 -0
  117. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  118. package/framework/templates/CLAUDE.md.core.template +3 -3
  119. package/framework/templates/IDE-SNIPPETS.md +262 -262
  120. package/framework/templates/REGISTRY.json +9 -9
  121. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  122. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  123. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  124. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  125. package/framework/templates/docs/plan.md +3 -1
  126. package/framework/templates/docs/ui-mockups.md +4 -0
  127. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  128. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  129. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  130. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  131. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  132. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  133. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  134. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  135. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  136. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  137. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  138. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  139. package/framework/templates/state.template.json +17 -17
  140. package/framework/templates/ui/design-system.css +226 -226
  141. package/package.json +1 -1
  142. package/src/commands/context/context-refresh.js +0 -16
  143. package/src/commands/dashboard/dashboard.js +2 -8
  144. package/src/commands/e2e/e2e.js +17 -104
  145. package/src/commands/graph/affected.js +0 -14
  146. package/src/commands/graph/build.js +11 -50
  147. package/src/commands/graph/explain.js +0 -16
  148. package/src/commands/graph/status.js +1 -12
  149. package/src/commands/mcp/mcp-setup.js +4 -62
  150. package/src/commands/observability/cost.js +5 -57
  151. package/src/commands/observability/telemetry.js +1 -14
  152. package/src/commands/project/branch.js +4 -45
  153. package/src/commands/project/claim.js +4 -48
  154. package/src/commands/project/doctor.js +48 -302
  155. package/src/commands/project/env.js +1 -27
  156. package/src/commands/project/fleet.js +3 -32
  157. package/src/commands/project/init.js +4 -68
  158. package/src/commands/project/install-plugin-cmd.js +0 -1
  159. package/src/commands/project/standards.js +1 -62
  160. package/src/commands/project/status.js +0 -53
  161. package/src/commands/project/update.js +31 -239
  162. package/src/commands/project/worktree.js +108 -882
  163. package/src/commands/scaffold/scaffold.js +0 -25
  164. package/src/commands/state/advance.js +14 -85
  165. package/src/commands/state/approve.js +15 -97
  166. package/src/commands/state/archive.js +12 -64
  167. package/src/commands/state/auto-approval.js +2 -5
  168. package/src/commands/state/create.js +22 -109
  169. package/src/commands/state/delete.js +11 -63
  170. package/src/commands/state/finish.js +98 -475
  171. package/src/commands/state/gate-check.js +22 -132
  172. package/src/commands/state/gate-decision.js +3 -39
  173. package/src/commands/state/handoff.js +3 -30
  174. package/src/commands/state/list.js +14 -84
  175. package/src/commands/state/report-check.js +8 -58
  176. package/src/commands/state/retype.js +4 -26
  177. package/src/commands/validation/index.js +0 -3
  178. package/src/commands/validation/mutate.js +11 -11
  179. package/src/commands/validation/plan-check.js +4 -2
  180. package/src/commands/validation/tests-audit.js +7 -0
  181. package/src/commands/validation/validate-feature.js +10 -97
  182. package/src/commands/validation/verify.js +30 -113
  183. package/src/core/paths/output-schema.js +2 -101
  184. package/src/core/state/feature-identity.js +1 -25
  185. package/src/core/state/gate-order.js +0 -27
  186. package/src/core/state/lifecycle-audit.js +8 -100
  187. package/src/core/state/state-manager.js +22 -304
  188. package/src/core/templates/template-registry.js +2 -97
  189. package/src/core/templates/template-renderer.js +13 -93
  190. package/src/core/templates/template-seed.js +0 -1
  191. package/src/core/work-types.js +1 -36
  192. package/src/dashboard/app/App.jsx +1 -8
  193. package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
  194. package/src/dashboard/app/components/CommandPalette.jsx +0 -7
  195. package/src/dashboard/app/components/DetailPanel.jsx +1 -5
  196. package/src/dashboard/app/components/EntityLink.jsx +0 -12
  197. package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
  198. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
  199. package/src/dashboard/app/components/Markdown.jsx +0 -9
  200. package/src/dashboard/app/components/PhaseRail.jsx +0 -11
  201. package/src/dashboard/app/components/SearchFilter.jsx +0 -4
  202. package/src/dashboard/app/components/Sidebar.jsx +0 -5
  203. package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
  204. package/src/dashboard/app/hooks/usePref.js +1 -5
  205. package/src/dashboard/app/lib/format.js +0 -2
  206. package/src/dashboard/app/lib/phases.js +1 -5
  207. package/src/dashboard/app/lib/project.js +0 -12
  208. package/src/dashboard/app/main.jsx +13 -13
  209. package/src/dashboard/app/pages/AgentMap.jsx +1 -8
  210. package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
  211. package/src/dashboard/app/pages/Features.jsx +1 -8
  212. package/src/dashboard/app/pages/Guide.jsx +0 -1
  213. package/src/dashboard/app/pages/HookInspector.jsx +2 -23
  214. package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
  215. package/src/dashboard/app/pages/Portfolio.jsx +0 -1
  216. package/src/dashboard/app/pages/RulesMap.jsx +1 -16
  217. package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
  218. package/src/dashboard/app/pages/Standards.jsx +2 -17
  219. package/src/dashboard/scanner/activity-scanner.js +10 -69
  220. package/src/dashboard/scanner/agents-scanner.js +1 -23
  221. package/src/dashboard/scanner/docs-scanner.js +0 -6
  222. package/src/dashboard/scanner/feature-status.js +2 -5
  223. package/src/dashboard/scanner/features-scanner.js +10 -66
  224. package/src/dashboard/scanner/hooks-scanner.js +5 -28
  225. package/src/dashboard/scanner/index.js +0 -4
  226. package/src/dashboard/scanner/portfolio.js +5 -29
  227. package/src/dashboard/scanner/project-api.js +8 -54
  228. package/src/dashboard/scanner/rules-scanner.js +1 -2
  229. package/src/dashboard/scanner/skills-scanner.js +2 -12
  230. package/src/dashboard/scanner/standards-scanner.js +2 -6
  231. package/src/dashboard/vite-plugin-morph-data.js +5 -19
  232. package/src/dashboard/vite.config.js +24 -24
  233. package/src/lib/ai/ai-checks.js +7 -52
  234. package/src/lib/ai/ai-project-scan.js +14 -108
  235. package/src/lib/ai/code-audit.js +7 -63
  236. package/src/lib/ai/pin-drift.js +8 -51
  237. package/src/lib/ai/registry-audit.js +11 -90
  238. package/src/lib/ci/ci-verify.js +4 -19
  239. package/src/lib/ci/github-ci.js +9 -37
  240. package/src/lib/ci/workflow-scaffold.js +14 -10
  241. package/src/lib/detectors/claude-config-detector.js +13 -74
  242. package/src/lib/detectors/index.js +4 -50
  243. package/src/lib/e2e/applicability.js +37 -0
  244. package/src/lib/e2e/compose-env.js +5 -66
  245. package/src/lib/e2e/stack-teardown.js +6 -40
  246. package/src/lib/generators/architecture-scanner.js +18 -156
  247. package/src/lib/generators/handoff-generator.js +9 -53
  248. package/src/lib/generators/scaffold-generator.js +5 -101
  249. package/src/lib/generators/settings-generator.js +4 -37
  250. package/src/lib/git/branch-classifier.js +11 -84
  251. package/src/lib/git/default-branch.js +14 -169
  252. package/src/lib/git/evidence-staleness.js +1 -0
  253. package/src/lib/git/gh.js +2 -17
  254. package/src/lib/git/pending-prs.js +6 -37
  255. package/src/lib/git/pr-files.js +3 -28
  256. package/src/lib/git/pr-index.js +3 -34
  257. package/src/lib/git/tree-fingerprint.js +3 -15
  258. package/src/lib/git/tree-state.js +6 -33
  259. package/src/lib/graph/god-nodes.js +2 -22
  260. package/src/lib/graph/graph-health.js +3 -24
  261. package/src/lib/graph/graph-loader.js +5 -56
  262. package/src/lib/graph/graphify-runtime.js +7 -37
  263. package/src/lib/graph/graphifyignore.js +8 -74
  264. package/src/lib/graph/sanitize.js +16 -136
  265. package/src/lib/heavy/cleanup.js +1 -1
  266. package/src/lib/installers/effects.js +8 -93
  267. package/src/lib/installers/mcp-installer.js +9 -169
  268. package/src/lib/installers/memory-installer.js +4 -27
  269. package/src/lib/installers/templates-reconcile.js +5 -60
  270. package/src/lib/mutation/adapters.js +6 -17
  271. package/src/lib/mutation/dotnet-introspection.js +20 -106
  272. package/src/lib/mutation/runner.js +23 -75
  273. package/src/lib/mutation/subset.js +1 -4
  274. package/src/lib/mutation/trx.js +0 -1
  275. package/src/lib/plan/artifact-vocabulary.js +5 -25
  276. package/src/lib/plan/checks.js +34 -33
  277. package/src/lib/plan/plan-parser.js +7 -8
  278. package/src/lib/projects/registry.js +7 -44
  279. package/src/lib/review-reports.js +11 -26
  280. package/src/lib/risk-detector.js +41 -173
  281. package/src/lib/specs/adr-index.js +6 -34
  282. package/src/lib/specs/frontmatter.js +2 -15
  283. package/src/lib/specs/gfm-table.js +1 -7
  284. package/src/lib/specs/revocations.js +1 -14
  285. package/src/lib/specs/screen-checklist.js +47 -124
  286. package/src/lib/stack/dotnet-build-ignores.js +2 -6
  287. package/src/lib/stack/stack-profile.js +1 -28
  288. package/src/lib/stack/stack-scan.js +1 -8
  289. package/src/lib/stack-filter.js +4 -27
  290. package/src/lib/standards/registry.js +1 -7
  291. package/src/lib/standards/sections.js +2 -13
  292. package/src/lib/standards/verified-against.js +19 -191
  293. package/src/lib/tasks/eval-node.js +21 -169
  294. package/src/lib/tasks/linux-runner.js +18 -142
  295. package/src/lib/tasks/test-nodes.js +43 -240
  296. package/src/lib/tasks/test-runner.js +43 -341
  297. package/src/lib/telemetry/metrics.js +7 -36
  298. package/src/lib/telemetry/telemetry-reader.js +13 -125
  299. package/src/lib/telemetry/verify-event.js +2 -30
  300. package/src/lib/tests-audit/audit.js +96 -0
  301. package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
  302. package/src/lib/validators/code-budget/index.js +0 -7
  303. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
  304. package/src/lib/validators/contracts/index.js +0 -6
  305. package/src/lib/validators/nextjs/index.js +0 -5
  306. package/src/lib/validators/nextjs/next-component-validator.js +4 -81
  307. package/src/lib/validators/packages/index.js +0 -6
  308. package/src/lib/validators/packages/package-validator.js +6 -56
  309. package/src/lib/validators/reuse/index.js +0 -8
  310. package/src/lib/validators/reuse/reuse-validator.js +11 -101
  311. package/src/lib/validators/reuse/symbol-scanner.js +6 -62
  312. package/src/lib/validators/traceability-check.js +5 -63
  313. package/src/lib/validators/validation-runner.js +21 -158
  314. package/src/lib/validators/verify-runner.js +85 -376
  315. package/src/lib/work-type-detector.js +5 -67
  316. package/src/lib/worktree/docker-audit.js +17 -136
  317. package/src/lib/worktree/env-allocator.js +10 -98
  318. package/src/lib/worktree/feature-truth.js +12 -115
  319. package/src/lib/worktree/finish-backup.js +32 -242
  320. package/src/lib/worktree/fleet-scanner.js +13 -95
  321. package/src/lib/worktree/ignored-triage.js +15 -91
  322. package/src/lib/worktree/vscode-settings.js +2 -3
  323. package/src/lib/worktree/worktree-audit.js +21 -213
  324. package/src/lib/worktree/worktree-include.js +3 -5
  325. package/src/scripts/global-install.js +3 -9
  326. package/src/scripts/install-plugin.js +1 -13
  327. package/src/scripts/setup-infra.js +17 -147
  328. package/src/utils/agents-installer.js +29 -276
  329. package/src/utils/banner.js +4 -25
  330. package/src/utils/claude-md-injector.js +1 -37
  331. package/src/utils/claude-settings-manager.js +1 -52
  332. package/src/utils/file-copier.js +11 -95
  333. package/src/utils/hooks-installer.js +21 -298
  334. package/src/utils/skills-installer.js +4 -101
  335. package/src/utils/version-checker.js +4 -83
  336. package/framework/commands/morph-proposal.md +0 -9
  337. package/framework/hooks/git/commit-msg/conventional-commits.sh +0 -33
  338. package/framework/hooks/git/pre-commit/agents.sh +0 -25
  339. package/framework/hooks/git/pre-commit/orchestrator.sh +0 -64
  340. package/framework/hooks/git/pre-push/run-tests.sh +0 -44
  341. package/src/commands/utils/index.js +0 -3
  342. package/src/lib/detectors/design-system-detector.js +0 -188
  343. package/src/lib/generators/design-system-generator.js +0 -508
  344. package/src/lib/specs/old-format.js +0 -68
  345. package/src/utils/agent-id-mapper.js +0 -11
@@ -1,13 +1,3 @@
1
- /**
2
- * claude-md-injector.js
3
- *
4
- * Manages the root CLAUDE.md file in client projects.
5
- * Instead of overwriting the user's CLAUDE.md, injects an @import line
6
- * that references the morph-spec managed .claude/CLAUDE.md.
7
- *
8
- * The root CLAUDE.md belongs to the USER — morph-spec only adds its import.
9
- */
10
-
11
1
  import { pathExists, readFile } from './file-copier.js';
12
2
  import { realEffects } from '../lib/installers/effects.js';
13
3
 
@@ -15,20 +5,7 @@ const MORPH_IMPORT = '@.claude/CLAUDE.md';
15
5
  const MORPH_MARKER = 'MORPH-SPEC';
16
6
 
17
7
  /**
18
- * Injects the morph-spec @import into the root CLAUDE.md.
19
- *
20
- * Three scenarios:
21
- * 1. No CLAUDE.md exists → create minimal file with the import
22
- * 2. Existing morph-only CLAUDE.md (old overwrite) → replace with minimal + import
23
- * 3. Existing user CLAUDE.md → append import section at the end
24
- *
25
- * Idempotent — does nothing if the import already exists.
26
- *
27
- * @param {string} claudeMdPath - Absolute path to root CLAUDE.md
28
- * @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [fx]
29
- * Coletor de efeitos; o default executa e registra numa lista descartada.
30
- * A DECISÃO (criar / migrar / anexar / nada) sai de uma LEITURA, que
31
- * acontece igual nos dois modos — por isso previsão e execução concordam.
8
+ * Leaves the user's root CLAUDE.md in place and only adds the morph-spec @import.
32
9
  * @returns {Promise<'created'|'migrated'|'injected'|'already-present'>}
33
10
  */
34
11
  export async function injectMorphImport(claudeMdPath, fx = realEffects()) {
@@ -48,20 +25,14 @@ export async function injectMorphImport(claudeMdPath, fx = realEffects()) {
48
25
  return 'migrated';
49
26
  }
50
27
 
51
- // User has their own content — append the import section
52
28
  const separator = content.endsWith('\n') ? '\n' : '\n\n';
53
29
  fx.writeFile(claudeMdPath, `${content}${separator}## MORPH-SPEC\n\n${MORPH_IMPORT}\n`);
54
30
  return 'injected';
55
31
  }
56
32
 
57
- /**
58
- * Checks if CLAUDE.md contains ONLY morph-spec content (old overwrite pattern).
59
- * Heuristic: contains MORPH-SPEC marker AND no substantial user content.
60
- */
61
33
  function isMorphOnly(content) {
62
34
  if (!content.includes(MORPH_MARKER)) return false;
63
35
 
64
- // Strip morph-spec boilerplate markers and check if anything meaningful remains
65
36
  const stripped = content
66
37
  .replace(/# MORPH-SPEC Runtime Instructions/g, '')
67
38
  .replace(/by Polymorphism Tech[^\n]*/g, '')
@@ -80,13 +51,6 @@ function isMorphOnly(content) {
80
51
  return stripped.length < 50;
81
52
  }
82
53
 
83
- /**
84
- * Checks if the root CLAUDE.md has the morph-spec import.
85
- * Used by doctor.js for health checks.
86
- *
87
- * @param {string} claudeMdPath - Absolute path to root CLAUDE.md
88
- * @returns {Promise<'ok'|'missing'|'missing-import'>}
89
- */
90
54
  export async function checkClaudeMdImport(claudeMdPath) {
91
55
  if (!await pathExists(claudeMdPath)) return 'missing';
92
56
  const content = await readFile(claudeMdPath);
@@ -1,35 +1,12 @@
1
- /**
2
- * Claude Settings Manager
3
- *
4
- * Extends the original hooks-installer with broader settings management:
5
- * permissions, env vars, and managed entry tracking.
6
- *
7
- * Re-exports installClaudeHooks for backward compatibility.
8
- */
9
-
10
1
  import { join } from 'path';
11
2
  import { readFile, writeFile, mkdir, copyFile } from 'fs/promises';
12
3
  import { existsSync } from 'fs';
13
4
 
14
5
  const MORPH_MANAGED_KEY = '_morph_managed';
15
6
 
16
- // ============================================================================
17
- // Re-export from hooks-installer (v2 registry-based)
18
- // ============================================================================
19
-
20
7
  export { installClaudeHooks, installGlobalStatusline, getHooksVersion } from './hooks-installer.js';
21
8
  export { installAgents } from './agents-installer.js';
22
9
 
23
- // ============================================================================
24
- // Extended settings management
25
- // ============================================================================
26
-
27
- /**
28
- * Install environment variables into .claude/settings.local.json
29
- * @param {string} targetPath - Project root directory
30
- * @param {Object} envVars - Key-value pairs to add to env
31
- * @returns {Promise<Object>} Summary of changes
32
- */
33
10
  export async function installEnvVars(targetPath, envVars) {
34
11
  const settings = await loadSettings(targetPath);
35
12
 
@@ -43,7 +20,6 @@ export async function installEnvVars(targetPath, envVars) {
43
20
  }
44
21
  }
45
22
 
46
- // Track in _morph_managed
47
23
  settings[MORPH_MANAGED_KEY] = settings[MORPH_MANAGED_KEY] || {};
48
24
  settings[MORPH_MANAGED_KEY].env_added =
49
25
  settings[MORPH_MANAGED_KEY].env_added || [];
@@ -59,12 +35,6 @@ export async function installEnvVars(targetPath, envVars) {
59
35
  return { added, total: Object.keys(settings.env).length };
60
36
  }
61
37
 
62
- /**
63
- * Remove only morph-spec managed entries from settings.
64
- * Creates backup before modifying.
65
- * @param {string} targetPath - Project root directory
66
- * @returns {Promise<Object>} Summary of what was removed
67
- */
68
38
  export async function resetMorphSettings(targetPath) {
69
39
  const claudeDir = join(targetPath, '.claude');
70
40
  const settingsPath = join(claudeDir, 'settings.local.json');
@@ -80,11 +50,9 @@ export async function resetMorphSettings(targetPath) {
80
50
  return { removed: false, reason: 'no _morph_managed section found' };
81
51
  }
82
52
 
83
- // Backup before modifying
84
53
  const backupPath = settingsPath + '.backup';
85
54
  await copyFile(settingsPath, backupPath);
86
55
 
87
- // Remove managed permissions
88
56
  if (managed.permissions_added && Array.isArray(settings.permissions)) {
89
57
  settings.permissions = settings.permissions.filter(
90
58
  p => !managed.permissions_added.includes(p)
@@ -94,7 +62,6 @@ export async function resetMorphSettings(targetPath) {
94
62
  }
95
63
  }
96
64
 
97
- // Remove managed env vars
98
65
  if (managed.env_added && settings.env) {
99
66
  for (const key of managed.env_added) {
100
67
  delete settings.env[key];
@@ -104,7 +71,6 @@ export async function resetMorphSettings(targetPath) {
104
71
  }
105
72
  }
106
73
 
107
- // Remove managed hooks (v2 format: identified by path pattern)
108
74
  if (settings.hooks) {
109
75
  const morphPattern = /framework\/hooks\/claude-code\//;
110
76
  const oldPattern = /agent-teams\/dispatch\.js/;
@@ -130,7 +96,6 @@ export async function resetMorphSettings(targetPath) {
130
96
  }
131
97
  }
132
98
 
133
- // Remove managed MCP servers from .mcp.json (primary location)
134
99
  if (managed.mcps_installed) {
135
100
  const mcpJson = await loadMcpJson(targetPath);
136
101
  if (mcpJson.mcpServers) {
@@ -140,7 +105,6 @@ export async function resetMorphSettings(targetPath) {
140
105
  if (Object.keys(mcpJson.mcpServers).length === 0) {
141
106
  delete mcpJson.mcpServers;
142
107
  }
143
- // Write back (or remove file if empty)
144
108
  if (Object.keys(mcpJson).length === 0) {
145
109
  const mcpJsonPath = join(targetPath, '.mcp.json');
146
110
  if (existsSync(mcpJsonPath)) {
@@ -152,7 +116,6 @@ export async function resetMorphSettings(targetPath) {
152
116
  }
153
117
  }
154
118
 
155
- // Also clean legacy entries from settings.local.json
156
119
  if (settings.mcpServers) {
157
120
  for (const mcpName of managed.mcps_installed) {
158
121
  delete settings.mcpServers[mcpName];
@@ -163,7 +126,6 @@ export async function resetMorphSettings(targetPath) {
163
126
  }
164
127
  }
165
128
 
166
- // Remove the managed marker
167
129
  delete settings[MORPH_MANAGED_KEY];
168
130
 
169
131
  await saveSettings(targetPath, settings);
@@ -179,15 +141,7 @@ export async function resetMorphSettings(targetPath) {
179
141
  };
180
142
  }
181
143
 
182
- /**
183
- * Install MCP server entries into .mcp.json at the project root.
184
- * Claude Code reads MCP servers from .mcp.json (not settings.local.json).
185
- * Tracks installed names in settings.local.json _morph_managed.mcps_installed for safe reset.
186
- *
187
- * @param {string} targetPath - Project root directory
188
- * @param {Object} servers - Map of server name → config { type?, command, args, env? }
189
- * @returns {Promise<Object>} Summary of changes { added, skipped, total }
190
- */
144
+ // Claude Code reads MCP servers from .mcp.json, not settings.local.json.
191
145
  export async function installMcpServers(targetPath, servers) {
192
146
  const mcpJson = await loadMcpJson(targetPath);
193
147
 
@@ -207,7 +161,6 @@ export async function installMcpServers(targetPath, servers) {
207
161
 
208
162
  await saveMcpJson(targetPath, mcpJson);
209
163
 
210
- // Track in settings.local.json _morph_managed
211
164
  const settings = await loadSettings(targetPath);
212
165
  settings[MORPH_MANAGED_KEY] = settings[MORPH_MANAGED_KEY] || {};
213
166
  settings[MORPH_MANAGED_KEY].mcps_installed =
@@ -224,10 +177,6 @@ export async function installMcpServers(targetPath, servers) {
224
177
  return { added, skipped, total: Object.keys(mcpJson.mcpServers).length };
225
178
  }
226
179
 
227
- // ============================================================================
228
- // Helpers
229
- // ============================================================================
230
-
231
180
  async function loadMcpJson(targetPath) {
232
181
  const mcpJsonPath = join(targetPath, '.mcp.json');
233
182
  try {
@@ -39,10 +39,7 @@ export async function pathExists(path) {
39
39
  }
40
40
 
41
41
  export async function readJson(path) {
42
- // BOM-tolerant: PowerShell 5.1's `Out-File -Encoding utf8` writes UTF-8
43
- // WITH BOM, and JSON.parse rejects it. A hand-edited config.json on Windows
44
- // would then silently fall back to empty project tags (fail-open reads),
45
- // installing only universal personas. Strip the BOM before parsing.
42
+ // PowerShell 5.1's `Out-File -Encoding utf8` writes a BOM that JSON.parse rejects.
46
43
  const raw = await fs.readFile(path, 'utf-8');
47
44
  return JSON.parse(raw.replace(/^/, ''));
48
45
  }
@@ -69,18 +66,7 @@ export async function readFile(path) {
69
66
  return fs.readFile(path, 'utf8');
70
67
  }
71
68
 
72
- /**
73
- * Create a link to a file or directory with platform-aware fallbacks.
74
- *
75
- * For files: hard link → symlink → copy
76
- * Hard links work on Windows without admin (same NTFS volume).
77
- * For dirs: delegates to createDirectoryLink (junction on Windows).
78
- *
79
- * @param {string} target - Path to the original file/folder
80
- * @param {string} link - Path where the link should be created
81
- * @param {string} type - 'file' or 'dir'
82
- * @returns {Promise<'hardlink' | 'symlink' | 'junction' | 'copy'>}
83
- */
69
+ // Hard link first: it needs no admin on Windows (same NTFS volume).
84
70
  export async function createSymlink(target, link, type = 'file') {
85
71
  await fs.ensureDir(dirname(link));
86
72
 
@@ -88,7 +74,6 @@ export async function createSymlink(target, link, type = 'file') {
88
74
  return createDirectoryLink(target, link);
89
75
  }
90
76
 
91
- // File linking: hard link first (no admin on Windows), then symlink, then copy
92
77
  try {
93
78
  await fs.link(target, link);
94
79
  return 'hardlink';
@@ -103,24 +88,15 @@ export async function createSymlink(target, link, type = 'file') {
103
88
  }
104
89
  }
105
90
 
106
- /**
107
- * Create a directory link (junction on Windows, symlink on other platforms)
108
- * with fallback to directory copy if linking fails.
109
- * @param {string} target - Path to the original directory
110
- * @param {string} link - Path where the link should be created
111
- * @returns {Promise<'junction' | 'symlink' | 'copy'>} - How the link was created
112
- */
113
91
  export async function createDirectoryLink(target, link) {
114
92
  await fs.ensureDir(dirname(link));
115
93
 
116
- // Remove existing link/directory before recreating
117
94
  if (await pathExists(link)) {
118
95
  await fs.remove(link);
119
96
  }
120
97
 
121
98
  if (process.platform === 'win32') {
122
- // Junction on Windows — does NOT require admin privileges.
123
- // Skip ensureSymlink('dir') fallback: it also needs admin on Windows.
99
+ // A junction needs no admin; ensureSymlink('dir') would.
124
100
  try {
125
101
  await fs.symlink(target, link, 'junction');
126
102
  return 'junction';
@@ -139,16 +115,8 @@ export async function createDirectoryLink(target, link) {
139
115
  }
140
116
  }
141
117
 
142
- /**
143
- * The morph-owned block of the installed .gitignore. Exported so the worktree
144
- * container canary (test/lib/worktree/dirs.test.js) can prove both names are
145
- * covered without rendering a file.
146
- */
147
118
  export const MORPH_GITIGNORE_ENTRIES = [
148
- // Derivable, per-machine state index — rebuilt from the committed
149
- // .morph/features/{feature}/feature.json files. Never commit it: doing so
150
- // sweeps every feature's accumulated diffs into one commit. The feature
151
- // state that matters travels inside each feature folder (which IS committed).
119
+ // Rebuilt from the committed feature.json files; committing it sweeps every feature into one commit.
152
120
  '.morph/state.json',
153
121
  '.morph/state.json.tmp.*',
154
122
  '.morph/state.json.lock/',
@@ -156,9 +124,6 @@ export const MORPH_GITIGNORE_ENTRIES = [
156
124
  '.morph/memory/',
157
125
  '.morph/plans/',
158
126
  '.morph/analytics/',
159
- // Per-instance activity/session logs (activity-logger.js). Each worktree
160
- // writes its own — committing this guarantees a merge conflict between
161
- // feature branches.
162
127
  '.morph/logs/',
163
128
  '.claude/commands/',
164
129
  '.claude/rules/',
@@ -166,25 +131,13 @@ export const MORPH_GITIGNORE_ENTRIES = [
166
131
  '.claude/agents/',
167
132
  '.claude/CLAUDE.md',
168
133
  '.claude/settings.local.json',
169
- // Both container names, anchored at the root. The legacy name (pre-C1) is
170
- // kept so a project that already has `.worktrees/` on disk does not
171
- // suddenly see it as untracked dirt — which would also make `finish`'s
172
- // clean-root check refuse.
134
+ // The legacy name stays so an existing `.worktrees/` is not dirt that makes `finish` refuse.
173
135
  ...WORKTREE_DIR_NAMES.map((d) => `/${d}/`),
174
136
  '.playwright-mcp/',
175
137
  'docs/superpowers/',
176
- // Grafo de codebase — artefato derivado, reconstruído pelo hook do
177
- // graphify a cada commit. Committá-lo faria de todo commit um diff de
178
- // megabytes de JSON gerado.
179
138
  'graphify-out/',
180
139
  ];
181
140
 
182
- /**
183
- * @param {string} projectPath
184
- * @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [fx]
185
- * Coletor de efeitos. O default executa e registra numa lista descartada, de
186
- * modo que todo chamador antigo (`setup-infra`, `init`) siga idêntico.
187
- */
188
141
  export async function updateGitignore(projectPath, fx = realEffects()) {
189
142
  const gitignorePath = join(projectPath, '.gitignore');
190
143
 
@@ -193,7 +146,6 @@ export async function updateGitignore(projectPath, fx = realEffects()) {
193
146
  let content = '';
194
147
  let hasMorphSection = false;
195
148
 
196
- // Read existing .gitignore if it exists
197
149
  if (await pathExists(gitignorePath)) {
198
150
  content = await readFile(gitignorePath);
199
151
  hasMorphSection = content.includes('# MORPH-SPEC');
@@ -201,12 +153,10 @@ export async function updateGitignore(projectPath, fx = realEffects()) {
201
153
  const original = content;
202
154
  const eol = content.includes('\r\n') ? '\r\n' : '\n';
203
155
 
204
- // If MORPH section already exists, check if rules are up to date
205
156
  if (hasMorphSection) {
206
157
  const lines = content.split(/\r?\n/);
207
158
  const morphStartIndex = lines.findIndex(line => line.trim() === '# MORPH-SPEC');
208
159
 
209
- // Find the end of MORPH section (next empty line or section header)
210
160
  let morphEndIndex = morphStartIndex + 1;
211
161
  while (morphEndIndex < lines.length &&
212
162
  lines[morphEndIndex].trim() !== '' &&
@@ -214,22 +164,18 @@ export async function updateGitignore(projectPath, fx = realEffects()) {
214
164
  morphEndIndex++;
215
165
  }
216
166
 
217
- // Extract current MORPH rules
218
167
  const currentMorphRules = lines.slice(morphStartIndex, morphEndIndex + 1);
219
- const expectedMorphRules = morphRules.slice(1, -1); // Remove empty lines from comparison
168
+ const expectedMorphRules = morphRules.slice(1, -1);
220
169
 
221
- // Check if all expected rules are present
222
170
  const missingRules = expectedMorphRules.filter(rule =>
223
171
  rule.startsWith('#') || !currentMorphRules.some(line => line.trim() === rule)
224
172
  );
225
173
 
226
174
  if (missingRules.length > 0) {
227
- // Update the section with all rules
228
175
  lines.splice(morphStartIndex, morphEndIndex - morphStartIndex, ...morphRules.slice(1, -1));
229
176
  content = lines.join(eol);
230
177
  }
231
178
  } else {
232
- // Add MORPH section
233
179
  if (content && !content.endsWith('\n')) {
234
180
  content += eol;
235
181
  }
@@ -247,12 +193,7 @@ const ignoredDir = (line) => line.trim()
247
193
  .replace(/^\[(\w)(\w)\]/, (_, a, b) => (a.toLowerCase() === b.toLowerCase() ? a : `[${a}${b}]`))
248
194
  .toLowerCase();
249
195
 
250
- /**
251
- * Appends `bin/` and `obj/` to the project `.gitignore` when no line already
252
- * ignores them. Never rewrites an existing line.
253
- *
254
- * @returns {string[]} the entries added
255
- */
196
+ /** Never rewrites an existing line. @returns {string[]} the entries added */
256
197
  export function ensureDotnetBuildIgnores(projectPath) {
257
198
  const gitignorePath = join(projectPath, '.gitignore');
258
199
  const content = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf8') : '';
@@ -266,23 +207,8 @@ export function ensureDotnetBuildIgnores(projectPath) {
266
207
  return missing;
267
208
  }
268
209
 
269
- /**
270
- * Install a MORPH-SPEC .gitattributes section pinning morph state files to LF.
271
- *
272
- * Rationale: `saveState`/`atomicWriteJson` write `.morph` JSON with LF. On
273
- * Windows with `core.autocrlf=true` (the Git-for-Windows default) and no
274
- * attribute pinning EOL, Git flags EVERY rewritten feature.json as modified
275
- * even when the content is byte-identical. That silently breaks the whole-root
276
- * clean-check in `morph-spec finish` as soon as a project has more
277
- * than one feature — precisely the multi-instance / parallel-worktree flow,
278
- * where one `saveState` touches sibling feature.json files and marks them dirty.
279
- * This mirrors the .gitattributes this framework repo already carries for its
280
- * own generated files. Scoped to `.morph/**` so it never imposes an EOL policy
281
- * on the user's own source. Idempotent and non-destructive: appends a marked
282
- * block, leaving any pre-existing .gitattributes rules untouched.
283
- *
284
- * @param {string} projectPath
285
- */
210
+ // Without the LF pin, Windows autocrlf marks every rewritten feature.json dirty
211
+ // and breaks the clean-root check of `finish`. Scoped to `.morph/**` only.
286
212
  export async function ensureGitattributes(projectPath, fx = realEffects()) {
287
213
  const gitattributesPath = join(projectPath, '.gitattributes');
288
214
  const MARKER = '# MORPH-SPEC';
@@ -297,7 +223,7 @@ export async function ensureGitattributes(projectPath, fx = realEffects()) {
297
223
  let content = '';
298
224
  if (await pathExists(gitattributesPath)) {
299
225
  content = await readFile(gitattributesPath);
300
- if (content.includes(MARKER)) return; // already installed — idempotent
226
+ if (content.includes(MARKER)) return;
301
227
  if (content && !content.endsWith('\n')) content += '\n';
302
228
  content += '\n';
303
229
  }
@@ -305,17 +231,7 @@ export async function ensureGitattributes(projectPath, fx = realEffects()) {
305
231
  fx.writeFile(gitattributesPath, content);
306
232
  }
307
233
 
308
- /**
309
- * Garante entradas no .claudeignore, criando o arquivo se preciso.
310
- *
311
- * Existe por causa do grafo: um `graph.json` de 12 MB reescrito a cada commit
312
- * invalidaria o prompt cache do Claude Code em toda sessão. Append idempotente
313
- * por linha — o que o usuário já tem nunca é reescrito.
314
- *
315
- * @param {string} projectPath
316
- * @param {string[]} entries
317
- * @returns {Promise<{added: string[], path: string}>}
318
- */
234
+ /** Idempotent per-line append; never rewrites what the user already has. */
319
235
  export async function ensureClaudeignore(projectPath, entries) {
320
236
  const claudeignorePath = join(projectPath, '.claudeignore');
321
237
  const MARKER = '# MORPH-SPEC';