@polymorphism-tech/morph-spec 9.0.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 (304) hide show
  1. package/CLAUDE.md +8 -9
  2. package/README.md +38 -36
  3. package/bin/morph-spec.js +4 -62
  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 +1 -1
  9. package/framework/MORPH.md +1 -1
  10. package/framework/agents.json +12 -4
  11. package/framework/commands/morph-apply.md +1 -1
  12. package/framework/commands/morph-status.md +1 -1
  13. package/framework/docs/CLI.md +6 -17
  14. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
  15. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
  16. package/framework/hooks/claude-code/core/chain.js +4 -65
  17. package/framework/hooks/claude-code/core/comment-guard.js +2 -16
  18. package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
  19. package/framework/hooks/claude-code/core/gate-guard.js +7 -67
  20. package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
  21. package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
  22. package/framework/hooks/claude-code/core/heavy-release.js +2 -13
  23. package/framework/hooks/claude-code/core/loop-detect.js +2 -31
  24. package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
  25. package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
  26. package/framework/hooks/claude-code/core/state-sync.js +11 -67
  27. package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
  28. package/framework/hooks/claude-code/core/validate-completion.js +11 -57
  29. package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
  30. package/framework/hooks/claude-code/core/worktree-context.js +10 -110
  31. package/framework/hooks/claude-code/core/worktree-create.js +6 -70
  32. package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
  33. package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
  34. package/framework/hooks/shared/activity-logger.js +9 -89
  35. package/framework/hooks/shared/agent-reap.js +2 -17
  36. package/framework/hooks/shared/build-check.js +6 -75
  37. package/framework/hooks/shared/claim-registry.js +9 -119
  38. package/framework/hooks/shared/command-tokens.js +6 -51
  39. package/framework/hooks/shared/comment-rules.js +5 -20
  40. package/framework/hooks/shared/comment-scanner.js +6 -25
  41. package/framework/hooks/shared/comment-stripper.js +11 -85
  42. package/framework/hooks/shared/e2e-check.js +0 -0
  43. package/framework/hooks/shared/errata.js +2 -15
  44. package/framework/hooks/shared/feature-locator.js +11 -95
  45. package/framework/hooks/shared/frontend-locations.js +1 -3
  46. package/framework/hooks/shared/gate-question.js +1 -7
  47. package/framework/hooks/shared/git-exclude.js +3 -20
  48. package/framework/hooks/shared/git-paths.js +8 -104
  49. package/framework/hooks/shared/graph-freshness.js +22 -166
  50. package/framework/hooks/shared/graph-refresh.js +21 -149
  51. package/framework/hooks/shared/heavy-command.js +4 -12
  52. package/framework/hooks/shared/hook-response.js +8 -83
  53. package/framework/hooks/shared/morph-cli.js +8 -52
  54. package/framework/hooks/shared/payload-utils.js +10 -132
  55. package/framework/hooks/shared/phase-utils.js +1 -42
  56. package/framework/hooks/shared/redact-secrets.js +4 -15
  57. package/framework/hooks/shared/reparse-scan.js +6 -66
  58. package/framework/hooks/shared/slice-progress.js +4 -23
  59. package/framework/hooks/shared/source-files.js +4 -17
  60. package/framework/hooks/shared/state-reader.js +13 -183
  61. package/framework/hooks/shared/state-writer.js +8 -52
  62. package/framework/hooks/shared/stdin-reader.js +1 -11
  63. package/framework/hooks/shared/telemetry-logger.js +7 -67
  64. package/framework/hooks/shared/transcript-usage.js +7 -29
  65. package/framework/hooks/shared/worktree-root.js +7 -29
  66. package/framework/rules/csharp-standards.md +1 -1
  67. package/framework/rules/frontend-standards.md +1 -0
  68. package/framework/rules/hangfire-standards.md +1 -1
  69. package/framework/rules/neon-standards.md +1 -1
  70. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  71. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  72. package/framework/skills/core/morph-apply/SKILL.md +2 -2
  73. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  74. package/framework/skills/core/morph-machine/SKILL.md +1 -1
  75. package/framework/skills/core/morph-media/SKILL.md +1 -1
  76. package/framework/skills/core/morph-plan/SKILL.md +2 -2
  77. package/framework/skills/core/morph-review/SKILL.md +1 -1
  78. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  79. package/framework/skills/core/morph-uiux/SKILL.md +29 -12
  80. package/framework/standards/STANDARDS.json +34 -12
  81. package/framework/standards/STANDARDS.meta.json +16 -2
  82. package/framework/standards/ai-agents/agent-archetypes.md +2 -2
  83. package/framework/standards/ai-agents/agent-session.md +1 -1
  84. package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
  85. package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
  86. package/framework/standards/ai-agents/guardrails.md +1 -1
  87. package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
  88. package/framework/standards/ai-agents/observability-patterns.md +1 -1
  89. package/framework/standards/ai-agents/production.md +1 -1
  90. package/framework/standards/ai-agents/prompt-sources.md +2 -2
  91. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  92. package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
  93. package/framework/standards/ai-agents/setup.md +2 -2
  94. package/framework/standards/ai-agents/sweet-spot.md +2 -2
  95. package/framework/standards/ai-agents/tool-adapters.md +0 -2
  96. package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
  97. package/framework/standards/ai-agents/workflows.md +1 -1
  98. package/framework/standards/architecture/code-comments.md +1 -1
  99. package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
  100. package/framework/standards/backend/api/validation.md +1 -1
  101. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
  102. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
  103. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
  104. package/framework/standards/data/nosql/cache/redis.md +1 -1
  105. package/framework/standards/data/nosql/cosmos-db.md +3 -3
  106. package/framework/standards/frontend/admin-app/forms.md +1 -1
  107. package/framework/standards/frontend/design-system/art-direction.md +88 -0
  108. package/framework/standards/frontend/design-system/premium-finish.md +77 -2
  109. package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
  110. package/framework/templates/CLAUDE.md.core.template +3 -3
  111. package/framework/templates/REGISTRY.json +9 -9
  112. package/framework/templates/docs/ui-mockups.md +4 -0
  113. package/package.json +1 -1
  114. package/src/commands/context/context-refresh.js +0 -16
  115. package/src/commands/dashboard/dashboard.js +2 -8
  116. package/src/commands/e2e/e2e.js +17 -104
  117. package/src/commands/graph/affected.js +0 -14
  118. package/src/commands/graph/build.js +11 -50
  119. package/src/commands/graph/explain.js +0 -16
  120. package/src/commands/graph/status.js +1 -12
  121. package/src/commands/mcp/mcp-setup.js +4 -62
  122. package/src/commands/observability/cost.js +5 -57
  123. package/src/commands/observability/telemetry.js +1 -14
  124. package/src/commands/project/branch.js +4 -45
  125. package/src/commands/project/claim.js +4 -48
  126. package/src/commands/project/doctor.js +48 -302
  127. package/src/commands/project/env.js +1 -27
  128. package/src/commands/project/fleet.js +3 -32
  129. package/src/commands/project/init.js +4 -68
  130. package/src/commands/project/install-plugin-cmd.js +0 -1
  131. package/src/commands/project/standards.js +1 -62
  132. package/src/commands/project/status.js +0 -53
  133. package/src/commands/project/update.js +31 -239
  134. package/src/commands/project/worktree.js +108 -882
  135. package/src/commands/scaffold/scaffold.js +0 -25
  136. package/src/commands/state/advance.js +14 -85
  137. package/src/commands/state/approve.js +14 -96
  138. package/src/commands/state/archive.js +12 -64
  139. package/src/commands/state/auto-approval.js +2 -5
  140. package/src/commands/state/create.js +22 -109
  141. package/src/commands/state/delete.js +11 -63
  142. package/src/commands/state/finish.js +98 -475
  143. package/src/commands/state/gate-check.js +22 -132
  144. package/src/commands/state/gate-decision.js +3 -39
  145. package/src/commands/state/handoff.js +3 -30
  146. package/src/commands/state/list.js +14 -84
  147. package/src/commands/state/report-check.js +8 -58
  148. package/src/commands/state/retype.js +4 -26
  149. package/src/commands/validation/index.js +0 -3
  150. package/src/commands/validation/mutate.js +2 -5
  151. package/src/commands/validation/validate-feature.js +5 -94
  152. package/src/commands/validation/verify.js +15 -98
  153. package/src/core/paths/output-schema.js +2 -101
  154. package/src/core/state/feature-identity.js +1 -25
  155. package/src/core/state/gate-order.js +0 -27
  156. package/src/core/state/lifecycle-audit.js +8 -100
  157. package/src/core/state/state-manager.js +22 -304
  158. package/src/core/templates/template-registry.js +2 -97
  159. package/src/core/templates/template-renderer.js +13 -93
  160. package/src/core/templates/template-seed.js +0 -1
  161. package/src/core/work-types.js +1 -36
  162. package/src/dashboard/app/App.jsx +1 -8
  163. package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
  164. package/src/dashboard/app/components/CommandPalette.jsx +0 -7
  165. package/src/dashboard/app/components/DetailPanel.jsx +1 -5
  166. package/src/dashboard/app/components/EntityLink.jsx +0 -12
  167. package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
  168. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
  169. package/src/dashboard/app/components/Markdown.jsx +0 -9
  170. package/src/dashboard/app/components/PhaseRail.jsx +0 -11
  171. package/src/dashboard/app/components/SearchFilter.jsx +0 -4
  172. package/src/dashboard/app/components/Sidebar.jsx +0 -5
  173. package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
  174. package/src/dashboard/app/hooks/usePref.js +1 -5
  175. package/src/dashboard/app/lib/format.js +0 -2
  176. package/src/dashboard/app/lib/phases.js +1 -5
  177. package/src/dashboard/app/lib/project.js +0 -12
  178. package/src/dashboard/app/pages/AgentMap.jsx +1 -8
  179. package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
  180. package/src/dashboard/app/pages/Features.jsx +1 -8
  181. package/src/dashboard/app/pages/Guide.jsx +0 -1
  182. package/src/dashboard/app/pages/HookInspector.jsx +2 -23
  183. package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
  184. package/src/dashboard/app/pages/Portfolio.jsx +0 -1
  185. package/src/dashboard/app/pages/RulesMap.jsx +1 -16
  186. package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
  187. package/src/dashboard/app/pages/Standards.jsx +2 -17
  188. package/src/dashboard/scanner/activity-scanner.js +10 -69
  189. package/src/dashboard/scanner/agents-scanner.js +1 -23
  190. package/src/dashboard/scanner/docs-scanner.js +0 -6
  191. package/src/dashboard/scanner/feature-status.js +2 -5
  192. package/src/dashboard/scanner/features-scanner.js +10 -66
  193. package/src/dashboard/scanner/hooks-scanner.js +5 -28
  194. package/src/dashboard/scanner/index.js +0 -4
  195. package/src/dashboard/scanner/portfolio.js +5 -29
  196. package/src/dashboard/scanner/project-api.js +8 -54
  197. package/src/dashboard/scanner/rules-scanner.js +1 -2
  198. package/src/dashboard/scanner/skills-scanner.js +2 -12
  199. package/src/dashboard/scanner/standards-scanner.js +2 -6
  200. package/src/dashboard/vite-plugin-morph-data.js +5 -19
  201. package/src/lib/ai/ai-checks.js +7 -52
  202. package/src/lib/ai/ai-project-scan.js +14 -108
  203. package/src/lib/ai/code-audit.js +7 -63
  204. package/src/lib/ai/pin-drift.js +8 -51
  205. package/src/lib/ai/registry-audit.js +11 -90
  206. package/src/lib/ci/ci-verify.js +4 -19
  207. package/src/lib/ci/github-ci.js +9 -37
  208. package/src/lib/ci/workflow-scaffold.js +2 -10
  209. package/src/lib/detectors/claude-config-detector.js +13 -74
  210. package/src/lib/detectors/index.js +4 -50
  211. package/src/lib/e2e/compose-env.js +5 -66
  212. package/src/lib/e2e/stack-teardown.js +6 -40
  213. package/src/lib/generators/architecture-scanner.js +18 -156
  214. package/src/lib/generators/handoff-generator.js +9 -53
  215. package/src/lib/generators/scaffold-generator.js +5 -101
  216. package/src/lib/generators/settings-generator.js +4 -37
  217. package/src/lib/git/branch-classifier.js +11 -84
  218. package/src/lib/git/default-branch.js +14 -169
  219. package/src/lib/git/evidence-staleness.js +1 -0
  220. package/src/lib/git/gh.js +2 -17
  221. package/src/lib/git/pending-prs.js +6 -37
  222. package/src/lib/git/pr-files.js +3 -28
  223. package/src/lib/git/pr-index.js +3 -34
  224. package/src/lib/git/tree-fingerprint.js +3 -15
  225. package/src/lib/git/tree-state.js +6 -33
  226. package/src/lib/graph/god-nodes.js +2 -22
  227. package/src/lib/graph/graph-health.js +3 -24
  228. package/src/lib/graph/graph-loader.js +5 -56
  229. package/src/lib/graph/graphify-runtime.js +7 -37
  230. package/src/lib/graph/graphifyignore.js +8 -74
  231. package/src/lib/graph/sanitize.js +16 -136
  232. package/src/lib/heavy/cleanup.js +1 -1
  233. package/src/lib/installers/effects.js +8 -93
  234. package/src/lib/installers/mcp-installer.js +9 -169
  235. package/src/lib/installers/memory-installer.js +4 -27
  236. package/src/lib/installers/templates-reconcile.js +5 -60
  237. package/src/lib/mutation/dotnet-introspection.js +20 -106
  238. package/src/lib/mutation/subset.js +1 -4
  239. package/src/lib/plan/artifact-vocabulary.js +5 -25
  240. package/src/lib/plan/checks.js +5 -31
  241. package/src/lib/plan/plan-parser.js +1 -6
  242. package/src/lib/projects/registry.js +7 -44
  243. package/src/lib/review-reports.js +5 -23
  244. package/src/lib/risk-detector.js +39 -173
  245. package/src/lib/specs/adr-index.js +6 -34
  246. package/src/lib/specs/frontmatter.js +2 -15
  247. package/src/lib/specs/gfm-table.js +1 -7
  248. package/src/lib/specs/revocations.js +1 -14
  249. package/src/lib/specs/screen-checklist.js +47 -124
  250. package/src/lib/stack/dotnet-build-ignores.js +2 -6
  251. package/src/lib/stack/stack-profile.js +1 -28
  252. package/src/lib/stack/stack-scan.js +1 -8
  253. package/src/lib/stack-filter.js +4 -27
  254. package/src/lib/standards/registry.js +1 -7
  255. package/src/lib/standards/sections.js +2 -13
  256. package/src/lib/standards/verified-against.js +19 -191
  257. package/src/lib/tasks/eval-node.js +21 -169
  258. package/src/lib/tasks/linux-runner.js +18 -142
  259. package/src/lib/tasks/test-nodes.js +43 -240
  260. package/src/lib/tasks/test-runner.js +43 -341
  261. package/src/lib/telemetry/metrics.js +7 -36
  262. package/src/lib/telemetry/telemetry-reader.js +13 -125
  263. package/src/lib/telemetry/verify-event.js +2 -30
  264. package/src/lib/validators/code-budget/code-budget-validator.js +3 -43
  265. package/src/lib/validators/code-budget/index.js +0 -7
  266. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
  267. package/src/lib/validators/contracts/index.js +0 -6
  268. package/src/lib/validators/nextjs/index.js +0 -5
  269. package/src/lib/validators/nextjs/next-component-validator.js +4 -81
  270. package/src/lib/validators/packages/index.js +0 -6
  271. package/src/lib/validators/packages/package-validator.js +6 -56
  272. package/src/lib/validators/reuse/index.js +0 -8
  273. package/src/lib/validators/reuse/reuse-validator.js +11 -101
  274. package/src/lib/validators/reuse/symbol-scanner.js +6 -62
  275. package/src/lib/validators/traceability-check.js +5 -63
  276. package/src/lib/validators/validation-runner.js +20 -125
  277. package/src/lib/validators/verify-runner.js +52 -358
  278. package/src/lib/work-type-detector.js +5 -67
  279. package/src/lib/worktree/docker-audit.js +17 -136
  280. package/src/lib/worktree/env-allocator.js +10 -98
  281. package/src/lib/worktree/feature-truth.js +12 -115
  282. package/src/lib/worktree/finish-backup.js +32 -242
  283. package/src/lib/worktree/fleet-scanner.js +13 -95
  284. package/src/lib/worktree/ignored-triage.js +15 -91
  285. package/src/lib/worktree/vscode-settings.js +2 -3
  286. package/src/lib/worktree/worktree-audit.js +21 -213
  287. package/src/lib/worktree/worktree-include.js +3 -5
  288. package/src/scripts/global-install.js +3 -9
  289. package/src/scripts/install-plugin.js +1 -13
  290. package/src/scripts/setup-infra.js +17 -147
  291. package/src/utils/agents-installer.js +28 -275
  292. package/src/utils/banner.js +4 -25
  293. package/src/utils/claude-md-injector.js +1 -37
  294. package/src/utils/claude-settings-manager.js +1 -52
  295. package/src/utils/file-copier.js +11 -95
  296. package/src/utils/hooks-installer.js +21 -298
  297. package/src/utils/skills-installer.js +4 -101
  298. package/src/utils/version-checker.js +4 -83
  299. package/framework/commands/morph-proposal.md +0 -9
  300. package/src/commands/utils/index.js +0 -3
  301. package/src/lib/detectors/design-system-detector.js +0 -188
  302. package/src/lib/generators/design-system-generator.js +0 -508
  303. package/src/lib/specs/old-format.js +0 -68
  304. package/src/utils/agent-id-mapper.js +0 -11
@@ -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';