infra-kit 0.1.113 → 0.1.115

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 (218) hide show
  1. package/.eslintcache +1 -1
  2. package/.omc/state/agent-replay-0d1515ce-4ebe-4ce8-a865-2897f68de168.jsonl +1 -0
  3. package/.omc/state/agent-replay-1fcfe0ec-7567-41c3-9bfd-501e99e86819.jsonl +19 -0
  4. package/.omc/state/agent-replay-38382db5-3088-4b5a-aff7-2561a60827c7.jsonl +17 -0
  5. package/.omc/state/agent-replay-50c36be9-a3a2-42b9-9c85-5a06d3e22f6a.jsonl +5 -0
  6. package/.omc/state/agent-replay-8198b3e5-fad6-4ee0-9c59-ef773ad89af4.jsonl +1 -0
  7. package/.omc/state/agent-replay-8cd7652a-b8b5-49cf-9b1f-bee8e5972458.jsonl +3 -0
  8. package/.omc/state/agent-replay-9bea518f-a9ee-4d14-bb53-a936ebaf647c.jsonl +7 -0
  9. package/.omc/state/agent-replay-b52d89fb-0e24-4901-bc66-27e3c1697126.jsonl +2 -0
  10. package/.omc/state/agent-replay-f581a1ae-0ee4-48a3-96f3-ce31a0633bb0.jsonl +1 -0
  11. package/.omc/state/agent-replay-f99d8dad-81d1-4987-8183-62edf0843d0c.jsonl +2 -0
  12. package/.omc/state/agent-replay-fe1057b4-9a25-4e8e-8c3f-3e11652ee697.jsonl +5 -0
  13. package/.omc/state/idle-notif-cooldown.json +1 -1
  14. package/.omc/state/sessions/0d1515ce-4ebe-4ce8-a865-2897f68de168/pre-tool-advisory-throttle.json +10 -0
  15. package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/last-tool-error-state.json +7 -0
  16. package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/mission-state.json +109 -0
  17. package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/pre-tool-advisory-throttle.json +42 -0
  18. package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/ralph-state.json +13 -0
  19. package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/skill-active-state.json +15 -0
  20. package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/subagent-tracking-state.json +62 -0
  21. package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/ultrawork-state.json +11 -0
  22. package/.omc/state/sessions/23d0ff82-17e5-428b-8743-38f7979bb6f3/last-tool-error-state.json +7 -0
  23. package/.omc/state/sessions/23d0ff82-17e5-428b-8743-38f7979bb6f3/pre-tool-advisory-throttle.json +18 -0
  24. package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/last-tool-error-state.json +7 -0
  25. package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/mission-state.json +145 -0
  26. package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/pre-tool-advisory-throttle.json +38 -0
  27. package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/ralplan-state.json +11 -0
  28. package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/subagent-tracking-state.json +98 -0
  29. package/.omc/state/sessions/41336850-028c-476b-be1a-2164e1e46cac/last-tool-error-state.json +7 -0
  30. package/.omc/state/sessions/41336850-028c-476b-be1a-2164e1e46cac/pre-tool-advisory-throttle.json +18 -0
  31. package/.omc/state/sessions/50c36be9-a3a2-42b9-9c85-5a06d3e22f6a/mission-state.json +61 -0
  32. package/.omc/state/sessions/50c36be9-a3a2-42b9-9c85-5a06d3e22f6a/pre-tool-advisory-throttle.json +22 -0
  33. package/.omc/state/sessions/50c36be9-a3a2-42b9-9c85-5a06d3e22f6a/skill-active-state.json +15 -0
  34. package/.omc/state/sessions/50c36be9-a3a2-42b9-9c85-5a06d3e22f6a/subagent-tracking-state.json +26 -0
  35. package/.omc/state/sessions/560ea519-574a-4713-b134-5bf6cfaf3c33/pre-tool-advisory-throttle.json +18 -0
  36. package/.omc/state/sessions/8198b3e5-fad6-4ee0-9c59-ef773ad89af4/pre-tool-advisory-throttle.json +14 -0
  37. package/.omc/state/sessions/8198b3e5-fad6-4ee0-9c59-ef773ad89af4/subagent-tracking-state.json +17 -0
  38. package/.omc/state/sessions/8cd7652a-b8b5-49cf-9b1f-bee8e5972458/pre-tool-advisory-throttle.json +18 -0
  39. package/.omc/state/sessions/8cd7652a-b8b5-49cf-9b1f-bee8e5972458/ralplan-state.json +11 -0
  40. package/.omc/state/sessions/8cd7652a-b8b5-49cf-9b1f-bee8e5972458/subagent-tracking-state.json +35 -0
  41. package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/last-tool-error-state.json +7 -0
  42. package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/mission-state.json +79 -0
  43. package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/pre-tool-advisory-throttle.json +22 -0
  44. package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/progress.txt +7 -0
  45. package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/skill-active-state.json +15 -0
  46. package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/subagent-tracking-state.json +26 -0
  47. package/.omc/state/sessions/b52d89fb-0e24-4901-bc66-27e3c1697126/pre-tool-advisory-throttle.json +14 -0
  48. package/.omc/state/sessions/b52d89fb-0e24-4901-bc66-27e3c1697126/subagent-tracking-state.json +17 -0
  49. package/.omc/state/sessions/c513ef38-cc3e-4a29-9c4c-1e0137245dd5/pre-tool-advisory-throttle.json +18 -0
  50. package/.omc/state/sessions/ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a/pre-tool-advisory-throttle.json +18 -0
  51. package/.omc/state/sessions/f581a1ae-0ee4-48a3-96f3-ce31a0633bb0/pre-tool-advisory-throttle.json +18 -0
  52. package/.omc/state/sessions/f581a1ae-0ee4-48a3-96f3-ce31a0633bb0/subagent-tracking-state.json +17 -0
  53. package/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/pre-tool-advisory-throttle.json +14 -0
  54. package/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/ralph-state.json +13 -0
  55. package/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/ultrawork-state.json +11 -0
  56. package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/last-tool-error-state.json +7 -0
  57. package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/mission-state.json +61 -0
  58. package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/prd.json +22 -0
  59. package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/pre-tool-advisory-throttle.json +22 -0
  60. package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/progress.txt +22 -0
  61. package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/skill-active-state.json +15 -0
  62. package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/subagent-tracking-state.json +26 -0
  63. package/.turbo/turbo-build.log +3 -3
  64. package/.turbo/turbo-test.log +92 -44
  65. package/dist/boot-K4QUAFSP.js +2 -0
  66. package/dist/boot-K4QUAFSP.js.map +7 -0
  67. package/dist/chunk-RTSP2FCA.js +2 -0
  68. package/dist/chunk-RTSP2FCA.js.map +7 -0
  69. package/dist/chunk-VPBZR2PY.js +166 -0
  70. package/dist/chunk-VPBZR2PY.js.map +7 -0
  71. package/dist/cli.js +11 -107
  72. package/dist/cli.js.map +4 -4
  73. package/dist/entry/index.d.ts +2 -0
  74. package/dist/index.js +1 -1
  75. package/dist/index.js.map +4 -4
  76. package/dist/lib/vendor/config-schema.d.ts +58 -0
  77. package/dist/mcp.js +1 -54
  78. package/dist/mcp.js.map +4 -4
  79. package/eslint.config.js +33 -1
  80. package/package.json +5 -1
  81. package/scripts/build.js +11 -1
  82. package/src/.omc/state/agent-replay-c513ef38-cc3e-4a29-9c4c-1e0137245dd5.jsonl +4 -0
  83. package/src/.omc/state/agent-replay-ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a.jsonl +2 -0
  84. package/src/.omc/state/idle-notif-cooldown.json +1 -1
  85. package/src/.omc/state/sessions/c513ef38-cc3e-4a29-9c4c-1e0137245dd5/mission-state.json +79 -0
  86. package/src/.omc/state/sessions/c513ef38-cc3e-4a29-9c4c-1e0137245dd5/pre-tool-advisory-throttle.json +22 -0
  87. package/src/.omc/state/sessions/c513ef38-cc3e-4a29-9c4c-1e0137245dd5/subagent-tracking-state.json +26 -0
  88. package/src/.omc/state/sessions/ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a/pre-tool-advisory-throttle.json +18 -0
  89. package/src/.omc/state/sessions/ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a/ralph-state.json +13 -0
  90. package/src/.omc/state/sessions/ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a/ultrawork-state.json +11 -0
  91. package/src/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/pre-tool-advisory-throttle.json +14 -0
  92. package/src/commands/config/.omc/state/sessions/41336850-028c-476b-be1a-2164e1e46cac/last-tool-error-state.json +7 -0
  93. package/src/commands/config/.omc/state/sessions/41336850-028c-476b-be1a-2164e1e46cac/pre-tool-advisory-throttle.json +18 -0
  94. package/src/commands/config/config.ts +7 -37
  95. package/src/commands/doctor/__tests__/agent-files.test.ts +8 -30
  96. package/src/commands/doctor/__tests__/check-legacy-user-global-config.test.ts +110 -0
  97. package/src/commands/doctor/doctor.ts +110 -44
  98. package/src/commands/env-autoload/env-autoload.ts +12 -0
  99. package/src/commands/env-autoload/index.ts +1 -0
  100. package/src/commands/env-clear/__tests__/env-clear.test.ts +27 -0
  101. package/src/commands/env-clear/env-clear.ts +22 -8
  102. package/src/commands/env-load/__tests__/env-load.test.ts +36 -1
  103. package/src/commands/env-load/env-load.ts +152 -35
  104. package/src/commands/env-load/index.ts +2 -1
  105. package/src/commands/env-status/__tests__/env-status.test.ts +78 -0
  106. package/src/commands/env-status/env-status.ts +19 -7
  107. package/src/commands/gh-release-deploy-all/gh-release-deploy-all.ts +45 -1
  108. package/src/commands/gh-release-deploy-selected/gh-release-deploy-selected.ts +46 -1
  109. package/src/commands/init/__tests__/agent-files.test.ts +145 -27
  110. package/src/commands/init/__tests__/migrate-config.test.ts +136 -5
  111. package/src/commands/init/__tests__/migrate-factory-config.test.ts +93 -0
  112. package/src/commands/init/__tests__/seed-user-global-config.test.ts +64 -0
  113. package/src/commands/init/__tests__/shell-body.test.ts +30 -0
  114. package/src/commands/init/agent-files.ts +65 -45
  115. package/src/commands/init/init.ts +140 -31
  116. package/src/commands/init/migrate-config.ts +160 -32
  117. package/src/commands/vendor-check/__tests__/runtime-isolation.test.ts +71 -0
  118. package/src/commands/vendor-check/__tests__/vendor-check.test.ts +87 -0
  119. package/src/commands/vendor-check/index.ts +1 -0
  120. package/src/commands/vendor-check/vendor-check.ts +127 -0
  121. package/src/commands/vendor-config/__tests__/vendor-config.test.ts +103 -0
  122. package/src/commands/vendor-config/index.ts +1 -0
  123. package/src/commands/vendor-config/vendor-config.ts +162 -0
  124. package/src/commands/vendor-diff/__tests__/vendor-diff.test.ts +74 -0
  125. package/src/commands/vendor-diff/index.ts +1 -0
  126. package/src/commands/vendor-diff/vendor-diff.ts +77 -0
  127. package/src/commands/vendor-manifest/index.ts +1 -0
  128. package/src/commands/vendor-manifest/vendor-manifest.ts +60 -0
  129. package/src/commands/vendor-sync/__tests__/vendor-write.test.ts +86 -0
  130. package/src/commands/vendor-sync/index.ts +1 -0
  131. package/src/commands/vendor-sync/vendor-sync.ts +90 -0
  132. package/src/commands/worktrees-add/worktrees-add.ts +1 -1
  133. package/src/commands/worktrees-reload/worktrees-reload.ts +6 -5
  134. package/src/commands/worktrees-remove/worktrees-remove.ts +10 -1
  135. package/src/commands/worktrees-sync/worktrees-sync.ts +9 -1
  136. package/src/entry/cli.ts +417 -210
  137. package/src/entry/index.ts +7 -1
  138. package/src/integrations/cmux/__tests__/open-workspace-with-layout.test.ts +127 -0
  139. package/src/integrations/cmux/close-workspace-by-title.ts +2 -2
  140. package/src/integrations/cmux/list-workspace-titles.ts +1 -1
  141. package/src/integrations/cmux/open-workspace-with-layout.ts +15 -4
  142. package/src/integrations/cursor/.omc/state/agent-replay-f99d8dad-81d1-4987-8183-62edf0843d0c.jsonl +2 -0
  143. package/src/integrations/cursor/.omc/state/idle-notif-cooldown.json +3 -0
  144. package/src/integrations/cursor/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/pre-tool-advisory-throttle.json +18 -0
  145. package/src/integrations/cursor/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/subagent-tracking-state.json +17 -0
  146. package/src/integrations/cursor/index.ts +1 -0
  147. package/src/integrations/cursor/launch-cursor.ts +12 -0
  148. package/src/integrations/cursor/open-cursor-workspace.ts +8 -14
  149. package/src/integrations/doppler/__tests__/doppler-errors.test.ts +76 -0
  150. package/src/integrations/doppler/doppler-cli-auth.ts +12 -4
  151. package/src/integrations/doppler/doppler-errors.ts +88 -0
  152. package/src/integrations/doppler/doppler-project.ts +80 -0
  153. package/src/integrations/doppler/index.ts +3 -1
  154. package/src/integrations/gh/gh-cli-auth/gh-cli-auth.ts +9 -5
  155. package/src/integrations/gh/gh-release-prs/gh-release-prs.ts +16 -8
  156. package/src/integrations/ide/__tests__/ide-facade.test.ts +57 -10
  157. package/src/integrations/ide/add-ide-worktree-folders.ts +2 -4
  158. package/src/integrations/ide/open-ide-workspace.ts +6 -6
  159. package/src/integrations/ide/remove-ide-worktree-folders.ts +95 -30
  160. package/src/integrations/ide/types.ts +10 -1
  161. package/src/integrations/zed/__tests__/open-zed-workspace.test.ts +1 -17
  162. package/src/integrations/zed/__tests__/reuse-zed-workspace.test.ts +86 -0
  163. package/src/integrations/zed/index.ts +1 -0
  164. package/src/integrations/zed/open-zed-workspace.ts +4 -10
  165. package/src/integrations/zed/reuse-zed-workspace.ts +57 -0
  166. package/src/lib/command-catalog/__tests__/__snapshots__/command-catalog.test.ts.snap +212 -0
  167. package/src/lib/command-catalog/__tests__/command-catalog.test.ts +193 -0
  168. package/src/lib/command-catalog/command-catalog.ts +141 -0
  169. package/src/lib/command-catalog/index.ts +2 -0
  170. package/src/lib/command-echo/__tests__/command-echo.test.ts +127 -0
  171. package/src/lib/constants/constants.ts +13 -0
  172. package/src/lib/constants/index.ts +2 -0
  173. package/src/lib/env-autoload/__tests__/env-autoload.test.ts +288 -0
  174. package/src/lib/env-autoload/env-autoload.ts +294 -0
  175. package/src/lib/env-autoload/index.ts +9 -0
  176. package/src/lib/errors/operation-error.ts +1 -1
  177. package/src/lib/infra-kit-config/__tests__/env-autoload-schema.test.ts +71 -0
  178. package/src/lib/infra-kit-config/__tests__/infra-kit-config.test.ts +77 -6
  179. package/src/lib/infra-kit-config/index.ts +9 -1
  180. package/src/lib/infra-kit-config/infra-kit-config.ts +71 -11
  181. package/src/lib/json-output/__tests__/json-output.test.ts +139 -0
  182. package/src/lib/json-output/index.ts +1 -0
  183. package/src/lib/json-output/json-output.ts +47 -0
  184. package/src/lib/managed-block/managed-block.ts +4 -4
  185. package/src/lib/path-display/index.ts +1 -0
  186. package/src/lib/path-display/path-display.ts +42 -0
  187. package/src/lib/render/__tests__/render.test.ts +48 -0
  188. package/src/lib/render/index.ts +1 -0
  189. package/src/lib/render/render.ts +24 -0
  190. package/src/lib/vendor/__tests__/config-schema.test.ts +31 -0
  191. package/src/lib/vendor/__tests__/config.test.ts +63 -0
  192. package/src/lib/vendor/__tests__/factory-config.test.ts +92 -0
  193. package/src/lib/vendor/__tests__/vendor-lib.test.ts +158 -0
  194. package/src/lib/vendor/__tests__/zx-mock.ts +32 -0
  195. package/src/lib/vendor/config-schema.ts +78 -0
  196. package/src/lib/vendor/config.ts +58 -0
  197. package/src/lib/vendor/factory-config-schema.ts +35 -0
  198. package/src/lib/vendor/factory-config.ts +91 -0
  199. package/src/lib/vendor/hash.ts +16 -0
  200. package/src/lib/vendor/index.ts +23 -0
  201. package/src/lib/vendor/manifest.ts +151 -0
  202. package/src/lib/vendor/skip-sets.ts +48 -0
  203. package/src/lib/vendor/sync-ops.ts +290 -0
  204. package/src/lib/vendor/walk.ts +56 -0
  205. package/src/lib/worktrees/remove-worktrees.ts +15 -0
  206. package/src/mcp/tools/index.ts +5 -42
  207. package/src/sanity.test.tsx +1 -0
  208. package/src/tui/__tests__/no-react-boundary.test.ts +51 -0
  209. package/src/tui/boot.tsx +45 -0
  210. package/src/tui/screens/__tests__/command-palette.test.tsx +136 -0
  211. package/src/tui/screens/command-palette.tsx +159 -0
  212. package/src/tui/types.ts +8 -0
  213. package/tsconfig.json +1 -0
  214. package/tsconfig.tsbuildinfo +1 -1
  215. package/vitest.config.ts +8 -0
  216. package/src/commands/worktrees-open/__tests__/open-cmux.test.ts +0 -96
  217. package/src/commands/worktrees-open/index.ts +0 -1
  218. package/src/commands/worktrees-open/worktrees-open.ts +0 -195
