@polymorphism-tech/morph-spec 8.47.0 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (377) hide show
  1. package/CLAUDE.md +51 -43
  2. package/README.md +39 -38
  3. package/bin/morph-spec.js +41 -113
  4. package/bin/validate.js +1 -147
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +1 -1
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/CLAUDE.md +41 -101
  9. package/framework/MORPH.md +95 -252
  10. package/framework/agents.json +86 -21
  11. package/framework/commands/morph-apply.md +5 -28
  12. package/framework/commands/morph-archive.md +1 -1
  13. package/framework/commands/morph-hotfix.md +8 -8
  14. package/framework/commands/morph-infra.md +1 -1
  15. package/framework/commands/morph-preflight.md +2 -3
  16. package/framework/commands/morph-status.md +4 -4
  17. package/framework/docs/CLI.md +574 -286
  18. package/framework/evals/review-checklist.md +82 -41
  19. package/framework/hooks/README.md +10 -11
  20. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
  21. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
  22. package/framework/hooks/claude-code/core/chain.js +4 -65
  23. package/framework/hooks/claude-code/core/comment-guard.js +3 -17
  24. package/framework/hooks/claude-code/core/denylist.json +33 -3
  25. package/framework/hooks/claude-code/core/enforce-phase-writes.js +15 -63
  26. package/framework/hooks/claude-code/core/gate-guard.js +9 -78
  27. package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
  28. package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
  29. package/framework/hooks/claude-code/core/heavy-release.js +2 -13
  30. package/framework/hooks/claude-code/core/loop-detect.js +2 -31
  31. package/framework/hooks/claude-code/core/pre-bash-denylist.js +36 -2
  32. package/framework/hooks/claude-code/core/protect-spec-files.js +25 -297
  33. package/framework/hooks/claude-code/core/state-sync.js +15 -85
  34. package/framework/hooks/claude-code/core/telemetry-log.js +15 -96
  35. package/framework/hooks/claude-code/core/validate-completion.js +37 -147
  36. package/framework/hooks/claude-code/core/warn-bash-source-write.js +23 -0
  37. package/framework/hooks/claude-code/core/worktree-context.js +15 -114
  38. package/framework/hooks/claude-code/core/worktree-create.js +6 -70
  39. package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
  40. package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
  41. package/framework/hooks/claude-code/statusline.py +55 -41
  42. package/framework/hooks/package.json +3 -0
  43. package/framework/hooks/shared/activity-logger.js +9 -89
  44. package/framework/hooks/shared/agent-reap.js +2 -17
  45. package/framework/hooks/shared/build-check.js +11 -76
  46. package/framework/hooks/shared/claim-registry.js +9 -119
  47. package/framework/hooks/shared/command-tokens.js +168 -44
  48. package/framework/hooks/shared/comment-rules.js +5 -20
  49. package/framework/hooks/shared/comment-scanner.js +11 -43
  50. package/framework/hooks/shared/comment-stripper.js +11 -85
  51. package/framework/hooks/shared/e2e-check.js +0 -0
  52. package/framework/hooks/shared/errata.js +59 -0
  53. package/framework/hooks/shared/feature-locator.js +11 -95
  54. package/framework/hooks/shared/frontend-locations.js +4 -0
  55. package/framework/hooks/shared/gate-question.js +12 -0
  56. package/framework/hooks/shared/git-exclude.js +3 -20
  57. package/framework/hooks/shared/git-paths.js +8 -104
  58. package/framework/hooks/shared/graph-freshness.js +22 -166
  59. package/framework/hooks/shared/graph-refresh.js +21 -149
  60. package/framework/hooks/shared/heavy-command.js +4 -12
  61. package/framework/hooks/shared/hook-response.js +8 -83
  62. package/framework/hooks/shared/morph-cli.js +8 -52
  63. package/framework/hooks/shared/payload-utils.js +10 -132
  64. package/framework/hooks/shared/phase-utils.js +14 -72
  65. package/framework/hooks/shared/redact-secrets.js +4 -15
  66. package/framework/hooks/shared/reparse-scan.js +6 -66
  67. package/framework/hooks/shared/slice-progress.js +99 -0
  68. package/framework/hooks/shared/source-files.js +4 -17
  69. package/framework/hooks/shared/spawn-command.js +23 -0
  70. package/framework/hooks/shared/state-reader.js +51 -247
  71. package/framework/hooks/shared/state-writer.js +9 -54
  72. package/framework/hooks/shared/stdin-reader.js +1 -11
  73. package/framework/hooks/shared/telemetry-logger.js +7 -67
  74. package/framework/hooks/shared/transcript-usage.js +7 -29
  75. package/framework/hooks/shared/worktree-root.js +7 -29
  76. package/framework/mcp/registry.json +3 -4
  77. package/framework/phases.json +23 -39
  78. package/framework/rules/csharp-standards.md +1 -1
  79. package/framework/rules/frontend-standards.md +1 -0
  80. package/framework/rules/hangfire-standards.md +1 -1
  81. package/framework/rules/neon-standards.md +1 -1
  82. package/framework/schemas/config.schema.json +14 -9
  83. package/framework/schemas/feature.schema.json +15 -50
  84. package/framework/skills/README.md +3 -4
  85. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  86. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  87. package/framework/skills/core/morph-apply/SKILL.md +149 -222
  88. package/framework/skills/core/morph-hotfix/SKILL.md +33 -42
  89. package/framework/skills/core/morph-machine/SKILL.md +2 -2
  90. package/framework/skills/core/morph-media/SKILL.md +1 -1
  91. package/framework/skills/core/morph-plan/SKILL.md +137 -299
  92. package/framework/skills/core/morph-review/SKILL.md +111 -136
  93. package/framework/skills/core/morph-standards/SKILL.md +27 -39
  94. package/framework/skills/core/morph-uiux/SKILL.md +94 -152
  95. package/framework/standards/STANDARDS.json +109 -1219
  96. package/framework/standards/STANDARDS.meta.json +1319 -0
  97. package/framework/standards/ai-agents/agent-archetypes.md +2 -2
  98. package/framework/standards/ai-agents/agent-session.md +1 -1
  99. package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
  100. package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
  101. package/framework/standards/ai-agents/evals-with-cache.md +1 -2
  102. package/framework/standards/ai-agents/guardrails.md +1 -1
  103. package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
  104. package/framework/standards/ai-agents/observability-patterns.md +1 -1
  105. package/framework/standards/ai-agents/production.md +1 -1
  106. package/framework/standards/ai-agents/prompt-sources.md +2 -2
  107. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  108. package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
  109. package/framework/standards/ai-agents/setup.md +3 -3
  110. package/framework/standards/ai-agents/sweet-spot.md +2 -2
  111. package/framework/standards/ai-agents/tool-adapters.md +0 -2
  112. package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
  113. package/framework/standards/ai-agents/workflows.md +1 -1
  114. package/framework/standards/architecture/code-comments.md +7 -7
  115. package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
  116. package/framework/standards/backend/api/validation.md +1 -1
  117. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
  118. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
  119. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
  120. package/framework/standards/data/nosql/cache/redis.md +1 -1
  121. package/framework/standards/data/nosql/cosmos-db.md +3 -3
  122. package/framework/standards/frontend/admin-app/forms.md +1 -1
  123. package/framework/standards/frontend/design-system/art-direction.md +88 -0
  124. package/framework/standards/frontend/design-system/premium-finish.md +77 -2
  125. package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
  126. package/framework/standards/integration/mcp/mcp-tools.md +7 -7
  127. package/framework/templates/CLAUDE.md.core.template +3 -3
  128. package/framework/templates/README.md +2 -3
  129. package/framework/templates/REGISTRY.json +15 -179
  130. package/framework/templates/docs/plan.md +72 -0
  131. package/framework/templates/docs/ui-mockups.md +4 -0
  132. package/framework/templates/dotnet/ai-kit/README.md +1 -9
  133. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
  134. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
  135. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
  136. package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
  137. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
  138. package/package.json +1 -1
  139. package/src/commands/context/context-refresh.js +0 -16
  140. package/src/commands/dashboard/dashboard.js +2 -8
  141. package/src/commands/e2e/e2e.js +18 -105
  142. package/src/commands/graph/affected.js +0 -14
  143. package/src/commands/graph/build.js +11 -50
  144. package/src/commands/graph/explain.js +0 -16
  145. package/src/commands/graph/status.js +1 -12
  146. package/src/commands/mcp/mcp-setup.js +4 -62
  147. package/src/commands/observability/cost.js +17 -111
  148. package/src/commands/observability/telemetry.js +1 -14
  149. package/src/commands/project/branch.js +4 -45
  150. package/src/commands/project/claim.js +4 -48
  151. package/src/commands/project/doctor-procs.js +1 -1
  152. package/src/commands/project/doctor.js +53 -307
  153. package/src/commands/project/env.js +1 -27
  154. package/src/commands/project/fleet.js +6 -35
  155. package/src/commands/project/heavy.js +3 -3
  156. package/src/commands/project/init.js +8 -68
  157. package/src/commands/project/install-plugin-cmd.js +0 -1
  158. package/src/commands/project/setup-infra-cmd.js +2 -1
  159. package/src/commands/project/stack-scan.js +58 -0
  160. package/src/commands/project/standards.js +65 -72
  161. package/src/commands/project/status.js +28 -121
  162. package/src/commands/project/update.js +31 -223
  163. package/src/commands/project/worktree.js +109 -883
  164. package/src/commands/scaffold/scaffold.js +0 -25
  165. package/src/commands/state/advance.js +65 -120
  166. package/src/commands/state/approve.js +87 -229
  167. package/src/commands/state/archive.js +23 -139
  168. package/src/commands/state/auto-approval.js +65 -0
  169. package/src/commands/state/create.js +49 -109
  170. package/src/commands/state/delete.js +11 -63
  171. package/src/commands/state/finish.js +105 -494
  172. package/src/commands/state/gate-check.js +74 -183
  173. package/src/commands/state/gate-decision.js +8 -42
  174. package/src/commands/state/handoff.js +4 -31
  175. package/src/commands/state/list.js +21 -91
  176. package/src/commands/state/report-check.js +134 -338
  177. package/src/commands/state/retype.js +12 -28
  178. package/src/commands/validation/index.js +0 -3
  179. package/src/commands/validation/mutate.js +79 -19
  180. package/src/commands/validation/plan-check.js +81 -0
  181. package/src/commands/validation/validate-feature.js +52 -216
  182. package/src/commands/validation/verify.js +45 -160
  183. package/src/core/paths/output-schema.js +9 -155
  184. package/src/core/state/feature-identity.js +1 -25
  185. package/src/core/state/gate-order.js +2 -30
  186. package/src/core/state/lifecycle-audit.js +8 -100
  187. package/src/core/state/state-manager.js +32 -395
  188. package/src/core/templates/template-registry.js +2 -97
  189. package/src/core/templates/template-renderer.js +13 -93
  190. package/src/core/templates/template-seed.js +21 -0
  191. package/src/core/work-types.js +10 -45
  192. package/src/dashboard/app/App.jsx +1 -8
  193. package/src/dashboard/app/components/ActivityFeed.jsx +2 -8
  194. package/src/dashboard/app/components/CommandPalette.jsx +4 -11
  195. package/src/dashboard/app/components/DetailPanel.jsx +1 -5
  196. package/src/dashboard/app/components/EntityLink.jsx +0 -12
  197. package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
  198. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
  199. package/src/dashboard/app/components/Markdown.jsx +0 -9
  200. package/src/dashboard/app/components/PhaseRail.jsx +0 -11
  201. package/src/dashboard/app/components/SearchFilter.jsx +0 -4
  202. package/src/dashboard/app/components/Sidebar.jsx +0 -5
  203. package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
  204. package/src/dashboard/app/hooks/usePref.js +1 -5
  205. package/src/dashboard/app/lib/format.js +0 -2
  206. package/src/dashboard/app/lib/phases.js +3 -9
  207. package/src/dashboard/app/lib/project.js +0 -12
  208. package/src/dashboard/app/pages/Activity.jsx +1 -1
  209. package/src/dashboard/app/pages/AgentMap.jsx +1 -8
  210. package/src/dashboard/app/pages/FeatureDetail.jsx +139 -133
  211. package/src/dashboard/app/pages/Features.jsx +9 -16
  212. package/src/dashboard/app/pages/Guide.jsx +2 -3
  213. package/src/dashboard/app/pages/HookInspector.jsx +2 -23
  214. package/src/dashboard/app/pages/PhasePipeline.jsx +3 -7
  215. package/src/dashboard/app/pages/Portfolio.jsx +0 -1
  216. package/src/dashboard/app/pages/RulesMap.jsx +1 -16
  217. package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
  218. package/src/dashboard/app/pages/Standards.jsx +2 -17
  219. package/src/dashboard/scanner/activity-scanner.js +14 -83
  220. package/src/dashboard/scanner/agents-scanner.js +1 -23
  221. package/src/dashboard/scanner/docs-scanner.js +1 -7
  222. package/src/dashboard/scanner/feature-status.js +2 -5
  223. package/src/dashboard/scanner/features-scanner.js +92 -113
  224. package/src/dashboard/scanner/hooks-scanner.js +5 -28
  225. package/src/dashboard/scanner/index.js +0 -4
  226. package/src/dashboard/scanner/portfolio.js +5 -29
  227. package/src/dashboard/scanner/project-api.js +8 -54
  228. package/src/dashboard/scanner/rules-scanner.js +1 -2
  229. package/src/dashboard/scanner/skills-scanner.js +2 -12
  230. package/src/dashboard/scanner/standards-scanner.js +4 -7
  231. package/src/dashboard/vite-plugin-morph-data.js +5 -19
  232. package/src/lib/ai/ai-checks.js +7 -52
  233. package/src/lib/ai/ai-project-scan.js +14 -108
  234. package/src/lib/ai/code-audit.js +7 -63
  235. package/src/lib/ai/pin-drift.js +8 -51
  236. package/src/lib/ai/registry-audit.js +11 -90
  237. package/src/lib/ci/ci-verify.js +14 -13
  238. package/src/lib/ci/github-ci.js +9 -37
  239. package/src/lib/ci/workflow-scaffold.js +2 -10
  240. package/src/lib/detectors/claude-config-detector.js +13 -74
  241. package/src/lib/detectors/index.js +8 -59
  242. package/src/lib/e2e/compose-env.js +5 -66
  243. package/src/lib/e2e/stack-teardown.js +6 -40
  244. package/src/lib/generators/architecture-scanner.js +18 -156
  245. package/src/lib/generators/handoff-generator.js +37 -112
  246. package/src/lib/generators/scaffold-generator.js +5 -101
  247. package/src/lib/generators/settings-generator.js +11 -122
  248. package/src/lib/git/branch-classifier.js +11 -84
  249. package/src/lib/git/default-branch.js +14 -169
  250. package/src/lib/git/evidence-staleness.js +2 -1
  251. package/src/lib/git/gh.js +2 -17
  252. package/src/lib/git/pending-prs.js +6 -37
  253. package/src/lib/git/pr-files.js +3 -28
  254. package/src/lib/git/pr-index.js +3 -34
  255. package/src/lib/git/tree-fingerprint.js +17 -17
  256. package/src/lib/git/tree-state.js +6 -33
  257. package/src/lib/graph/god-nodes.js +2 -22
  258. package/src/lib/graph/graph-health.js +3 -24
  259. package/src/lib/graph/graph-loader.js +5 -56
  260. package/src/lib/graph/graphify-runtime.js +7 -37
  261. package/src/lib/graph/graphifyignore.js +8 -74
  262. package/src/lib/graph/sanitize.js +16 -136
  263. package/src/lib/heavy/cleanup.js +2 -6
  264. package/src/lib/installers/effects.js +8 -93
  265. package/src/lib/installers/mcp-installer.js +9 -169
  266. package/src/lib/installers/memory-installer.js +8 -33
  267. package/src/lib/installers/templates-reconcile.js +5 -60
  268. package/src/lib/mutation/commands.js +3 -2
  269. package/src/lib/mutation/dotnet-introspection.js +281 -0
  270. package/src/lib/mutation/plan.js +14 -6
  271. package/src/lib/mutation/runner.js +1 -1
  272. package/src/lib/mutation/subset.js +47 -13
  273. package/src/lib/plan/artifact-vocabulary.js +40 -0
  274. package/src/lib/plan/checks.js +208 -0
  275. package/src/lib/plan/plan-parser.js +204 -0
  276. package/src/lib/plan/plan-template.js +18 -0
  277. package/src/lib/projects/registry.js +7 -44
  278. package/src/lib/review-reports.js +204 -0
  279. package/src/lib/risk-detector.js +131 -274
  280. package/src/lib/specs/adr-index.js +38 -85
  281. package/src/lib/specs/design-seed.js +18 -0
  282. package/src/lib/specs/frontmatter.js +31 -0
  283. package/src/lib/specs/gfm-table.js +11 -0
  284. package/src/lib/specs/revocations.js +31 -102
  285. package/src/lib/specs/screen-checklist.js +56 -132
  286. package/src/lib/stack/dotnet-build-ignores.js +43 -0
  287. package/src/lib/stack/stack-profile.js +1 -28
  288. package/src/lib/stack/stack-scan.js +260 -0
  289. package/src/lib/stack-filter.js +4 -27
  290. package/src/lib/standards/registry.js +37 -0
  291. package/src/lib/standards/sections.js +47 -0
  292. package/src/lib/standards/verified-against.js +19 -191
  293. package/src/lib/tasks/eval-node.js +23 -179
  294. package/src/lib/tasks/linux-runner.js +18 -142
  295. package/src/lib/tasks/test-nodes.js +43 -313
  296. package/src/lib/tasks/test-runner.js +107 -447
  297. package/src/lib/telemetry/metrics.js +13 -103
  298. package/src/lib/telemetry/telemetry-reader.js +13 -238
  299. package/src/lib/telemetry/verify-event.js +4 -34
  300. package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
  301. package/src/lib/validators/code-budget/index.js +0 -7
  302. package/src/lib/validators/contracts/contract-compliance-validator.js +4 -26
  303. package/src/lib/validators/contracts/index.js +0 -6
  304. package/src/lib/validators/nextjs/index.js +0 -5
  305. package/src/lib/validators/nextjs/next-component-validator.js +4 -81
  306. package/src/lib/validators/packages/index.js +0 -6
  307. package/src/lib/validators/packages/package-validator.js +6 -56
  308. package/src/lib/validators/reuse/index.js +0 -8
  309. package/src/lib/validators/reuse/reuse-validator.js +24 -137
  310. package/src/lib/validators/reuse/symbol-scanner.js +6 -62
  311. package/src/lib/validators/traceability-check.js +54 -156
  312. package/src/lib/validators/validation-runner.js +20 -125
  313. package/src/lib/validators/verify-runner.js +152 -575
  314. package/src/lib/work-type-detector.js +5 -67
  315. package/src/lib/worktree/docker-audit.js +17 -137
  316. package/src/lib/worktree/env-allocator.js +10 -98
  317. package/src/lib/worktree/feature-truth.js +12 -115
  318. package/src/lib/worktree/finish-backup.js +32 -242
  319. package/src/lib/worktree/fleet-scanner.js +31 -126
  320. package/src/lib/worktree/ignored-triage.js +15 -91
  321. package/src/lib/worktree/vscode-settings.js +2 -3
  322. package/src/lib/worktree/worktree-audit.js +21 -213
  323. package/src/lib/worktree/worktree-include.js +3 -5
  324. package/src/scripts/global-install.js +3 -9
  325. package/src/scripts/install-plugin.js +1 -13
  326. package/src/scripts/setup-infra.js +17 -138
  327. package/src/utils/agents-installer.js +92 -272
  328. package/src/utils/banner.js +4 -25
  329. package/src/utils/claude-md-injector.js +1 -37
  330. package/src/utils/claude-settings-manager.js +1 -87
  331. package/src/utils/file-copier.js +32 -90
  332. package/src/utils/hooks-installer.js +26 -296
  333. package/src/utils/skills-installer.js +4 -102
  334. package/src/utils/version-checker.js +4 -83
  335. package/framework/commands/morph-proposal.md +0 -57
  336. package/framework/evals/trace-schema.md +0 -67
  337. package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
  338. package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
  339. package/framework/hooks/git/pre-commit/specs.sh +0 -50
  340. package/framework/hooks/shared/tasks-validator.js +0 -111
  341. package/framework/rules/morph-task-tracking.md +0 -54
  342. package/framework/rules/morph-workflow.md +0 -244
  343. package/framework/schemas/tasks.schema.json +0 -79
  344. package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
  345. package/framework/skills/core/morph-implement/SKILL.md +0 -194
  346. package/framework/skills/core/morph-proposal/SKILL.md +0 -152
  347. package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
  348. package/framework/templates/docs/proposal.md +0 -192
  349. package/framework/templates/docs/spec.md +0 -57
  350. package/framework/templates/docs/ui-components.md +0 -52
  351. package/framework/templates/docs/ui-design-system.md +0 -76
  352. package/framework/templates/docs/ui-flows.md +0 -167
  353. package/framework/templates/examples/spec-examples.md +0 -91
  354. package/framework/templates/feature/decisions.md +0 -124
  355. package/framework/templates/feature/recap.md +0 -145
  356. package/framework/templates/tasks.template.json +0 -17
  357. package/src/commands/state/usecase.js +0 -143
  358. package/src/commands/tasks/dag.js +0 -181
  359. package/src/commands/utils/index.js +0 -3
  360. package/src/lib/agents/output-collector.js +0 -76
  361. package/src/lib/detectors/design-system-detector.js +0 -188
  362. package/src/lib/evaluator-report.js +0 -104
  363. package/src/lib/generators/design-system-generator.js +0 -508
  364. package/src/lib/specs/acervo-format.js +0 -70
  365. package/src/lib/specs/acervo-lifecycle.js +0 -264
  366. package/src/lib/specs/done-criteria-check.js +0 -200
  367. package/src/lib/specs/usecase-check.js +0 -301
  368. package/src/lib/specs/usecase-promoter.js +0 -301
  369. package/src/lib/specs/usecase-registry.js +0 -237
  370. package/src/lib/tasks/dispatch-planner.js +0 -319
  371. package/src/lib/tasks/dotnet-test-introspection.js +0 -632
  372. package/src/lib/tasks/mermaid-renderer.js +0 -132
  373. package/src/lib/tasks/task-classifier.js +0 -30
  374. package/src/lib/tasks/task-parser.js +0 -209
  375. package/src/lib/tasks/tasks-json-parser.js +0 -296
  376. package/src/lib/tasks/test-scope.js +0 -597
  377. package/src/utils/agent-id-mapper.js +0 -11
