@cleocode/caamp 2026.10.8 → 2026.10.9-rc.ci.676

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.
@@ -0,0 +1,184 @@
1
+ import { HookConfigObject, HookConfigEdit } from '@cleocode/contracts/project-hook-delivery.js';
2
+ import { WriteFileAtomic } from '@cleocode/contracts/tools/atomic';
3
+
4
+ /**
5
+ * Atomic file writes and cross-process file locking.
6
+ *
7
+ * CAAMP mutates files that are shared by *every* project on the machine — most
8
+ * critically `~/.agents/AGENTS.md`, which every `cleo init`, `cleo upgrade` and
9
+ * `cleo doctor` run rewrites regardless of which project it was invoked from.
10
+ * A plain `writeFile` on such a path is `open(O_TRUNC)` followed by one or more
11
+ * `write(2)` calls: two processes interleaving there can leave a caller reading
12
+ * a half-written file, and a reader racing a writer can observe a truncated
13
+ * one.
14
+ *
15
+ * Two primitives remove that class of failure:
16
+ *
17
+ * - `writeFileAtomic` — the canonical tmp-then-rename primitive from
18
+ * `@cleocode/core/tools/fs.js`. `rename(2)` within a filesystem is atomic, so
19
+ * a concurrent reader sees either the whole old file or the whole new one,
20
+ * never a mixture. It is re-exported here for callers already importing this
21
+ * module; the definition lives in core, per the tools-vs-skills boundary.
22
+ * - {@link withFileLock} — serialise a whole read-modify-write cycle across
23
+ * processes via an `O_EXCL` guard file, so two writers cannot both read the
24
+ * pre-state and then clobber each other's result. Generalised from the
25
+ * bespoke copy that lived inline in `lock-utils.ts` (now a caller).
26
+ *
27
+ * @task T12051
28
+ */
29
+
30
+ /** Alias the canonical primitive through its shared contract, preserving runtime identity. */
31
+ declare const writeFileAtomic: WriteFileAtomic;
32
+ /**
33
+ * Options controlling {@link withFileLock}.
34
+ *
35
+ * @public
36
+ */
37
+ interface FileLockOptions {
38
+ /**
39
+ * Number of acquisition attempts before throwing.
40
+ *
41
+ * @defaultValue 400
42
+ */
43
+ retries?: number;
44
+ /**
45
+ * Delay between attempts, in milliseconds.
46
+ *
47
+ * @defaultValue 25
48
+ */
49
+ delayMs?: number;
50
+ /**
51
+ * Age at which an existing guard file is treated as abandoned and removed.
52
+ *
53
+ * @defaultValue 30000
54
+ */
55
+ staleMs?: number;
56
+ }
57
+ /**
58
+ * Run `fn` while holding an exclusive cross-process lock on `targetPath`.
59
+ *
60
+ * The lock is a `<targetPath>.lock` guard file created with `O_EXCL`, which is
61
+ * atomic on POSIX and on Windows. A guard left behind by a crashed process is
62
+ * reclaimed once it exceeds `staleMs`.
63
+ *
64
+ * The guard is always released, including when `fn` throws.
65
+ *
66
+ * @param targetPath - Path being protected (the guard is a sibling of it)
67
+ * @param fn - Work to perform while holding the lock
68
+ * @param options - Retry, delay and staleness tuning
69
+ * @returns Whatever `fn` returns
70
+ * @throws Error if the lock cannot be acquired within `retries` attempts
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * const action = await withFileLock(agentsMd, async () => {
75
+ * const before = await readFile(agentsMd, "utf-8");
76
+ * await writeFileAtomic(agentsMd, transform(before));
77
+ * return "updated";
78
+ * });
79
+ * ```
80
+ *
81
+ * @public
82
+ */
83
+ declare function withFileLock<T>(targetPath: string, fn: () => Promise<T>, options?: FileLockOptions): Promise<T>;
84
+ /**
85
+ * Throw if a read looks like it landed inside another process's
86
+ * truncate-then-write window.
87
+ *
88
+ * `writeFileAtomic` makes *our* writes indivisible, but callers outside this
89
+ * package still rewrite instruction files with a plain `writeFile`, which is
90
+ * `open(O_TRUNC)` followed by `write(2)`. A read landing between those two
91
+ * returns an empty string for a file that is not empty on disk. Reconciling
92
+ * from that observation would replace every byte of the user's content with a
93
+ * lone CAAMP block.
94
+ *
95
+ * Failing closed is the right trade: the caller retries or reports, and the
96
+ * file is left exactly as it was.
97
+ *
98
+ * @param filePath - Path that was read, for the error message
99
+ * @param content - What the read returned
100
+ * @param sizeOnDisk - `stat().size` for the same path
101
+ * @throws Error when `content` is empty but `sizeOnDisk` is greater than zero
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * const text = await readFile(p, "utf-8");
106
+ * if (text.length === 0) assertNotTornRead(p, text, (await stat(p)).size);
107
+ * ```
108
+ *
109
+ * @public
110
+ */
111
+ declare function assertNotTornRead(filePath: string, content: string, sizeOnDisk: number): void;
112
+ /**
113
+ * A JSON config file could not be parsed, so it was left untouched.
114
+ *
115
+ * @remarks
116
+ * Thrown by {@link updateJsonConfigFile} instead of treating the file as empty.
117
+ * The pre-T12385 provider adapters caught the parse error and started from
118
+ * `{}`, so a torn read of `~/.claude/settings.json` (Claude Code rewriting it
119
+ * at the same moment) replaced the user's permissions, env, model and hooks
120
+ * with CLEO's entries alone.
121
+ *
122
+ * @example
123
+ * ```typescript
124
+ * try {
125
+ * await updateJsonConfigFile(path, (config) => false);
126
+ * } catch (err) {
127
+ * if (err instanceof JsonConfigParseError) console.error(err.filePath);
128
+ * }
129
+ * ```
130
+ *
131
+ * @public
132
+ */
133
+ declare class JsonConfigParseError extends Error {
134
+ /** Path of the file that failed to parse. */
135
+ readonly filePath: string;
136
+ /**
137
+ * @param filePath - Path of the file that failed to parse
138
+ * @param reason - Parser message
139
+ */
140
+ constructor(filePath: string, reason: string);
141
+ }
142
+ /**
143
+ * Read-modify-write a JSON object file under the cross-process lock, writing
144
+ * atomically, and never replacing an unparseable file.
145
+ *
146
+ * @remarks
147
+ * Composes the two existing primitives — {@link withFileLock} and core's
148
+ * `writeFileAtomic` — for provider config files such as
149
+ * `~/.claude/settings.json` that the provider itself also rewrites (T12385):
150
+ *
151
+ * - The whole read → mutate → write cycle holds the `<file>.lock` guard, so
152
+ * two CLEO writers cannot both read the pre-state and clobber each other.
153
+ * - The write is tmp-then-rename, so a concurrent reader (including the
154
+ * provider) sees the whole old file or the whole new one.
155
+ * - A missing file starts from `{}`. A file that exists but is empty on read
156
+ * while non-empty on disk, is not valid JSON, or is not a JSON object throws
157
+ * ({@link assertNotTornRead} / {@link JsonConfigParseError}) and the file is
158
+ * left byte-identical.
159
+ *
160
+ * @param filePath - JSON file to update
161
+ * @param mutate - Edits the parsed object in place; returns `true` when it
162
+ * changed something that must be written
163
+ * @returns Whether the file was written
164
+ * @throws {@link JsonConfigParseError} when the existing file cannot be parsed
165
+ * as a JSON object; the file is not modified
166
+ *
167
+ * @example
168
+ * ```typescript
169
+ * const wrote = await updateJsonConfigFile(settingsPath, (settings) => {
170
+ * if (settings.enabled === true) return false;
171
+ * settings.enabled = true;
172
+ * return true;
173
+ * });
174
+ * ```
175
+ *
176
+ * @public
177
+ */
178
+ declare function updateJsonConfigFile(filePath: string, mutate: (config: Record<string, unknown>) => boolean): Promise<boolean>;
179
+ /** Apply surgical JSONC edits inside the existing cross-process atomic writer. */
180
+ declare function editJsonConfigFile(filePath: string, edit: (config: HookConfigObject) => HookConfigEdit[]): Promise<boolean>;
181
+ /** Read a bounded JSONC object without mutation, including in dry-run inspections. */
182
+ declare function readManagedJsonConfigFile(filePath: string): Promise<HookConfigObject>;
183
+
184
+ export { type FileLockOptions, JsonConfigParseError, assertNotTornRead, editJsonConfigFile, readManagedJsonConfigFile, updateJsonConfigFile, withFileLock, writeFileAtomic };
package/dist/atomic.js ADDED
@@ -0,0 +1,20 @@
1
+ import {
2
+ JsonConfigParseError,
3
+ assertNotTornRead,
4
+ editJsonConfigFile,
5
+ readManagedJsonConfigFile,
6
+ updateJsonConfigFile,
7
+ withFileLock,
8
+ writeFileAtomic
9
+ } from "./chunk-D6CWC4BX.js";
10
+ import "./chunk-PZ5AY32C.js";
11
+ export {
12
+ JsonConfigParseError,
13
+ assertNotTornRead,
14
+ editJsonConfigFile,
15
+ readManagedJsonConfigFile,
16
+ updateJsonConfigFile,
17
+ withFileLock,
18
+ writeFileAtomic
19
+ };
20
+ //# sourceMappingURL=atomic.js.map
@@ -1,132 +1,24 @@
1
+ import {
2
+ assertNotTornRead,
3
+ withFileLock
4
+ } from "./chunk-D6CWC4BX.js";
1
5
  import {
2
6
  getAgentsHome,
3
7
  resolveProviderSkillsDir,
4
8
  resolveProvidersRegistryPath,
5
9
  resolveRegistryTemplatePath
6
- } from "./chunk-HDZYZTR2.js";
10
+ } from "./chunk-5INWYMO4.js";
7
11
 
