@polymorphism-tech/morph-spec 9.0.0 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/CLAUDE.md +8 -9
  2. package/README.md +38 -36
  3. package/bin/morph-spec.js +4 -62
  4. package/bin/validate.js +1 -39
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +1 -1
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/CLAUDE.md +1 -1
  9. package/framework/MORPH.md +1 -1
  10. package/framework/agents.json +12 -4
  11. package/framework/commands/morph-apply.md +1 -1
  12. package/framework/commands/morph-status.md +1 -1
  13. package/framework/docs/CLI.md +6 -17
  14. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
  15. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
  16. package/framework/hooks/claude-code/core/chain.js +4 -65
  17. package/framework/hooks/claude-code/core/comment-guard.js +2 -16
  18. package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
  19. package/framework/hooks/claude-code/core/gate-guard.js +7 -67
  20. package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
  21. package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
  22. package/framework/hooks/claude-code/core/heavy-release.js +2 -13
  23. package/framework/hooks/claude-code/core/loop-detect.js +2 -31
  24. package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
  25. package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
  26. package/framework/hooks/claude-code/core/state-sync.js +11 -67
  27. package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
  28. package/framework/hooks/claude-code/core/validate-completion.js +11 -57
  29. package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
  30. package/framework/hooks/claude-code/core/worktree-context.js +10 -110
  31. package/framework/hooks/claude-code/core/worktree-create.js +6 -70
  32. package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
  33. package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
  34. package/framework/hooks/shared/activity-logger.js +9 -89
  35. package/framework/hooks/shared/agent-reap.js +2 -17
  36. package/framework/hooks/shared/build-check.js +6 -75
  37. package/framework/hooks/shared/claim-registry.js +9 -119
  38. package/framework/hooks/shared/command-tokens.js +6 -51
  39. package/framework/hooks/shared/comment-rules.js +5 -20
  40. package/framework/hooks/shared/comment-scanner.js +6 -25
  41. package/framework/hooks/shared/comment-stripper.js +11 -85
  42. package/framework/hooks/shared/e2e-check.js +0 -0
  43. package/framework/hooks/shared/errata.js +2 -15
  44. package/framework/hooks/shared/feature-locator.js +11 -95
  45. package/framework/hooks/shared/frontend-locations.js +1 -3
  46. package/framework/hooks/shared/gate-question.js +1 -7
  47. package/framework/hooks/shared/git-exclude.js +3 -20
  48. package/framework/hooks/shared/git-paths.js +8 -104
  49. package/framework/hooks/shared/graph-freshness.js +22 -166
  50. package/framework/hooks/shared/graph-refresh.js +21 -149
  51. package/framework/hooks/shared/heavy-command.js +4 -12
  52. package/framework/hooks/shared/hook-response.js +8 -83
  53. package/framework/hooks/shared/morph-cli.js +8 -52
  54. package/framework/hooks/shared/payload-utils.js +10 -132
  55. package/framework/hooks/shared/phase-utils.js +1 -42
  56. package/framework/hooks/shared/redact-secrets.js +4 -15
  57. package/framework/hooks/shared/reparse-scan.js +6 -66
  58. package/framework/hooks/shared/slice-progress.js +4 -23
  59. package/framework/hooks/shared/source-files.js +4 -17
  60. package/framework/hooks/shared/state-reader.js +13 -183
  61. package/framework/hooks/shared/state-writer.js +8 -52
  62. package/framework/hooks/shared/stdin-reader.js +1 -11
  63. package/framework/hooks/shared/telemetry-logger.js +7 -67
  64. package/framework/hooks/shared/transcript-usage.js +7 -29
  65. package/framework/hooks/shared/worktree-root.js +7 -29
  66. package/framework/rules/csharp-standards.md +1 -1
  67. package/framework/rules/frontend-standards.md +1 -0
  68. package/framework/rules/hangfire-standards.md +1 -1
  69. package/framework/rules/neon-standards.md +1 -1
  70. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  71. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  72. package/framework/skills/core/morph-apply/SKILL.md +2 -2
  73. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  74. package/framework/skills/core/morph-machine/SKILL.md +1 -1
  75. package/framework/skills/core/morph-media/SKILL.md +1 -1
  76. package/framework/skills/core/morph-plan/SKILL.md +2 -2
  77. package/framework/skills/core/morph-review/SKILL.md +1 -1
  78. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  79. package/framework/skills/core/morph-uiux/SKILL.md +29 -12
  80. package/framework/standards/STANDARDS.json +34 -12
  81. package/framework/standards/STANDARDS.meta.json +16 -2
  82. package/framework/standards/ai-agents/agent-archetypes.md +2 -2
  83. package/framework/standards/ai-agents/agent-session.md +1 -1
  84. package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
  85. package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
  86. package/framework/standards/ai-agents/guardrails.md +1 -1
  87. package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
  88. package/framework/standards/ai-agents/observability-patterns.md +1 -1
  89. package/framework/standards/ai-agents/production.md +1 -1
  90. package/framework/standards/ai-agents/prompt-sources.md +2 -2
  91. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  92. package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
  93. package/framework/standards/ai-agents/setup.md +2 -2
  94. package/framework/standards/ai-agents/sweet-spot.md +2 -2
  95. package/framework/standards/ai-agents/tool-adapters.md +0 -2
  96. package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
  97. package/framework/standards/ai-agents/workflows.md +1 -1
  98. package/framework/standards/architecture/code-comments.md +1 -1
  99. package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
  100. package/framework/standards/backend/api/validation.md +1 -1
  101. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
  102. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
  103. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
  104. package/framework/standards/data/nosql/cache/redis.md +1 -1
  105. package/framework/standards/data/nosql/cosmos-db.md +3 -3
  106. package/framework/standards/frontend/admin-app/forms.md +1 -1
  107. package/framework/standards/frontend/design-system/art-direction.md +88 -0
  108. package/framework/standards/frontend/design-system/premium-finish.md +77 -2
  109. package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
  110. package/framework/templates/CLAUDE.md.core.template +3 -3
  111. package/framework/templates/REGISTRY.json +9 -9
  112. package/framework/templates/docs/ui-mockups.md +4 -0
  113. package/package.json +1 -1
  114. package/src/commands/context/context-refresh.js +0 -16
  115. package/src/commands/dashboard/dashboard.js +2 -8
  116. package/src/commands/e2e/e2e.js +17 -104
  117. package/src/commands/graph/affected.js +0 -14
  118. package/src/commands/graph/build.js +11 -50
  119. package/src/commands/graph/explain.js +0 -16
  120. package/src/commands/graph/status.js +1 -12
  121. package/src/commands/mcp/mcp-setup.js +4 -62
  122. package/src/commands/observability/cost.js +5 -57
  123. package/src/commands/observability/telemetry.js +1 -14
  124. package/src/commands/project/branch.js +4 -45
  125. package/src/commands/project/claim.js +4 -48
  126. package/src/commands/project/doctor.js +48 -302
  127. package/src/commands/project/env.js +1 -27
  128. package/src/commands/project/fleet.js +3 -32
  129. package/src/commands/project/init.js +4 -68
  130. package/src/commands/project/install-plugin-cmd.js +0 -1
  131. package/src/commands/project/standards.js +1 -62
  132. package/src/commands/project/status.js +0 -53
  133. package/src/commands/project/update.js +31 -239
  134. package/src/commands/project/worktree.js +108 -882
  135. package/src/commands/scaffold/scaffold.js +0 -25
  136. package/src/commands/state/advance.js +14 -85
  137. package/src/commands/state/approve.js +14 -96
  138. package/src/commands/state/archive.js +12 -64
  139. package/src/commands/state/auto-approval.js +2 -5
  140. package/src/commands/state/create.js +22 -109
  141. package/src/commands/state/delete.js +11 -63
  142. package/src/commands/state/finish.js +98 -475
  143. package/src/commands/state/gate-check.js +22 -132
  144. package/src/commands/state/gate-decision.js +3 -39
  145. package/src/commands/state/handoff.js +3 -30
  146. package/src/commands/state/list.js +14 -84
  147. package/src/commands/state/report-check.js +8 -58
  148. package/src/commands/state/retype.js +4 -26
  149. package/src/commands/validation/index.js +0 -3
  150. package/src/commands/validation/mutate.js +2 -5
  151. package/src/commands/validation/validate-feature.js +5 -94
  152. package/src/commands/validation/verify.js +15 -98
  153. package/src/core/paths/output-schema.js +2 -101
  154. package/src/core/state/feature-identity.js +1 -25
  155. package/src/core/state/gate-order.js +0 -27
  156. package/src/core/state/lifecycle-audit.js +8 -100
  157. package/src/core/state/state-manager.js +22 -304
  158. package/src/core/templates/template-registry.js +2 -97
  159. package/src/core/templates/template-renderer.js +13 -93
  160. package/src/core/templates/template-seed.js +0 -1
  161. package/src/core/work-types.js +1 -36
  162. package/src/dashboard/app/App.jsx +1 -8
  163. package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
  164. package/src/dashboard/app/components/CommandPalette.jsx +0 -7
  165. package/src/dashboard/app/components/DetailPanel.jsx +1 -5
  166. package/src/dashboard/app/components/EntityLink.jsx +0 -12
  167. package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
  168. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
  169. package/src/dashboard/app/components/Markdown.jsx +0 -9
  170. package/src/dashboard/app/components/PhaseRail.jsx +0 -11
  171. package/src/dashboard/app/components/SearchFilter.jsx +0 -4
  172. package/src/dashboard/app/components/Sidebar.jsx +0 -5
  173. package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
  174. package/src/dashboard/app/hooks/usePref.js +1 -5
  175. package/src/dashboard/app/lib/format.js +0 -2
  176. package/src/dashboard/app/lib/phases.js +1 -5
  177. package/src/dashboard/app/lib/project.js +0 -12
  178. package/src/dashboard/app/pages/AgentMap.jsx +1 -8
  179. package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
  180. package/src/dashboard/app/pages/Features.jsx +1 -8
  181. package/src/dashboard/app/pages/Guide.jsx +0 -1
  182. package/src/dashboard/app/pages/HookInspector.jsx +2 -23
  183. package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
  184. package/src/dashboard/app/pages/Portfolio.jsx +0 -1
  185. package/src/dashboard/app/pages/RulesMap.jsx +1 -16
  186. package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
  187. package/src/dashboard/app/pages/Standards.jsx +2 -17
  188. package/src/dashboard/scanner/activity-scanner.js +10 -69
  189. package/src/dashboard/scanner/agents-scanner.js +1 -23
  190. package/src/dashboard/scanner/docs-scanner.js +0 -6
  191. package/src/dashboard/scanner/feature-status.js +2 -5
  192. package/src/dashboard/scanner/features-scanner.js +10 -66
  193. package/src/dashboard/scanner/hooks-scanner.js +5 -28
  194. package/src/dashboard/scanner/index.js +0 -4
  195. package/src/dashboard/scanner/portfolio.js +5 -29
  196. package/src/dashboard/scanner/project-api.js +8 -54
  197. package/src/dashboard/scanner/rules-scanner.js +1 -2
  198. package/src/dashboard/scanner/skills-scanner.js +2 -12
  199. package/src/dashboard/scanner/standards-scanner.js +2 -6
  200. package/src/dashboard/vite-plugin-morph-data.js +5 -19
  201. package/src/lib/ai/ai-checks.js +7 -52
  202. package/src/lib/ai/ai-project-scan.js +14 -108
  203. package/src/lib/ai/code-audit.js +7 -63
  204. package/src/lib/ai/pin-drift.js +8 -51
  205. package/src/lib/ai/registry-audit.js +11 -90
  206. package/src/lib/ci/ci-verify.js +4 -19
  207. package/src/lib/ci/github-ci.js +9 -37
  208. package/src/lib/ci/workflow-scaffold.js +2 -10
  209. package/src/lib/detectors/claude-config-detector.js +13 -74
  210. package/src/lib/detectors/index.js +4 -50
  211. package/src/lib/e2e/compose-env.js +5 -66
  212. package/src/lib/e2e/stack-teardown.js +6 -40
  213. package/src/lib/generators/architecture-scanner.js +18 -156
  214. package/src/lib/generators/handoff-generator.js +9 -53
  215. package/src/lib/generators/scaffold-generator.js +5 -101
  216. package/src/lib/generators/settings-generator.js +4 -37
  217. package/src/lib/git/branch-classifier.js +11 -84
  218. package/src/lib/git/default-branch.js +14 -169
  219. package/src/lib/git/evidence-staleness.js +1 -0
  220. package/src/lib/git/gh.js +2 -17
  221. package/src/lib/git/pending-prs.js +6 -37
  222. package/src/lib/git/pr-files.js +3 -28
  223. package/src/lib/git/pr-index.js +3 -34
  224. package/src/lib/git/tree-fingerprint.js +3 -15
  225. package/src/lib/git/tree-state.js +6 -33
  226. package/src/lib/graph/god-nodes.js +2 -22
  227. package/src/lib/graph/graph-health.js +3 -24
  228. package/src/lib/graph/graph-loader.js +5 -56
  229. package/src/lib/graph/graphify-runtime.js +7 -37
  230. package/src/lib/graph/graphifyignore.js +8 -74
  231. package/src/lib/graph/sanitize.js +16 -136
  232. package/src/lib/heavy/cleanup.js +1 -1
  233. package/src/lib/installers/effects.js +8 -93
  234. package/src/lib/installers/mcp-installer.js +9 -169
  235. package/src/lib/installers/memory-installer.js +4 -27
  236. package/src/lib/installers/templates-reconcile.js +5 -60
  237. package/src/lib/mutation/dotnet-introspection.js +20 -106
  238. package/src/lib/mutation/subset.js +1 -4
  239. package/src/lib/plan/artifact-vocabulary.js +5 -25
  240. package/src/lib/plan/checks.js +5 -31
  241. package/src/lib/plan/plan-parser.js +1 -6
  242. package/src/lib/projects/registry.js +7 -44
  243. package/src/lib/review-reports.js +5 -23
  244. package/src/lib/risk-detector.js +39 -173
  245. package/src/lib/specs/adr-index.js +6 -34
  246. package/src/lib/specs/frontmatter.js +2 -15
  247. package/src/lib/specs/gfm-table.js +1 -7
  248. package/src/lib/specs/revocations.js +1 -14
  249. package/src/lib/specs/screen-checklist.js +47 -124
  250. package/src/lib/stack/dotnet-build-ignores.js +2 -6
  251. package/src/lib/stack/stack-profile.js +1 -28
  252. package/src/lib/stack/stack-scan.js +1 -8
  253. package/src/lib/stack-filter.js +4 -27
  254. package/src/lib/standards/registry.js +1 -7
  255. package/src/lib/standards/sections.js +2 -13
  256. package/src/lib/standards/verified-against.js +19 -191
  257. package/src/lib/tasks/eval-node.js +21 -169
  258. package/src/lib/tasks/linux-runner.js +18 -142
  259. package/src/lib/tasks/test-nodes.js +43 -240
  260. package/src/lib/tasks/test-runner.js +43 -341
  261. package/src/lib/telemetry/metrics.js +7 -36
  262. package/src/lib/telemetry/telemetry-reader.js +13 -125
  263. package/src/lib/telemetry/verify-event.js +2 -30
  264. package/src/lib/validators/code-budget/code-budget-validator.js +3 -43
  265. package/src/lib/validators/code-budget/index.js +0 -7
  266. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
  267. package/src/lib/validators/contracts/index.js +0 -6
  268. package/src/lib/validators/nextjs/index.js +0 -5
  269. package/src/lib/validators/nextjs/next-component-validator.js +4 -81
  270. package/src/lib/validators/packages/index.js +0 -6
  271. package/src/lib/validators/packages/package-validator.js +6 -56
  272. package/src/lib/validators/reuse/index.js +0 -8
  273. package/src/lib/validators/reuse/reuse-validator.js +11 -101
  274. package/src/lib/validators/reuse/symbol-scanner.js +6 -62
  275. package/src/lib/validators/traceability-check.js +5 -63
  276. package/src/lib/validators/validation-runner.js +20 -125
  277. package/src/lib/validators/verify-runner.js +52 -358
  278. package/src/lib/work-type-detector.js +5 -67
  279. package/src/lib/worktree/docker-audit.js +17 -136
  280. package/src/lib/worktree/env-allocator.js +10 -98
  281. package/src/lib/worktree/feature-truth.js +12 -115
  282. package/src/lib/worktree/finish-backup.js +32 -242
  283. package/src/lib/worktree/fleet-scanner.js +13 -95
  284. package/src/lib/worktree/ignored-triage.js +15 -91
  285. package/src/lib/worktree/vscode-settings.js +2 -3
  286. package/src/lib/worktree/worktree-audit.js +21 -213
  287. package/src/lib/worktree/worktree-include.js +3 -5
  288. package/src/scripts/global-install.js +3 -9
  289. package/src/scripts/install-plugin.js +1 -13
  290. package/src/scripts/setup-infra.js +17 -147
  291. package/src/utils/agents-installer.js +28 -275
  292. package/src/utils/banner.js +4 -25
  293. package/src/utils/claude-md-injector.js +1 -37
  294. package/src/utils/claude-settings-manager.js +1 -52
  295. package/src/utils/file-copier.js +11 -95
  296. package/src/utils/hooks-installer.js +21 -298
  297. package/src/utils/skills-installer.js +4 -101
  298. package/src/utils/version-checker.js +4 -83
  299. package/framework/commands/morph-proposal.md +0 -9
  300. package/src/commands/utils/index.js +0 -3
  301. package/src/lib/detectors/design-system-detector.js +0 -188
  302. package/src/lib/generators/design-system-generator.js +0 -508
  303. package/src/lib/specs/old-format.js +0 -68
  304. package/src/utils/agent-id-mapper.js +0 -11
