@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
@@ -35,16 +35,10 @@ import { reconcileLegacyTemplatesCopy } from '../../lib/installers/templates-rec
35
35
  import { createEffects } from '../../lib/installers/effects.js';
36
36
  import { applyEditorSettings } from '../../lib/worktree/vscode-settings.js';
37
37
  import { registerProject } from '../../lib/projects/registry.js';
38
- import { findOldFormatFeatures, oldFormatGuardLines } from '../../lib/specs/old-format.js';
39
38
  import {
40
39
  writeGraphifyignore, describeOutdatedIgnore, graphifyignoreTracked,
41
40
  } from '../../lib/graph/graphifyignore.js';
42
41
 
43
- /**
44
- * Backup user's config.json before cleaning
45
- * @param {string} morphPath - Path to .morph directory
46
- * @returns {Promise<object|null>} Backup of config.json or null
47
- */
48
42
  async function backupUserConfig(morphPath) {
49
43
  try {
50
44
  const configPath = join(morphPath, 'config', 'config.json');
@@ -53,40 +47,18 @@ async function backupUserConfig(morphPath) {
53
47
  return config;
54
48
  }
55
49
  } catch (error) {
56
- // If backup fails, return null and continue
57
50
  }
58
51
  return null;
59
52
  }
60
53
 