8
12
  // src/core/instructions/injector.ts
9
- import { existsSync as existsSync2 } from "fs";
10
- import { readFile as readFile3, stat as stat3 } from "fs/promises";
11
- import { homedir as homedir3 } from "os";
12
- import { dirname as dirname4, isAbsolute, join as join2 } from "path";
13
- import { writeFileAtomic as writeFileAtomic2 } from "@cleocode/core/tools/fs.js";
14
-
15
- // src/core/fs/atomic.ts
16
13
  import { existsSync } from "fs";
17
- import { mkdir, open, readFile, rm, stat, writeFile } from "fs/promises";
18
- import { dirname } from "path";
14
+ import { readFile as readFile2, stat as stat2 } from "fs/promises";
15
+ import { homedir as homedir3 } from "os";
16
+ import { dirname as dirname3, isAbsolute, join as join2 } from "path";
19
17
  import { writeFileAtomic } from "@cleocode/core/tools/fs.js";
20
- var DEFAULT_STALE_LOCK_MS = 3e4;
21
- var DEFAULT_LOCK_RETRIES = 400;
22
- var DEFAULT_LOCK_DELAY_MS = 25;
23
- function sleep(ms) {
24
- return new Promise((resolve3) => setTimeout(resolve3, ms));
25
- }
26
- async function removeStaleGuard(guardPath, staleMs, expectedToken) {
27
- try {
28
- const info = await stat(guardPath);
29
- if (Date.now() - info.mtimeMs <= staleMs) return false;
30
- const current = await readFile(guardPath, "utf-8").catch(() => null);
31
- if (expectedToken !== null && current !== null && current !== expectedToken) return false;
32
- await rm(guardPath, { force: true });
33
- return true;
34
- } catch {
35
- }
36
- return false;
37
- }
38
- async function withFileLock(targetPath, fn, options = {}) {
39
- const retries = options.retries ?? DEFAULT_LOCK_RETRIES;
40
- const delayMs = options.delayMs ?? DEFAULT_LOCK_DELAY_MS;
41
- const staleMs = options.staleMs ?? DEFAULT_STALE_LOCK_MS;
42
- const guardPath = `${targetPath}.lock`;
43
- const token = `${process.pid}:${Date.now()}:${Math.random().toString(36).slice(2, 12)}`;
44
- await mkdir(dirname(targetPath), { recursive: true });
45
- let acquired = false;
46
- for (let attempt = 0; attempt < retries && !acquired; attempt += 1) {
47
- try {
48
- const handle = await open(guardPath, "wx");
49
- await handle.close();
50
- await writeFile(guardPath, token, "utf-8");
51
- acquired = true;
52
- } catch (error) {
53
- const code = error.code;
54
- if (code !== "EEXIST") throw error;
55
- const observed = await readFile(guardPath, "utf-8").catch(() => null);
56
- if (await removeStaleGuard(guardPath, staleMs, observed)) continue;
57
- await sleep(delayMs);
58
- }
59
- }
60
- if (!acquired) {
61
- throw new Error(
62
- `Timed out acquiring lock for ${targetPath} after ${retries} attempts (~${Math.round(retries * delayMs / 1e3)}s)`
63
- );
64
- }
65
- try {
66
- return await fn();
67
- } finally {
68
- const current = await readFile(guardPath, "utf-8").catch(() => null);
69
- if (current === null || current === token) {
70
- await rm(guardPath, { force: true }).catch(() => {
71
- });
72
- }
73
- }
74
- }
75
- function assertNotTornRead(filePath, content, sizeOnDisk) {
76
- if (content.length === 0 && sizeOnDisk > 0) {
77
- throw new Error(
78
- `Refusing to rewrite ${filePath}: read 0 bytes but the file is ${sizeOnDisk} bytes on disk (torn read from a concurrent non-atomic writer).`
79
- );
80
- }
81
- }
82
- function isJsonObject(value) {
83
- return typeof value === "object" && value !== null && !Array.isArray(value);
84
- }
85
- var JsonConfigParseError = class extends Error {
86
- /** Path of the file that failed to parse. */
87
- filePath;
88
- /**
89
- * @param filePath - Path of the file that failed to parse
90
- * @param reason - Parser message
91
- */
92
- constructor(filePath, reason) {
93
- super(`Refusing to rewrite ${filePath}: it is not a valid JSON object (${reason}).`);
94
- this.name = "JsonConfigParseError";
95
- this.filePath = filePath;
96
- }
97
- };
98
- async function updateJsonConfigFile(filePath, mutate) {
99
- return withFileLock(filePath, async () => {
100
- let config = {};
101
- if (existsSync(filePath)) {
102
- const raw = await readFile(filePath, "utf-8");
103
- if (raw.length === 0) assertNotTornRead(filePath, raw, (await stat(filePath)).size);
104
- if (raw.trim().length > 0) {
105
- let parsed;
106
- try {
107
- parsed = JSON.parse(raw);
108
- } catch (err) {
109
- throw new JsonConfigParseError(
110
- filePath,
111
- err instanceof Error ? err.message : String(err)
112
- );
113
- }
114
- if (!isJsonObject(parsed)) {
115
- throw new JsonConfigParseError(filePath, "top-level value is not an object");
116
- }
117
- config = parsed;
118
- }
119
- }
120
- if (!mutate(config)) return false;
121
- await writeFileAtomic({ path: filePath, content: `${JSON.stringify(config, null, 2)}
122
- ` });
123
- return true;
124
- });
125
- }
126
18
 