@@ -1,4 +1,3 @@
1
- // src/scripts/install-plugin.js
2
1
  import { join } from 'path';
3
2
  import { homedir } from 'os';
4
3
  import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
@@ -10,7 +9,6 @@ const CACHE_DIR = join(PLUGINS_DIR, 'cache', 'claude-plugins-official');
10
9
  const INSTALLED_FILE = join(PLUGINS_DIR, 'installed_plugins.json');
11
10
  const GH_REPO = 'anthropics/claude-plugins-official';
12
11
 
13
- /** Fetch JSON from GitHub API (no auth — public repo) */
14
12
  async function fetchGitHub(path) {
15
13
  return new Promise((resolve, reject) => {
16
14
  const opts = {
@@ -29,7 +27,6 @@ async function fetchGitHub(path) {
29
27
  });
30
28
  }
31
29
 
32
- /** Download raw file content from a URL, following one redirect */
33
30
  async function downloadFile(rawUrl) {
34
31
  return new Promise((resolve, reject) => {
35
32
  const doGet = (url) => {
@@ -51,25 +48,21 @@ async function downloadFile(rawUrl) {
51
48
  });
52
49
  }
53
50
 
54
- /** Read installed_plugins.json safely */
55
51
  export function readInstalledPlugins() {
56
52
  if (!existsSync(INSTALLED_FILE)) return { version: 2, plugins: {} };
57
53
  try { return JSON.parse(readFileSync(INSTALLED_FILE, 'utf8')); }
58
54
  catch { return { version: 2, plugins: {} }; }
59
55
  }
60
56
 
61
- /** Check if plugin key exists in the plugins map */
62
57
  export function isPluginInstalled(plugins, name) {
63
58
  return `${name}@claude-plugins-official` in plugins;
64
59
  }
65
60
 
66
- /** Build an installed_plugins.json entry */
67
61
  export function buildInstallEntry(pluginName, installPath, gitCommitSha, version) {
68
62
  const now = new Date().toISOString();
69
63
  return { scope: 'user', installPath, version, installedAt: now, lastUpdated: now, gitCommitSha };
70
64
  }
71
65
 
72
- /** Resolve latest commit SHA for a plugin path in the repo */
73
66
  export async function resolvePluginVersion(pluginName) {
74
67
  const commits = await fetchGitHub(`/repos/${GH_REPO}/commits?path=${pluginName}&per_page=1`);
75
68
  if (!Array.isArray(commits) || commits.length === 0) {
@@ -79,7 +72,6 @@ export async function resolvePluginVersion(pluginName) {
79
72
  return { sha, shortSha: sha.slice(0, 12) };
80
73
  }
81
74
 
82
- /** Download all files for a plugin from the repo tree */
83
75
  async function downloadPluginFiles(pluginName, sha, destDir) {
84
76
  const tree = await fetchGitHub(`/repos/${GH_REPO}/git/trees/${sha}?recursive=1`);
85
77
  if (!tree.tree) throw new Error('GitHub tree API returned unexpected shape');
@@ -98,11 +90,7 @@ async function downloadPluginFiles(pluginName, sha, destDir) {
98
90
  }
99
91
  }
100
92
 
101
- /**
102
- * Install a Claude Code plugin from GitHub.
103
- * @param {string} pluginName - e.g. "context7"
104
- * @returns {{ installed: boolean, version: string, alreadyInstalled: boolean }}
105
- */
93
+ /** @returns {{ installed: boolean, version: string, alreadyInstalled: boolean }} */
106
94
  export async function installPlugin(pluginName) {
107
95
  const data = readInstalledPlugins();
108
96
  if (isPluginInstalled(data.plugins, pluginName)) {
@@ -1,15 +1,3 @@
1
- /**
2
- * setup-infra.js
3
- *
4
- * Headless infrastructure setup for MORPH-SPEC.
5
- * Extracted from init.js (steps 1-10) so it can run as
6
- * `morph-spec setup-infra` without interactive prompts or stack detection.
7
- *
8
- * Usage:
9
- * import { setupInfra } from './setup-infra.js';
10
- * await setupInfra('/path/to/project');
11
- */
12
-
13
1
  import { join, dirname, resolve } from 'path';
14
2
  import { fileURLToPath } from 'url';
15
3
  import { execSync } from 'child_process';
@@ -32,7 +20,6 @@ import { installClaudeHooks, installGlobalStatusline } from '../utils/claude-set
32
20
  import { installSkills } from '../utils/skills-installer.js';
33
21
  import { installAgents, installDomainAgents } from '../utils/agents-installer.js';
34
22
  import { resolvePrimaryRoot } from '../commands/project/worktree.js';
35
- import { findOldFormatFeatures, oldFormatGuardLines } from '../lib/specs/old-format.js';
36
23
  import { reconcileLegacyTemplatesCopy } from '../lib/installers/templates-reconcile.js';
37
24
  import { realEffects } from '../lib/installers/effects.js';
38
25
 
@@ -54,14 +41,7 @@ async function installRequiredPlugins(log, exec) {
54
41
  }
55
42
  }
56
43
 
57
- /**
58
- * Install only stack-matching rules from rulesSrc to rulesDest.
59
- * Also removes orphan rules in rulesDest that are no longer in the install set.
60
- *
61
- * @param {string} rulesSrc - Source rules directory (framework/rules/)
62
- * @param {string} rulesDest - Destination rules directory (.claude/rules/)
63
- * @param {string[]} projectTags - Stack tags from config (e.g., ['dotnet', 'blazor'])
64
- */
44
+ /** Also removes orphan rules in rulesDest that are no longer in the install set. */
65
45
  export function installRulesForStack(rulesSrc, rulesDest, projectTags, fx = realEffects()) {
66
46
  const sourceFiles = readdirSync(rulesSrc).filter(f => f.endsWith('.md'));
67
47
  const installed = new Set();
@@ -75,9 +55,7 @@ export function installRulesForStack(rulesSrc, rulesDest, projectTags, fx = real
75
55
  }
76
56
  }
77
57
 
78
- // Cleanup orphans — calado em `dryRun` pelo mesmo motivo dos laços gêmeos em
79
- // skills-installer.js e agents-installer.js (D6): `cleanFrameworkDirs()` já
80
- // apagou `.claude/rules/` inteiro antes de este código rodar de verdade.
58
+ // Calado em `dryRun`: `cleanFrameworkDirs()` já apagou `.claude/rules/` inteiro antes da execução real.
81
59
  if (!fx.dryRun) {
82
60
  for (const file of readdirSync(rulesDest).filter(f => f.endsWith('.md'))) {
83
61
  if (!installed.has(file)) {
@@ -89,11 +67,7 @@ export function installRulesForStack(rulesSrc, rulesDest, projectTags, fx = real
89
67
  return { installed: installed.size };
90
68
  }
91
69
 
92
- /**
93
- * Slash commands filtered by `stacks:` frontmatter. Clean-slate like the
94
- * skills: removes only a framework command that the stack filtered out, never
95
- * a command the user wrote into `.claude/commands/`.
96
- */
70
+ /** Clean-slate: removes only a framework command the stack filtered out, never one the user wrote. */
97
71
  export function installCommandsForStack(commandsSrc, commandsDest, projectTags, fx = realEffects()) {
98
72
  const sourceFiles = readdirSync(commandsSrc).filter(f => f.endsWith('.md'));
99
73
  let installed = 0;
@@ -109,22 +83,13 @@ export function installCommandsForStack(commandsSrc, commandsDest, projectTags,
109
83
  return { installed };
110
84
  }
111
85
 
112
- /**
113
- * Re-install only stack-aware assets (commands, rules, skills, agents) using current config.json.
114
- * Skips infrastructure steps (dirs, framework copy, hooks).
115
- *
116
- * @param {string} targetPath - Absolute path to the target project directory
117
- * @param {Function} log - Logging function
118
- * @returns {{ agentCounts: object, domainCounts: object, projectTags: string[] }}
119
- */
120
86
  async function installStackAwareAssets(targetPath, log) {
121
87
  const configPath = join(targetPath, '.morph', 'config', 'config.json');
122
88
  let projectTags = [];
123
89
  if (await pathExists(configPath)) {
124
90
  try {
125
- // BOM-tolerant: a config.json hand-edited on Windows (PS 5.1 Out-File)
126
- // carries a UTF-8 BOM; without stripping it this parse fails silently
127
- // and only universal personas get installed.
91
+ // BOM-tolerant: a config.json written by PS 5.1 Out-File carries a BOM, and a failed parse
92
+ // here silently installs only the universal personas.
128
93
  const config = JSON.parse(readFileSync(configPath, 'utf-8').replace(/^/, ''));
129
94
  projectTags = getProjectTags(config);
130
95
  } catch { /* ignore malformed config */ }
@@ -160,20 +125,8 @@ async function installStackAwareAssets(targetPath, log) {
160
125
  }
161
126
 
162
127
  /**
163
- * Rewrite the managed personas block in .claude/CLAUDE.md with the agents
164
- * ACTUALLY installed (derived list — never a hand-written promise).
165
- * No markers / no file → no-op (fail-open).
166
- * Exported so `morph-spec update` derives the same block on upgrades.
167
- *
168
- * @param {string} claudeMdPath - Absolute path to the installed .claude/CLAUDE.md
169
- * @param {Array<{ name: string, title: string, domains: string[] }>} agents
170
- * @param {object} [options]
171
- * @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
172
- * @param {string} [options.readFrom] - De onde LER o conteúdo-base.
173
- * Num `--dry-run` o arquivo de destino ainda não recebeu a cópia que o passo
174
- * anterior previu, então ler o destino diria "sem marcadores" e o efeito de
175
- * escrita sumiria da previsão — divergindo da execução real. Lendo a MESMA
176
- * origem que seria copiada, previsão e execução decidem igual.
128
+ * No markers or no file is a no-op. `readFrom`: on `--dry-run` the destination has not received
129
+ * the predicted copy yet, so read the copy's source and let preview and real run decide alike.
177
130
  */
178
131
  export async function updateClaudeMdAgentsBlock(claudeMdPath, agents = [], options = {}) {
179
132
  const { fx = realEffects(), readFrom = claudeMdPath } = options;
@@ -196,20 +149,8 @@ export async function updateClaudeMdAgentsBlock(claudeMdPath, agents = [], optio
196
149
  }
197
150
 
198
151
  /**
199
- * Installs MORPH-SPEC infrastructure into the target project directory.
200
- * Headless — no prompts, no spinner (suppressed when MORPH_QUIET=1), no stack detection.
201
- *
202
- * @param {string} targetPath - Absolute path to the target project directory
203
- * @param {object} options
204
- * @param {Function} options._exec - exec function (for testing)
205
- * @param {Function} [options._detectGraphifyVersion] - graphify probe (for testing: the
206
- * step-18b branches differ with graphify installed or not, and CI has no Python)
207
- * @param {Function} [options._log] - log sink (for testing: replaces stdout, which the test
208
- * runner itself reports through — stubbing it swallows test results)
209
- * @param {boolean} options.reinstall - If true, only re-install stack-aware assets (rules, skills, agents)
210
- * @param {boolean} options.force - Bypass the secondary-worktree guard (see below). Callers
211
- * (e.g. `initCommand`) that already performed their own equivalent check pass this so the
212
- * check isn't redundantly re-applied here.
152
+ * Headless install: no prompts, no stack detection. `force` skips the secondary-worktree guard
153
+ * for callers that already ran an equivalent check.
213
154
  */
214
155
  export async function setupInfra(targetPath, {
215
156
  _exec = execSync, _detectGraphifyVersion = null, _log = null, reinstall = false, force = false,
@@ -221,16 +162,9 @@ export async function setupInfra(targetPath, {
221
162
  else if (!quiet) process.stdout.write(msg + '\n');
222
163
  }
223
164
 
224
- // Guard: refuse a FULL install from inside a secondary (linked) worktree.
225
- // `.claude/` and `.morph/framework/` are junctions back to the primary root
226
- // there, but `.morph/config/`, `.morph/context/`, `.morph/checkpoints/`,
227
- // `.morph/memory/`, `.morph/archive/` and `.morph/state.json` are NOT — a
228
- // full install would scaffold a second, disconnected config/state tree
229
- // inside the worktree. `reinstall` mode is exempt: it only touches
230
- // .claude/* stack-aware assets, which are the junctioned (safe) subset.
231
- // `resolvePrimaryRoot` fails open to `targetPath` itself outside a git repo
232
- // (or on any git error), so non-git projects and the primary root are
233
- // never blocked.
165
+ // In a linked worktree only .claude/ and .morph/framework/ are junctions to the root: a full
166
+ // install would scaffold a second, disconnected config/state tree. `reinstall` touches only
167
+ // the junctioned subset.
234
168
  if (!reinstall && !force) {
235
169
  const target = resolve(targetPath).replace(/\\/g, '/');
236
170
  const root = resolvePrimaryRoot(targetPath);
@@ -242,20 +176,9 @@ export async function setupInfra(targetPath, {
242
176
  }
243
177
  }
244
178
 
245
- // Same refusal as `update`, and neither `force` nor `reinstall` waives it:
246
- // `init --force` and `setup-infra` overwrite `.morph/` without passing through it.
247
- const oldFormat = findOldFormatFeatures(targetPath);
248
- if (oldFormat.length) {
249
- for (const line of oldFormatGuardLines(oldFormat, targetPath)) log(line);
250
- return { refused: true, reason: 'old-format-features', features: oldFormat };
251
- }
252
-
253
- // --- Reinstall mode: only re-install stack-aware assets ---
254
179
  if (reinstall) {
255
180
  log('setup-infra (reinstall): re-installing stack-aware assets...');
256
181
  const { agentCounts, domainCounts } = await installStackAwareAssets(targetPath, log);
257
- // Keep the derived personas list in .claude/CLAUDE.md in sync with what
258
- // was actually (re)installed.
259
182
  await updateClaudeMdAgentsBlock(join(targetPath, '.claude', 'CLAUDE.md'), agentCounts?.agents ?? []);
260
183
  log('setup-infra (reinstall): complete.');
261
184
  return {
@@ -267,17 +190,14 @@ export async function setupInfra(targetPath, {
267
190
  };
268
191
  }
269
192
 
270
- // --- 0. Install required Claude Code plugins ---
271
193
  await installRequiredPlugins(log, _exec);
272
194
 
273
- // --- 1. Inject @import into root CLAUDE.md (preserves user content) ---
274
195
  log('Step 1: Setting up CLAUDE.md...');
275
196
  const claudeMdSrc = join(FRAMEWORK_DIR, 'CLAUDE.md');
276
197
  const claudeMdDest = join(targetPath, 'CLAUDE.md');
277
198
  const importResult = await injectMorphImport(claudeMdDest);
278
199
  log(` ✓ Root CLAUDE.md: ${importResult}`);
279
200
 
280
- // --- 2. Create .morph directory structure ---
281
201
  log('Step 2: Creating .morph structure...');
282
202
  const morphPath = join(targetPath, '.morph');
283
203
  const configDir = join(morphPath, 'config');
@@ -296,7 +216,6 @@ export async function setupInfra(targetPath, {
296
216
  await ensureDir(memoryDir);
297
217
  await ensureDir(archiveDir);
298
218
 
299
- // --- 3. Write minimal config.json (only if not exists) ---
300
219
  log('Step 3: Writing config.json...');
301
220
  const configPath = join(configDir, 'config.json');
302
221
  if (!(await pathExists(configPath))) {
@@ -312,7 +231,6 @@ export async function setupInfra(targetPath, {
312
231
  await writeJson(configPath, config);
313
232
  }
314
233
 
315
- // --- 4. Write placeholder context/README.md (only if not exists) ---
316
234
  log('Step 4: Writing context/README.md...');
317
235
  const contextReadmePath = join(contextDir, 'README.md');
318
236
  if (!(await pathExists(contextReadmePath))) {
@@ -321,27 +239,9 @@ export async function setupInfra(targetPath, {
321
239
  await writeFile(contextReadmePath, readmeContent);
322
240
  }
323
241
 
324
- // --- 5. framework/templates is intentionally NOT copied to
325
- // .morph/framework/templates anymore (R3 perf plan, Task 2). Template
326
- // resolution is layered (template-registry.js, architecture-scanner.js
327
- // resolveTemplatePath): a project-local .morph/framework/templates/{path}
328
- // is still checked FIRST as an explicit override, then falls back to the
329
- // npm package's own framework/templates/ — which is what every template
330
- // actually rendered from until now anyway, since the installed copy was
331
- // never read as anything but that override. Pre-populating it was a
332
- // byte-for-byte redundant 348KB copy on every install/update.
333
242
  log('Step 5: Skipping framework templates copy (resolved from package; local override still supported)...');
334
- // Cópia legada de instalação pré-R3: se existir, ela SOMBREIA os templates
335
- // do pacote (override-first). `update` sempre reconcilia porque
336
- // checkProjectOutdated() já leu a versão anterior do projeto antes de
337
- // chamar update. Aqui (init/setup-infra) fazemos o mesmo QUANDO a mesma
338
- // fonte está disponível: getProjectMorphVersion() lê .morph/.morphversion
339
- // — o MESMO arquivo/campo que checkProjectOutdated().current usa — e essa
340
- // leitura acontece ANTES do Passo 17 (saveProjectMorphVersion) sobrescrever
341
- // esse arquivo com a versão atual, então "versão anterior" aqui é real, não
342
- // a que este próprio run está prestes a gravar. Sem esse registro (projeto
343
- // nunca rastreado antes, ou greenfield), não há fonte segura — mantém o
344
- // aviso como fallback, igual ao comportamento anterior.
243
+ // A cópia legada SOMBREIA os templates do pacote (override-first). A versão anterior é lida
244
+ // aqui, antes de o passo 17 regravar .morph/.morphversion; sem ela, não há fonte segura e só avisa.
345
245
  const legacyTemplatesDir = join(frameworkDestDir, 'templates');
346
246
  if (await pathExists(legacyTemplatesDir)) {
347
247
  const priorVersionInfo = await getProjectMorphVersion(targetPath);
@@ -357,7 +257,6 @@ export async function setupInfra(targetPath, {
357
257
  }
358
258
  }
359
259
 
360
- // --- 6. Copy framework/standards → .morph/framework/standards ---
361
260
  log('Step 6: Copying framework standards...');
362
261
  const frameworkStandardsSrc = join(FRAMEWORK_DIR, 'standards');
363
262
  const frameworkStandardsDest = join(frameworkDestDir, 'standards');
@@ -365,7 +264,6 @@ export async function setupInfra(targetPath, {
365
264
  await copyDirectory(frameworkStandardsSrc, frameworkStandardsDest);
366
265
  }
367
266
 
368
- // --- 7. Copy framework/hooks → .morph/framework/hooks ---
369
267
  log('Step 7: Copying hooks...');
370
268
  const hooksSrc = join(FRAMEWORK_DIR, 'hooks');
371
269
  const hooksDest = join(frameworkDestDir, 'hooks');
@@ -373,7 +271,6 @@ export async function setupInfra(targetPath, {
373
271
  await copyDirectory(hooksSrc, hooksDest);
374
272
  }
375
273
 
376
- // --- 7c. Copy framework/evals → .morph/framework/evals ---
377
274
  log('Step 7c: Copying evals (review checklist + trace schema)...');
378
275
  const evalsSrc = join(FRAMEWORK_DIR, 'evals');
379
276
  const evalsDest = join(frameworkDestDir, 'evals');
@@ -381,7 +278,6 @@ export async function setupInfra(targetPath, {
381
278
  await copyDirectory(evalsSrc, evalsDest);
382
279
  }
383
280
 
384
- // --- 8. Copy framework/agents.json → .morph/framework/agents.json ---
385
281
  log('Step 8: Copying agents.json...');
386
282
  const agentsSrc = join(FRAMEWORK_DIR, 'agents.json');
387
283
  const agentsDest = join(frameworkDestDir, 'agents.json');
@@ -389,7 +285,6 @@ export async function setupInfra(targetPath, {
389
285
  await copyFile(agentsSrc, agentsDest);
390
286
  }
391
287
 
392
- // --- 8b. Copy framework/MORPH.md → .morph/framework/MORPH.md ---
393
288
  log('Step 8b: Copying MORPH.md (NLH charter)...');
394
289
  const morphMdSrc = join(FRAMEWORK_DIR, 'MORPH.md');
395
290
  const morphMdDest = join(frameworkDestDir, 'MORPH.md');
@@ -397,9 +292,6 @@ export async function setupInfra(targetPath, {
397
292
  await copyFile(morphMdSrc, morphMdDest);
398
293
  }
399
294
 
400
- // --- 8b2. Copy framework/docs/CLI.md → .morph/framework/CLI.md ---
401
- // Referência completa dos comandos (flags, semântica, exemplos) — o
402
- // CLAUDE.md residente carrega só a tabela compacta e aponta para cá.
403
295
  log('Step 8b2: Copying CLI.md (command reference)...');
404
296
  const cliMdSrc = join(FRAMEWORK_DIR, 'docs', 'CLI.md');
405
297
  const cliMdDest = join(frameworkDestDir, 'CLI.md');
@@ -407,7 +299,6 @@ export async function setupInfra(targetPath, {
407
299
  await copyFile(cliMdSrc, cliMdDest);
408
300
  }
409
301
 
410
- // --- 8c. Copy framework/phases.json → .morph/framework/phases.json ---
411
302
  log('Step 8c: Copying phases.json...');
412
303
  const phasesSrc = join(FRAMEWORK_DIR, 'phases.json');
413
304
  const phasesDest = join(frameworkDestDir, 'phases.json');
@@ -415,7 +306,6 @@ export async function setupInfra(targetPath, {
415
306
  await copyFile(phasesSrc, phasesDest);
416
307
  }
417
308
 
418
- // --- 8d. Copy framework/schemas → .morph/framework/schemas (JSON schemas) ---
419
309
  log('Step 8d: Copying schemas...');
420
310
  const schemasSrc = join(FRAMEWORK_DIR, 'schemas');
421
311
  const schemasDest = join(frameworkDestDir, 'schemas');
@@ -425,20 +315,16 @@ export async function setupInfra(targetPath, {
425
315
 
426
316
  const claudeDest = join(targetPath, '.claude');
427
317
 
428
- // --- 9-13. Install stack-aware assets (commands, rules, skills, agents) ---
429
318
  log('Steps 10-13: Installing stack-aware assets...');
430
319
  const { agentCounts, domainCounts, projectTags } = await installStackAwareAssets(targetPath, log);
431
320
 
432
- // --- 14. Copy framework/CLAUDE.md → .claude/CLAUDE.md (morph-owned, gitignored) ---
433
321
  log('Step 14: Installing .claude/CLAUDE.md...');
434
322
  const runtimeClaudeMdDest = join(claudeDest, 'CLAUDE.md');
435
323
  if (await pathExists(claudeMdSrc)) {
436
324
  await copyFile(claudeMdSrc, runtimeClaudeMdDest);
437
- // Derive the personas section from what step 10-13 actually installed.
438
325
  await updateClaudeMdAgentsBlock(runtimeClaudeMdDest, agentCounts?.agents ?? []);
439
326
  }
440
327
 
441
- // --- 15. installGlobalStatusline (non-critical) ---
442
328
  log('Step 15: Installing global statusline...');
443
329
  const HOOKS_SRC = join(FRAMEWORK_DIR, 'hooks', 'claude-code');
444
330
  try {
@@ -447,16 +333,13 @@ export async function setupInfra(targetPath, {
447
333
  // Non-critical — global ~/.claude may not be writable
448
334
  }
449
335
 
450
- // --- 16. installClaudeHooks ---
451
336
  log('Step 16: Installing Claude Code hooks...');
452
337
  await installClaudeHooks(targetPath);
453
338
 
454
- // --- 17. saveProjectMorphVersion ---
455
339
  log('Step 17: Saving version info...');
456
340
  const cliVersion = getInstalledCLIVersion();
457
341
  await saveProjectMorphVersion(targetPath, cliVersion);
458
342
 
459
- // --- 18. updateGitignore + .gitattributes ---
460
343
  log('Step 18: Updating .gitignore + .gitattributes...');
461
344
  await updateGitignore(targetPath);
462
345
  await ensureGitattributes(targetPath);
@@ -464,10 +347,7 @@ export async function setupInfra(targetPath, {
464
347
  log('Step 18a: Editor excludes for worktrees...');
465
348
  try { applyEditorSettings(targetPath); } catch { /* fail-open */ }
466
349
 
467
- // --- 18b. Codebase graph (graphify) ---
468
- // Fail-open por inteiro (ADR-002): sem Python, sem uv e sem pipx, o step
469
- // registra e segue. O wrapper de CLI mata o processo em qualquer throw
470
- // (setup-infra-cmd.js), então nada aqui pode escapar do try.
350
+ // Fail-open por inteiro: o wrapper de CLI mata o processo em qualquer throw.
471
351
  log('Step 18b: Codebase graph (graphify)...');
472
352
  try {
473
353
  const { writeGraphifyignore, describeOutdatedIgnore } = await import('../lib/graph/graphifyignore.js');
@@ -495,7 +375,6 @@ export async function setupInfra(targetPath, {
495
375
  } else {
496
376
  const { kind } = detectInstaller();
497
377
  if (!kind) {
498
- // Registra e segue: o framework funciona sem grafo, por grep.
499
378
  log(` ⚠ Sem uv/pipx/python — grafo indisponível. Depois: uv tool install ${GRAPHIFY_PACKAGE}==${GRAPHIFY_VERSION}`);
500
379
  } else {
501
380
  // Sem o extra [sql] os arquivos .sql são ignorados em silêncio.
@@ -510,16 +389,8 @@ export async function setupInfra(targetPath, {
510
389
  }
511
390
  }
512
391
 
513
- // Nenhum hook do graphify: o refresh mora nos hooks do Claude (step 16 —
514
- // graph-refresh-kick + SessionStart), que o `update` reinstala. O
515
- // `graphify hook install` ficou de fora por três defeitos medidos no 0.9.50:
516
- // sai sem fazer nada em worktree vinculada, não tem post-merge (merge e pull
517
- // nunca rebuildam) e, num commit depois de um fast-forward, carimba o HEAD
518
- // num grafo sem os arquivos do merge. E nunca `graphify claude install`:
519
- // escreveria no CLAUDE.md e um PreToolUse próprio.
520
- //
521
- // A linha só sai com o graphify presente, e diz o que dispara de fato: um
522
- // commit feito no terminal não dispara nada até o próximo desses.
392
+ // Sem hook git do graphify: o refresh mora nos hooks do Claude (step 16). O `graphify hook install`
393
+ // 0.9.50 não roda em worktree vinculada, não tem post-merge e carimba HEAD num grafo sem o merge.
523
394
  if (probe()) {
524
395
  log(' ✓ refresh automático do grafo da raiz: depois dos comandos git/gh/morph-spec do agente e no início de cada sessão');
525
396
  }
@@ -527,7 +398,6 @@ export async function setupInfra(targetPath, {
527
398
  log(` ⚠ Step do grafo pulado: ${err.message}`);
528
399
  }
529
400
 
530
- // --- 19. Initialize state.json (only if not exists) ---
531
401
  log('Step 19: Initializing state.json...');
532
402
  const statePath = join(morphPath, 'state.json');
533
403
  if (!(await pathExists(statePath))) {