@cleocode/caamp 2026.9.19 → 2026.9.21

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.
@@ -6,15 +6,17 @@ import {
6
6
  } from "./chunk-HDZYZTR2.js";
7
7
 
8
8
  // src/core/instructions/injector.ts
9
- import { existsSync } from "fs";
9
+ import { existsSync as existsSync2 } from "fs";
10
10
  import { readFile as readFile3, stat as stat3 } from "fs/promises";
11
11
  import { homedir as homedir2 } from "os";
12
12
  import { dirname as dirname4, isAbsolute, join as join2 } from "path";
13
- import { writeFileAtomic } from "@cleocode/core/tools/fs.js";
13
+ import { writeFileAtomic as writeFileAtomic2 } from "@cleocode/core/tools/fs.js";
14
14
 
15
15
  // src/core/fs/atomic.ts
16
+ import { existsSync } from "fs";
16
17
  import { mkdir, open, readFile, rm, stat, writeFile } from "fs/promises";
17
18
  import { dirname } from "path";
19
+ import { writeFileAtomic } from "@cleocode/core/tools/fs.js";
18
20
  var DEFAULT_STALE_LOCK_MS = 3e4;
19
21
  var DEFAULT_LOCK_RETRIES = 400;
20
22
  var DEFAULT_LOCK_DELAY_MS = 25;
@@ -77,6 +79,50 @@ function assertNotTornRead(filePath, content, sizeOnDisk) {
77
79
  );
78
80
  }
79
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
+ }
80
126
 
81
127
  // src/core/registry/providers.ts
82
128
  import { readFileSync } from "fs";
@@ -353,6 +399,130 @@ function providerSupportsById(idOrAlias, capabilityPath) {
353
399
  if (!provider) return false;
354
400
  return providerSupports(provider, capabilityPath);
355
401
  }