61
- /**
62
- * Os DEZ diretórios que `cleanFrameworkDirs` apaga por inteiro, sem condição.
63
- *
64
- * O número está pinado à mão em `test/commands/update-dry-run.test.js`
65
- * (`DIRETORIOS_APAGADOS_POR_INTEIRO`), com os dez caminhos escritos: derivar a
66
- * expectativa desta função deixaria a suíte cega a uma entrada removida.
67
- *
68
- * Exportado porque o relatório do `--dry-run` é obrigado a nomeá-los: "update"
69
- * não sugere a ninguém que `.claude/skills/` e `.claude/agents/` são apagados
70
- * inteiros antes da reinstalação.
71
- *
72
- * @param {string} morphPath
73
- * @param {string} targetPath
74
- * @returns {string[]}
75
- */
76
54
  export function frameworkDirsToClean(morphPath, targetPath) {
77
55
  const claudeDir = join(targetPath, '.claude');
78
56
  return [
79
- // NOTE: framework/templates is intentionally NOT in this list (R3 perf
80
- // plan, Task 2). It's no longer copied/refreshed here — see
81
- // reconcileLegacyTemplatesCopy() below, which decides per-run whether a
82
- // pre-existing local copy is safe to remove or must be preserved as a
83
- // user override. Blindly deleting it here (the old behavior) destroyed
84
- // any customization on every single `update` run, override or not.
57
+ // framework/templates is left out on purpose: reconcileLegacyTemplatesCopy() decides whether
58
+ // a local copy is a user override to preserve.
85
59
  join(morphPath, 'framework', 'standards'),
86
60
  join(morphPath, 'framework', 'hooks'),
87
61
  join(morphPath, 'framework', 'schemas'),
88
- // Não é mais instalado: fica na lista para apagar o de instalações antigas.
89
- join(morphPath, 'framework', 'rubrics'),
90
62
  join(morphPath, 'framework', 'evals'),
91
63
  join(morphPath, 'config'),
92
64
  join(claudeDir, 'commands'),
@@ -96,14 +68,6 @@ export function frameworkDirsToClean(morphPath, targetPath) {
96
68
  ];
97
69
  }
98
70
 
99
- /**
100
- * Emite (e, fora do dry, executa) a remoção recursiva de um diretório.
101
- *
102
- * Um efeito por arquivo, mais um pelo diretório. É a varredura que a §5.2 do
103
- * spec exige, e é dela que sai a contagem "N arquivos" do relatório.
104
- *
105
- * @returns {number} arquivos varridos
106
- */
107
71
  function sweepRemove(dir, fx) {
108
72
  let entries;
109
73
  try {
@@ -126,23 +90,8 @@ function sweepRemove(dir, fx) {
126
90
  }
127
91
 
128
92
  /**
129
- * Clean framework directories but preserve .morph/project/.
130
- *
131
- * **A armadilha que esta função resolve (decisions.md D6).** Ela apaga
132
- * exatamente os destinos que os laços de limpeza de órfão DEPOIS varrem
133
- * (`skills-installer.js`, `agents-installer.js`, `installRulesForStack`).
134
- * Numa execução real esses laços sempre encontram diretório vazio e não
135
- * apagam nada. Num `--dry-run` ingênuo nada foi apagado, eles encontrariam o
136
- * diretório POPULADO e o relatório preveria remoções que a execução real
137
- * nunca faz — errado na direção que assusta o usuário. Como o clean é
138
- * incondicional e total, a previsão correta é "tudo que hoje está sob estes
139
- * dez é apagado", e os laços de órfão ficam calados em dry: são provadamente
140
- * subsumidos.
141
- *
142
- * @param {string} morphPath - Path to .morph directory
143
- * @param {string} targetPath - Project root path
144
- * @param {ReturnType<import('../../lib/installers/effects.js').createEffects>} fx
145
- * @returns {Array<{ dir: string, files: number }>} o que foi (ou seria) apagado
93
+ * Os laços de órfão depois varrem estes mesmos destinos já vazios: em dry eles ficam calados,
94
+ * senão o relatório preveria remoções que a execução real nunca faz.
146
95
  */
147
96
  function cleanFrameworkDirs(morphPath, targetPath, fx) {
148
97
  const cleaned = [];
@@ -152,31 +101,20 @@ function cleanFrameworkDirs(morphPath, targetPath, fx) {
152
101
  cleaned.push({ dir, files: sweepRemove(dir, fx) });
153
102
  }
154
103
  } catch (error) {
155
- // Continue even if cleanup fails for one directory
156
104
  }
157
105
  }
158
106
  return cleaned;
159
107
  }
160
108
 
161
- /**
162
- * Restore user's config.json after reinstallation
163
- * @param {string} morphPath - Path to .morph directory
164
- * @param {string} targetPath - Project root path
165
- * @param {object|null} configBackup - Backup of config.json
166
- * @param {string} currentVersion - Current CLI version
167
- * @param {ReturnType<import('../../lib/installers/effects.js').createEffects>} fx
168
- */
169
109
  function restoreUserConfig(morphPath, targetPath, configBackup, currentVersion, fx) {
170
110
  const configDir = join(morphPath, 'config');
171
111
  fx.ensureDir(configDir);
172
112
  const configPath = join(configDir, 'config.json');
173
113
 
174
114
  if (configBackup) {
175
- // Preserve user data but update framework version
176
115
  configBackup.frameworkVersion = currentVersion;
177
116
  fx.writeJson(configPath, configBackup);
178
117
  } else {
179
- // Create basic config if none existed
180
118
  const dirName = targetPath.split(/[\\/]/).pop();
181
119
  const defaultConfig = {
182
120
  framework: 'global',
@@ -190,15 +128,7 @@ function restoreUserConfig(morphPath, targetPath, configBackup, currentVersion,
190
128
  }
191
129
  }
192
130
 
193
- /**
194
- * `.morph/state.json` já está no índice do git?
195
- *
196
- * SONDA, separada da mutação de propósito: sondar é ler, e ler acontece nos
197
- * dois modos. É o que faz previsão e execução concordarem sobre este efeito.
198
- *
199
- * @param {string} targetPath
200
- * @returns {boolean}
201
- */
131
+ /** Sonda separada da mutação: ler acontece nos dois modos, e é o que faz previsão e execução concordarem. */
202
132
  export function stateJsonIsTracked(targetPath) {
203
133
  try {
204
134
  const isRepo = spawnSync('git', ['rev-parse', '--is-inside-work-tree'], {
@@ -215,38 +145,14 @@ export function stateJsonIsTracked(targetPath) {
215
145
  }
216
146
  }
217
147
 
218
- /**
219
- * Untrack an already-committed `.morph/state.json` from git.
220
- *
221
- * Before v5's per-feature model, `.morph/state.json` was committed. Adding it to
222
- * `.gitignore` does NOT stop git from tracking a file already in the index — so on
223
- * exactly the repos that had the P1 sweep problem, `git commit -a` would keep
224
- * staging state.json. This runs `git rm --cached` once so the next commit drops it.
225
- * The working-tree file is left in place (it's rebuilt from feature.json on load).
226
- *
227
- * Mildly destructive (mutates the git index), but no data is lost: state.json is a
228
- * derivable index. Fail-open — a non-git project or any git error is a silent no-op.
229
- *
230
- * @param {string} targetPath - Project root
231
- * @param {ReturnType<import('../../lib/installers/effects.js').createEffects>} fx
232
- * @returns {boolean} true if state.json was untracked, false otherwise
233
- */
234
148
  export function untrackCommittedStateJson(targetPath, fx = createEffects({ dryRun: false })) {
235
- if (!stateJsonIsTracked(targetPath)) return false; // not tracked — nothing to migrate
149
+ if (!stateJsonIsTracked(targetPath)) return false;
236
150
  return fx.gitIndex('rm --cached --quiet', '.morph/state.json', { cwd: targetPath });
237
151
  }
238
152
 
239
153
  /**
240
- * Os dois registros de versão que um projeto morph-spec carrega.
241
- *
242
- * `.morph/.morphversion → morphVersion` é o que o `update` LÊ para decidir.
243
- * `config.json → frameworkVersion` é o que o `update` ESCREVE e nunca lê para
244
- * decidir coisa alguma — e no MORPH_OS os dois divergem por um major inteiro
245
- * (7.5.0 contra 6.0.0). Um freio calibrado no campo errado recusaria (ou
246
- * liberaria) com base num número que ninguém mantém.
247
- *
248
- * @param {string} targetPath
249
- * @returns {Promise<{ morphVersion: string|null, frameworkVersion: string|null, diverged: boolean }>}
154
+ * `.morphversion` é o que o `update` LÊ para decidir; `config.json → frameworkVersion` só é
155
+ * escrito e pode divergir por um major inteiro.
250
156
  */
251
157
  export async function readVersionRegisters(targetPath) {
252
158
  let morphVersion = null;
@@ -254,30 +160,17 @@ export async function readVersionRegisters(targetPath) {
254
160
  try {
255
161
  const v = await readJson(join(targetPath, '.morph', '.morphversion'));
256
162
  morphVersion = typeof v?.morphVersion === 'string' ? v.morphVersion : null;
257
- } catch { /* ausente ou ilegível — indeterminável, e isso é um dado */ }
163
+ } catch {}
258
164
  try {
259
165
  const c = await readJson(join(targetPath, '.morph', 'config', 'config.json'));
260
166
  frameworkVersion = typeof c?.frameworkVersion === 'string' ? c.frameworkVersion : null;
261
- } catch { /* idem */ }
167
+ } catch {}
262
168
 
263
169
  const diverged = Boolean(morphVersion && frameworkVersion && morphVersion !== frameworkVersion);
264
170
  return { morphVersion, frameworkVersion, diverged };
265
171
  }
266
172
 
267
- /**
268
- * As linhas da recusa do freio de major.
269
- *
270
- * Separada do comando para ser testável sem rodar o `update`, e porque a
271
- * mensagem carrega três obrigações: a versão instalada, a versão de destino e
272
- * **o registro de onde a versão instalada foi lida**. Quando os dois registros
273
- * discordam, imprime OS DOIS e diz qual decidiu — escolher em silêncio é
274
- * acertar metade das vezes e nunca saber qual metade.
275
- *
276
- * @param {{ refuse: boolean, reason: string|null, jump: number|null }} verdict
277
- * @param {{ morphVersion: string|null, frameworkVersion: string|null, diverged: boolean }} registers
278
- * @param {string} targetVersion
279
- * @returns {string[]}
280
- */
173
+ /** Quando os dois registros discordam, imprime OS DOIS e diz qual decidiu. */
281
174
  export function majorGuardLines(verdict, registers, targetVersion) {
282
175
  const lines = [];
283
176
  if (verdict.reason === 'unknown-installed') {
@@ -299,7 +192,6 @@ export function majorGuardLines(verdict, registers, targetVersion) {
299
192
  return lines;
300
193
  }
301
194
 
302
- /** Rótulo humano de cada verbo, para agrupar o relatório por natureza do efeito. */
303
195
  const VERB_GROUPS = {
304
196
  remove: 'Apagar',
305
197
  ensureDir: 'Criar diretório',
@@ -318,20 +210,8 @@ function isUnder(path, root) {
318
210
  return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel);
319
211
  }
320
212
 
321
- /**
322
- * ONDE um efeito age.
323
- *
324
- * Para quase todo verbo é o próprio `target`. Para `spawn` NÃO é: o `target`
325
- * de um spawn é o binário (`node`), e classificar por ele esconderia
326
- * exatamente o efeito que a §5.3 obriga a nomear — `generate-refs.js`
327
- * escrevendo `framework/hooks/shared/phase-utils.js` DENTRO do pacote da CLI.
328
- *
329
- * O script sai de `effect.args`, o **array**, nunca de um split do `detail`
330
- * por espaço: o caminho real deste pacote é
331
- * `R:\Polymorphism Tech\repos\...` — um espaço no meio, e o split devolvia
332
- * `Tech\repos\...\generate-refs.js`, que não é subcaminho de nada. O efeito ia
333
- * calado para o grupo errado. Medido rodando o comando de verdade.
334
- */
213
+ // O `target` de um spawn é o binário (`node`): classifica pelo script em `effect.args`, o array —
214
+ // um split do `detail` por espaço quebra em caminho com espaço.
335
215
  function effectLocation(effect) {
336
216
  if (effect.verb !== 'spawn') return effect.target;
337
217
  const args = Array.isArray(effect.args) ? effect.args : [];
@@ -339,26 +219,12 @@ function effectLocation(effect) {
339
219
  return script || effect.target;
340
220
  }
341
221
 
342
- /**
343
- * Imprime o relatório de efeitos.
344
- *
345
- * Três grupos ganham nome próprio, porque o usuário não os adivinharia da
346
- * palavra "update": o que é apagado por INTEIRO antes da reinstalação, o que
347
- * acontece FORA do projeto, e o que é escrito DENTRO do pacote da CLI (que num
348
- * `npm link` de desenvolvimento é o próprio repositório do framework).
349
- *
350
- * @param {Array} effects - saída de `fx.list()`
351
- * @param {object} context
352
- */
353
222
  export function renderEffectsReport(effects, context) {
354
223
  const { targetPath, packageRoot, dryRun, cleaned = [], notices = [] } = context;
355
224
  const globalClaudeDir = join(homedir(), '.claude');
356
225
 
357
226
  logger.blank();
358
- // O cabeçalho não pode dizer "nem executado": em dry o `spawn` do
359
- // `generate-refs.js` RODA, na variante `--check`, que verifica e não escreve.
360
- // Prometer silêncio total e rodar um processo é mentir num detalhe pequeno —
361
- // e é o tipo de detalhe pequeno que faz o usuário parar de acreditar no resto.
227
+ // Não diz "nem executado": em dry o spawn de `generate-refs.js --check` roda.
362
228
  logger.info(dryRun
363
229
  ? 'Relatório do --dry-run — nada foi escrito, apagado nem removido '
364
230
  + '(o único processo executado é a variante `--check` do gerador, que só verifica):'
@@ -420,7 +286,7 @@ export function renderEffectsReport(effects, context) {
420
286
  + `${outside.length} fora do projeto, ${inPackage.length} dentro do pacote da CLI.`);
421
287
  logger.blank();
422
288
 
423
- // Risco conhecido, reportado em vez de escondido (decisions.md D9): o backup
289
+ // Risco conhecido, reportado em vez de escondido: o backup
424
290
  // de `config.json` só existe em memória durante a execução.
425
291
  logger.dim('Nota: durante uma execução real o backup de `.morph/config/config.json` existe apenas '
426
292
  + 'em memória — uma falha no meio do caminho deixa o arquivo apagado. Follow-up nomeado.');
@@ -438,11 +304,7 @@ export async function updateCommand(options = {}) {
438
304
 
439
305
  logger.header(dryRun ? 'MORPH-SPEC Update (--dry-run)' : 'MORPH-SPEC Update');
440
306
 
441
- // ── Versões primeiro, e NADA de mutação antes do freio ───────────────────
442
- // O passo de criação de diretórios ficava aqui em cima e criava `.morph/`
443
- // ANTES de qualquer guarda. Recusar depois de mutar é meio-estado — o mesmo
444
- // defeito que o `finish --merge` pagou uma vez na v8.24.3 ("valida a raiz
445
- // ANTES de arquivar").
307
+ // Nenhuma mutação antes do freio: recusar depois de mutar deixa meio-estado.
446
308
  const spinner = ora('Checking versions...').start();
447
309
  // `cliCheck` é injetável para que o canário de igualdade dry × real não
448
310
  // dependa do registro npm; a CLI nunca o passa.
@@ -451,7 +313,6 @@ export async function updateCommand(options = {}) {
451
313
  const registers = await readVersionRegisters(targetPath);
452
314
  spinner.stop();
453
315
 
454
- // Display version info
455
316
  logger.blank();
456
317
  logger.info('Version Status:');
457
318
  logger.dim(` CLI version: ${cliCheck.current}`);
@@ -468,10 +329,7 @@ export async function updateCommand(options = {}) {
468
329
  }
469
330
  logger.blank();
470
331
 
471
- // If CLI is outdated, stop and instruct user.
472
- // O `--dry-run` PASSA POR CIMA desta saída antecipada: bloquear o relatório
473
- // justamente quando o usuário está decidindo se atualiza seria inútil. O
474
- // aviso vira a primeira linha do relatório.
332
+ // O `--dry-run` passa por cima desta saída antecipada: o aviso vira a 1ª linha do relatório.
475
333
  if (cliCheck.isOutdated && cliCheck.latest) {
476
334
  if (dryRun) {
477
335
  notices.push(`A CLI local está desatualizada (${cliCheck.current} → ${cliCheck.latest}). `
@@ -501,7 +359,6 @@ export async function updateCommand(options = {}) {
501
359
  }
502
360
  }
503
361
 
504
- // ── Freio de major, ANTES de qualquer mutação ────────────────────────────
505
362
  const needsInitialization = !(await pathExists(morphPath));
506
363
  const targetVersion = cliCheck.current ?? getInstalledCLIVersion();
507
364
  const verdict = needsInitialization
@@ -515,8 +372,7 @@ export async function updateCommand(options = {}) {
515
372
  if (force) {
516
373
  notices.push(`Freio de major DISPENSADO por \`--force\`: ${lines[0]}`);
517
374
  } else if (dryRun) {
518
- // UC-27 A1: em dry ninguém precisa ser protegido de nada, e esconder o
519
- // relatório seria esconder justamente a informação que motivou o comando.
375
+ // Em dry não há recusa: esconder o relatório esconderia a informação que motivou o comando.
520
376
  notices.push(...lines);
521
377
  } else {
522
378
  logger.error(lines[0]);
@@ -532,22 +388,6 @@ export async function updateCommand(options = {}) {
532
388
  + `Quem decide é o primeiro.`);
533
389
  }
534
390
 
535
- // `--force` não dispensa: esta CLI não sabe conduzir essas features.
536
- const oldFormat = needsInitialization ? [] : findOldFormatFeatures(targetPath);
537
- if (oldFormat.length) {
538
- const lines = oldFormatGuardLines(oldFormat, targetPath);
539
- if (dryRun) {
540
- notices.push(...lines);
541
- } else {
542
- logger.error(lines[0]);
543
- for (const l of lines.slice(1)) logger.dim(l);
544
- logger.blank();
545
- process.exitCode = 1;
546
- return { refused: true, reason: 'old-format-features', features: oldFormat, effects: fx.list() };
547
- }
548
- }
549
-
550
- // ── A partir daqui, tudo passa pelo coletor ──────────────────────────────
551
391
  if (needsInitialization) {
552
392
  logger.warn('MORPH not fully initialized. Creating basic structure...');
553
393
  fx.ensureDir(join(morphPath, 'context'));
@@ -557,19 +397,16 @@ export async function updateCommand(options = {}) {
557
397
  logger.dim(' ✓ Created .morph directory structure');
558
398
  }
559
399
 
560
- // Proceed with update
561
400
  const updateSpinner = ora(dryRun ? 'Prevendo efeitos...' : 'Updating MORPH-SPEC...').start();
562
401
  let cleaned = [];
563
402
 
564
403
  try {
565
- // Backup user config before cleaning
566
404
  updateSpinner.text = 'Backing up user configuration...';
567
405
  const configBackup = await backupUserConfig(morphPath);
568
406
  if (configBackup) {
569
407
  logger.dim(' ✓ User config backed up');
570
408
  }
571
409
 
572
- // Clean framework directories (preserve .morph/project/)
573
410
  updateSpinner.text = 'Cleaning old framework files...';
574
411
  cleaned = cleanFrameworkDirs(morphPath, targetPath, fx);
575
412
  logger.dim(' ✓ Old framework files removed');
@@ -577,9 +414,6 @@ export async function updateCommand(options = {}) {
577
414
  const updateTemplates = !options.standards || options.templates;
578
415
  const updateStandards = !options.templates || options.standards;
579
416
 
580
- // Templates are no longer copied to .morph/framework/templates/ (Task 2,
581
- // R3 perf plan) — see reconcileLegacyTemplatesCopy()'s doc comment for
582
- // why. Only reconcile a leftover copy from a pre-R3 install.
583
417
  let templatesReconcile = 'absent';
584
418
  if (updateTemplates) {
585
419
  updateSpinner.text = 'Checking for a legacy templates copy...';
@@ -587,7 +421,6 @@ export async function updateCommand(options = {}) {
587
421
  templatesReconcile = reconcileLegacyTemplatesCopy(templatesDest, projectCheck.current, fx);
588
422
  }
589
423
 
590
- // Update standards
591
424
  if (updateStandards) {
592
425
  updateSpinner.text = 'Updating standards...';
593
426
  const standardsSrc = join(contentDir, 'framework', 'standards');
@@ -595,7 +428,6 @@ export async function updateCommand(options = {}) {
595
428
  fx.copyDir(standardsSrc, standardsDest);
596
429
  }
597
430
 
598
- // Update hooks (runtime hook scripts under .morph/framework/hooks/)
599
431
  updateSpinner.text = 'Updating hooks...';
600
432
  const hooksSrc = join(__dirname, '..', '..', '..', 'framework', 'hooks');
601
433
  const hooksDest = join(morphPath, 'framework', 'hooks');
@@ -603,7 +435,6 @@ export async function updateCommand(options = {}) {
603
435
  fx.copyDir(hooksSrc, hooksDest);
604
436
  }
605
437
 
606
- // Update agents.json (sourced from framework/ — canonical single source of truth)
607
438
  updateSpinner.text = 'Updating agents configuration...';
608
439
  const agentsSrc = join(__dirname, '..', '..', '..', 'framework', 'agents.json');
609
440
  const agentsDest = join(morphPath, 'framework', 'agents.json');
@@ -611,7 +442,6 @@ export async function updateCommand(options = {}) {
611
442
  fx.copyFile(agentsSrc, agentsDest);
612
443
  }
613
444
 
614
- // Paridade com setup-infra 7c/8b/8c/8d: sem isto o charter e os evals instalados ficam velhos.
615
445
  updateSpinner.text = 'Updating charter, schemas and evals...';
616
446
  const canonicalFrameworkDir = join(__dirname, '..', '..', '..', 'framework');
617
447
  for (const file of ['MORPH.md', 'phases.json']) {
@@ -626,18 +456,13 @@ export async function updateCommand(options = {}) {
626
456
  fx.copyDir(src, join(morphPath, 'framework', dir));
627
457
  }
628
458
  }
629
- // CLI.md vive em framework/docs/ na fonte mas instala achatado em
630
- // .morph/framework/CLI.md (o CLAUDE.md residente aponta para lá) —
631
- // por isso não entra no loop de nomes acima.
459
+ // CLI.md instala achatado em .morph/framework/CLI.md, por isso fica fora do loop acima.
632
460
  const cliMdSrc = join(canonicalFrameworkDir, 'docs', 'CLI.md');
633
461
  if (await pathExists(cliMdSrc)) {
634
462
  fx.copyFile(cliMdSrc, join(morphPath, 'framework', 'CLI.md'));
635
463
  }
636
464
 
637
- // Regenerate derived files (phase-utils.js, skill output tables) from output-schema.js.
638
- // Escreve DENTRO do pacote da CLI — num `npm link` de desenvolvimento, no
639
- // próprio repositório do framework. Em dry a variante `--check` roda no
640
- // lugar: ela verifica e não escreve.
465
+ // Escreve DENTRO do pacote da CLI (o repositório do framework num `npm link`); em dry roda `--check`.
641
466
  updateSpinner.text = 'Syncing generated refs...';
642
467
  try {
643
468
  const generateScript = join(__dirname, '..', '..', '..', 'scripts', 'generate-refs.js');
@@ -650,24 +475,18 @@ export async function updateCommand(options = {}) {
650
475
  }
651
476
  }
652
477
  } catch {
653
- // Non-fatal: generator unavailable or files already in sync
654
478
  }
655
479
 
656
- // Update .claude commands and skills
657
- // Source: framework/ (canonical for all stacks)
658
480
  updateSpinner.text = 'Setting up Claude Code integration...';
659
481
  const frameworkDir = join(__dirname, '..', '..', '..', 'framework');
660
482
  const claudeDest = join(targetPath, '.claude');
661
483
 
662
- // Compute project tags for stack-aware installation of skills, agents, and rules.
663
- // MUST come from the pre-clean backup: cleanFrameworkDirs() deleted .morph/config/
664
- // above and restoreUserConfig() only runs at the end — re-reading the file here
665
- // yields [] and silently downgrades the whole install to universal-only assets
666
- // (the ".claude/agents/ only has morph-verifier.md after update" bug).
484
+ // MUST come from the pre-clean backup: cleanFrameworkDirs() deleted .morph/config/ and re-reading
485
+ // it here yields [], silently downgrading the install to universal-only assets.
667
486
  let projectTags = [];
668
487
  try {
669
488
  if (configBackup) projectTags = getProjectTags(configBackup);
670
- } catch { /* ignore — malformed config */ }
489
+ } catch {}
671
490
 
672
491
  let commandsCopied = false;
673
492
  const commandsSrc = join(frameworkDir, 'commands');
@@ -680,18 +499,13 @@ export async function updateCommand(options = {}) {
680
499
  logger.warn(' ⚠ framework/commands/ source missing — commands not updated');
681
500
  }
682
501
 
683
- // Sync morph skills to .claude/skills/ for native Claude Code discovery
684
502
  updateSpinner.text = 'Syncing morph skills to .claude/skills/...';
685
503
  const skillsResult = await installSkills(targetPath, { projectTags, fx });
686
504
 
687
- // Sync native subagents to .claude/agents/
688
505
  updateSpinner.text = 'Syncing agents to .claude/agents/...';
689
506
  const agentsResult = await installAgents(targetPath, frameworkDir, { projectTags, fx });
690
507
  await installDomainAgents(targetPath, frameworkDir, { projectTags, fx });
691
508
 
692
- // Sync path-scoped rules to .claude/rules/ — stack-filtered with orphan
693
- // cleanup (parity with setup-infra), so `update` is a true superset of
694
- // `setup-infra --reinstall` and also covers project.stack changes.
695
509
  updateSpinner.text = 'Syncing rules to .claude/rules/...';
696
510
  const rulesSrc = join(frameworkDir, 'rules');
697
511
  const rulesDest = join(claudeDest, 'rules');
@@ -701,8 +515,6 @@ export async function updateCommand(options = {}) {
701
515
  rulesResult = installRulesForStack(rulesSrc, rulesDest, projectTags, fx);
702
516
  }
703
517
 
704
- // Sync runtime CLAUDE.md to .claude/CLAUDE.md and derive the personas
705
- // block from what installAgents actually installed (managed markers).
706
518
  updateSpinner.text = 'Syncing .claude/CLAUDE.md...';
707
519
  const runtimeSrc = join(frameworkDir, 'CLAUDE.md');
708
520
  const runtimeDest = join(claudeDest, 'CLAUDE.md');
@@ -715,45 +527,34 @@ export async function updateCommand(options = {}) {
715
527
  });
716
528
  }
717
529
 
718
- // Sync statusline globally to ~/.claude/
719
530
  updateSpinner.text = 'Syncing statusline to ~/.claude/...';
720
531
  const HOOKS_SRC = join(__dirname, '..', '..', '..', 'framework', 'hooks', 'claude-code');
721
532
  try {
722
533
  await installGlobalStatusline(HOOKS_SRC, null, { fx });
723
534
  } catch {
724
- // Non-critical: global dir may not be writable in all environments
725
535
  logger.dim(' ⚠ Could not install statusline globally (non-critical)');
726
536
  }
727
537
 
728
- // Update Claude Code hooks in .claude/settings.local.json
729
538
  updateSpinner.text = 'Updating Claude Code hooks...';
730
539
  const hooksResult = await installClaudeHooks(targetPath, { fx });
731
540
 
732
- // Ensure root CLAUDE.md has morph-spec @import (preserves user content)
733
541
  updateSpinner.text = 'Checking CLAUDE.md import...';
734
542
  const claudeMdDest = join(targetPath, 'CLAUDE.md');
735
543
  await injectMorphImport(claudeMdDest, fx);
736
544
 
737
- // Restore user config after framework reinstallation
738
545
  updateSpinner.text = 'Restoring user configuration...';
739
546
  restoreUserConfig(morphPath, targetPath, configBackup, cliCheck.current, fx);
740
547
  logger.dim(' ✓ User config restored with updated framework version');
741
548
 
742
- // Update .gitignore + .gitattributes with current morph rules
743
549
  updateSpinner.text = 'Updating .gitignore + .gitattributes...';
744
550
  await updateGitignore(targetPath, fx);
745
551
  await ensureGitattributes(targetPath, fx);
746
552
 
747
553
  updateSpinner.text = 'Editor excludes for worktrees (.vscode/settings.json)...';
748
- try { applyEditorSettings(targetPath, { fx }); } catch { /* fail-open */ }
749
-
750
- // Migration: a .graphifyignore the previous version generated and nobody
751
- // edited is swapped for the current one when its RULES change (test doubles
752
- // and fixtures back in the graph — a header-only difference is left alone);
753
- // an edited one is left alone, with the change to make. Only an existing
754
- // file — the graph is opt-in, and `graph build` writes it. A tracked file
755
- // reaches worktrees and teammates only once committed: each checkout reads
756
- // its own.
554
+ try { applyEditorSettings(targetPath, { fx }); } catch {}
555
+
556
+ // Swap an unedited generated .graphifyignore when its RULES change (header-only diffs are left);
557
+ // an edited one is left alone with the change printed. Only an existing file: the graph is opt-in.
757
558
  let graphIgnore = null;
758
559
  if (existsSync(join(targetPath, '.graphifyignore'))) {
759
560
  updateSpinner.text = 'Checking .graphifyignore...';
@@ -765,11 +566,8 @@ export async function updateCommand(options = {}) {
765
566
  }
766
567
  }
767
568
 
768
- // Migration: untrack a pre-v5 committed state.json so it stops being swept
769
- // into feature commits. Gitignore alone can't do this for an already-tracked file.
770
569
  const untracked = untrackCommittedStateJson(targetPath, fx);
771
570
 
772
- // Update .morphversion
773
571
  updateSpinner.text = 'Saving version info...';
774
572
  await saveProjectMorphVersion(targetPath, cliCheck.current, fx);
775
573
 
@@ -777,11 +575,9 @@ export async function updateCommand(options = {}) {
777
575
  logger.blank();
778
576
 
779
577
  if (!dryRun) {
780
- // The dashboard's portfolio learns about the project here (fail-open;
781
- // outside the project, so not an effect of the update itself).
578
+ // Outside the project, so not an effect of the update itself.
782
579
  registerProject(targetPath, 'update');
783
580
 
784
- // Show version change
785
581
  if (projectCheck.current && projectCheck.current !== cliCheck.current) {
786
582
  logger.success(`Updated: ${projectCheck.current} → ${cliCheck.current}`);
787
583
  } else if (!projectCheck.current) {
@@ -802,10 +598,8 @@ export async function updateCommand(options = {}) {
802
598
  }
803
599
  }
804
600
 
805
- // Counts make a silent stack-filter downgrade visible at a glance —
806
- // a dotnet project reporting "1 persona" is a red flag, not a success.
807
- // Display only compact tags: project.integrations entries are long
808
- // free-text descriptions and would flood the summary line.
601
+ // Counts make a silent stack-filter downgrade visible (a dotnet project with "1 persona");
602
+ // only compact tags, since integrations are long free text.
809
603
  const compactTags = projectTags.filter(t => !t.includes(' '));
810
604
  const tagsLabel = compactTags.length > 0 ? compactTags.join('+') : 'universal only';
811
605
  logger.dim(` ✓ .claude/settings.local.json (${hooksResult.installed} hooks installed)`);
@@ -827,9 +621,7 @@ export async function updateCommand(options = {}) {
827
621
  logger.info('Your config.json was preserved.');
828
622
  }
829
623
 
830
- // O resumo sai de `list()` NOS DOIS MODOS — é essa escolha que faz um
831
- // ponto de emissão esquecido sumir das duas saídas ao mesmo tempo, em vez
832
- // de só do `--dry-run`.
624
+ // O resumo sai de `list()` NOS DOIS MODOS: um ponto de emissão esquecido some das duas saídas.
833
625
  renderEffectsReport(fx.list(), {
834
626
  targetPath, packageRoot: contentDir, dryRun, cleaned, notices,
835
627
  });