@@ -39,11 +39,6 @@ import {
39
39
  writeGraphifyignore, describeOutdatedIgnore, graphifyignoreTracked,
40
40
  } from '../../lib/graph/graphifyignore.js';
41
41
 
42
- /**
43
- * Backup user's config.json before cleaning
44
- * @param {string} morphPath - Path to .morph directory
45
- * @returns {Promise<object|null>} Backup of config.json or null
46
- */
47
42
  async function backupUserConfig(morphPath) {
48
43
  try {
49
44
  const configPath = join(morphPath, 'config', 'config.json');
@@ -52,40 +47,18 @@ async function backupUserConfig(morphPath) {
52
47
  return config;
53
48
  }
54
49
  } catch (error) {
55
- // If backup fails, return null and continue
56
50
  }
57
51
  return null;
58
52
  }
59
53
 
60
- /**
61
- * Os DEZ diretórios que `cleanFrameworkDirs` apaga por inteiro, sem condição.
62
- *
63
- * O número está pinado à mão em `test/commands/update-dry-run.test.js`
64
- * (`DIRETORIOS_APAGADOS_POR_INTEIRO`), com os dez caminhos escritos: derivar a
65
- * expectativa desta função deixaria a suíte cega a uma entrada removida.
66
- *
67
- * Exportado porque o relatório do `--dry-run` é obrigado a nomeá-los: "update"
68
- * não sugere a ninguém que `.claude/skills/` e `.claude/agents/` são apagados
69
- * inteiros antes da reinstalação.
70
- *
71
- * @param {string} morphPath
72
- * @param {string} targetPath
73
- * @returns {string[]}
74
- */
75
54
  export function frameworkDirsToClean(morphPath, targetPath) {
76
55
  const claudeDir = join(targetPath, '.claude');
77
56
  return [
78
- // NOTE: framework/templates is intentionally NOT in this list (R3 perf
79
- // plan, Task 2). It's no longer copied/refreshed here — see
80
- // reconcileLegacyTemplatesCopy() below, which decides per-run whether a
81
- // pre-existing local copy is safe to remove or must be preserved as a
82
- // user override. Blindly deleting it here (the old behavior) destroyed
83
- // 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.
84
59
  join(morphPath, 'framework', 'standards'),
85
60
  join(morphPath, 'framework', 'hooks'),
86
61
  join(morphPath, 'framework', 'schemas'),
87
- // Não é mais instalado: fica na lista para apagar o de instalações antigas.
88
- join(morphPath, 'framework', 'rubrics'),
89
62
  join(morphPath, 'framework', 'evals'),
90
63
  join(morphPath, 'config'),
91
64
  join(claudeDir, 'commands'),
@@ -95,14 +68,6 @@ export function frameworkDirsToClean(morphPath, targetPath) {
95
68
  ];
96
69
  }
97
70
 
98
- /**
99
- * Emite (e, fora do dry, executa) a remoção recursiva de um diretório.
100
- *
101
- * Um efeito por arquivo, mais um pelo diretório. É a varredura que a §5.2 do
102
- * spec exige, e é dela que sai a contagem "N arquivos" do relatório.
103
- *
104
- * @returns {number} arquivos varridos
105
- */
106
71
  function sweepRemove(dir, fx) {
107
72
  let entries;
108
73
  try {
@@ -125,23 +90,8 @@ function sweepRemove(dir, fx) {
125
90
  }
126
91
 
127
92
  /**
128
- * Clean framework directories but preserve .morph/project/.
129
- *
130
- * **A armadilha que esta função resolve (decisions.md D6).** Ela apaga
131
- * exatamente os destinos que os laços de limpeza de órfão DEPOIS varrem
132
- * (`skills-installer.js`, `agents-installer.js`, `installRulesForStack`).
133
- * Numa execução real esses laços sempre encontram diretório vazio e não
134
- * apagam nada. Num `--dry-run` ingênuo nada foi apagado, eles encontrariam o
135
- * diretório POPULADO e o relatório preveria remoções que a execução real
136
- * nunca faz — errado na direção que assusta o usuário. Como o clean é
137
- * incondicional e total, a previsão correta é "tudo que hoje está sob estes
138
- * dez é apagado", e os laços de órfão ficam calados em dry: são provadamente
139
- * subsumidos.
140
- *
141
- * @param {string} morphPath - Path to .morph directory
142
- * @param {string} targetPath - Project root path
143
- * @param {ReturnType<import('../../lib/installers/effects.js').createEffects>} fx
144
- * @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.
145
95
  */
146
96
  function cleanFrameworkDirs(morphPath, targetPath, fx) {
147
97
  const cleaned = [];
@@ -151,31 +101,20 @@ function cleanFrameworkDirs(morphPath, targetPath, fx) {
151
101
  cleaned.push({ dir, files: sweepRemove(dir, fx) });
152
102
  }
153
103
  } catch (error) {
154
- // Continue even if cleanup fails for one directory
155
104
  }
156
105
  }
157
106
  return cleaned;
158
107
  }
159
108
 
160
- /**
161
- * Restore user's config.json after reinstallation
162
- * @param {string} morphPath - Path to .morph directory
163
- * @param {string} targetPath - Project root path
164
- * @param {object|null} configBackup - Backup of config.json
165
- * @param {string} currentVersion - Current CLI version
166
- * @param {ReturnType<import('../../lib/installers/effects.js').createEffects>} fx
167
- */
168
109
  function restoreUserConfig(morphPath, targetPath, configBackup, currentVersion, fx) {
169
110
  const configDir = join(morphPath, 'config');
170
111
  fx.ensureDir(configDir);
171
112
  const configPath = join(configDir, 'config.json');
172
113
 
173
114
  if (configBackup) {
174
- // Preserve user data but update framework version
175
115
  configBackup.frameworkVersion = currentVersion;
176
116
  fx.writeJson(configPath, configBackup);
177
117
  } else {
178
- // Create basic config if none existed
179
118
  const dirName = targetPath.split(/[\\/]/).pop();
180
119
  const defaultConfig = {
181
120
  framework: 'global',
@@ -189,15 +128,7 @@ function restoreUserConfig(morphPath, targetPath, configBackup, currentVersion,
189
128
  }
190
129
  }
191
130
 
192
- /**
193
- * `.morph/state.json` já está no índice do git?
194
- *
195
- * SONDA, separada da mutação de propósito: sondar é ler, e ler acontece nos
196
- * dois modos. É o que faz previsão e execução concordarem sobre este efeito.
197
- *
198
- * @param {string} targetPath
199
- * @returns {boolean}
200
- */
131
+ /** Sonda separada da mutação: ler acontece nos dois modos, e é o que faz previsão e execução concordarem. */
201
132
  export function stateJsonIsTracked(targetPath) {
202
133
  try {
203
134
  const isRepo = spawnSync('git', ['rev-parse', '--is-inside-work-tree'], {
@@ -214,38 +145,14 @@ export function stateJsonIsTracked(targetPath) {
214
145
  }
215
146
  }
216
147
 
217
- /**
218
- * Untrack an already-committed `.morph/state.json` from git.
219
- *
220
- * Before v5's per-feature model, `.morph/state.json` was committed. Adding it to
221
- * `.gitignore` does NOT stop git from tracking a file already in the index — so on
222
- * exactly the repos that had the P1 sweep problem, `git commit -a` would keep
223
- * staging state.json. This runs `git rm --cached` once so the next commit drops it.
224
- * The working-tree file is left in place (it's rebuilt from feature.json on load).
225
- *
226
- * Mildly destructive (mutates the git index), but no data is lost: state.json is a
227
- * derivable index. Fail-open — a non-git project or any git error is a silent no-op.
228
- *
229
- * @param {string} targetPath - Project root
230
- * @param {ReturnType<import('../../lib/installers/effects.js').createEffects>} fx
231
- * @returns {boolean} true if state.json was untracked, false otherwise
232
- */
233
148
  export function untrackCommittedStateJson(targetPath, fx = createEffects({ dryRun: false })) {
234
- if (!stateJsonIsTracked(targetPath)) return false; // not tracked — nothing to migrate
149
+ if (!stateJsonIsTracked(targetPath)) return false;
235
150
  return fx.gitIndex('rm --cached --quiet', '.morph/state.json', { cwd: targetPath });
236
151
  }
237
152
 
238
153
  /**
239
- * Os dois registros de versão que um projeto morph-spec carrega.
240
- *
241
- * `.morph/.morphversion → morphVersion` é o que o `update` LÊ para decidir.
242
- * `config.json → frameworkVersion` é o que o `update` ESCREVE e nunca lê para
243
- * decidir coisa alguma — e no MORPH_OS os dois divergem por um major inteiro
244
- * (7.5.0 contra 6.0.0). Um freio calibrado no campo errado recusaria (ou
245
- * liberaria) com base num número que ninguém mantém.
246
- *
247
- * @param {string} targetPath
248
- * @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.
249
156
  */
250
157
  export async function readVersionRegisters(targetPath) {
251
158
  let morphVersion = null;
@@ -253,30 +160,17 @@ export async function readVersionRegisters(targetPath) {
253
160
  try {
254
161
  const v = await readJson(join(targetPath, '.morph', '.morphversion'));
255
162
  morphVersion = typeof v?.morphVersion === 'string' ? v.morphVersion : null;
256
- } catch { /* ausente ou ilegível — indeterminável, e isso é um dado */ }
163
+ } catch {}
257
164
  try {
258
165
  const c = await readJson(join(targetPath, '.morph', 'config', 'config.json'));
259
166
  frameworkVersion = typeof c?.frameworkVersion === 'string' ? c.frameworkVersion : null;
260
- } catch { /* idem */ }
167
+ } catch {}
261
168
 
262
169
  const diverged = Boolean(morphVersion && frameworkVersion && morphVersion !== frameworkVersion);
263
170
  return { morphVersion, frameworkVersion, diverged };
264
171
  }
265
172
 
266
- /**
267
- * As linhas da recusa do freio de major.
268
- *
269
- * Separada do comando para ser testável sem rodar o `update`, e porque a
270
- * mensagem carrega três obrigações: a versão instalada, a versão de destino e
271
- * **o registro de onde a versão instalada foi lida**. Quando os dois registros
272
- * discordam, imprime OS DOIS e diz qual decidiu — escolher em silêncio é
273
- * acertar metade das vezes e nunca saber qual metade.
274
- *
275
- * @param {{ refuse: boolean, reason: string|null, jump: number|null }} verdict
276
- * @param {{ morphVersion: string|null, frameworkVersion: string|null, diverged: boolean }} registers
277
- * @param {string} targetVersion
278
- * @returns {string[]}
279
- */
173
+ /** Quando os dois registros discordam, imprime OS DOIS e diz qual decidiu. */
280
174
  export function majorGuardLines(verdict, registers, targetVersion) {
281
175
  const lines = [];
282
176
  if (verdict.reason === 'unknown-installed') {
@@ -298,7 +192,6 @@ export function majorGuardLines(verdict, registers, targetVersion) {
298
192
  return lines;
299
193
  }
300
194
 
301
- /** Rótulo humano de cada verbo, para agrupar o relatório por natureza do efeito. */
302
195
  const VERB_GROUPS = {
303
196
  remove: 'Apagar',
304
197
  ensureDir: 'Criar diretório',
@@ -317,20 +210,8 @@ function isUnder(path, root) {
317
210
  return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel);
318
211
  }
319
212
 
320
- /**
321
- * ONDE um efeito age.
322
- *
323
- * Para quase todo verbo é o próprio `target`. Para `spawn` NÃO é: o `target`
324
- * de um spawn é o binário (`node`), e classificar por ele esconderia
325
- * exatamente o efeito que a §5.3 obriga a nomear — `generate-refs.js`
326
- * escrevendo `framework/hooks/shared/phase-utils.js` DENTRO do pacote da CLI.
327
- *
328
- * O script sai de `effect.args`, o **array**, nunca de um split do `detail`
329
- * por espaço: o caminho real deste pacote é
330
- * `R:\Polymorphism Tech\repos\...` — um espaço no meio, e o split devolvia
331
- * `Tech\repos\...\generate-refs.js`, que não é subcaminho de nada. O efeito ia
332
- * calado para o grupo errado. Medido rodando o comando de verdade.
333
- */
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.
334
215
  function effectLocation(effect) {
335
216
  if (effect.verb !== 'spawn') return effect.target;
336
217
  const args = Array.isArray(effect.args) ? effect.args : [];
@@ -338,26 +219,12 @@ function effectLocation(effect) {
338
219
  return script || effect.target;
339
220
  }
340
221
 
341
- /**
342
- * Imprime o relatório de efeitos.
343
- *
344
- * Três grupos ganham nome próprio, porque o usuário não os adivinharia da
345
- * palavra "update": o que é apagado por INTEIRO antes da reinstalação, o que
346
- * acontece FORA do projeto, e o que é escrito DENTRO do pacote da CLI (que num
347
- * `npm link` de desenvolvimento é o próprio repositório do framework).
348
- *
349
- * @param {Array} effects - saída de `fx.list()`
350
- * @param {object} context
351
- */
352
222
  export function renderEffectsReport(effects, context) {
353
223
  const { targetPath, packageRoot, dryRun, cleaned = [], notices = [] } = context;
354
224
  const globalClaudeDir = join(homedir(), '.claude');
355
225
 
356
226
  logger.blank();
357
- // O cabeçalho não pode dizer "nem executado": em dry o `spawn` do
358
- // `generate-refs.js` RODA, na variante `--check`, que verifica e não escreve.
359
- // Prometer silêncio total e rodar um processo é mentir num detalhe pequeno —
360
- // 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.
361
228
  logger.info(dryRun
362
229
  ? 'Relatório do --dry-run — nada foi escrito, apagado nem removido '
363
230
  + '(o único processo executado é a variante `--check` do gerador, que só verifica):'
@@ -419,7 +286,7 @@ export function renderEffectsReport(effects, context) {
419
286
  + `${outside.length} fora do projeto, ${inPackage.length} dentro do pacote da CLI.`);
420
287
  logger.blank();
421
288
 
422
- // Risco conhecido, reportado em vez de escondido (decisions.md D9): o backup
289
+ // Risco conhecido, reportado em vez de escondido: o backup
423
290
  // de `config.json` só existe em memória durante a execução.
424
291
  logger.dim('Nota: durante uma execução real o backup de `.morph/config/config.json` existe apenas '
425
292
  + 'em memória — uma falha no meio do caminho deixa o arquivo apagado. Follow-up nomeado.');
@@ -437,11 +304,7 @@ export async function updateCommand(options = {}) {
437
304
 
438
305
  logger.header(dryRun ? 'MORPH-SPEC Update (--dry-run)' : 'MORPH-SPEC Update');
439
306
 
440
- // ── Versões primeiro, e NADA de mutação antes do freio ───────────────────
441
- // O passo de criação de diretórios ficava aqui em cima e criava `.morph/`
442
- // ANTES de qualquer guarda. Recusar depois de mutar é meio-estado — o mesmo
443
- // defeito que o `finish --merge` pagou uma vez na v8.24.3 ("valida a raiz
444
- // ANTES de arquivar").
307
+ // Nenhuma mutação antes do freio: recusar depois de mutar deixa meio-estado.
445
308
  const spinner = ora('Checking versions...').start();
446
309
  // `cliCheck` é injetável para que o canário de igualdade dry × real não
447
310
  // dependa do registro npm; a CLI nunca o passa.
@@ -450,7 +313,6 @@ export async function updateCommand(options = {}) {
450
313
  const registers = await readVersionRegisters(targetPath);
451
314
  spinner.stop();
452
315
 
453
- // Display version info
454
316
  logger.blank();
455
317
  logger.info('Version Status:');
456
318
  logger.dim(` CLI version: ${cliCheck.current}`);
@@ -467,10 +329,7 @@ export async function updateCommand(options = {}) {
467
329
  }
468
330
  logger.blank();
469
331
 
470
- // If CLI is outdated, stop and instruct user.
471
- // O `--dry-run` PASSA POR CIMA desta saída antecipada: bloquear o relatório
472
- // justamente quando o usuário está decidindo se atualiza seria inútil. O
473
- // 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.
474
333
  if (cliCheck.isOutdated && cliCheck.latest) {
475
334
  if (dryRun) {
476
335
  notices.push(`A CLI local está desatualizada (${cliCheck.current} → ${cliCheck.latest}). `
@@ -500,7 +359,6 @@ export async function updateCommand(options = {}) {
500
359
  }
501
360
  }
502
361
 
503
- // ── Freio de major, ANTES de qualquer mutação ────────────────────────────
504
362
  const needsInitialization = !(await pathExists(morphPath));
505
363
  const targetVersion = cliCheck.current ?? getInstalledCLIVersion();
506
364
  const verdict = needsInitialization
@@ -514,8 +372,7 @@ export async function updateCommand(options = {}) {
514
372
  if (force) {
515
373
  notices.push(`Freio de major DISPENSADO por \`--force\`: ${lines[0]}`);
516
374
  } else if (dryRun) {
517
- // UC-27 A1: em dry ninguém precisa ser protegido de nada, e esconder o
518
- // 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.
519
376
  notices.push(...lines);
520
377
  } else {
521
378
  logger.error(lines[0]);
@@ -531,7 +388,6 @@ export async function updateCommand(options = {}) {
531
388
  + `Quem decide é o primeiro.`);
532
389
  }
533
390
 
534
- // ── A partir daqui, tudo passa pelo coletor ──────────────────────────────
535
391
  if (needsInitialization) {
536
392
  logger.warn('MORPH not fully initialized. Creating basic structure...');
537
393
  fx.ensureDir(join(morphPath, 'context'));
@@ -541,19 +397,16 @@ export async function updateCommand(options = {}) {
541
397
  logger.dim(' ✓ Created .morph directory structure');
542
398
  }
543
399
 
544
- // Proceed with update
545
400
  const updateSpinner = ora(dryRun ? 'Prevendo efeitos...' : 'Updating MORPH-SPEC...').start();
546
401
  let cleaned = [];
547
402
 
548
403
  try {
549
- // Backup user config before cleaning
550
404
  updateSpinner.text = 'Backing up user configuration...';
551
405
  const configBackup = await backupUserConfig(morphPath);
552
406
  if (configBackup) {
553
407
  logger.dim(' ✓ User config backed up');
554
408
  }
555
409
 
556
- // Clean framework directories (preserve .morph/project/)
557
410
  updateSpinner.text = 'Cleaning old framework files...';
558
411
  cleaned = cleanFrameworkDirs(morphPath, targetPath, fx);
559
412
  logger.dim(' ✓ Old framework files removed');
@@ -561,9 +414,6 @@ export async function updateCommand(options = {}) {
561
414
  const updateTemplates = !options.standards || options.templates;
562
415
  const updateStandards = !options.templates || options.standards;
563
416
 
564
- // Templates are no longer copied to .morph/framework/templates/ (Task 2,
565
- // R3 perf plan) — see reconcileLegacyTemplatesCopy()'s doc comment for
566
- // why. Only reconcile a leftover copy from a pre-R3 install.
567
417
  let templatesReconcile = 'absent';
568
418
  if (updateTemplates) {
569
419
  updateSpinner.text = 'Checking for a legacy templates copy...';
@@ -571,7 +421,6 @@ export async function updateCommand(options = {}) {
571
421
  templatesReconcile = reconcileLegacyTemplatesCopy(templatesDest, projectCheck.current, fx);
572
422
  }
573
423
 
574
- // Update standards
575
424
  if (updateStandards) {
576
425
  updateSpinner.text = 'Updating standards...';
577
426
  const standardsSrc = join(contentDir, 'framework', 'standards');
@@ -579,7 +428,6 @@ export async function updateCommand(options = {}) {
579
428
  fx.copyDir(standardsSrc, standardsDest);
580
429
  }
581
430
 
582
- // Update hooks (runtime hook scripts under .morph/framework/hooks/)
583
431
  updateSpinner.text = 'Updating hooks...';
584
432
  const hooksSrc = join(__dirname, '..', '..', '..', 'framework', 'hooks');
585
433
  const hooksDest = join(morphPath, 'framework', 'hooks');
@@ -587,7 +435,6 @@ export async function updateCommand(options = {}) {
587
435
  fx.copyDir(hooksSrc, hooksDest);
588
436
  }
589
437
 
590
- // Update agents.json (sourced from framework/ — canonical single source of truth)
591
438
  updateSpinner.text = 'Updating agents configuration...';
592
439
  const agentsSrc = join(__dirname, '..', '..', '..', 'framework', 'agents.json');
593
440
  const agentsDest = join(morphPath, 'framework', 'agents.json');
@@ -595,7 +442,6 @@ export async function updateCommand(options = {}) {
595
442
  fx.copyFile(agentsSrc, agentsDest);
596
443
  }
597
444
 
598
- // Paridade com setup-infra 7c/8b/8c/8d: sem isto o charter e os evals instalados ficam velhos.
599
445
  updateSpinner.text = 'Updating charter, schemas and evals...';
600
446
  const canonicalFrameworkDir = join(__dirname, '..', '..', '..', 'framework');
601
447
  for (const file of ['MORPH.md', 'phases.json']) {
@@ -610,18 +456,13 @@ export async function updateCommand(options = {}) {
610
456
  fx.copyDir(src, join(morphPath, 'framework', dir));
611
457
  }
612
458
  }
613
- // CLI.md vive em framework/docs/ na fonte mas instala achatado em
614
- // .morph/framework/CLI.md (o CLAUDE.md residente aponta para lá) —
615
- // 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.
616
460
  const cliMdSrc = join(canonicalFrameworkDir, 'docs', 'CLI.md');
617
461
  if (await pathExists(cliMdSrc)) {
618
462
  fx.copyFile(cliMdSrc, join(morphPath, 'framework', 'CLI.md'));
619
463
  }
620
464
 
621
- // Regenerate derived files (phase-utils.js, skill output tables) from output-schema.js.
622
- // Escreve DENTRO do pacote da CLI — num `npm link` de desenvolvimento, no
623
- // próprio repositório do framework. Em dry a variante `--check` roda no
624
- // 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`.
625
466
  updateSpinner.text = 'Syncing generated refs...';
626
467
  try {
627
468
  const generateScript = join(__dirname, '..', '..', '..', 'scripts', 'generate-refs.js');
@@ -634,24 +475,18 @@ export async function updateCommand(options = {}) {
634
475
  }
635
476
  }
636
477
  } catch {
637
- // Non-fatal: generator unavailable or files already in sync
638
478
  }
639
479
 
640
- // Update .claude commands and skills
641
- // Source: framework/ (canonical for all stacks)
642
480
  updateSpinner.text = 'Setting up Claude Code integration...';
643
481
  const frameworkDir = join(__dirname, '..', '..', '..', 'framework');
644
482
  const claudeDest = join(targetPath, '.claude');
645
483
 
646
- // Compute project tags for stack-aware installation of skills, agents, and rules.
647
- // MUST come from the pre-clean backup: cleanFrameworkDirs() deleted .morph/config/
648
- // above and restoreUserConfig() only runs at the end — re-reading the file here
649
- // yields [] and silently downgrades the whole install to universal-only assets
650
- // (the ".claude/agents/ only has morph-evaluator.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.
651
486
  let projectTags = [];
652
487
  try {
653
488
  if (configBackup) projectTags = getProjectTags(configBackup);
654
- } catch { /* ignore — malformed config */ }
489
+ } catch {}
655
490
 
656
491
  let commandsCopied = false;
657
492
  const commandsSrc = join(frameworkDir, 'commands');
@@ -664,18 +499,13 @@ export async function updateCommand(options = {}) {
664
499
  logger.warn(' ⚠ framework/commands/ source missing — commands not updated');
665
500
  }
666
501
 
667
- // Sync morph skills to .claude/skills/ for native Claude Code discovery
668
502
  updateSpinner.text = 'Syncing morph skills to .claude/skills/...';
669
503
  const skillsResult = await installSkills(targetPath, { projectTags, fx });
670
504
 
671
- // Sync native subagents to .claude/agents/
672
505
  updateSpinner.text = 'Syncing agents to .claude/agents/...';
673
506
  const agentsResult = await installAgents(targetPath, frameworkDir, { projectTags, fx });
674
507
  await installDomainAgents(targetPath, frameworkDir, { projectTags, fx });
675
508
 
676
- // Sync path-scoped rules to .claude/rules/ — stack-filtered with orphan
677
- // cleanup (parity with setup-infra), so `update` is a true superset of
678
- // `setup-infra --reinstall` and also covers project.stack changes.
679
509
  updateSpinner.text = 'Syncing rules to .claude/rules/...';
680
510
  const rulesSrc = join(frameworkDir, 'rules');
681
511
  const rulesDest = join(claudeDest, 'rules');
@@ -685,8 +515,6 @@ export async function updateCommand(options = {}) {
685
515
  rulesResult = installRulesForStack(rulesSrc, rulesDest, projectTags, fx);
686
516
  }
687
517
 
688
- // Sync runtime CLAUDE.md to .claude/CLAUDE.md and derive the personas
689
- // block from what installAgents actually installed (managed markers).
690
518
  updateSpinner.text = 'Syncing .claude/CLAUDE.md...';
691
519
  const runtimeSrc = join(frameworkDir, 'CLAUDE.md');
692
520
  const runtimeDest = join(claudeDest, 'CLAUDE.md');
@@ -699,45 +527,34 @@ export async function updateCommand(options = {}) {
699
527
  });
700
528
  }
701
529
 
702
- // Sync statusline globally to ~/.claude/
703
530
  updateSpinner.text = 'Syncing statusline to ~/.claude/...';
704
531
  const HOOKS_SRC = join(__dirname, '..', '..', '..', 'framework', 'hooks', 'claude-code');
705
532
  try {
706
533
  await installGlobalStatusline(HOOKS_SRC, null, { fx });
707
534
  } catch {
708
- // Non-critical: global dir may not be writable in all environments
709
535
  logger.dim(' ⚠ Could not install statusline globally (non-critical)');
710
536
  }
711
537
 
712
- // Update Claude Code hooks in .claude/settings.local.json
713
538
  updateSpinner.text = 'Updating Claude Code hooks...';
714
539
  const hooksResult = await installClaudeHooks(targetPath, { fx });
715
540
 
716
- // Ensure root CLAUDE.md has morph-spec @import (preserves user content)
717
541
  updateSpinner.text = 'Checking CLAUDE.md import...';
718
542
  const claudeMdDest = join(targetPath, 'CLAUDE.md');
719
543
  await injectMorphImport(claudeMdDest, fx);
720
544
 
721
- // Restore user config after framework reinstallation
722
545
  updateSpinner.text = 'Restoring user configuration...';
723
546
  restoreUserConfig(morphPath, targetPath, configBackup, cliCheck.current, fx);
724
547
  logger.dim(' ✓ User config restored with updated framework version');
725
548
 
726
- // Update .gitignore + .gitattributes with current morph rules
727
549
  updateSpinner.text = 'Updating .gitignore + .gitattributes...';
728
550
  await updateGitignore(targetPath, fx);
729
551
  await ensureGitattributes(targetPath, fx);
730
552
 
731
553
  updateSpinner.text = 'Editor excludes for worktrees (.vscode/settings.json)...';
732
- try { applyEditorSettings(targetPath, { fx }); } catch { /* fail-open */ }
733
-
734
- // Migration: a .graphifyignore the previous version generated and nobody
735
- // edited is swapped for the current one when its RULES change (test doubles
736
- // and fixtures back in the graph — a header-only difference is left alone);
737
- // an edited one is left alone, with the change to make. Only an existing
738
- // file — the graph is opt-in, and `graph build` writes it. A tracked file
739
- // reaches worktrees and teammates only once committed: each checkout reads
740
- // 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.
741
558
  let graphIgnore = null;
742
559
  if (existsSync(join(targetPath, '.graphifyignore'))) {
743
560
  updateSpinner.text = 'Checking .graphifyignore...';
@@ -749,11 +566,8 @@ export async function updateCommand(options = {}) {
749
566
  }
750
567
  }
751
568
 
752
- // Migration: untrack a pre-v5 committed state.json so it stops being swept
753
- // into feature commits. Gitignore alone can't do this for an already-tracked file.
754
569
  const untracked = untrackCommittedStateJson(targetPath, fx);
755
570
 
756
- // Update .morphversion
757
571
  updateSpinner.text = 'Saving version info...';
758
572
  await saveProjectMorphVersion(targetPath, cliCheck.current, fx);
759
573
 
@@ -761,11 +575,9 @@ export async function updateCommand(options = {}) {
761
575
  logger.blank();
762
576
 
763
577
  if (!dryRun) {
764
- // The dashboard's portfolio learns about the project here (fail-open;
765
- // outside the project, so not an effect of the update itself).
578
+ // Outside the project, so not an effect of the update itself.
766
579
  registerProject(targetPath, 'update');
767
580
 
768
- // Show version change
769
581
  if (projectCheck.current && projectCheck.current !== cliCheck.current) {
770
582
  logger.success(`Updated: ${projectCheck.current} → ${cliCheck.current}`);
771
583
  } else if (!projectCheck.current) {
@@ -786,10 +598,8 @@ export async function updateCommand(options = {}) {
786
598
  }
787
599
  }
788
600
 
789
- // Counts make a silent stack-filter downgrade visible at a glance —
790
- // a dotnet project reporting "1 persona" is a red flag, not a success.
791
- // Display only compact tags: project.integrations entries are long
792
- // 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.
793
603
  const compactTags = projectTags.filter(t => !t.includes(' '));
794
604
  const tagsLabel = compactTags.length > 0 ? compactTags.join('+') : 'universal only';
795
605
  logger.dim(` ✓ .claude/settings.local.json (${hooksResult.installed} hooks installed)`);
@@ -811,9 +621,7 @@ export async function updateCommand(options = {}) {
811
621
  logger.info('Your config.json was preserved.');
812
622
  }
813
623
 
814
- // O resumo sai de `list()` NOS DOIS MODOS — é essa escolha que faz um
815
- // ponto de emissão esquecido sumir das duas saídas ao mesmo tempo, em vez
816
- // 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.
817
625
  renderEffectsReport(fx.list(), {
818
626
  targetPath, packageRoot: contentDir, dryRun, cleaned, notices,
819
627
  });