@cleocode/caamp 2026.9.8 → 2026.9.9

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.
@@ -7,7 +7,7 @@ import {
7
7
  parseBlocks,
8
8
  reconcile,
9
9
  withFileLock
10
- } from "./chunk-3IQUGSIV.js";
10
+ } from "./chunk-G3CEXEW2.js";
11
11
  import {
12
12
  __export,
13
13
  getAgentsConfigPath,
@@ -4699,4 +4699,4 @@ export {
4699
4699
  discoverSkillsMulti,
4700
4700
  validateSkill
4701
4701
  };
4702
- //# sourceMappingURL=chunk-6KBUDJZI.js.map
4702
+ //# sourceMappingURL=chunk-52ZWZXEN.js.map
@@ -7,9 +7,9 @@ import {
7
7
 
8
8
  // src/core/instructions/injector.ts
9
9
  import { existsSync } from "fs";
10
- import { readFile as readFile2, stat as stat2 } from "fs/promises";
11
- import { homedir } from "os";
12
- import { join as join2 } from "path";
10
+ import { readFile as readFile3, stat as stat3 } from "fs/promises";
11
+ import { homedir as homedir2 } from "os";
12
+ import { dirname as dirname4, join as join2 } from "path";
13
13
  import { writeFileAtomic } from "@cleocode/core/tools/fs.js";
14
14
 
15
15
  // src/core/fs/atomic.ts
@@ -19,7 +19,7 @@ var DEFAULT_STALE_LOCK_MS = 3e4;
19
19
  var DEFAULT_LOCK_RETRIES = 400;
20
20
  var DEFAULT_LOCK_DELAY_MS = 25;
21
21
  function sleep(ms) {
22
- return new Promise((resolve) => setTimeout(resolve, ms));
22
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
23
23
  }
24
24
  async function removeStaleGuard(guardPath, staleMs, expectedToken) {
25
25
  try {
@@ -363,13 +363,17 @@ function blockPattern() {
363
363
  function normalizeMarkers(content) {
364
364
  let repaired = 0;
365
365
  const heal = (input, source, canonical) => input.replace(new RegExp(source, "gmi"), (match) => {
366
- if (match === canonical) return match;
366
+ const leading = /^[ \t\r]*/.exec(match)?.[0] ?? "";
367
+ const trailing = /[ \t\r]*$/.exec(match)?.[0] ?? "";
368
+ const replacement = leading + canonical + trailing;
369
+ if (match === replacement) return match;
367
370
  repaired += 1;
368
- return canonical;
371
+ return replacement;
369
372
  });
370
- let out = heal(content, CAAMP_DAMAGED_START_PATTERN_SOURCE, CAAMP_MARKER_START);
373
+ const bom = content.startsWith("\uFEFF") ? "\uFEFF" : "";
374
+ let out = heal(content.slice(bom.length), CAAMP_DAMAGED_START_PATTERN_SOURCE, CAAMP_MARKER_START);
371
375
  out = heal(out, CAAMP_DAMAGED_END_PATTERN_SOURCE, CAAMP_MARKER_END);
372
- return { content: out, repaired };
376
+ return { content: bom + out, repaired };
373
377
  }
374
378
  function parseBlocks(fileContent) {
375
379
  const blocks = [];
@@ -390,24 +394,40 @@ function buildBlock(content) {
390
394
  ${content}
391
395
  ${CAAMP_MARKER_END}`;
392
396
  }
393
- function tidy(content) {
394
- const collapsed = content.replace(/\n{3,}/g, "\n\n").trimEnd();
395
- return collapsed.length > 0 ? `${collapsed}
396
- ` : "";
397
+ function assertBalancedMarkers(content) {
398
+ let open2 = false;
399
+ const markers = new RegExp(`${CAAMP_MARKER_START}|${CAAMP_MARKER_END}`, "g");
400
+ for (const match of content.matchAll(markers)) {
401
+ const opening = match[0] === CAAMP_MARKER_START;
402
+ if (opening === open2) {
403
+ throw new Error(
404
+ "Ambiguous CAAMP markers: nested or unmatched delimiter; original content preserved."
405
+ );
406
+ }
407
+ open2 = opening;
408
+ }
409
+ if (open2)
410
+ throw new Error(
411
+ "Ambiguous CAAMP markers: opening delimiter has no end; original content preserved."
412
+ );
397
413
  }
398
414
  function reconcile(existing, desiredContent, insert = "prepend") {
399
415
  const { content: healed, repaired } = normalizeMarkers(existing);
416
+ assertBalancedMarkers(healed);
400
417
  const blocks = parseBlocks(healed);
401
418
  const desiredBlock = buildBlock(desiredContent.trim());
419
+ assertBalancedMarkers(desiredBlock);
402
420
  if (blocks.length === 0) {
403
- const body = healed.trim();
404
- if (body.length === 0) return { content: tidy(desiredBlock), blocksBefore: 0, repaired };
421
+ if (healed.length === 0) return { content: `${desiredBlock}
422
+ `, blocksBefore: 0, repaired };
423
+ const bom = healed.startsWith("\uFEFF") ? "\uFEFF" : "";
405
424
  return {
406
- content: insert === "append" ? tidy(`${body}
425
+ content: insert === "append" ? `${healed}
407
426
 
408
- ${desiredBlock}`) : tidy(`${desiredBlock}
427
+ ${desiredBlock}
428
+ ` : `${bom}${desiredBlock}
409
429
 
410
- ${body}`),
430
+ ${healed.slice(bom.length)}`,
411
431
  blocksBefore: 0,
412
432
  repaired
413
433
  };
@@ -420,7 +440,7 @@ ${body}`),
420
440
  if (index === 0) out += desiredBlock;
421
441
  }
422
442
  out += healed.slice(cursor);
423
- return { content: tidy(out), blocksBefore: blocks.length, repaired };
443
+ return { content: out, blocksBefore: blocks.length, repaired };
424
444
  }
425
445
  function mergeBlockBodies(blocks) {
426
446
  const seen = /* @__PURE__ */ new Set();
@@ -437,6 +457,7 @@ function mergeBlockBodies(blocks) {
437
457
  }
438
458
  function repairContent(existing) {
439
459
  const { content: healed, repaired } = normalizeMarkers(existing);
460
+ assertBalancedMarkers(healed);
440
461
  const blocks = parseBlocks(healed);
441
462
  if (blocks.length === 0) {
442
463
  return { content: healed, blocksBefore: 0, repaired };
@@ -446,6 +467,10 @@ function repairContent(existing) {
446
467
  }
447
468
 
448
469
  // src/core/instructions/templates.ts
470
+ import { createHash } from "crypto";
471
+ import { readFile as readFile2, realpath, stat as stat2 } from "fs/promises";
472
+ import { homedir } from "os";
473
+ import { dirname as dirname3, resolve } from "path";
449
474
  function buildInjectionContent(template) {
450
475
  const lines = [];
451
476
  for (const ref of template.references) {
@@ -512,6 +537,113 @@ function groupByInstructFile(providers) {
512
537
  }
513
538
  return groups;
514
539
  }
540
+ async function resolveInstructionDelivery(content, baseDir) {
541
+ const result = {
542
+ content: "",
543
+ sources: [],
544
+ findings: [],
545
+ liveEvaluation: "unverified"
546
+ };
547
+ const visited = /* @__PURE__ */ new Set();
548
+ let bytes = 0;
549
+ async function expand(text, directory, ancestors) {
550
+ const lines = [];
551
+ let fence = null;
552
+ for (const line of text.split("\n")) {
553
+ const fenceMatch = /^\s*(`{3,}|~{3,})/.exec(line);
554
+ if (fenceMatch?.[1]) {
555
+ if (!fence) fence = fenceMatch[1];
556
+ else if (fenceMatch[1][0] === fence[0] && fenceMatch[1].length >= fence.length)
557
+ fence = null;
558
+ lines.push(line);
559
+ continue;
560
+ }
561
+ const stamp = !fence ? /^<!-- CAAMP:SOURCE (\S+) ([a-f0-9]{64}) -->$/.exec(line) : null;
562
+ if (stamp?.[1] && stamp[2]) {
563
+ let sourcePath = stamp[1];
564
+ try {
565
+ sourcePath = decodeURIComponent(sourcePath);
566
+ const sourceSize = (await stat2(sourcePath)).size;
567
+ bytes += sourceSize;
568
+ if (bytes > 524288) {
569
+ result.findings.push({
570
+ kind: "limit",
571
+ path: sourcePath,
572
+ reason: "Instruction byte budget exceeded."
573
+ });
574
+ } else {
575
+ const digest = createHash("sha256").update(await readFile2(sourcePath)).digest("hex");
576
+ if (digest !== stamp[2])
577
+ result.findings.push({
578
+ kind: "stale",
579
+ path: sourcePath,
580
+ reason: "Embedded source changed after delivery."
581
+ });
582
+ }
583
+ } catch (error) {
584
+ result.findings.push({
585
+ kind: "missing-reference",
586
+ path: sourcePath,
587
+ reason: String(error)
588
+ });
589
+ }
590
+ lines.push(line);
591
+ continue;
592
+ }
593
+ const ref = !fence ? /^\s*@([^\s]+)\s*$/.exec(line)?.[1] : void 0;
594
+ if (!ref) {
595
+ if (!/^\s*<!-- CAAMP:(START|END) -->\s*$/.test(line)) lines.push(line);
596
+ continue;
597
+ }
598
+ let path = resolve(directory, ref.startsWith("~/") ? resolve(homedir(), ref.slice(2)) : ref);
599
+ try {
600
+ path = await realpath(path);
601
+ } catch (error) {
602
+ result.findings.push({ kind: "missing-reference", path, reason: String(error) });
603
+ continue;
604
+ }
605
+ if (ancestors.includes(path)) {
606
+ result.findings.push({ kind: "cycle", path, reason: "Reference repeats an ancestor." });
607
+ continue;
608
+ }
609
+ if (visited.has(path)) {
610
+ result.findings.push({ kind: "duplicate", path, reason: "Source already embedded once." });
611
+ continue;
612
+ }
613
+ if (ancestors.length >= 16 || visited.size >= 64 || bytes >= 524288) {
614
+ result.findings.push({
615
+ kind: "limit",
616
+ path,
617
+ reason: "Instruction expansion budget exceeded."
618
+ });
619
+ continue;
620
+ }
621
+ try {
622
+ bytes += (await stat2(path)).size;
623
+ if (bytes > 524288) {
624
+ result.findings.push({
625
+ kind: "limit",
626
+ path,
627
+ reason: "Instruction byte budget exceeded."
628
+ });
629
+ continue;
630
+ }
631
+ const source = await readFile2(path, "utf8");
632
+ visited.add(path);
633
+ result.sources.push(path);
634
+ lines.push(
635
+ `<!-- CAAMP:SOURCE ${encodeURIComponent(path)} ${createHash("sha256").update(source).digest("hex")} -->`
636
+ );
637
+ lines.push(await expand(source, dirname3(path), [...ancestors, path]));
638
+ } catch (error) {
639
+ result.findings.push({ kind: "missing-reference", path, reason: String(error) });
640
+ }
641
+ }
642
+ return lines.join("\n");
643
+ }
644
+ result.content = await expand(content, baseDir, []);
645
+ return result;
646
+ }
515
647
 
516
648
  // src/core/instructions/injector.ts
517
649
  function parseCaampBlocks(fileContent) {
@@ -522,8 +654,9 @@ async function dedupeFile(filePath) {
522
654
  return { filePath, removed: 0, kept: 0, modified: false, repaired: 0 };
523
655
  }
524
656
  return withFileLock(filePath, async () => {
525
- const original = await readFile2(filePath, "utf-8");
657
+ const original = await readFile3(filePath, "utf-8");
526
658
  const { content: healed, repaired } = normalizeMarkers(original);
659
+ assertBalancedMarkers(healed);
527
660
  const blocks = parseBlocks(healed);
528
661
  if (blocks.length === 0) {
529
662
  if (repaired > 0 && healed !== original) {
@@ -548,8 +681,6 @@ async function dedupeFile(filePath) {
548
681
  }
549
682
  }
550
683
  result += healed.slice(cursor);
551
- result = `${result.replace(/\n{3,}/g, "\n\n").trimEnd()}
552
- `;
553
684
  if (removed === 0 && repaired === 0) {
554
685
  return { filePath, removed: 0, kept: blocks.length, modified: false, repaired };
555
686
  }
@@ -585,7 +716,7 @@ async function repairInstructionFiles(projectDir, providers) {
585
716
  for (const filePath of paths) {
586
717
  files.push(
587
718
  await withFileLock(filePath, async () => {
588
- const original = await readFile2(filePath, "utf-8");
719
+ const original = await readFile3(filePath, "utf-8");
589
720
  const { content, blocksBefore, repaired } = repairContent(original);
590
721
  const removed = Math.max(0, blocksBefore - 1);
591
722
  if (removed === 0 && repaired === 0) {
@@ -614,7 +745,7 @@ async function repairInstructionFiles(projectDir, providers) {
614
745
  }
615
746
  async function checkInjection(filePath, expectedContent) {
616
747
  if (!existsSync(filePath)) return "missing";
617
- const raw = await readFile2(filePath, "utf-8");
748
+ const raw = await readFile3(filePath, "utf-8");
618
749
  const { content, repaired } = normalizeMarkers(raw);
619
750
  const blocks = parseBlocks(content);
620
751
  if (blocks.length === 0) return "none";
@@ -626,17 +757,16 @@ async function checkInjection(filePath, expectedContent) {
626
757
  }
627
758
  async function inject(filePath, content) {
628
759
  const body = content.trim();
629
- if (!existsSync(filePath)) {
630
- return withFileLock(filePath, async () => {
760
+ assertBalancedMarkers(buildBlock(body));
761
+ return withFileLock(filePath, async () => {
762
+ if (!existsSync(filePath)) {
631
763
  await writeFileAtomic({ path: filePath, content: `${buildBlock(body)}
632
764
  ` });
633
765
  return "created";
634
- });
635
- }
636
- return withFileLock(filePath, async () => {
637
- const existing = await readFile2(filePath, "utf-8");
766
+ }
767
+ const existing = await readFile3(filePath, "utf-8");
638
768
  if (existing.length === 0) {
639
- assertNotTornRead(filePath, existing, (await stat2(filePath)).size);
769
+ assertNotTornRead(filePath, existing, (await stat3(filePath)).size);
640
770
  }
641
771
  const { content: next, blocksBefore, repaired } = reconcile(existing, body);
642
772
  if (next === existing) return "intact";
@@ -650,16 +780,16 @@ async function inject(filePath, content) {
650
780
  async function removeInjection(filePath) {
651
781
  if (!existsSync(filePath)) return false;
652
782
  return withFileLock(filePath, async () => {
653
- const original = await readFile2(filePath, "utf-8");
783
+ const original = await readFile3(filePath, "utf-8");
654
784
  const { content } = normalizeMarkers(original);
785
+ assertBalancedMarkers(content);
655
786
  if (parseBlocks(content).length === 0) return false;
656
- const cleaned = content.replace(blockPattern(), "").replace(/^\n{2,}/, "\n").trim();
787
+ const cleaned = content.replace(blockPattern(), "");
657
788
  if (!cleaned) {
658
789
  const { rm: rm2 } = await import("fs/promises");
659
790
  await rm2(filePath);
660
791
  } else {
661
- await writeFileAtomic({ path: filePath, content: `${cleaned}
662
- ` });
792
+ await writeFileAtomic({ path: filePath, content: cleaned });
663
793
  }
664
794
  return true;
665
795
  });
@@ -671,8 +801,26 @@ async function checkAllInjections(providers, projectDir, scope, expectedContent)
671
801
  const filePath = scope === "global" ? join2(provider.pathGlobal, provider.instructFile) : join2(projectDir, provider.instructFile);
672
802
  if (checked.has(filePath)) continue;
673
803
  checked.add(filePath);
674
- const status = await checkInjection(filePath, expectedContent);
804
+ let status = await checkInjection(filePath, expectedContent);
805
+ const delivery = await resolveInstructionDelivery(`@${filePath}`, dirname4(filePath));
806
+ if (expectedContent !== void 0 && status === "outdated") {
807
+ delivery.findings.push({
808
+ kind: "stale",
809
+ path: filePath,
810
+ reason: "Managed content differs from the expected version."
811
+ });
812
+ }
813
+ if (existsSync(filePath) && parseBlocks(await readFile3(filePath, "utf8")).length > 1) {
814
+ delivery.findings.push({
815
+ kind: "duplicate",
816
+ path: filePath,
817
+ reason: "Multiple managed blocks are delivered."
818
+ });
819
+ }
820
+ if (delivery.findings.length > 0 && status === "current") status = "outdated";
675
821
  results.push({
822
+ deliveryFindings: delivery.findings,
823
+ liveEvaluation: delivery.liveEvaluation,
676
824
  file: filePath,
677
825
  provider: provider.id,
678
826
  status,
@@ -700,12 +848,30 @@ async function ensureProviderInstructionFile(providerId, projectDir, options) {
700
848
  }
701
849
  const scope = options.scope ?? "project";
702
850
  const filePath = scope === "global" ? join2(provider.pathGlobal, provider.instructFile) : join2(projectDir, provider.instructFile);
703
- const references = options.references ?? getProviderInstructionReferences(providerId);
851
+ let references = options.references ?? getProviderInstructionReferences(providerId);
852
+ let content = options.content;
853
+ if (options.delivery !== "verified-references" && references.includes("@.cleo/memory-bridge.md") && !existsSync(join2(dirname4(filePath), ".cleo", "memory-bridge.md"))) {
854
+ references = references.filter((reference) => reference !== "@.cleo/memory-bridge.md");
855
+ content = [
856
+ ...content ?? [],
857
+ "Project memory bridge unavailable. Run `cleo memory digest` to inspect current evidence."
858
+ ];
859
+ }
704
860
  const template = {
705
861
  references,
706
- content: options.content
862
+ content
707
863
  };
708
- const injectionContent = buildInjectionContent(template);
864
+ let injectionContent = buildInjectionContent(template);
865
+ if (options.delivery !== "verified-references") {
866
+ const delivery = await resolveInstructionDelivery(injectionContent, dirname4(filePath));
867
+ const failures = delivery.findings.filter((finding) => finding.kind !== "duplicate");
868
+ if (failures.length > 0) {
869
+ throw new Error(
870
+ `Instruction delivery failed: ${failures.map((finding) => `${finding.kind}: ${finding.path}`).join("; ")}`
871
+ );
872
+ }
873
+ injectionContent = delivery.content;
874
+ }
709
875
  const action = await inject(filePath, injectionContent);
710
876
  return {
711
877
  filePath,
@@ -743,7 +909,7 @@ async function ensureAllProviderInstructionFiles(providerIds, projectDir, option
743
909
  return results;
744
910
  }
745
911
  function getProviderAgentFolder(providerId) {
746
- const home = homedir();
912
+ const home = homedir2();
747
913
  switch (providerId) {
748
914
  case "claude-code":
749
915
  case "claude-sdk":
@@ -828,6 +994,7 @@ export {
828
994
  generateInjectionContent,
829
995
  generateSkillsSection,
830
996
  groupByInstructFile,
997
+ resolveInstructionDelivery,
831
998
  parseCaampBlocks,
832
999
  dedupeFile,
833
1000
  dedupeFiles,
@@ -843,4 +1010,4 @@ export {
843
1010
  getProviderAgentFolder,
844
1011
  writeAgentFileToAllProviders
845
1012
  };
846
- //# sourceMappingURL=chunk-3IQUGSIV.js.map
1013
+ //# sourceMappingURL=chunk-G3CEXEW2.js.map