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.
- package/.eslintcache +1 -1
- package/.omc/state/agent-replay-0d1515ce-4ebe-4ce8-a865-2897f68de168.jsonl +1 -0
- package/.omc/state/agent-replay-1fcfe0ec-7567-41c3-9bfd-501e99e86819.jsonl +19 -0
- package/.omc/state/agent-replay-38382db5-3088-4b5a-aff7-2561a60827c7.jsonl +17 -0
- package/.omc/state/agent-replay-50c36be9-a3a2-42b9-9c85-5a06d3e22f6a.jsonl +5 -0
- package/.omc/state/agent-replay-8198b3e5-fad6-4ee0-9c59-ef773ad89af4.jsonl +1 -0
- package/.omc/state/agent-replay-8cd7652a-b8b5-49cf-9b1f-bee8e5972458.jsonl +3 -0
- package/.omc/state/agent-replay-9bea518f-a9ee-4d14-bb53-a936ebaf647c.jsonl +7 -0
- package/.omc/state/agent-replay-b52d89fb-0e24-4901-bc66-27e3c1697126.jsonl +2 -0
- package/.omc/state/agent-replay-f581a1ae-0ee4-48a3-96f3-ce31a0633bb0.jsonl +1 -0
- package/.omc/state/agent-replay-f99d8dad-81d1-4987-8183-62edf0843d0c.jsonl +2 -0
- package/.omc/state/agent-replay-fe1057b4-9a25-4e8e-8c3f-3e11652ee697.jsonl +5 -0
- package/.omc/state/idle-notif-cooldown.json +1 -1
- package/.omc/state/sessions/0d1515ce-4ebe-4ce8-a865-2897f68de168/pre-tool-advisory-throttle.json +10 -0
- package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/mission-state.json +109 -0
- package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/pre-tool-advisory-throttle.json +42 -0
- package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/ralph-state.json +13 -0
- package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/skill-active-state.json +15 -0
- package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/subagent-tracking-state.json +62 -0
- package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/ultrawork-state.json +11 -0
- package/.omc/state/sessions/23d0ff82-17e5-428b-8743-38f7979bb6f3/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/23d0ff82-17e5-428b-8743-38f7979bb6f3/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/mission-state.json +145 -0
- package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/pre-tool-advisory-throttle.json +38 -0
- package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/ralplan-state.json +11 -0
- package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/subagent-tracking-state.json +98 -0
- package/.omc/state/sessions/41336850-028c-476b-be1a-2164e1e46cac/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/41336850-028c-476b-be1a-2164e1e46cac/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/50c36be9-a3a2-42b9-9c85-5a06d3e22f6a/mission-state.json +61 -0
- package/.omc/state/sessions/50c36be9-a3a2-42b9-9c85-5a06d3e22f6a/pre-tool-advisory-throttle.json +22 -0
- package/.omc/state/sessions/50c36be9-a3a2-42b9-9c85-5a06d3e22f6a/skill-active-state.json +15 -0
- package/.omc/state/sessions/50c36be9-a3a2-42b9-9c85-5a06d3e22f6a/subagent-tracking-state.json +26 -0
- package/.omc/state/sessions/560ea519-574a-4713-b134-5bf6cfaf3c33/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/8198b3e5-fad6-4ee0-9c59-ef773ad89af4/pre-tool-advisory-throttle.json +14 -0
- package/.omc/state/sessions/8198b3e5-fad6-4ee0-9c59-ef773ad89af4/subagent-tracking-state.json +17 -0
- package/.omc/state/sessions/8cd7652a-b8b5-49cf-9b1f-bee8e5972458/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/8cd7652a-b8b5-49cf-9b1f-bee8e5972458/ralplan-state.json +11 -0
- package/.omc/state/sessions/8cd7652a-b8b5-49cf-9b1f-bee8e5972458/subagent-tracking-state.json +35 -0
- package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/mission-state.json +79 -0
- package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/pre-tool-advisory-throttle.json +22 -0
- package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/progress.txt +7 -0
- package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/skill-active-state.json +15 -0
- package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/subagent-tracking-state.json +26 -0
- package/.omc/state/sessions/b52d89fb-0e24-4901-bc66-27e3c1697126/pre-tool-advisory-throttle.json +14 -0
- package/.omc/state/sessions/b52d89fb-0e24-4901-bc66-27e3c1697126/subagent-tracking-state.json +17 -0
- package/.omc/state/sessions/c513ef38-cc3e-4a29-9c4c-1e0137245dd5/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/f581a1ae-0ee4-48a3-96f3-ce31a0633bb0/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/f581a1ae-0ee4-48a3-96f3-ce31a0633bb0/subagent-tracking-state.json +17 -0
- package/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/pre-tool-advisory-throttle.json +14 -0
- package/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/ralph-state.json +13 -0
- package/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/ultrawork-state.json +11 -0
- package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/mission-state.json +61 -0
- package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/prd.json +22 -0
- package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/pre-tool-advisory-throttle.json +22 -0
- package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/progress.txt +22 -0
- package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/skill-active-state.json +15 -0
- package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/subagent-tracking-state.json +26 -0
- package/.turbo/turbo-build.log +3 -3
- package/.turbo/turbo-test.log +92 -44
- package/dist/boot-K4QUAFSP.js +2 -0
- package/dist/boot-K4QUAFSP.js.map +7 -0
- package/dist/chunk-RTSP2FCA.js +2 -0
- package/dist/chunk-RTSP2FCA.js.map +7 -0
- package/dist/chunk-VPBZR2PY.js +166 -0
- package/dist/chunk-VPBZR2PY.js.map +7 -0
- package/dist/cli.js +11 -107
- package/dist/cli.js.map +4 -4
- package/dist/entry/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +4 -4
- package/dist/lib/vendor/config-schema.d.ts +58 -0
- package/dist/mcp.js +1 -54
- package/dist/mcp.js.map +4 -4
- package/eslint.config.js +33 -1
- package/package.json +5 -1
- package/scripts/build.js +11 -1
- package/src/.omc/state/agent-replay-c513ef38-cc3e-4a29-9c4c-1e0137245dd5.jsonl +4 -0
- package/src/.omc/state/agent-replay-ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a.jsonl +2 -0
- package/src/.omc/state/idle-notif-cooldown.json +1 -1
- package/src/.omc/state/sessions/c513ef38-cc3e-4a29-9c4c-1e0137245dd5/mission-state.json +79 -0
- package/src/.omc/state/sessions/c513ef38-cc3e-4a29-9c4c-1e0137245dd5/pre-tool-advisory-throttle.json +22 -0
- package/src/.omc/state/sessions/c513ef38-cc3e-4a29-9c4c-1e0137245dd5/subagent-tracking-state.json +26 -0
- package/src/.omc/state/sessions/ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a/pre-tool-advisory-throttle.json +18 -0
- package/src/.omc/state/sessions/ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a/ralph-state.json +13 -0
- package/src/.omc/state/sessions/ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a/ultrawork-state.json +11 -0
- package/src/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/pre-tool-advisory-throttle.json +14 -0
- package/src/commands/config/.omc/state/sessions/41336850-028c-476b-be1a-2164e1e46cac/last-tool-error-state.json +7 -0
- package/src/commands/config/.omc/state/sessions/41336850-028c-476b-be1a-2164e1e46cac/pre-tool-advisory-throttle.json +18 -0
- package/src/commands/config/config.ts +7 -37
- package/src/commands/doctor/__tests__/agent-files.test.ts +8 -30
- package/src/commands/doctor/__tests__/check-legacy-user-global-config.test.ts +110 -0
- package/src/commands/doctor/doctor.ts +110 -44
- package/src/commands/env-autoload/env-autoload.ts +12 -0
- package/src/commands/env-autoload/index.ts +1 -0
- package/src/commands/env-clear/__tests__/env-clear.test.ts +27 -0
- package/src/commands/env-clear/env-clear.ts +22 -8
- package/src/commands/env-load/__tests__/env-load.test.ts +36 -1
- package/src/commands/env-load/env-load.ts +152 -35
- package/src/commands/env-load/index.ts +2 -1
- package/src/commands/env-status/__tests__/env-status.test.ts +78 -0
- package/src/commands/env-status/env-status.ts +19 -7
- package/src/commands/gh-release-deploy-all/gh-release-deploy-all.ts +45 -1
- package/src/commands/gh-release-deploy-selected/gh-release-deploy-selected.ts +46 -1
- package/src/commands/init/__tests__/agent-files.test.ts +145 -27
- package/src/commands/init/__tests__/migrate-config.test.ts +136 -5
- package/src/commands/init/__tests__/migrate-factory-config.test.ts +93 -0
- package/src/commands/init/__tests__/seed-user-global-config.test.ts +64 -0
- package/src/commands/init/__tests__/shell-body.test.ts +30 -0
- package/src/commands/init/agent-files.ts +65 -45
- package/src/commands/init/init.ts +140 -31
- package/src/commands/init/migrate-config.ts +160 -32
- package/src/commands/vendor-check/__tests__/runtime-isolation.test.ts +71 -0
- package/src/commands/vendor-check/__tests__/vendor-check.test.ts +87 -0
- package/src/commands/vendor-check/index.ts +1 -0
- package/src/commands/vendor-check/vendor-check.ts +127 -0
- package/src/commands/vendor-config/__tests__/vendor-config.test.ts +103 -0
- package/src/commands/vendor-config/index.ts +1 -0
- package/src/commands/vendor-config/vendor-config.ts +162 -0
- package/src/commands/vendor-diff/__tests__/vendor-diff.test.ts +74 -0
- package/src/commands/vendor-diff/index.ts +1 -0
- package/src/commands/vendor-diff/vendor-diff.ts +77 -0
- package/src/commands/vendor-manifest/index.ts +1 -0
- package/src/commands/vendor-manifest/vendor-manifest.ts +60 -0
- package/src/commands/vendor-sync/__tests__/vendor-write.test.ts +86 -0
- package/src/commands/vendor-sync/index.ts +1 -0
- package/src/commands/vendor-sync/vendor-sync.ts +90 -0
- package/src/commands/worktrees-add/worktrees-add.ts +1 -1
- package/src/commands/worktrees-reload/worktrees-reload.ts +6 -5
- package/src/commands/worktrees-remove/worktrees-remove.ts +10 -1
- package/src/commands/worktrees-sync/worktrees-sync.ts +9 -1
- package/src/entry/cli.ts +417 -210
- package/src/entry/index.ts +7 -1
- package/src/integrations/cmux/__tests__/open-workspace-with-layout.test.ts +127 -0
- package/src/integrations/cmux/close-workspace-by-title.ts +2 -2
- package/src/integrations/cmux/list-workspace-titles.ts +1 -1
- package/src/integrations/cmux/open-workspace-with-layout.ts +15 -4
- package/src/integrations/cursor/.omc/state/agent-replay-f99d8dad-81d1-4987-8183-62edf0843d0c.jsonl +2 -0
- package/src/integrations/cursor/.omc/state/idle-notif-cooldown.json +3 -0
- package/src/integrations/cursor/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/pre-tool-advisory-throttle.json +18 -0
- package/src/integrations/cursor/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/subagent-tracking-state.json +17 -0
- package/src/integrations/cursor/index.ts +1 -0
- package/src/integrations/cursor/launch-cursor.ts +12 -0
- package/src/integrations/cursor/open-cursor-workspace.ts +8 -14
- package/src/integrations/doppler/__tests__/doppler-errors.test.ts +76 -0
- package/src/integrations/doppler/doppler-cli-auth.ts +12 -4
- package/src/integrations/doppler/doppler-errors.ts +88 -0
- package/src/integrations/doppler/doppler-project.ts +80 -0
- package/src/integrations/doppler/index.ts +3 -1
- package/src/integrations/gh/gh-cli-auth/gh-cli-auth.ts +9 -5
- package/src/integrations/gh/gh-release-prs/gh-release-prs.ts +16 -8
- package/src/integrations/ide/__tests__/ide-facade.test.ts +57 -10
- package/src/integrations/ide/add-ide-worktree-folders.ts +2 -4
- package/src/integrations/ide/open-ide-workspace.ts +6 -6
- package/src/integrations/ide/remove-ide-worktree-folders.ts +95 -30
- package/src/integrations/ide/types.ts +10 -1
- package/src/integrations/zed/__tests__/open-zed-workspace.test.ts +1 -17
- package/src/integrations/zed/__tests__/reuse-zed-workspace.test.ts +86 -0
- package/src/integrations/zed/index.ts +1 -0
- package/src/integrations/zed/open-zed-workspace.ts +4 -10
- package/src/integrations/zed/reuse-zed-workspace.ts +57 -0
- package/src/lib/command-catalog/__tests__/__snapshots__/command-catalog.test.ts.snap +212 -0
- package/src/lib/command-catalog/__tests__/command-catalog.test.ts +193 -0
- package/src/lib/command-catalog/command-catalog.ts +141 -0
- package/src/lib/command-catalog/index.ts +2 -0
- package/src/lib/command-echo/__tests__/command-echo.test.ts +127 -0
- package/src/lib/constants/constants.ts +13 -0
- package/src/lib/constants/index.ts +2 -0
- package/src/lib/env-autoload/__tests__/env-autoload.test.ts +288 -0
- package/src/lib/env-autoload/env-autoload.ts +294 -0
- package/src/lib/env-autoload/index.ts +9 -0
- package/src/lib/errors/operation-error.ts +1 -1
- package/src/lib/infra-kit-config/__tests__/env-autoload-schema.test.ts +71 -0
- package/src/lib/infra-kit-config/__tests__/infra-kit-config.test.ts +77 -6
- package/src/lib/infra-kit-config/index.ts +9 -1
- package/src/lib/infra-kit-config/infra-kit-config.ts +71 -11
- package/src/lib/json-output/__tests__/json-output.test.ts +139 -0
- package/src/lib/json-output/index.ts +1 -0
- package/src/lib/json-output/json-output.ts +47 -0
- package/src/lib/managed-block/managed-block.ts +4 -4
- package/src/lib/path-display/index.ts +1 -0
- package/src/lib/path-display/path-display.ts +42 -0
- package/src/lib/render/__tests__/render.test.ts +48 -0
- package/src/lib/render/index.ts +1 -0
- package/src/lib/render/render.ts +24 -0
- package/src/lib/vendor/__tests__/config-schema.test.ts +31 -0
- package/src/lib/vendor/__tests__/config.test.ts +63 -0
- package/src/lib/vendor/__tests__/factory-config.test.ts +92 -0
- package/src/lib/vendor/__tests__/vendor-lib.test.ts +158 -0
- package/src/lib/vendor/__tests__/zx-mock.ts +32 -0
- package/src/lib/vendor/config-schema.ts +78 -0
- package/src/lib/vendor/config.ts +58 -0
- package/src/lib/vendor/factory-config-schema.ts +35 -0
- package/src/lib/vendor/factory-config.ts +91 -0
- package/src/lib/vendor/hash.ts +16 -0
- package/src/lib/vendor/index.ts +23 -0
- package/src/lib/vendor/manifest.ts +151 -0
- package/src/lib/vendor/skip-sets.ts +48 -0
- package/src/lib/vendor/sync-ops.ts +290 -0
- package/src/lib/vendor/walk.ts +56 -0
- package/src/lib/worktrees/remove-worktrees.ts +15 -0
- package/src/mcp/tools/index.ts +5 -42
- package/src/sanity.test.tsx +1 -0
- package/src/tui/__tests__/no-react-boundary.test.ts +51 -0
- package/src/tui/boot.tsx +45 -0
- package/src/tui/screens/__tests__/command-palette.test.tsx +136 -0
- package/src/tui/screens/command-palette.tsx +159 -0
- package/src/tui/types.ts +8 -0
- package/tsconfig.json +1 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +8 -0
- package/src/commands/worktrees-open/__tests__/open-cmux.test.ts +0 -96
- package/src/commands/worktrees-open/index.ts +0 -1
- 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', '
|
|
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
|
-
|
|
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(
|
|
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', '
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
|
70
|
-
'- `ik release
|
|
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
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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:
|
|
204
|
+
{ path: agentsPath, action: migrateLegacyAgentsFile(agentsPath) },
|
|
186
205
|
]
|
|
187
206
|
|
|
188
|
-
assertBlockPresent(
|
|
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
|
|