402
+ var HITL_REQUEST_FALLBACK = {
403
+ operation: "hitl.request",
404
+ fields: ["question", "options", "recommended"],
405
+ optionFields: ["label", "description"]
406
+ };
407
+ var UNKNOWN_ASK_TOOL = {
408
+ status: "unknown",
409
+ toolName: null,
410
+ caveat: null,
411
+ source: "not verified (T12482): no doc or source located"
412
+ };
413
+ var PROVIDER_ASK_TOOLS = {
414
+ "claude-code": {
415
+ status: "native",
416
+ toolName: "AskUserQuestion",
417
+ caveat: "Main session only; subagents cannot call it",
418
+ source: "https://code.claude.com/docs/en/settings#tools-available-to-claude"
419
+ },
420
+ codex: {
421
+ status: "native",
422
+ toolName: "request_user_input",
423
+ caveat: "Plan mode by default; default mode needs [features] default_mode_request_user_input = true; unsupported in `codex exec`",
424
+ source: "codex-cli 0.157.1 binary (core/src/tools/handlers/request_user_input.rs); openai/codex#10384, #15293"
425
+ },
426
+ "gemini-cli": {
427
+ status: "native",
428
+ toolName: "ask_user",
429
+ caveat: null,
430
+ source: "https://geminicli.com/docs/tools/ask-user"
431
+ },
432
+ "copilot-cli": {
433
+ status: "native",
434
+ toolName: "ask_user",
435
+ caveat: "Disabled by --no-ask-user",
436
+ source: "https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-command-reference"
437
+ },
438
+ opencode: {
439
+ status: "native",
440
+ toolName: "question",
441
+ caveat: "Gated by permission.question",
442
+ source: "https://opencode.ai/docs/tools; opencode v2.0.15 binary (QuestionTool)"
443
+ },
444
+ kimi: {
445
+ status: "native",
446
+ toolName: "AskUserQuestion",
447
+ caveat: null,
448
+ source: 'kimi 2.1.1 binary (~/.kimi-code/bin/kimi: name: "AskUserQuestion")'
449
+ },
450
+ cursor: {
451
+ status: "native",
452
+ toolName: "AskQuestion",
453
+ caveat: "Plan mode only; not offered under `agent acp`",
454
+ source: "https://forum.cursor.com/t/allow-askquestion-tool-calls-in-agent-mode-or-any-mode/152517"
455
+ },
456
+ cline: {
457
+ status: "native",
458
+ toolName: "ask_followup_question",
459
+ caveat: null,
460
+ source: "Cline system prompt (ask_followup_question with options[])"
461
+ },
462
+ roo: {
463
+ status: "native",
464
+ toolName: "ask_followup_question",
465
+ caveat: null,
466
+ source: "https://docs.roocode.com/advanced-usage/available-tools/ask-followup-question"
467
+ },
468
+ "kilo-code": {
469
+ status: "native",
470
+ toolName: "ask_followup_question",
471
+ caveat: "Verified for the legacy IDE extension only",
472
+ source: "Kilo-Org/kilocode-legacy docs/legacy-ides/automate/tools/ask-followup-question.md"
473
+ },
474
+ pi: {
475
+ status: "none",
476
+ toolName: null,
477
+ caveat: "Optional `question` tool only via examples/extensions/question.ts",
478
+ source: "@mariozechner/pi-coding-agent 0.66.0 dist/core/tools (bash/edit/find/grep/ls/read/write)"
479
+ },
480
+ "openai-sdk": {
481
+ status: "none",
482
+ toolName: null,
483
+ caveat: "Library, not a harness: the host application defines every tool",
484
+ source: "providers/registry.json (spawnMechanism: sdk)"
485
+ },
486
+ windsurf: UNKNOWN_ASK_TOOL,
487
+ "github-copilot": UNKNOWN_ASK_TOOL,
488
+ vscode: UNKNOWN_ASK_TOOL,
489
+ zed: UNKNOWN_ASK_TOOL,
490
+ "claude-desktop": UNKNOWN_ASK_TOOL,
491
+ continue: UNKNOWN_ASK_TOOL,
492
+ goose: UNKNOWN_ASK_TOOL,
493
+ antigravity: UNKNOWN_ASK_TOOL,
494
+ "kiro-cli": UNKNOWN_ASK_TOOL,
495
+ amp: UNKNOWN_ASK_TOOL,
496
+ trae: UNKNOWN_ASK_TOOL,
497
+ aide: UNKNOWN_ASK_TOOL,
498
+ "pear-ai": UNKNOWN_ASK_TOOL,
499
+ "void-ai": UNKNOWN_ASK_TOOL,
500
+ cody: UNKNOWN_ASK_TOOL,
501
+ "qwen-code": UNKNOWN_ASK_TOOL,
502
+ openhands: UNKNOWN_ASK_TOOL,
503
+ codebuddy: UNKNOWN_ASK_TOOL,
504
+ codestory: UNKNOWN_ASK_TOOL,
505
+ aider: UNKNOWN_ASK_TOOL,
506
+ "amazon-q": UNKNOWN_ASK_TOOL,
507
+ tabnine: UNKNOWN_ASK_TOOL,
508
+ augment: UNKNOWN_ASK_TOOL,
509
+ devin: UNKNOWN_ASK_TOOL,
510
+ mentat: UNKNOWN_ASK_TOOL,
511
+ "blackbox-ai": UNKNOWN_ASK_TOOL,
512
+ sourcery: UNKNOWN_ASK_TOOL,
513
+ "replit-agent": UNKNOWN_ASK_TOOL,
514
+ "jetbrains-ai": UNKNOWN_ASK_TOOL,
515
+ codegen: UNKNOWN_ASK_TOOL,
516
+ double: UNKNOWN_ASK_TOOL,
517
+ "swe-agent": UNKNOWN_ASK_TOOL,
518
+ forge: UNKNOWN_ASK_TOOL,
519
+ "gemini-code-assist": UNKNOWN_ASK_TOOL
520
+ };
521
+ function getProviderAskTool(idOrAlias) {
522
+ const providerId = Object.hasOwn(PROVIDER_ASK_TOOLS, idOrAlias) ? idOrAlias : resolveAlias(idOrAlias);
523
+ const entry = PROVIDER_ASK_TOOLS[providerId] ?? UNKNOWN_ASK_TOOL;
524
+ return { ...entry, providerId, fallback: HITL_REQUEST_FALLBACK };
525
+ }
356
526
 