@@ -6,7 +6,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
6
6
  // Import AFTER the mock is declared so the module picks up the mocked dep.
7
7
  import { getProjectRoot, getRepoName } from 'src/lib/git-utils'
8
8
 
9
- import { migrateLegacyConfig, normalizeLegacyIdeStructures } from '../migrate-config'
9
+ import { migrateLegacyConfig, migrateUserGlobalConfigFilename, normalizeLegacyIdeStructures } from '../migrate-config'
10
10
 
11
11
  vi.mock('src/lib/git-utils', () => {
12
12
  return {
@@ -105,8 +105,10 @@ describe('migrateLegacyConfig', () => {
105
105
  await withTmpRepo(async (tmp) => {
106
106
  const mainYml = path.join(tmp, 'infra-kit.yml')
107
107
  const mainJson = path.join(tmp, 'infra-kit.json')
108
+ // The user-global legacy YAML keeps the old `config.yml` name; its JSON
109
+ // target is the renamed canonical `infra-kit.json`.
108
110
  const userGlobalYml = path.join(tmp, '.infra-kit', 'config.yml')
109
- const userGlobalJson = path.join(tmp, '.infra-kit', 'config.json')
111
+ const userGlobalJson = path.join(tmp, '.infra-kit', 'infra-kit.json')
110
112
 
111
113
  writeFile(mainYml, MAIN_YML)
112
114
  // Invalid override: environments present but empty (min(1) fails).
@@ -151,9 +153,17 @@ describe('migrateLegacyConfig', () => {
151
153
 
152
154
  await migrateLegacyConfig()
153
155
 
154
- for (const yml of [mainYml, userGlobalYml, userProjectYml]) {
156
+ // The user-global legacy `config.yml` migrates to the renamed canonical
157
+ // `infra-kit.json`; main/userProject keep their `.yml`→`.json` sibling name.
158
+ const pairs: [yml: string, json: string][] = [
159
+ [mainYml, path.join(tmp, 'infra-kit.json')],
160
+ [userGlobalYml, path.join(tmp, '.infra-kit', 'infra-kit.json')],
161
+ [userProjectYml, path.join(tmp, '.infra-kit', 'projects', projectName, 'infra-kit.json')],
162
+ ]
163
+
164
+ for (const [yml, json] of pairs) {
155
165
  expect(fs.existsSync(yml)).toBe(false)
156
- expect(fs.existsSync(yml.replace(/\.yml$/, '.json'))).toBe(true)
166
+ expect(fs.existsSync(json)).toBe(true)
157
167
  }
158
168
  })
159
169
  })
@@ -221,7 +231,7 @@ describe('normalizeLegacyIdeStructures', () => {
221
231
 
222
232
  it('normalizes the user-global config layer too', async () => {
223
233
  await withTmpRepo(async (tmp) => {
224
- const userGlobalJson = path.join(tmp, '.infra-kit', 'config.json')
234
+ const userGlobalJson = path.join(tmp, '.infra-kit', 'infra-kit.json')
225
235
 
226
236
  writeFile(userGlobalJson, JSON.stringify({ ide: { provider: 'zed', config: { mode: 'windows' } } }))
227
237
 
@@ -289,3 +299,124 @@ describe('normalizeLegacyIdeStructures', () => {
289
299
  })
290
300
  })
291
301
  })
302
+
303
+ describe('migrateUserGlobalConfigFilename', () => {
304
+ beforeEach(() => {
305
+ vi.clearAllMocks()
306
+ })
307
+
308
+ afterEach(() => {
309
+ vi.clearAllMocks()
310
+ })
311
+
312
+ const userGlobalPaths = (tmp: string) => {
313
+ const dir = path.join(tmp, '.infra-kit')
314
+
315
+ return {
316
+ legacyConfig: path.join(dir, 'config.json'),
317
+ newConfig: path.join(dir, 'infra-kit.json'),
318
+ legacyExample: path.join(dir, 'config.example.jsonc'),
319
+ newExample: path.join(dir, 'infra-kit.example.jsonc'),
320
+ }
321
+ }
322
+
323
+ it('renames config.json → infra-kit.json (and the example) preserving content', async () => {
324
+ await withTmpRepo(async (tmp) => {
325
+ const p = userGlobalPaths(tmp)
326
+ const configBody = JSON.stringify({ worktrees: { openInCmux: true } })
327
+ const exampleBody = '// example\n{}\n'
328
+
329
+ writeFile(p.legacyConfig, configBody)
330
+ writeFile(p.legacyExample, exampleBody)
331
+
332
+ await migrateUserGlobalConfigFilename()
333
+
334
+ expect(fs.existsSync(p.legacyConfig)).toBe(false)
335
+ expect(fs.existsSync(p.legacyExample)).toBe(false)
336
+ expect(fs.readFileSync(p.newConfig, 'utf-8')).toBe(configBody)
337
+ expect(fs.readFileSync(p.newExample, 'utf-8')).toBe(exampleBody)
338
+ })
339
+ })
340
+
341
+ it('is a no-op when there is no legacy config.json (idempotent)', async () => {
342
+ await withTmpRepo(async (tmp) => {
343
+ const p = userGlobalPaths(tmp)
344
+
345
+ await expect(migrateUserGlobalConfigFilename()).resolves.toBeUndefined()
346
+
347
+ expect(fs.existsSync(p.legacyConfig)).toBe(false)
348
+ expect(fs.existsSync(p.newConfig)).toBe(false)
349
+ })
350
+ })
351
+
352
+ it('never overwrites an existing infra-kit.json; leaves the stale config.json in place', async () => {
353
+ await withTmpRepo(async (tmp) => {
354
+ const p = userGlobalPaths(tmp)
355
+ const legacyBody = JSON.stringify({ worktrees: { openInCmux: false } })
356
+ const activeBody = JSON.stringify({ worktrees: { openInCmux: true } })
357
+
358
+ writeFile(p.legacyConfig, legacyBody)
359
+ writeFile(p.newConfig, activeBody)
360
+
361
+ await migrateUserGlobalConfigFilename()
362
+
363
+ // Active config untouched (NOT clobbered); legacy file preserved for the user.
364
+ expect(fs.readFileSync(p.newConfig, 'utf-8')).toBe(activeBody)
365
+ expect(fs.readFileSync(p.legacyConfig, 'utf-8')).toBe(legacyBody)
366
+ })
367
+ })
368
+
369
+ it('migrates each file independently (example present, config absent)', async () => {
370
+ await withTmpRepo(async (tmp) => {
371
+ const p = userGlobalPaths(tmp)
372
+ const exampleBody = '// example only\n{}\n'
373
+
374
+ writeFile(p.legacyExample, exampleBody)
375
+
376
+ await migrateUserGlobalConfigFilename()
377
+
378
+ expect(fs.existsSync(p.legacyExample)).toBe(false)
379
+ expect(fs.readFileSync(p.newExample, 'utf-8')).toBe(exampleBody)
380
+ // No config.json existed → nothing created on that side.
381
+ expect(fs.existsSync(p.newConfig)).toBe(false)
382
+ })
383
+ })
384
+
385
+ it('yAML-legacy upgrade: migrateLegacyConfig + rename land config at infra-kit.json', async () => {
386
+ await withTmpRepo(async (tmp) => {
387
+ const p = userGlobalPaths(tmp)
388
+ const legacyYml = path.join(tmp, '.infra-kit', 'config.yml')
389
+
390
+ writeFile(legacyYml, 'worktrees:\n openInCmux: true\n')
391
+
392
+ // migrateLegacyConfig converts the pinned config.yml source → infra-kit.json;
393
+ // the rename then has nothing left to do for the user-global config.
394
+ await migrateLegacyConfig()
395
+ await migrateUserGlobalConfigFilename()
396
+
397
+ expect(fs.existsSync(legacyYml)).toBe(false)
398
+ expect(fs.existsSync(p.legacyConfig)).toBe(false)
399
+ expect(JSON.parse(fs.readFileSync(p.newConfig, 'utf-8')).worktrees.openInCmux).toBe(true)
400
+ })
401
+ })
402
+
403
+ it('both-legacy files: YAML wins via migrateLegacyConfig, config.json is left untouched', async () => {
404
+ await withTmpRepo(async (tmp) => {
405
+ const p = userGlobalPaths(tmp)
406
+ const legacyYml = path.join(tmp, '.infra-kit', 'config.yml')
407
+ const jsonBody = JSON.stringify({ worktrees: { openInCmux: false } })
408
+
409
+ writeFile(legacyYml, 'worktrees:\n openInCmux: true\n')
410
+ writeFile(p.legacyConfig, jsonBody)
411
+
412
+ // init() order: migrateLegacyConfig (yml→infra-kit.json) then the rename.
413
+ await migrateLegacyConfig()
414
+ await migrateUserGlobalConfigFilename()
415
+
416
+ // YAML content won at infra-kit.json; the rename's no-overwrite guard then
417
+ // skipped config.json, leaving it untouched (no data loss).
418
+ expect(JSON.parse(fs.readFileSync(p.newConfig, 'utf-8')).worktrees.openInCmux).toBe(true)
419
+ expect(fs.readFileSync(p.legacyConfig, 'utf-8')).toBe(jsonBody)
420
+ })
421
+ })
422
+ })
@@ -0,0 +1,93 @@
1
+ import fs from 'node:fs'
2
+ import os from 'node:os'
3
+ import path from 'node:path'
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5
+
6
+ import { logger } from 'src/lib/logger'
7
+
8
+ import { migrateFactoryConfigToJson } from '../migrate-config'
9
+
10
+ let home: string
11
+ let dir: string
12
+ let oldTs: string
13
+ let newJson: string
14
+ let infoSpy: ReturnType<typeof vi.spyOn>
15
+
16
+ const messages = (): string => {
17
+ return infoSpy.mock.calls
18
+ .map((c: unknown[]) => {
19
+ return String(c[0])
20
+ })
21
+ .join('\n')
22
+ }
23
+
24
+ beforeEach(() => {
25
+ home = fs.mkdtempSync(path.join(os.tmpdir(), 'migrate-factory-test-'))
26
+ vi.spyOn(os, 'homedir').mockReturnValue(home)
27
+ dir = path.join(home, '.infra-kit')
28
+ oldTs = path.join(dir, 'vendor.config.ts')
29
+ newJson = path.join(dir, 'vendor.json')
30
+ fs.mkdirSync(dir, { recursive: true })
31
+ infoSpy = vi.spyOn(logger, 'info').mockImplementation(() => {})
32
+ })
33
+
34
+ afterEach(() => {
35
+ vi.restoreAllMocks()
36
+ fs.rmSync(home, { recursive: true, force: true })
37
+ })
38
+
39
+ describe('migrateFactoryConfigToJson', () => {
40
+ it('converts an object-form vendor.config.ts to vendor.json and removes the .ts (plain ✓, no warning)', async () => {
41
+ fs.writeFileSync(oldTs, `export default { workspaceDir: '~/projects', targets: ['repo-a'] }`)
42
+
43
+ await migrateFactoryConfigToJson()
44
+
45
+ expect(fs.existsSync(oldTs)).toBe(false)
46
+ expect(JSON.parse(fs.readFileSync(newJson, 'utf-8'))).toEqual({ workspaceDir: '~/projects', targets: ['repo-a'] })
47
+ expect(messages()).toMatch(/✓ Migrated/)
48
+ expect(messages()).not.toMatch(/STATIC SNAPSHOT/)
49
+ })
50
+
51
+ it('converts a factory-function form AND emits the STATIC SNAPSHOT warning (not a plain ✓)', async () => {
52
+ fs.writeFileSync(oldTs, `export default () => ({ workspaceDir: '/abs/repos', targets: ['repo-b'] })`)
53
+
54
+ await migrateFactoryConfigToJson()
55
+
56
+ expect(fs.existsSync(oldTs)).toBe(false)
57
+ expect(JSON.parse(fs.readFileSync(newJson, 'utf-8'))).toEqual({ workspaceDir: '/abs/repos', targets: ['repo-b'] })
58
+ expect(messages()).toMatch(/STATIC SNAPSHOT/)
59
+ expect(messages()).not.toMatch(/✓ Migrated/)
60
+ })
61
+
62
+ it('is a no-op when there is no legacy vendor.config.ts (idempotent)', async () => {
63
+ await migrateFactoryConfigToJson()
64
+
65
+ expect(fs.existsSync(newJson)).toBe(false)
66
+ expect(infoSpy).not.toHaveBeenCalled()
67
+ })
68
+
69
+ it('never overwrites an existing vendor.json; leaves the .ts in place and warns', async () => {
70
+ const existing = { workspaceDir: '~/active', targets: ['keep-me'] }
71
+
72
+ fs.writeFileSync(oldTs, `export default { workspaceDir: '~/stale', targets: ['old'] }`)
73
+ fs.writeFileSync(newJson, JSON.stringify(existing))
74
+
75
+ await migrateFactoryConfigToJson()
76
+
77
+ // Active vendor.json untouched; the stale .ts is preserved (not deleted).
78
+ expect(JSON.parse(fs.readFileSync(newJson, 'utf-8'))).toEqual(existing)
79
+ expect(fs.existsSync(oldTs)).toBe(true)
80
+ expect(messages()).toMatch(/already exists/)
81
+ })
82
+
83
+ it('leaves an invalid old .ts in place (non-fatal) and writes no vendor.json', async () => {
84
+ // Missing required `targets` → fails factoryConfigSchema.
85
+ fs.writeFileSync(oldTs, `export default { workspaceDir: '~/projects' }`)
86
+
87
+ await expect(migrateFactoryConfigToJson()).resolves.toBeUndefined()
88
+
89
+ expect(fs.existsSync(oldTs)).toBe(true)
90
+ expect(fs.existsSync(newJson)).toBe(false)
91
+ expect(messages()).toMatch(/invalid factory config/)
92
+ })
93
+ })
@@ -0,0 +1,64 @@
1
+ import fs from 'node:fs'
2
+ import os from 'node:os'
3
+ import path from 'node:path'
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5
+
6
+ import { seedUserGlobalConfig } from '../init'
7
+
8
+ let home: string
9
+ let dir: string
10
+
11
+ beforeEach(() => {
12
+ home = fs.mkdtempSync(path.join(os.tmpdir(), 'seed-user-global-test-'))
13
+ vi.spyOn(os, 'homedir').mockReturnValue(home)
14
+ dir = path.join(home, '.infra-kit')
15
+ })
16
+
17
+ afterEach(() => {
18
+ vi.restoreAllMocks()
19
+ fs.rmSync(home, { recursive: true, force: true })
20
+ })
21
+
22
+ describe('seedUserGlobalConfig', () => {
23
+ it('seeds the infra-kit.json stub and both fully-commented example files', () => {
24
+ seedUserGlobalConfig()
25
+
26
+ expect(fs.readFileSync(path.join(dir, 'infra-kit.json'), 'utf-8')).toBe('{}\n')
27
+
28
+ const infraExample = fs.readFileSync(path.join(dir, 'infra-kit.example.jsonc'), 'utf-8')
29
+
30
+ // Every recognized infra-kit property is documented.
31
+ for (const key of ['environments', 'envManagement', 'ide', 'taskManager', 'worktrees']) {
32
+ expect(infraExample).toContain(key)
33
+ }
34
+
35
+ const vendorExample = fs.readFileSync(path.join(dir, 'vendor.example.jsonc'), 'utf-8')
36
+
37
+ expect(vendorExample).toContain('workspaceDir')
38
+ expect(vendorExample).toContain('targets')
39
+ })
40
+
41
+ it('never seeds a real vendor.json (only the example)', () => {
42
+ seedUserGlobalConfig()
43
+
44
+ expect(fs.existsSync(path.join(dir, 'vendor.example.jsonc'))).toBe(true)
45
+ expect(fs.existsSync(path.join(dir, 'vendor.json'))).toBe(false)
46
+ })
47
+
48
+ it('refreshes the example files on a re-run while leaving an existing infra-kit.json byte-unchanged', () => {
49
+ fs.mkdirSync(dir, { recursive: true })
50
+ const realConfig = '{ "environments": ["dev"] }\n'
51
+
52
+ fs.writeFileSync(path.join(dir, 'infra-kit.json'), realConfig)
53
+ // Stale example a previous version might have written.
54
+ fs.writeFileSync(path.join(dir, 'infra-kit.example.jsonc'), '// stale\n')
55
+
56
+ seedUserGlobalConfig()
57
+
58
+ // Real config preserved exactly…
59
+ expect(fs.readFileSync(path.join(dir, 'infra-kit.json'), 'utf-8')).toBe(realConfig)
60
+ // …examples refreshed to the current template (no longer "stale").
61
+ expect(fs.readFileSync(path.join(dir, 'infra-kit.example.jsonc'), 'utf-8')).not.toBe('// stale\n')
62
+ expect(fs.existsSync(path.join(dir, 'vendor.example.jsonc'))).toBe(true)
63
+ })
64
+ })
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { buildShellBody } from '../init'
4
+
5
+ describe('buildShellBody — env auto-load shell-startup block', () => {
6
+ const body = buildShellBody()
7
+
8
+ it('defines and invokes the one-shot startup autoload function', () => {
9
+ expect(body).toContain('_infra_kit_startup_autoload()')
10
+ expect(body).toContain('_infra_kit_startup_autoload\n')
11
+ })
12
+
13
+ it('gates on a session and skips subshells that already inherited an env', () => {
14
+ expect(body).toContain('[[ -z "$INFRA_KIT_SESSION" ]] && return')
15
+ expect(body).toContain('[[ -n "$INFRA_KIT_ENV_CONFIG" ]] && return')
16
+ })
17
+
18
+ it('walks up for infra-kit.json (project/worktree gate) before spawning node', () => {
19
+ expect(body).toContain('[[ -f "$dir/infra-kit.json" ]]')
20
+ expect(body).toContain('while [[ "$dir" != / ]]')
21
+ })
22
+
23
+ it('spawns env-autoload backgrounded and silenced (precmd does the sourcing)', () => {
24
+ expect(body).toContain('infra-kit env-autoload & ) >/dev/null 2>&1')
25
+ })
26
+
27
+ it('keeps the precmd hook as the sole sourcer of env-load.sh', () => {
28
+ expect(body).toContain('add-zsh-hook precmd _infra_kit_autoload')
29
+ })
30
+ })
@@ -3,7 +3,7 @@ import path from 'node:path'
3
3
 
4
4
  import { getInfraKitConfigPaths } from 'src/lib/infra-kit-config'
5
5
  import { logger } from 'src/lib/logger'
6
- import { hasManagedBlock, upsertManagedBlock } from 'src/lib/managed-block'
6
+ import { hasManagedBlock, removeManagedBlock, upsertManagedBlock } from 'src/lib/managed-block'
7
7
 
8
8
  import packageJson from '../../../package.json' with { type: 'json' }
9
9
 
@@ -13,23 +13,18 @@ export const AGENTS_MARKER_END = '<!-- infra-kit:end -->'
13
13
  /** The version line lives on its own line (OMC `OMC:VERSION:` precedent) so the markers stay constant-matchable. */
14
14
  const AGENTS_VERSION_PREFIX = '<!-- infra-kit:version '
15
15
 
16
- /** Separate marker pair for the small `@AGENTS.md` import region injected into CLAUDE.md. */
16
+ /**
17
+ * Marker pair for the legacy `@AGENTS.md` import region once injected into CLAUDE.md.
18
+ * Migration-only now: `writeAgentFiles` strips this region so the full guidance body
19
+ * can take its place. TODO(remove after ~2 release cycles once repos have re-run init).
20
+ */
17
21
  export const AGENTS_IMPORT_START = '<!-- infra-kit:import:begin -->'
18
22
  export const AGENTS_IMPORT_END = '<!-- infra-kit:import:end -->'
19
23
 
20
24
  const AGENTS_FILE = 'AGENTS.md'
21
25
  const CLAUDE_FILE = 'CLAUDE.md'
22
- const CURSOR_RULE_REL = path.join('.cursor', 'rules', 'infra-kit.mdc')
23
26
 
24
- const CURSOR_FRONTMATTER = [
25
- '---',
26
- 'description: infra-kit CLI usage and conventions',
27
- 'alwaysApply: true',
28
- '---',
29
- '',
30
- ].join('\n')
31
-
32
- export type WriteAction = 'created' | 'updated' | 'unchanged'
27
+ export type WriteAction = 'created' | 'updated' | 'unchanged' | 'removed'
33
28
 
34
29
  export interface AgentFileWrite {
35
30
  path: string
@@ -66,9 +61,9 @@ const buildAgentsBody = (version: string): string => {
66
61
  '## Commands (`ik` = `pnpm exec infra-kit`)',
67
62
  '',
68
63
  '- `ik env-load -c <config>` / `ik env-clear` / `ik env-status` — load, clear, or inspect Doppler env vars for a config (e.g. `dev`). Source the returned file to apply.',
69
- '- `ik worktrees-add` / `worktrees-list` / `worktrees-open` / `worktrees-remove` / `worktrees-sync` — manage release and feature git worktrees.',
70
- '- `ik release-create` / `release-list` / `release-deploy-all` / `release-deploy-selected` / `release-deliver` / `release-desc-edit` — release-branch and deploy flow.',
71
- '- `ik merge-dev` — merge the dev branch into every release branch.',
64
+ '- `ik worktrees add` / `worktrees list` / `worktrees reload` / `worktrees remove` / `worktrees sync` — manage release and feature git worktrees.',
65
+ '- `ik release create` / `release list` / `release deploy-all` / `release deploy-selected` / `release deliver` / `release desc-edit` — release-branch and deploy flow.',
66
+ '- `ik release merge-dev` — merge the dev branch into every release branch.',
72
67
  '- `ik audit` — audit packages against `infra-kit.config.ts` rules.',
73
68
  '- `ik doctor` — check gh / doppler / aws / rtk install + auth and repo setup.',
74
69
  '- `ik init` — (re)install shell integration and regenerate these agent-instruction files.',
@@ -86,6 +81,13 @@ const assertNotSymlink = (filePath: string): void => {
86
81
  }
87
82
  }
88
83
 
84
+ /** Copy a file to a timestamped `<file>.backup.<timestamp>` sibling. */
85
+ const backupFile = (filePath: string): void => {
86
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-')
87
+
88
+ fs.copyFileSync(filePath, `${filePath}.backup.${stamp}`)
89
+ }
90
+
89
91
  /**
90
92
  * Write `nextContent` to `filePath` with OMC-style safety: refuse symlinks,
91
93
  * back up the prior file with a timestamp before overwriting, and skip the
@@ -99,11 +101,7 @@ const writeManaged = (filePath: string, nextContent: string): WriteAction => {
99
101
 
100
102
  if (previous === nextContent) return 'unchanged'
101
103
 
102
- if (existed) {
103
- const stamp = new Date().toISOString().replace(/[:.]/g, '-')
104
-
105
- fs.copyFileSync(filePath, `${filePath}.backup.${stamp}`)
106
- }
104
+ if (existed) backupFile(filePath)
107
105
 
108
106
  fs.mkdirSync(path.dirname(filePath), { recursive: true })
109
107
  fs.writeFileSync(filePath, nextContent, 'utf-8')
@@ -124,10 +122,39 @@ const assertBlockPresent = (filePath: string, start: string, end: string): void
124
122
  }
125
123
 
126
124
  /**
127
- * Generate (or refresh) the repo agent-instruction files: the `AGENTS.md`
128
- * guidance block, a managed `@AGENTS.md` import region in `CLAUDE.md`
129
- * (preserving existing content), and a `.cursor/rules` rule file. Repo-gated:
130
- * a no-op outside an infra-kit repo. Idempotent and non-destructive.
125
+ * Migrate a legacy `AGENTS.md` now that the guidance lives solely in `CLAUDE.md`.
126
+ * Strips the infra-kit managed block, then asymmetrically:
127
+ * - no infra-kit block at all (hand-authored, not ours) leave untouched (`unchanged`),
128
+ * - file was purely generated (nothing but whitespace remains) → back up + delete (`removed`),
129
+ * - hand-authored content surrounds the block → back up + write the block-free remainder (`updated`).
130
+ * Every destructive path leaves a timestamped `.backup.` first.
131
+ */
132
+ const migrateLegacyAgentsFile = (agentsPath: string): WriteAction => {
133
+ if (!fs.existsSync(agentsPath)) return 'unchanged'
134
+
135
+ assertNotSymlink(agentsPath)
136
+
137
+ const content = fs.readFileSync(agentsPath, 'utf-8')
138
+ const stripped = removeManagedBlock(content, AGENTS_MARKER_START, AGENTS_MARKER_END)
139
+
140
+ if (stripped === null) return 'unchanged'
141
+
142
+ if (stripped.trim() === '') {
143
+ backupFile(agentsPath)
144
+ fs.rmSync(agentsPath)
145
+
146
+ return 'removed'
147
+ }
148
+
149
+ return writeManaged(agentsPath, stripped)
150
+ }
151
+
152
+ /**
153
+ * Generate (or refresh) the repo agent-instruction guidance, now hosted solely in
154
+ * `CLAUDE.md` (preserving hand-authored content outside the markers). Also migrates
155
+ * legacy setups — stripping the old `@AGENTS.md` import region from `CLAUDE.md` and
156
+ * backing up/removing a now-redundant `AGENTS.md`. Repo-gated: a no-op outside an
157
+ * infra-kit repo. Idempotent (within a CLI version) and non-destructive.
131
158
  *
132
159
  * @example
133
160
  * await writeAgentFiles()
@@ -156,40 +183,33 @@ export const writeAgentFiles = async (): Promise<WriteAgentFilesResult> => {
156
183
 
157
184
  const agentsPath = path.join(root, AGENTS_FILE)
158
185
  const claudePath = path.join(root, CLAUDE_FILE)
159
- const cursorPath = path.join(root, CURSOR_RULE_REL)
160
186
 
161
- const agentsContent = upsertManagedBlock({
162
- content: readOr(agentsPath, ''),
163
- body,
164
- startMarker: AGENTS_MARKER_START,
165
- endMarker: AGENTS_MARKER_END,
166
- })
187
+ // CLAUDE.md now hosts the full guidance body. Strip the legacy `@AGENTS.md`
188
+ // import region first (if present), then upsert the body under the begin/end
189
+ // markers — disjoint marker pairs, so the two operations never collide.
190
+ let claudeContent = readOr(claudePath, '')
167
191
 
168
- const claudeContent = upsertManagedBlock({
169
- content: readOr(claudePath, ''),
170
- body: `@${AGENTS_FILE}`,
171
- startMarker: AGENTS_IMPORT_START,
172
- endMarker: AGENTS_IMPORT_END,
173
- })
174
-
175
- const cursorContent = upsertManagedBlock({
176
- content: readOr(cursorPath, CURSOR_FRONTMATTER),
192
+ claudeContent = removeManagedBlock(claudeContent, AGENTS_IMPORT_START, AGENTS_IMPORT_END) ?? claudeContent
193
+ claudeContent = upsertManagedBlock({
194
+ content: claudeContent,
177
195
  body,
178
196
  startMarker: AGENTS_MARKER_START,
179
197
  endMarker: AGENTS_MARKER_END,
198
+ placement: 'replace-in-place',
180
199
  })
181
200
 
201
+ // CLAUDE.md is the only file we manage. A legacy AGENTS.md is migrated away.
182
202
  const written: AgentFileWrite[] = [
183
- { path: agentsPath, action: writeManaged(agentsPath, agentsContent) },
184
203
  { path: claudePath, action: writeManaged(claudePath, claudeContent) },
185
- { path: cursorPath, action: writeManaged(cursorPath, cursorContent) },
204
+ { path: agentsPath, action: migrateLegacyAgentsFile(agentsPath) },
186
205
  ]
187
206
 
188
- assertBlockPresent(agentsPath, AGENTS_MARKER_START, AGENTS_MARKER_END)
189
- assertBlockPresent(claudePath, AGENTS_IMPORT_START, AGENTS_IMPORT_END)
190
- assertBlockPresent(cursorPath, AGENTS_MARKER_START, AGENTS_MARKER_END)
207
+ assertBlockPresent(claudePath, AGENTS_MARKER_START, AGENTS_MARKER_END)
191
208
 
192
209
  for (const file of written) {
210
+ // Always report CLAUDE.md; only mention a migrated file when it actually changed.
211
+ if (file.action === 'unchanged' && file.path !== claudePath) continue
212
+
193
213
  logger.info(` ${file.action.padEnd(9)} ${path.relative(root, file.path)}`)
194
214
  }
195
215