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
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import process from 'node:process'
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
INFRA_KIT_ENV_AUTOLOADED_VAR,
|
|
9
|
+
INFRA_KIT_ENV_CLEARED_VAR,
|
|
10
|
+
INFRA_KIT_ENV_CONFIG_VAR,
|
|
11
|
+
INFRA_KIT_ENV_PROJECT_VAR,
|
|
12
|
+
INFRA_KIT_SESSION_VAR,
|
|
13
|
+
} from 'src/lib/constants'
|
|
14
|
+
|
|
15
|
+
import { envStatus } from '../env-status'
|
|
16
|
+
|
|
17
|
+
vi.mock('src/lib/logger', () => {
|
|
18
|
+
return { logger: { info: vi.fn(), warn: vi.fn() } }
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
// Pure local introspection: env-status must NOT call Doppler. If it did, importing
|
|
22
|
+
// this mock-free test (no doppler mock) and running offline would hang/throw — the
|
|
23
|
+
// fact these tests pass is itself the regression guard for the de-Doppler change.
|
|
24
|
+
|
|
25
|
+
const ORIGINAL_ENV = { ...process.env }
|
|
26
|
+
let cacheRoot: string
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
cacheRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ik-envstatus-'))
|
|
30
|
+
process.env.XDG_CACHE_HOME = cacheRoot
|
|
31
|
+
process.env[INFRA_KIT_SESSION_VAR] = 'sess-status'
|
|
32
|
+
delete process.env[INFRA_KIT_ENV_CONFIG_VAR]
|
|
33
|
+
delete process.env[INFRA_KIT_ENV_PROJECT_VAR]
|
|
34
|
+
delete process.env[INFRA_KIT_ENV_AUTOLOADED_VAR]
|
|
35
|
+
delete process.env[INFRA_KIT_ENV_CLEARED_VAR]
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
process.env = { ...ORIGINAL_ENV }
|
|
40
|
+
fs.rmSync(cacheRoot, { recursive: true, force: true })
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('envStatus', () => {
|
|
44
|
+
it('reports nothing loaded with autoLoaded=false, cleared=false', async () => {
|
|
45
|
+
const { structuredContent } = await envStatus()
|
|
46
|
+
|
|
47
|
+
expect(structuredContent.sessionConfig).toBeNull()
|
|
48
|
+
expect(structuredContent.autoLoaded).toBe(false)
|
|
49
|
+
expect(structuredContent.cleared).toBe(false)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('reports cleared=true when the clear sentinel is set', async () => {
|
|
53
|
+
process.env[INFRA_KIT_ENV_CLEARED_VAR] = '1'
|
|
54
|
+
|
|
55
|
+
const { structuredContent } = await envStatus()
|
|
56
|
+
|
|
57
|
+
expect(structuredContent.cleared).toBe(true)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('reports autoLoaded=true when the auto-load marker is present', async () => {
|
|
61
|
+
process.env[INFRA_KIT_ENV_CONFIG_VAR] = 'dev'
|
|
62
|
+
process.env[INFRA_KIT_ENV_PROJECT_VAR] = 'my-project'
|
|
63
|
+
process.env[INFRA_KIT_ENV_AUTOLOADED_VAR] = '1'
|
|
64
|
+
|
|
65
|
+
const { structuredContent } = await envStatus()
|
|
66
|
+
|
|
67
|
+
expect(structuredContent.sessionConfig).toBe('dev')
|
|
68
|
+
expect(structuredContent.autoLoaded).toBe(true)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('reports autoLoaded=false for a manual load (config set, no marker)', async () => {
|
|
72
|
+
process.env[INFRA_KIT_ENV_CONFIG_VAR] = 'dev'
|
|
73
|
+
|
|
74
|
+
const { structuredContent } = await envStatus()
|
|
75
|
+
|
|
76
|
+
expect(structuredContent.autoLoaded).toBe(false)
|
|
77
|
+
})
|
|
78
|
+
})
|
|
@@ -2,9 +2,10 @@ import path from 'node:path'
|
|
|
2
2
|
import process from 'node:process'
|
|
3
3
|
import { z } from 'zod'
|
|
4
4
|
|
|
5
|
-
import { validateDopplerCliAndAuth } from 'src/integrations/doppler'
|
|
6
5
|
import {
|
|
7
6
|
ENV_LOAD_FILE,
|
|
7
|
+
INFRA_KIT_ENV_AUTOLOADED_VAR,
|
|
8
|
+
INFRA_KIT_ENV_CLEARED_VAR,
|
|
8
9
|
INFRA_KIT_ENV_CONFIG_VAR,
|
|
9
10
|
INFRA_KIT_ENV_LOADED_AT_VAR,
|
|
10
11
|
INFRA_KIT_ENV_PROJECT_VAR,
|
|
@@ -16,11 +17,13 @@ import { logger } from 'src/lib/logger'
|
|
|
16
17
|
import { defineMcpTool, textContent } from 'src/types'
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
|
-
*
|
|
20
|
+
* Report which env is currently loaded in the terminal session. Pure local
|
|
21
|
+
* introspection: reads only `process.env` + the cached env-load.sh — it makes NO
|
|
22
|
+
* Doppler call (use `doctor` for auth/CLI checks), so it works offline and never
|
|
23
|
+
* hangs. Surfaces whether the env was auto-loaded vs manually loaded and whether a
|
|
24
|
+
* clear is currently suppressing auto-load.
|
|
20
25
|
*/
|
|
21
26
|
export const envStatus = async () => {
|
|
22
|
-
await validateDopplerCliAndAuth()
|
|
23
|
-
|
|
24
27
|
logger.info('Environment session status:')
|
|
25
28
|
|
|
26
29
|
// Check session-loaded vars — getSessionCacheDir() throws if INFRA_KIT_SESSION is unset
|
|
@@ -35,6 +38,8 @@ export const envStatus = async () => {
|
|
|
35
38
|
const sessionConfig = process.env[INFRA_KIT_ENV_CONFIG_VAR] ?? null
|
|
36
39
|
const sessionProject = process.env[INFRA_KIT_ENV_PROJECT_VAR] ?? null
|
|
37
40
|
const sessionLoadedAt = process.env[INFRA_KIT_ENV_LOADED_AT_VAR] ?? null
|
|
41
|
+
const autoLoaded = process.env[INFRA_KIT_ENV_AUTOLOADED_VAR] === '1'
|
|
42
|
+
const cleared = Boolean(process.env[INFRA_KIT_ENV_CLEARED_VAR])
|
|
38
43
|
|
|
39
44
|
if (sessionConfig) {
|
|
40
45
|
const varNames = parseVarNamesFromEnvFile(envLoadPath)
|
|
@@ -47,9 +52,10 @@ export const envStatus = async () => {
|
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
const loadedAtDisplay = sessionLoadedAt?.replace(/\.\d{3}Z$/, '') ?? null
|
|
55
|
+
const origin = autoLoaded ? 'auto-loaded' : 'manually loaded'
|
|
50
56
|
|
|
51
57
|
logger.info(
|
|
52
|
-
` ${sessionConfig}: ${sessionLoadedCount} of ${sessionTotalCount} vars loaded (project: ${sessionProject}, loadedAt: ${loadedAtDisplay}, session: ${sessionId})\n`,
|
|
58
|
+
` ${sessionConfig}: ${sessionLoadedCount} of ${sessionTotalCount} vars loaded (${origin}, project: ${sessionProject}, loadedAt: ${loadedAtDisplay}, session: ${sessionId})\n`,
|
|
53
59
|
)
|
|
54
60
|
|
|
55
61
|
if (sessionTotalCount > 0 && sessionLoadedCount < sessionTotalCount) {
|
|
@@ -60,7 +66,9 @@ export const envStatus = async () => {
|
|
|
60
66
|
)
|
|
61
67
|
}
|
|
62
68
|
} else {
|
|
63
|
-
|
|
69
|
+
const clearedNote = cleared ? ' (cleared — auto-load suppressed until a new shell or explicit env-load)' : ''
|
|
70
|
+
|
|
71
|
+
logger.info(` Session ${sessionId}: no env loaded${clearedNote}\n`)
|
|
64
72
|
}
|
|
65
73
|
|
|
66
74
|
const structuredContent = {
|
|
@@ -70,6 +78,8 @@ export const envStatus = async () => {
|
|
|
70
78
|
sessionConfig,
|
|
71
79
|
sessionProject,
|
|
72
80
|
sessionLoadedAt,
|
|
81
|
+
autoLoaded,
|
|
82
|
+
cleared,
|
|
73
83
|
}
|
|
74
84
|
|
|
75
85
|
return {
|
|
@@ -82,7 +92,7 @@ export const envStatus = async () => {
|
|
|
82
92
|
export const envStatusMcpTool = defineMcpTool({
|
|
83
93
|
name: 'env-status',
|
|
84
94
|
description:
|
|
85
|
-
'Report which Doppler project/config is currently loaded in the terminal session, when it was loaded,
|
|
95
|
+
'Report which Doppler project/config is currently loaded in the terminal session, when it was loaded, how many variables are cached, whether it was auto-loaded, and whether a clear is suppressing auto-load. Pure local introspection — makes NO Doppler call (use doctor for auth). Read-only — use env-load / env-clear to change the terminal session.',
|
|
86
96
|
inputSchema: {},
|
|
87
97
|
outputSchema: {
|
|
88
98
|
sessionId: z.string().describe('Current terminal session ID'),
|
|
@@ -91,6 +101,8 @@ export const envStatusMcpTool = defineMcpTool({
|
|
|
91
101
|
sessionConfig: z.string().nullable().describe('Doppler config name of the loaded session (environment name)'),
|
|
92
102
|
sessionProject: z.string().nullable().describe('Doppler project name of the loaded session'),
|
|
93
103
|
sessionLoadedAt: z.string().nullable().describe('ISO 8601 timestamp of when the env was loaded'),
|
|
104
|
+
autoLoaded: z.boolean().describe('True when the loaded env was applied by env auto-load (not a manual env-load)'),
|
|
105
|
+
cleared: z.boolean().describe('True when env-clear is currently suppressing auto-load in this shell'),
|
|
94
106
|
},
|
|
95
107
|
handler: envStatus,
|
|
96
108
|
})
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import confirm from '@inquirer/confirm'
|
|
1
2
|
import select from '@inquirer/select'
|
|
2
3
|
import { z } from 'zod'
|
|
3
4
|
import { $ } from 'zx'
|
|
@@ -21,13 +22,39 @@ interface GhReleaseDeployAllArgs {
|
|
|
21
22
|
version: string
|
|
22
23
|
env: string
|
|
23
24
|
skipTerraform?: boolean
|
|
25
|
+
confirmedCommand?: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface ConfirmDeployArgs {
|
|
29
|
+
confirmedCommand?: boolean
|
|
30
|
+
branch: string
|
|
31
|
+
env: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Gate the workflow dispatch behind an interactive confirmation. Returns true to
|
|
36
|
+
* proceed; `confirmedCommand` (CLI `--yes`) skips the prompt. Mirrors the
|
|
37
|
+
* confirm+commandEcho pattern used by worktrees-remove.
|
|
38
|
+
*/
|
|
39
|
+
const confirmDeploy = async (args: ConfirmDeployArgs): Promise<boolean> => {
|
|
40
|
+
const { confirmedCommand, branch, env } = args
|
|
41
|
+
|
|
42
|
+
if (confirmedCommand) return true
|
|
43
|
+
|
|
44
|
+
commandEcho.setInteractive()
|
|
45
|
+
|
|
46
|
+
const answer = await confirm({ message: `Deploy ${branch} → ${env}?`, default: false })
|
|
47
|
+
|
|
48
|
+
if (answer) commandEcho.addOption('--yes', true)
|
|
49
|
+
|
|
50
|
+
return answer
|
|
24
51
|
}
|
|
25
52
|
|
|
26
53
|
/**
|
|
27
54
|
* Deploy a release branch to an environment
|
|
28
55
|
*/
|
|
29
56
|
export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs) => {
|
|
30
|
-
const { version, env, skipTerraform } = args
|
|
57
|
+
const { version, env, skipTerraform, confirmedCommand } = args
|
|
31
58
|
|
|
32
59
|
commandEcho.start('release-deploy-all')
|
|
33
60
|
|
|
@@ -98,6 +125,23 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs) => {
|
|
|
98
125
|
commandEcho.addOption('--skip-terraform', true)
|
|
99
126
|
}
|
|
100
127
|
|
|
128
|
+
if (!(await confirmDeploy({ confirmedCommand, branch: selectedReleaseBranch, env: selectedEnv }))) {
|
|
129
|
+
logger.info('Deployment cancelled')
|
|
130
|
+
|
|
131
|
+
const structuredContent = {
|
|
132
|
+
releaseBranch: selectedReleaseBranch,
|
|
133
|
+
version: selectedVersion,
|
|
134
|
+
environment: selectedEnv,
|
|
135
|
+
skipTerraformDeploy: shouldSkipTerraform,
|
|
136
|
+
success: false,
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
content: textContent(JSON.stringify(structuredContent, null, 2)),
|
|
141
|
+
structuredContent,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
101
145
|
try {
|
|
102
146
|
$.quiet = true
|
|
103
147
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import checkbox from '@inquirer/checkbox'
|
|
2
|
+
import confirm from '@inquirer/confirm'
|
|
2
3
|
import select from '@inquirer/select'
|
|
3
4
|
import fs from 'node:fs/promises'
|
|
4
5
|
import { resolve } from 'node:path'
|
|
@@ -27,6 +28,32 @@ interface GhReleaseDeploySelectedArgs {
|
|
|
27
28
|
env: string
|
|
28
29
|
services: string[]
|
|
29
30
|
skipTerraform?: boolean
|
|
31
|
+
confirmedCommand?: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface ConfirmDeployArgs {
|
|
35
|
+
confirmedCommand?: boolean
|
|
36
|
+
branch: string
|
|
37
|
+
env: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Gate the workflow dispatch behind an interactive confirmation. Returns true to
|
|
42
|
+
* proceed; `confirmedCommand` (CLI `--yes`) skips the prompt. Mirrors the
|
|
43
|
+
* confirm+commandEcho pattern used by worktrees-remove.
|
|
44
|
+
*/
|
|
45
|
+
const confirmDeploy = async (args: ConfirmDeployArgs): Promise<boolean> => {
|
|
46
|
+
const { confirmedCommand, branch, env } = args
|
|
47
|
+
|
|
48
|
+
if (confirmedCommand) return true
|
|
49
|
+
|
|
50
|
+
commandEcho.setInteractive()
|
|
51
|
+
|
|
52
|
+
const answer = await confirm({ message: `Deploy ${branch} → ${env}?`, default: false })
|
|
53
|
+
|
|
54
|
+
if (answer) commandEcho.addOption('--yes', true)
|
|
55
|
+
|
|
56
|
+
return answer
|
|
30
57
|
}
|
|
31
58
|
|
|
32
59
|
/**
|
|
@@ -34,7 +61,7 @@ interface GhReleaseDeploySelectedArgs {
|
|
|
34
61
|
*/
|
|
35
62
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
36
63
|
export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs) => {
|
|
37
|
-
const { version, env, services, skipTerraform } = args
|
|
64
|
+
const { version, env, services, skipTerraform, confirmedCommand } = args
|
|
38
65
|
|
|
39
66
|
commandEcho.start('release-deploy-selected')
|
|
40
67
|
|
|
@@ -157,6 +184,24 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
|
|
|
157
184
|
commandEcho.addOption('--skip-terraform', true)
|
|
158
185
|
}
|
|
159
186
|
|
|
187
|
+
if (!(await confirmDeploy({ confirmedCommand, branch: selectedReleaseBranch, env: selectedEnv }))) {
|
|
188
|
+
logger.info('Deployment cancelled')
|
|
189
|
+
|
|
190
|
+
const structuredContent = {
|
|
191
|
+
releaseBranch: selectedReleaseBranch,
|
|
192
|
+
version: selectedVersion,
|
|
193
|
+
environment: selectedEnv,
|
|
194
|
+
services: selectedServices,
|
|
195
|
+
skipTerraformDeploy: shouldSkipTerraform,
|
|
196
|
+
success: false,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
content: textContent(JSON.stringify(structuredContent, null, 2)),
|
|
201
|
+
structuredContent,
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
160
205
|
try {
|
|
161
206
|
$.quiet = true
|
|
162
207
|
|
|
@@ -5,7 +5,13 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
5
5
|
|
|
6
6
|
import { getProjectRoot, getRepoName } from 'src/lib/git-utils'
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
AGENTS_IMPORT_END,
|
|
10
|
+
AGENTS_IMPORT_START,
|
|
11
|
+
AGENTS_MARKER_END,
|
|
12
|
+
AGENTS_MARKER_START,
|
|
13
|
+
writeAgentFiles,
|
|
14
|
+
} from '../agent-files'
|
|
9
15
|
|
|
10
16
|
vi.mock('src/lib/git-utils', () => {
|
|
11
17
|
return {
|
|
@@ -46,32 +52,30 @@ describe('writeAgentFiles', () => {
|
|
|
46
52
|
vi.clearAllMocks()
|
|
47
53
|
})
|
|
48
54
|
|
|
49
|
-
it('
|
|
55
|
+
it('writes the full guidance block only to CLAUDE.md, and creates no other files, on a fresh repo', async () => {
|
|
50
56
|
await withTmpRepo(async (tmp) => {
|
|
51
57
|
const result = await writeAgentFiles()
|
|
52
58
|
|
|
53
59
|
expect(result.skipped).toBe(false)
|
|
54
60
|
expect(result.root).toBe(tmp)
|
|
55
61
|
|
|
56
|
-
const agents = fs.readFileSync(path.join(tmp, 'AGENTS.md'), 'utf-8')
|
|
57
|
-
|
|
58
|
-
expect(agents).toContain(AGENTS_MARKER_START)
|
|
59
|
-
expect(agents).toContain(AGENTS_MARKER_END)
|
|
60
|
-
expect(agents).toContain('<!-- infra-kit:version ')
|
|
61
|
-
|
|
62
62
|
const claude = fs.readFileSync(path.join(tmp, 'CLAUDE.md'), 'utf-8')
|
|
63
63
|
|
|
64
|
-
expect(claude).toContain(
|
|
65
|
-
expect(claude).toContain(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
expect(
|
|
70
|
-
expect(
|
|
64
|
+
expect(claude).toContain(AGENTS_MARKER_START)
|
|
65
|
+
expect(claude).toContain(AGENTS_MARKER_END)
|
|
66
|
+
expect(claude).toContain('<!-- infra-kit:version ')
|
|
67
|
+
expect(claude).toContain('# infra-kit')
|
|
68
|
+
// The migrated `@AGENTS.md` import region must never appear.
|
|
69
|
+
expect(claude).not.toContain(AGENTS_IMPORT_START)
|
|
70
|
+
expect(claude).not.toContain('@AGENTS.md')
|
|
71
|
+
|
|
72
|
+
// CLAUDE.md is the only output — infra-kit no longer touches AGENTS.md or .cursor.
|
|
73
|
+
expect(fs.existsSync(path.join(tmp, 'AGENTS.md'))).toBe(false)
|
|
74
|
+
expect(fs.existsSync(path.join(tmp, '.cursor'))).toBe(false)
|
|
71
75
|
})
|
|
72
76
|
})
|
|
73
77
|
|
|
74
|
-
it('preserves existing hand-authored CLAUDE.md content
|
|
78
|
+
it('preserves existing hand-authored CLAUDE.md content and appends the block after it', async () => {
|
|
75
79
|
await withTmpRepo(async (tmp) => {
|
|
76
80
|
const claudePath = path.join(tmp, 'CLAUDE.md')
|
|
77
81
|
const userContent = '# CLAUDE.md\n\n## Ticket Naming Convention\n\n- `[FE]` — frontend\n- `[BE]` — backend\n'
|
|
@@ -84,17 +88,19 @@ describe('writeAgentFiles', () => {
|
|
|
84
88
|
|
|
85
89
|
expect(updated).toContain('## Ticket Naming Convention')
|
|
86
90
|
expect(updated).toContain('`[FE]` — frontend')
|
|
87
|
-
expect(updated).toContain(
|
|
91
|
+
expect(updated).toContain(AGENTS_MARKER_START)
|
|
92
|
+
// The managed block lands AFTER the hand-authored content.
|
|
93
|
+
expect(updated.indexOf('## Ticket Naming Convention')).toBeLessThan(updated.indexOf(AGENTS_MARKER_START))
|
|
88
94
|
})
|
|
89
95
|
})
|
|
90
96
|
|
|
91
|
-
it('is idempotent — a second run does not duplicate the block', async () => {
|
|
97
|
+
it('is idempotent — a second run does not duplicate the block and reports unchanged (same CLI version)', async () => {
|
|
92
98
|
await withTmpRepo(async (tmp) => {
|
|
93
99
|
await writeAgentFiles()
|
|
94
|
-
const first = fs.readFileSync(path.join(tmp, '
|
|
100
|
+
const first = fs.readFileSync(path.join(tmp, 'CLAUDE.md'), 'utf-8')
|
|
95
101
|
|
|
96
102
|
const second = await writeAgentFiles()
|
|
97
|
-
const after = fs.readFileSync(path.join(tmp, '
|
|
103
|
+
const after = fs.readFileSync(path.join(tmp, 'CLAUDE.md'), 'utf-8')
|
|
98
104
|
|
|
99
105
|
expect(after).toBe(first)
|
|
100
106
|
expect(after.match(new RegExp(AGENTS_MARKER_START, 'g'))?.length).toBe(1)
|
|
@@ -113,35 +119,147 @@ describe('writeAgentFiles', () => {
|
|
|
113
119
|
|
|
114
120
|
expect(result.skipped).toBe(true)
|
|
115
121
|
expect(result.root).toBeNull()
|
|
116
|
-
expect(fs.existsSync(path.join(tmp, '
|
|
122
|
+
expect(fs.existsSync(path.join(tmp, 'CLAUDE.md'))).toBe(false)
|
|
117
123
|
},
|
|
118
124
|
{ repo: false },
|
|
119
125
|
)
|
|
120
126
|
})
|
|
121
127
|
|
|
122
|
-
it('refuses to write through a symlinked
|
|
128
|
+
it('refuses to write through a symlinked CLAUDE.md', async () => {
|
|
123
129
|
await withTmpRepo(async (tmp) => {
|
|
124
130
|
const realTarget = path.join(tmp, 'elsewhere.md')
|
|
125
131
|
|
|
126
132
|
writeFile(realTarget, 'sensitive')
|
|
127
|
-
fs.symlinkSync(realTarget, path.join(tmp, '
|
|
133
|
+
fs.symlinkSync(realTarget, path.join(tmp, 'CLAUDE.md'))
|
|
128
134
|
|
|
129
135
|
await expect(writeAgentFiles()).rejects.toThrow(/symlink/)
|
|
130
136
|
})
|
|
131
137
|
})
|
|
132
138
|
|
|
133
|
-
it('backs up an existing
|
|
139
|
+
it('backs up an existing CLAUDE.md before overwriting', async () => {
|
|
134
140
|
await withTmpRepo(async (tmp) => {
|
|
135
|
-
writeFile(path.join(tmp, '
|
|
141
|
+
writeFile(path.join(tmp, 'CLAUDE.md'), 'previous content without markers\n')
|
|
136
142
|
|
|
137
143
|
await writeAgentFiles()
|
|
138
144
|
|
|
139
145
|
const backups = fs.readdirSync(tmp).filter((f) => {
|
|
140
|
-
return f.startsWith('
|
|
146
|
+
return f.startsWith('CLAUDE.md.backup.')
|
|
141
147
|
})
|
|
142
148
|
|
|
143
|
-
expect(backups
|
|
149
|
+
expect(backups).toHaveLength(1)
|
|
144
150
|
expect(fs.readFileSync(path.join(tmp, backups[0]!), 'utf-8')).toContain('previous content without markers')
|
|
145
151
|
})
|
|
146
152
|
})
|
|
153
|
+
|
|
154
|
+
describe('legacy AGENTS.md migration', () => {
|
|
155
|
+
/** Seed a repo in the pre-migration state: AGENTS.md (full block) + CLAUDE.md (@AGENTS.md import). */
|
|
156
|
+
const seedLegacy = async (
|
|
157
|
+
tmp: string,
|
|
158
|
+
opts: { agentsExtra?: string; claudeExtra?: string } = {},
|
|
159
|
+
): Promise<void> => {
|
|
160
|
+
const agentsBlock = `${AGENTS_MARKER_START}\n# infra-kit\nlegacy body\n${AGENTS_MARKER_END}\n`
|
|
161
|
+
const importBlock = `${AGENTS_IMPORT_START}\n@AGENTS.md\n${AGENTS_IMPORT_END}\n`
|
|
162
|
+
|
|
163
|
+
writeFile(path.join(tmp, 'AGENTS.md'), `${opts.agentsExtra ?? ''}${agentsBlock}`)
|
|
164
|
+
writeFile(path.join(tmp, 'CLAUDE.md'), `${opts.claudeExtra ?? ''}${importBlock}`)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
it('removes a purely-generated AGENTS.md (with a backup) and strips the @AGENTS.md import from CLAUDE.md', async () => {
|
|
168
|
+
await withTmpRepo(async (tmp) => {
|
|
169
|
+
await seedLegacy(tmp)
|
|
170
|
+
|
|
171
|
+
await writeAgentFiles()
|
|
172
|
+
|
|
173
|
+
// AGENTS.md is gone, but a backup survives.
|
|
174
|
+
expect(fs.existsSync(path.join(tmp, 'AGENTS.md'))).toBe(false)
|
|
175
|
+
const backups = fs.readdirSync(tmp).filter((f) => {
|
|
176
|
+
return f.startsWith('AGENTS.md.backup.')
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
expect(backups).toHaveLength(1)
|
|
180
|
+
|
|
181
|
+
// CLAUDE.md now carries the full block and no longer imports @AGENTS.md.
|
|
182
|
+
const claude = fs.readFileSync(path.join(tmp, 'CLAUDE.md'), 'utf-8')
|
|
183
|
+
|
|
184
|
+
expect(claude).toContain(AGENTS_MARKER_START)
|
|
185
|
+
expect(claude).not.toContain(AGENTS_IMPORT_START)
|
|
186
|
+
expect(claude).not.toContain('@AGENTS.md')
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it('retains an AGENTS.md that has hand-authored content (block removed, file kept, backup made)', async () => {
|
|
191
|
+
await withTmpRepo(async (tmp) => {
|
|
192
|
+
await seedLegacy(tmp, { agentsExtra: '# My own notes\n\nkeep me\n\n' })
|
|
193
|
+
|
|
194
|
+
await writeAgentFiles()
|
|
195
|
+
|
|
196
|
+
// File retained with hand content; the infra-kit block is stripped.
|
|
197
|
+
expect(fs.existsSync(path.join(tmp, 'AGENTS.md'))).toBe(true)
|
|
198
|
+
const agents = fs.readFileSync(path.join(tmp, 'AGENTS.md'), 'utf-8')
|
|
199
|
+
|
|
200
|
+
expect(agents).toContain('# My own notes')
|
|
201
|
+
expect(agents).toContain('keep me')
|
|
202
|
+
expect(agents).not.toContain(AGENTS_MARKER_START)
|
|
203
|
+
|
|
204
|
+
// A backup of the pre-strip file exists.
|
|
205
|
+
const backups = fs.readdirSync(tmp).filter((f) => {
|
|
206
|
+
return f.startsWith('AGENTS.md.backup.')
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
expect(backups).toHaveLength(1)
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('leaves an AGENTS.md with no infra-kit block untouched (not ours)', async () => {
|
|
214
|
+
await withTmpRepo(async (tmp) => {
|
|
215
|
+
const handAuthored = '# AGENTS\n\nentirely hand-written, no markers\n'
|
|
216
|
+
|
|
217
|
+
writeFile(path.join(tmp, 'AGENTS.md'), handAuthored)
|
|
218
|
+
|
|
219
|
+
await writeAgentFiles()
|
|
220
|
+
|
|
221
|
+
expect(fs.readFileSync(path.join(tmp, 'AGENTS.md'), 'utf-8')).toBe(handAuthored)
|
|
222
|
+
const backups = fs.readdirSync(tmp).filter((f) => {
|
|
223
|
+
return f.startsWith('AGENTS.md.backup.')
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
expect(backups).toHaveLength(0)
|
|
227
|
+
})
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('is idempotent after migration — a second run finds nothing left to migrate', async () => {
|
|
231
|
+
await withTmpRepo(async (tmp) => {
|
|
232
|
+
await seedLegacy(tmp)
|
|
233
|
+
await writeAgentFiles()
|
|
234
|
+
|
|
235
|
+
const second = await writeAgentFiles()
|
|
236
|
+
|
|
237
|
+
expect(
|
|
238
|
+
second.written.every((w) => {
|
|
239
|
+
return w.action === 'unchanged'
|
|
240
|
+
}),
|
|
241
|
+
).toBe(true)
|
|
242
|
+
})
|
|
243
|
+
})
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
it('does not touch a pre-existing .cursor/rules file — infra-kit no longer supports Cursor rules', async () => {
|
|
247
|
+
await withTmpRepo(async (tmp) => {
|
|
248
|
+
const cursorPath = path.join(tmp, '.cursor', 'rules', 'infra-kit.mdc')
|
|
249
|
+
// Even a file that carries our managed block is left completely alone now.
|
|
250
|
+
const existing = `---\ndescription: x\n---\n${AGENTS_MARKER_START}\n# infra-kit\nold body\n${AGENTS_MARKER_END}\n`
|
|
251
|
+
|
|
252
|
+
writeFile(cursorPath, existing)
|
|
253
|
+
|
|
254
|
+
await writeAgentFiles()
|
|
255
|
+
|
|
256
|
+
// Untouched: same content, no backup, no deletion.
|
|
257
|
+
expect(fs.readFileSync(cursorPath, 'utf-8')).toBe(existing)
|
|
258
|
+
const backups = fs.readdirSync(path.join(tmp, '.cursor', 'rules')).filter((f) => {
|
|
259
|
+
return f.startsWith('infra-kit.mdc.backup.')
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
expect(backups).toHaveLength(0)
|
|
263
|
+
})
|
|
264
|
+
})
|
|
147
265
|
})
|