357
527
  // src/core/instructions/markers.ts
358
528
  import {
@@ -675,7 +845,7 @@ function parseCaampBlocks(fileContent) {
675
845
  return parseBlocks(fileContent);
676
846
  }
677
847
  async function dedupeFile(filePath) {
678
- if (!existsSync(filePath)) {
848
+ if (!existsSync2(filePath)) {
679
849
  return { filePath, removed: 0, kept: 0, modified: false, repaired: 0 };
680
850
  }
681
851
  return withFileLock(filePath, async () => {
@@ -685,7 +855,7 @@ async function dedupeFile(filePath) {
685
855
  const blocks = parseBlocks(healed);
686
856
  if (blocks.length === 0) {
687
857
  if (repaired > 0 && healed !== original) {
688
- await writeFileAtomic({ path: filePath, content: healed });
858
+ await writeFileAtomic2({ path: filePath, content: healed });
689
859
  return { filePath, removed: 0, kept: 0, modified: true, repaired };
690
860
  }
691
861
  return { filePath, removed: 0, kept: 0, modified: false, repaired };
@@ -712,7 +882,7 @@ async function dedupeFile(filePath) {
712
882
  if (result === original) {
713
883
  return { filePath, removed: 0, kept: blocks.length, modified: false, repaired };
714
884
  }
715
- await writeFileAtomic({ path: filePath, content: result });
885
+ await writeFileAtomic2({ path: filePath, content: result });
716
886
  return { filePath, removed, kept: keepSet.size, modified: true, repaired };
717
887
  });
718
888
  }
@@ -744,7 +914,7 @@ function scopedInstructionPath(provider, projectDir, scope) {
744
914
  return scope === "global" ? resolveGlobalInstructionPath(provider) : join2(projectDir, provider.instructFile);
745
915
  }
746
916
  async function repairInstructionFiles(projectDir, providers) {
747
- const paths = instructionFileCascade(projectDir, providers).filter((p) => existsSync(p));
917
+ const paths = instructionFileCascade(projectDir, providers).filter((p) => existsSync2(p));
748
918
  const files = [];
749
919
  for (const filePath of paths) {
750
920
  files.push(
@@ -758,7 +928,7 @@ async function repairInstructionFiles(projectDir, providers) {
758
928
  if (content === original) {
759
929
  return { filePath, removed: 0, kept: blocksBefore, modified: false, repaired };
760
930
  }
761
- await writeFileAtomic({ path: filePath, content });
931
+ await writeFileAtomic2({ path: filePath, content });
762
932
  return {
763
933
  filePath,
764
934
  removed,
@@ -777,7 +947,7 @@ async function repairInstructionFiles(projectDir, providers) {
777
947
  };
778
948
  }
779
949
  async function checkInjection(filePath, expectedContent) {
780
- if (!existsSync(filePath)) return "missing";
950
+ if (!existsSync2(filePath)) return "missing";
781
951
  const raw = await readFile3(filePath, "utf-8");
782
952
  const { content, repaired } = normalizeMarkers(raw);
783
953
  const blocks = parseBlocks(content);
@@ -792,8 +962,8 @@ async function inject(filePath, content) {
792
962
  const body = content.trim();
793
963
  assertBalancedMarkers(buildBlock(body));
794
964
  return withFileLock(filePath, async () => {
795
- if (!existsSync(filePath)) {
796
- await writeFileAtomic({ path: filePath, content: `${buildBlock(body)}
965
+ if (!existsSync2(filePath)) {
966
+ await writeFileAtomic2({ path: filePath, content: `${buildBlock(body)}
797
967
  ` });
798
968
  return "created";
799
969
  }
@@ -803,7 +973,7 @@ async function inject(filePath, content) {
803
973
  }
804
974
  const { content: next, blocksBefore, repaired } = reconcile(existing, body);
805
975
  if (next === existing) return "intact";
806
- await writeFileAtomic({ path: filePath, content: next });
976
+ await writeFileAtomic2({ path: filePath, content: next });
807
977
  if (blocksBefore === 0) return "added";
808
978
  if (repaired > 0) return "repaired";
809
979
  if (blocksBefore > 1) return "consolidated";
@@ -811,7 +981,7 @@ async function inject(filePath, content) {
811
981
  });
812
982
  }
813
983
  async function removeInjection(filePath) {
814
- if (!existsSync(filePath)) return false;
984
+ if (!existsSync2(filePath)) return false;
815
985
  return withFileLock(filePath, async () => {
816
986
  const original = await readFile3(filePath, "utf-8");
817
987
  const { content } = normalizeMarkers(original);
@@ -822,7 +992,7 @@ async function removeInjection(filePath) {
822
992
  const { rm: rm2 } = await import("fs/promises");
823
993
  await rm2(filePath);
824
994
  } else {
825
- await writeFileAtomic({ path: filePath, content: cleaned });
995
+ await writeFileAtomic2({ path: filePath, content: cleaned });
826
996
  }
827
997
  return true;
828
998
  });
@@ -844,7 +1014,7 @@ async function checkAllInjections(providers, projectDir, scope, expectedContent)
844
1014
  reason: "Managed content differs from the expected version."
845
1015
  });
846
1016
  }
847
- if (existsSync(filePath) && parseBlocks(await readFile3(filePath, "utf8")).length > 1) {
1017
+ if (existsSync2(filePath) && parseBlocks(await readFile3(filePath, "utf8")).length > 1) {
848
1018
  delivery.findings.push({
849
1019
  kind: "duplicate",
850
1020
  path: filePath,
@@ -858,7 +1028,7 @@ async function checkAllInjections(providers, projectDir, scope, expectedContent)
858
1028
  file: filePath,
859
1029
  provider: provider.id,
860
1030
  status,
861
- fileExists: existsSync(filePath)
1031
+ fileExists: existsSync2(filePath)
862
1032
  });
863
1033
  }
864
1034
  return results;
@@ -888,7 +1058,7 @@ async function ensureProviderInstructionFile(providerId, projectDir, options) {
888
1058
  }
889
1059
  let references = options.references ?? getProviderInstructionReferences(providerId);
890
1060
  let content = options.content;
891
- if (options.delivery !== "verified-references" && references.includes("@.cleo/memory-bridge.md") && !existsSync(join2(dirname4(filePath), ".cleo", "memory-bridge.md"))) {
1061
+ if (options.delivery !== "verified-references" && references.includes("@.cleo/memory-bridge.md") && !existsSync2(join2(dirname4(filePath), ".cleo", "memory-bridge.md"))) {
892
1062
  references = references.filter((reference) => reference !== "@.cleo/memory-bridge.md");
893
1063
  content = [
894
1064
  ...content ?? [],
@@ -918,6 +1088,57 @@ async function ensureProviderInstructionFile(providerId, projectDir, options) {
918
1088
  providerId: provider.id
919
1089
  };
920
1090
  }
1091
+ function stripBareReferenceLines(content, references) {
1092
+ const refs = new Set(references);
1093
+ const kept = content.split("\n").filter((line) => !refs.has(line.trim()));
1094
+ return kept.join("\n").replace(/\n{3,}/g, "\n\n");
1095
+ }
1096
+ async function ensureProviderRuleFile(providerId, projectDir, options) {
1097
+ const provider = getProvider(providerId);
1098
+ if (!provider) {
1099
+ throw new Error(`Unknown provider: "${providerId}". Check CAAMP provider registry.`);
1100
+ }
1101
+ const filePath = join2(projectDir, options.relativePath);
1102
+ if (options.onlyIfExists === true && !existsSync2(filePath)) return null;
1103
+ const references = options.references ?? getProviderInstructionReferences(providerId);
1104
+ const body = buildInjectionContent({ references }).trim();
1105
+ const block = buildBlock(body);
1106
+ assertBalancedMarkers(block);
1107
+ const owned = options.preamble !== void 0 ? `${options.preamble}
1108
+ ${block}
1109
+ ` : null;
1110
+ return withFileLock(filePath, async () => {
1111
+ const result = (action) => ({
1112
+ filePath,
1113
+ action,
1114
+ providerId: provider.id
1115
+ });
1116
+ if (!existsSync2(filePath)) {
1117
+ if (options.onlyIfExists === true) return null;
1118
+ await writeFileAtomic2({ path: filePath, content: owned ?? `${block}
1119
+ ` });
1120
+ return result("created");
1121
+ }
1122
+ const existing = await readFile3(filePath, "utf-8");
1123
+ if (existing.length === 0) {
1124
+ assertNotTornRead(filePath, existing, (await stat3(filePath)).size);
1125
+ }
1126
+ if (owned !== null) {
1127
+ if (existing === owned) return result("intact");
1128
+ await writeFileAtomic2({ path: filePath, content: owned });
1129
+ return result("updated");
1130
+ }
1131
+ const hasBlock = parseBlocks(normalizeMarkers(existing).content).length > 0;
1132
+ const base = hasBlock ? existing : stripBareReferenceLines(existing, references).trimEnd();
1133
+ const { content: next, blocksBefore, repaired } = reconcile(base, body, "append");
1134
+ if (next === existing) return result("intact");
1135
+ await writeFileAtomic2({ path: filePath, content: next });
1136
+ if (blocksBefore === 0) return result("added");
1137
+ if (repaired > 0) return result("repaired");
1138
+ if (blocksBefore > 1) return result("consolidated");
1139
+ return result("updated");
1140
+ });
1141
+ }
921
1142
  async function ensureAllProviderInstructionFiles(providerIds, projectDir, options) {
922
1143
  const results = [];
923
1144
  const processed = /* @__PURE__ */ new Set();
@@ -988,7 +1209,7 @@ async function writeAgentFileToAllProviders(providerIds, options) {
988
1209
  continue;
989
1210
  }
990
1211
  processed.add(filePath);
991
- if (options.skipMissingFolders === true && !existsSync(folder)) {
1212
+ if (options.skipMissingFolders === true && !existsSync2(folder)) {
992
1213
  continue;
993
1214
  }
994
1215
  const action = await inject(filePath, options.content);
@@ -1000,6 +1221,8 @@ async function writeAgentFileToAllProviders(providerIds, options) {
1000
1221
  export {
1001
1222
  withFileLock,
1002
1223
  assertNotTornRead,
1224
+ JsonConfigParseError,
1225
+ updateJsonConfigFile,
1003
1226
  getAllProviders,
1004
1227
  getProvider,
1005
1228
  resolveAlias,
@@ -1022,6 +1245,9 @@ export {
1022
1245
  buildSkillsMap,
1023
1246
  getProviderCapabilities,
1024
1247
  providerSupportsById,
1248
+ HITL_REQUEST_FALLBACK,
1249
+ PROVIDER_ASK_TOOLS,
1250
+ getProviderAskTool,
1025
1251
  EmbeddedDeliveryDowngradeError,
1026
1252
  isEmbeddedDelivery,
1027
1253
  blockPattern,
@@ -1049,8 +1275,9 @@ export {
1049
1275
  checkAllInjections,
1050
1276
  injectAll,
1051
1277
  ensureProviderInstructionFile,
1278
+ ensureProviderRuleFile,
1052
1279
  ensureAllProviderInstructionFiles,
1053
1280
  getProviderAgentFolder,
1054
1281
  writeAgentFileToAllProviders
1055
1282
  };
1056
- //# sourceMappingURL=chunk-CHPNPLOK.js.map
1283
+ //# sourceMappingURL=chunk-UIXTFV35.js.map