127
19
  // src/core/registry/providers.ts
128
20
  import { readFileSync } from "fs";
129
- import { dirname as dirname2, join } from "path";
21
+ import { dirname, join } from "path";
130
22
  import { fileURLToPath } from "url";
131
23
  var DEFAULT_SKILLS_CAPABILITY = {
132
24
  agentsGlobalPath: null,
@@ -205,7 +97,7 @@ function resolveCapabilities(raw) {
205
97
  return { mcp, harness, skills, hooks, spawn };
206
98
  }
207
99
  function findRegistryPath() {
208
- const thisDir = dirname2(fileURLToPath(import.meta.url));
100
+ const thisDir = dirname(fileURLToPath(import.meta.url));
209
101
  return resolveProvidersRegistryPath(thisDir);
210
102
  }
211
103
  var _registry = null;
@@ -635,18 +527,18 @@ ${content}
635
527
  ${CAAMP_MARKER_END}`;
636
528
  }
637
529
  function assertBalancedMarkers(content) {
638
- let open2 = false;
530
+ let open = false;
639
531
  const markers = new RegExp(`${CAAMP_MARKER_START}|${CAAMP_MARKER_END}`, "g");
640
532
  for (const match of content.matchAll(markers)) {
641
533
  const opening = match[0] === CAAMP_MARKER_START;
642
- if (opening === open2) {
534
+ if (opening === open) {
643
535
  throw new Error(
644
536
  "Ambiguous CAAMP markers: nested or unmatched delimiter; original content preserved."
645
537
  );
646
538
  }
647
- open2 = opening;
539
+ open = opening;
648
540
  }
649
- if (open2)
541
+ if (open)
650
542
  throw new Error(
651
543
  "Ambiguous CAAMP markers: opening delimiter has no end; original content preserved."
652
544
  );
@@ -711,9 +603,9 @@ function repairContent(existing) {
711
603
 
712
604
  // src/core/instructions/templates.ts
713
605
  import { createHash } from "crypto";
714
- import { readFile as readFile2, realpath, stat as stat2 } from "fs/promises";
606
+ import { readFile, realpath, stat } from "fs/promises";
715
607
  import { homedir as homedir2 } from "os";
716
- import { dirname as dirname3, resolve as resolve2 } from "path";
608
+ import { dirname as dirname2, resolve as resolve2 } from "path";
717
609
  function buildInjectionContent(template) {
718
610
  const lines = [];
719
611
  for (const ref of template.references) {
@@ -806,7 +698,7 @@ async function resolveInstructionDelivery(content, baseDir) {
806
698
  let sourcePath = stamp[1];
807
699
  try {
808
700
  sourcePath = decodeURIComponent(sourcePath);
809
- const sourceSize = (await stat2(sourcePath)).size;
701
+ const sourceSize = (await stat(sourcePath)).size;
810
702
  bytes += sourceSize;
811
703
  if (bytes > 524288) {
812
704
  result.findings.push({
@@ -815,7 +707,7 @@ async function resolveInstructionDelivery(content, baseDir) {
815
707
  reason: "Instruction byte budget exceeded."
816
708
  });
817
709
  } else {
818
- const digest = createHash("sha256").update(await readFile2(sourcePath)).digest("hex");
710
+ const digest = createHash("sha256").update(await readFile(sourcePath)).digest("hex");
819
711
  if (digest !== stamp[2])
820
712
  result.findings.push({
821
713
  kind: "stale",
@@ -862,7 +754,7 @@ async function resolveInstructionDelivery(content, baseDir) {
862
754
  continue;
863
755
  }
864
756
  try {
865
- bytes += (await stat2(path)).size;
757
+ bytes += (await stat(path)).size;
866
758
  if (bytes > 524288) {
867
759
  result.findings.push({
868
760
  kind: "limit",
@@ -871,13 +763,13 @@ async function resolveInstructionDelivery(content, baseDir) {
871
763
  });
872
764
  continue;
873
765
  }
874
- const source = await readFile2(path, "utf8");
766
+ const source = await readFile(path, "utf8");
875
767
  visited.add(path);
876
768
  result.sources.push(path);
877
769
  lines.push(
878
770
  `<!-- CAAMP:SOURCE ${encodeURIComponent(path)} ${createHash("sha256").update(source).digest("hex")} -->`
879
771
  );
880
- lines.push(await expand(source, dirname3(path), [...ancestors, path]));
772
+ lines.push(await expand(source, dirname2(path), [...ancestors, path]));
881
773
  } catch (error) {
882
774
  result.findings.push({ kind: "missing-reference", path, reason: String(error) });
883
775
  }
@@ -893,17 +785,17 @@ function parseCaampBlocks(fileContent) {
893
785
  return parseBlocks(fileContent);
894
786
  }
895
787
  async function dedupeFile(filePath) {
896
- if (!existsSync2(filePath)) {
788
+ if (!existsSync(filePath)) {
897
789
  return { filePath, removed: 0, kept: 0, modified: false, repaired: 0 };
898
790
  }
899
791
  return withFileLock(filePath, async () => {
900
- const original = await readFile3(filePath, "utf-8");
792
+ const original = await readFile2(filePath, "utf-8");
901
793
  const { content: healed, repaired } = normalizeMarkers(original);
902
794
  assertBalancedMarkers(healed);
903
795
  const blocks = parseBlocks(healed);
904
796
  if (blocks.length === 0) {
905
797
  if (repaired > 0 && healed !== original) {
906
- await writeFileAtomic2({ path: filePath, content: healed });
798
+ await writeFileAtomic({ path: filePath, content: healed });
907
799
  return { filePath, removed: 0, kept: 0, modified: true, repaired };
908
800
  }
909
801
  return { filePath, removed: 0, kept: 0, modified: false, repaired };
@@ -930,7 +822,7 @@ async function dedupeFile(filePath) {
930
822
  if (result === original) {
931
823
  return { filePath, removed: 0, kept: blocks.length, modified: false, repaired };
932
824
  }
933
- await writeFileAtomic2({ path: filePath, content: result });
825
+ await writeFileAtomic({ path: filePath, content: result });
934
826
  return { filePath, removed, kept: keepSet.size, modified: true, repaired };
935
827
  });
936
828
  }
@@ -962,12 +854,12 @@ function scopedInstructionPath(provider, projectDir, scope) {
962
854
  return scope === "global" ? resolveGlobalInstructionPath(provider) : join2(projectDir, provider.instructFile);
963
855
  }
964
856
  async function repairInstructionFiles(projectDir, providers) {
965
- const paths = instructionFileCascade(projectDir, providers).filter((p) => existsSync2(p));
857
+ const paths = instructionFileCascade(projectDir, providers).filter((p) => existsSync(p));
966
858
  const files = [];
967
859
  for (const filePath of paths) {
968
860
  files.push(
969
861
  await withFileLock(filePath, async () => {
970
- const original = await readFile3(filePath, "utf-8");
862
+ const original = await readFile2(filePath, "utf-8");
971
863
  const { content, blocksBefore, repaired } = repairContent(original);
972
864
  const removed = Math.max(0, blocksBefore - 1);
973
865
  if (removed === 0 && repaired === 0) {
@@ -976,7 +868,7 @@ async function repairInstructionFiles(projectDir, providers) {
976
868
  if (content === original) {
977
869
  return { filePath, removed: 0, kept: blocksBefore, modified: false, repaired };
978
870
  }
979
- await writeFileAtomic2({ path: filePath, content });
871
+ await writeFileAtomic({ path: filePath, content });
980
872
  return {
981
873
  filePath,
982
874
  removed,
@@ -995,8 +887,8 @@ async function repairInstructionFiles(projectDir, providers) {
995
887
  };
996
888
  }
997
889
  async function checkInjection(filePath, expectedContent) {
998
- if (!existsSync2(filePath)) return "missing";
999
- const raw = await readFile3(filePath, "utf-8");
890
+ if (!existsSync(filePath)) return "missing";
891
+ const raw = await readFile2(filePath, "utf-8");
1000
892
  const { content, repaired } = normalizeMarkers(raw);
1001
893
  const blocks = parseBlocks(content);
1002
894
  if (blocks.length === 0) return "none";
@@ -1010,18 +902,18 @@ async function inject(filePath, content) {
1010
902
  const body = content.trim();
1011
903
  assertBalancedMarkers(buildBlock(body));
1012
904
  return withFileLock(filePath, async () => {
1013
- if (!existsSync2(filePath)) {
1014
- await writeFileAtomic2({ path: filePath, content: `${buildBlock(body)}
905
+ if (!existsSync(filePath)) {
906
+ await writeFileAtomic({ path: filePath, content: `${buildBlock(body)}
1015
907
  ` });
1016
908
  return "created";
1017
909
  }
1018
- const existing = await readFile3(filePath, "utf-8");
910
+ const existing = await readFile2(filePath, "utf-8");
1019
911
  if (existing.length === 0) {
1020
- assertNotTornRead(filePath, existing, (await stat3(filePath)).size);
912
+ assertNotTornRead(filePath, existing, (await stat2(filePath)).size);
1021
913
  }
1022
914
  const { content: next, blocksBefore, repaired } = reconcile(existing, body);
1023
915
  if (next === existing) return "intact";
1024
- await writeFileAtomic2({ path: filePath, content: next });
916
+ await writeFileAtomic({ path: filePath, content: next });
1025
917
  if (blocksBefore === 0) return "added";
1026
918
  if (repaired > 0) return "repaired";
1027
919
  if (blocksBefore > 1) return "consolidated";
@@ -1029,18 +921,18 @@ async function inject(filePath, content) {
1029
921
  });
1030
922
  }
1031
923
  async function removeInjection(filePath) {
1032
- if (!existsSync2(filePath)) return false;
924
+ if (!existsSync(filePath)) return false;
1033
925
  return withFileLock(filePath, async () => {
1034
- const original = await readFile3(filePath, "utf-8");
926
+ const original = await readFile2(filePath, "utf-8");
1035
927
  const { content } = normalizeMarkers(original);
1036
928
  assertBalancedMarkers(content);
1037
929
  if (parseBlocks(content).length === 0) return false;
1038
930
  const cleaned = content.replace(blockPattern(), "");
1039
931
  if (!cleaned) {
1040
- const { rm: rm2 } = await import("fs/promises");
1041
- await rm2(filePath);
932
+ const { rm } = await import("fs/promises");
933
+ await rm(filePath);
1042
934
  } else {
1043
- await writeFileAtomic2({ path: filePath, content: cleaned });
935
+ await writeFileAtomic({ path: filePath, content: cleaned });
1044
936
  }
1045
937
  return true;
1046
938
  });
@@ -1054,7 +946,7 @@ async function checkAllInjections(providers, projectDir, scope, expectedContent)
1054
946
  if (checked.has(filePath)) continue;
1055
947
  checked.add(filePath);
1056
948
  let status = await checkInjection(filePath, expectedContent);
1057
- const delivery = await resolveInstructionDelivery(`@${filePath}`, dirname4(filePath));
949
+ const delivery = await resolveInstructionDelivery(`@${filePath}`, dirname3(filePath));
1058
950
  if (expectedContent !== void 0 && status === "outdated") {
1059
951
  delivery.findings.push({
1060
952
  kind: "stale",
@@ -1062,7 +954,7 @@ async function checkAllInjections(providers, projectDir, scope, expectedContent)
1062
954
  reason: "Managed content differs from the expected version."
1063
955
  });
1064
956
  }
1065
- if (existsSync2(filePath) && parseBlocks(await readFile3(filePath, "utf8")).length > 1) {
957
+ if (existsSync(filePath) && parseBlocks(await readFile2(filePath, "utf8")).length > 1) {
1066
958
  delivery.findings.push({
1067
959
  kind: "duplicate",
1068
960
  path: filePath,
@@ -1076,7 +968,7 @@ async function checkAllInjections(providers, projectDir, scope, expectedContent)
1076
968
  file: filePath,
1077
969
  provider: provider.id,
1078
970
  status,
1079
- fileExists: existsSync2(filePath)
971
+ fileExists: existsSync(filePath)
1080
972
  });
1081
973
  }
1082
974
  return results;
@@ -1124,7 +1016,7 @@ async function ensureProviderInstructionFile(providerId, projectDir, options) {
1124
1016
  throw new HomeInstructionFileError(filePath);
1125
1017
  let references = options.references ?? getProviderInstructionReferences(providerId);
1126
1018
  let content = options.content;
1127
- if (options.delivery !== "verified-references" && references.includes("@.cleo/memory-bridge.md") && !existsSync2(join2(dirname4(filePath), ".cleo", "memory-bridge.md"))) {
1019
+ if (options.delivery !== "verified-references" && references.includes("@.cleo/memory-bridge.md") && !existsSync(join2(dirname3(filePath), ".cleo", "memory-bridge.md"))) {
1128
1020
  references = references.filter((reference) => reference !== "@.cleo/memory-bridge.md");
1129
1021
  content = [
1130
1022
  ...content ?? [],
@@ -1137,7 +1029,7 @@ async function ensureProviderInstructionFile(providerId, projectDir, options) {
1137
1029
  };
1138
1030
  let injectionContent = buildInjectionContent(template);
1139
1031
  if (options.delivery !== "verified-references") {
1140
- const delivery = await resolveInstructionDelivery(injectionContent, dirname4(filePath));
1032
+ const delivery = await resolveInstructionDelivery(injectionContent, dirname3(filePath));
1141
1033
  const failures = delivery.findings.filter((finding) => finding.kind !== "duplicate");
1142
1034
  if (failures.length > 0) {
1143
1035
  throw new Error(
@@ -1165,7 +1057,7 @@ async function ensureProviderRuleFile(providerId, projectDir, options) {
1165
1057
  throw new Error(`Unknown provider: "${providerId}". Check CAAMP provider registry.`);
1166
1058
  }
1167
1059
  const filePath = join2(projectDir, options.relativePath);
1168
- if (options.onlyIfExists === true && !existsSync2(filePath)) return null;
1060
+ if (options.onlyIfExists === true && !existsSync(filePath)) return null;
1169
1061
  const references = options.references ?? getProviderInstructionReferences(providerId);
1170
1062
  const body = buildInjectionContent({ references }).trim();
1171
1063
  const block = buildBlock(body);
@@ -1179,26 +1071,26 @@ ${block}
1179
1071
  action,
1180
1072
  providerId: provider.id
1181
1073
  });
1182
- if (!existsSync2(filePath)) {
1074
+ if (!existsSync(filePath)) {
1183
1075
  if (options.onlyIfExists === true) return null;
1184
- await writeFileAtomic2({ path: filePath, content: owned ?? `${block}
1076
+ await writeFileAtomic({ path: filePath, content: owned ?? `${block}
1185
1077
  ` });
1186
1078
  return result("created");
1187
1079
  }
1188
- const existing = await readFile3(filePath, "utf-8");
1080
+ const existing = await readFile2(filePath, "utf-8");
1189
1081
  if (existing.length === 0) {
1190
- assertNotTornRead(filePath, existing, (await stat3(filePath)).size);
1082
+ assertNotTornRead(filePath, existing, (await stat2(filePath)).size);
1191
1083
  }
1192
1084
  if (owned !== null) {
1193
1085
  if (existing === owned) return result("intact");
1194
- await writeFileAtomic2({ path: filePath, content: owned });
1086
+ await writeFileAtomic({ path: filePath, content: owned });
1195
1087
  return result("updated");
1196
1088
  }
1197
1089
  const hasBlock = parseBlocks(normalizeMarkers(existing).content).length > 0;
1198
1090
  const base = hasBlock ? existing : stripBareReferenceLines(existing, references).trimEnd();
1199
1091
  const { content: next, blocksBefore, repaired } = reconcile(base, body, "append");
1200
1092
  if (next === existing) return result("intact");
1201
- await writeFileAtomic2({ path: filePath, content: next });
1093
+ await writeFileAtomic({ path: filePath, content: next });
1202
1094
  if (blocksBefore === 0) return result("added");
1203
1095
  if (repaired > 0) return result("repaired");
1204
1096
  if (blocksBefore > 1) return result("consolidated");
@@ -1277,7 +1169,7 @@ async function writeAgentFileToAllProviders(providerIds, options) {
1277
1169
  continue;
1278
1170
  }
1279
1171
  processed.add(filePath);
1280
- if (options.skipMissingFolders === true && !existsSync2(folder)) {
1172
+ if (options.skipMissingFolders === true && !existsSync(folder)) {
1281
1173
  continue;
1282
1174
  }
1283
1175
  const action = await inject(filePath, options.content);
@@ -1287,10 +1179,6 @@ async function writeAgentFileToAllProviders(providerIds, options) {
1287
1179
  }
1288
1180
 
1289
1181
  export {
1290
- withFileLock,
1291
- assertNotTornRead,
1292
- JsonConfigParseError,
1293
- updateJsonConfigFile,
1294
1182
  getAllProviders,
1295
1183
  getProvider,
1296
1184
  resolveAlias,
@@ -1350,4 +1238,4 @@ export {
1350
1238
  getProviderAgentFolder,
1351
1239
  writeAgentFileToAllProviders
1352
1240
  };
1353
- //# sourceMappingURL=chunk-2FQWBKEG.js.map
1241
+ //# sourceMappingURL=chunk-2RZGY4XT.js.map