caveat-cli 0.17.3 → 0.17.4

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/dist/index.js CHANGED
@@ -38,14 +38,18 @@ import {
38
38
  get,
39
39
  hasAnyStruggleSignal,
40
40
  initOwnSync,
41
+ isPrivateOwnerStat,
42
+ isWindows,
41
43
  listRecent,
42
44
  listStale,
43
45
  loadConfig,
44
46
  logHookQueryMiss,
45
47
  markHit,
46
48
  maybeSweepPendingDirs,
49
+ nodeExecutableNames,
47
50
  observeRuntimeError,
48
51
  openDb,
52
+ powershellCallPrefix,
49
53
  prewarmSealedKeys,
50
54
  publishOwn,
51
55
  readCodexSessionSignals,
@@ -72,7 +76,7 @@ import {
72
76
  userPromptSubmitReminderText,
73
77
  writeDigestMarker,
74
78
  writeUserConfigPatch
75
- } from "./chunk-TRIQ5WFY.js";
79
+ } from "./chunk-ZBHCOA6R.js";
76
80
 
77
81
  // ../../node_modules/.pnpm/ajv@8.18.0/node_modules/ajv/dist/compile/codegen/code.js
78
82
  var require_code = __commonJS({
@@ -6885,25 +6889,67 @@ var stdoutLogger = {
6885
6889
 
6886
6890
  // src/commands/init.ts
6887
6891
  import { spawnSync as spawnSync4 } from "node:child_process";
6888
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, readdirSync, renameSync, rmdirSync, writeFileSync as writeFileSync4 } from "node:fs";
6889
- import { dirname as dirname4, join as join8 } from "node:path";
6892
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync, renameSync, rmdirSync, writeFileSync as writeFileSync3 } from "node:fs";
6893
+ import { dirname as dirname3, join as join8 } from "node:path";
6890
6894
 
6891
6895
  // src/claudeInstall.ts
6892
6896
  import { spawnSync } from "node:child_process";
6893
- import { accessSync, constants, copyFileSync, existsSync, mkdirSync, readFileSync as readFileSync2, realpathSync, statSync, writeFileSync } from "node:fs";
6894
- import { dirname as dirname2, isAbsolute, join as join3 } from "node:path";
6897
+ import { constants, existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
6898
+ import { join as join3 } from "node:path";
6899
+
6900
+ // src/installShared.ts
6901
+ import { accessSync, copyFileSync, existsSync, mkdirSync, realpathSync, statSync, writeFileSync } from "node:fs";
6902
+ import { dirname as dirname2, isAbsolute } from "node:path";
6903
+ function quoteIfSpaces(p) {
6904
+ return p.includes(" ") ? `"${p}"` : p;
6905
+ }
6906
+ function commandTokens(command) {
6907
+ const tokens = [];
6908
+ const pattern = /"([^"]*)"|([^\s"]+)/g;
6909
+ let end = 0;
6910
+ let match;
6911
+ while ((match = pattern.exec(command)) !== null) {
6912
+ if (command.slice(end, match.index).trim()) return null;
6913
+ tokens.push(match[1] ?? match[2]);
6914
+ end = pattern.lastIndex;
6915
+ }
6916
+ return command.slice(end).trim() ? null : tokens;
6917
+ }
6918
+ function isCanonicalAsset(path, expectedPath, mode) {
6919
+ if (typeof path !== "string" || !isAbsolute(path) || !isAbsolute(expectedPath)) return false;
6920
+ try {
6921
+ accessSync(path, mode);
6922
+ return statSync(path).isFile() && realpathSync(path) === realpathSync(expectedPath);
6923
+ } catch {
6924
+ return false;
6925
+ }
6926
+ }
6927
+ function writeFileWithBackup(path, text) {
6928
+ const dir = dirname2(path);
6929
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
6930
+ let backupPath = "";
6931
+ if (existsSync(path)) {
6932
+ backupPath = `${path}.caveat-backup-${Date.now()}`;
6933
+ copyFileSync(path, backupPath);
6934
+ }
6935
+ writeFileSync(path, text, "utf-8");
6936
+ return backupPath;
6937
+ }
6938
+ function writeJsonWithBackup(path, value) {
6939
+ return writeFileWithBackup(path, `${JSON.stringify(value, null, 2)}
6940
+ `);
6941
+ }
6942
+
6943
+ // src/claudeInstall.ts
6895
6944
  var EVENT_USER_PROMPT_SUBMIT = "UserPromptSubmit";
6896
6945
  var EVENT_POST_TOOL_USE = "PostToolUse";
6897
6946
  var EVENT_POST_TOOL_USE_FAILURE = "PostToolUseFailure";
6898
6947
  var EVENT_STOP = "Stop";
6899
- function quote(p) {
6900
- return p.includes(" ") ? `"${p}"` : p;
6901
- }
6902
6948
  function mcpArgs(cliScriptPath) {
6903
6949
  return ["--disable-warning=ExperimentalWarning", cliScriptPath, "mcp-server"];
6904
6950
  }
6905
6951
  function hookCommand(nodePath, cliScriptPath, event) {
6906
- return `${quote(nodePath)} ${quote(cliScriptPath)} hook ${event}`;
6952
+ return `${quoteIfSpaces(nodePath)} ${quoteIfSpaces(cliScriptPath)} hook ${event}`;
6907
6953
  }
6908
6954
  function upsertHook(settings, event, command) {
6909
6955
  settings.hooks ??= {};
@@ -6951,27 +6997,6 @@ function isCaveatClaudeHookCommand(actual, event) {
6951
6997
  const lower = actual.toLowerCase();
6952
6998
  return !isEnvPrefixedCommand(actual) && lower.includes("caveat") && actual.includes(`hook ${event}`);
6953
6999
  }
6954
- function commandTokens(command) {
6955
- const tokens = [];
6956
- const pattern = /"([^"]*)"|([^\s"]+)/g;
6957
- let end = 0;
6958
- let match;
6959
- while ((match = pattern.exec(command)) !== null) {
6960
- if (command.slice(end, match.index).trim()) return null;
6961
- tokens.push(match[1] ?? match[2]);
6962
- end = pattern.lastIndex;
6963
- }
6964
- return command.slice(end).trim() ? null : tokens;
6965
- }
6966
- function isCanonicalAsset(path, expectedPath, mode) {
6967
- if (typeof path !== "string" || !isAbsolute(path) || !isAbsolute(expectedPath)) return false;
6968
- try {
6969
- accessSync(path, mode);
6970
- return statSync(path).isFile() && realpathSync(path) === realpathSync(expectedPath);
6971
- } catch {
6972
- return false;
6973
- }
6974
- }
6975
7000
  function isCanonicalCaveatClaudeHookCommand(actual, event, nodePath, cliScriptPath) {
6976
7001
  const tokens = commandTokens(actual);
6977
7002
  return tokens?.length === 4 && isCanonicalAsset(tokens[0], nodePath, constants.X_OK) && isCanonicalAsset(tokens[1], cliScriptPath, constants.R_OK) && tokens[2] === "hook" && tokens[3] === event;
@@ -6982,19 +7007,11 @@ function isCaveatClaudeMcpRegistration(value, nodePath, cliScriptPath) {
6982
7007
  return Object.keys(server).length === 4 && ["args", "command", "env", "type"].every((key) => Object.hasOwn(server, key)) && server.type === "stdio" && isCanonicalAsset(server.command, nodePath, constants.X_OK) && Array.isArray(server.args) && server.args.length === 3 && server.args[0] === "--disable-warning=ExperimentalWarning" && isCanonicalAsset(server.args[1], cliScriptPath, constants.R_OK) && server.args[2] === "mcp-server" && server.env !== null && typeof server.env === "object" && !Array.isArray(server.env) && Object.keys(server.env).length === 0;
6983
7008
  }
6984
7009
  function readSettings(path) {
6985
- if (!existsSync(path)) return {};
7010
+ if (!existsSync2(path)) return {};
6986
7011
  return JSON.parse(readFileSync2(path, "utf-8"));
6987
7012
  }
6988
7013
  function writeSettings(path, settings) {
6989
- const dir = dirname2(path);
6990
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
6991
- let backupPath = "";
6992
- if (existsSync(path)) {
6993
- backupPath = `${path}.caveat-backup-${Date.now()}`;
6994
- copyFileSync(path, backupPath);
6995
- }
6996
- writeFileSync(path, JSON.stringify(settings, null, 2) + "\n", "utf-8");
6997
- return backupPath;
7014
+ return writeJsonWithBackup(path, settings);
6998
7015
  }
6999
7016
  var CLAUDE_BIN = "claude";
7000
7017
  function shellQuote(s) {
@@ -7112,51 +7129,32 @@ function uninstallClaudeIntegration(opts) {
7112
7129
  }
7113
7130
 
7114
7131
  // src/codexHookInstall.ts
7115
- import { accessSync as accessSync2, constants as constants2, copyFileSync as copyFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, realpathSync as realpathSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
7116
- import { dirname as dirname3, isAbsolute as isAbsolute2, join as join4 } from "node:path";
7132
+ import { constants as constants2, existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
7133
+ import { join as join4 } from "node:path";
7117
7134
  import { parse as parseToml } from "smol-toml";
7118
- function quote2(p) {
7119
- return p.includes(" ") ? `"${p}"` : p;
7120
- }
7121
7135
  function hookCommand2(nodePath, cliScriptPath, event, platform = process.platform) {
7122
- const prefix = platform === "win32" ? "& " : "";
7123
- return `${prefix}${quote2(nodePath)} ${quote2(cliScriptPath)} codex-hook ${event}`;
7136
+ return `${powershellCallPrefix(platform)}${quoteIfSpaces(nodePath)} ${quoteIfSpaces(cliScriptPath)} codex-hook ${event}`;
7124
7137
  }
7125
7138
  function eventCommandFragment(event) {
7126
7139
  return `codex-hook ${event}`;
7127
7140
  }
7128
7141
  function isSameHookCommand2(actual, expected) {
7129
- return actual === expected || actual.endsWith(` ${expected}`);
7142
+ const normalizedTokens = (command) => {
7143
+ const parsed = commandTokens(command);
7144
+ return parsed?.[0] === "&" ? parsed.slice(1) : parsed;
7145
+ };
7146
+ const actualTokens = normalizedTokens(actual);
7147
+ const expectedTokens = normalizedTokens(expected);
7148
+ return actualTokens !== null && expectedTokens !== null && actualTokens.length === expectedTokens.length && actualTokens.every((token, index) => token === expectedTokens[index]);
7130
7149
  }
7131
7150
  function isCaveatCodexHookCommand(actual, event) {
7132
7151
  const lower = actual.toLowerCase();
7133
7152
  return lower.includes("caveat") && actual.includes(eventCommandFragment(event));
7134
7153
  }
7135
- function commandTokens2(command) {
7136
- const tokens = [];
7137
- const pattern = /"([^"]*)"|([^\s"]+)/g;
7138
- let end = 0;
7139
- let match;
7140
- while ((match = pattern.exec(command)) !== null) {
7141
- if (command.slice(end, match.index).trim()) return null;
7142
- tokens.push(match[1] ?? match[2]);
7143
- end = pattern.lastIndex;
7144
- }
7145
- return command.slice(end).trim() ? null : tokens;
7146
- }
7147
- function isCanonicalAsset2(path, expectedPath, mode) {
7148
- if (typeof path !== "string" || !isAbsolute2(path) || !isAbsolute2(expectedPath)) return false;
7149
- try {
7150
- accessSync2(path, mode);
7151
- return statSync2(path).isFile() && realpathSync2(path) === realpathSync2(expectedPath);
7152
- } catch {
7153
- return false;
7154
- }
7155
- }
7156
7154
  function isCanonicalCaveatCodexHookCommand(actual, event, nodePath, cliScriptPath) {
7157
- const parsed = commandTokens2(actual);
7155
+ const parsed = commandTokens(actual);
7158
7156
  const tokens = parsed?.[0] === "&" ? parsed.slice(1) : parsed;
7159
- return tokens?.length === 4 && isCanonicalAsset2(tokens[0], nodePath, constants2.X_OK) && isCanonicalAsset2(tokens[1], cliScriptPath, constants2.R_OK) && tokens[2] === "codex-hook" && tokens[3] === event;
7157
+ return tokens?.length === 4 && isCanonicalAsset(tokens[0], nodePath, constants2.X_OK) && isCanonicalAsset(tokens[1], cliScriptPath, constants2.R_OK) && tokens[2] === "codex-hook" && tokens[3] === event;
7160
7158
  }
7161
7159
  function isCanonicalCaveatCodexHookEntry(value, event, nodePath, cliScriptPath) {
7162
7160
  if (!isPlainRecord(value) || typeof value.type !== "string" || typeof value.command !== "string") return false;
@@ -7168,21 +7166,9 @@ function hasCaveatHook(hooksJson, event, fragment) {
7168
7166
  ) ?? false;
7169
7167
  }
7170
7168
  function readHooks(path) {
7171
- if (!existsSync2(path)) return {};
7169
+ if (!existsSync3(path)) return {};
7172
7170
  return JSON.parse(readFileSync3(path, "utf-8"));
7173
7171
  }
7174
- function writeJsonWithBackup(path, value) {
7175
- const dir = dirname3(path);
7176
- if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
7177
- let backupPath = "";
7178
- if (existsSync2(path)) {
7179
- backupPath = `${path}.caveat-backup-${Date.now()}`;
7180
- copyFileSync2(path, backupPath);
7181
- }
7182
- writeFileSync2(path, `${JSON.stringify(value, null, 2)}
7183
- `, "utf-8");
7184
- return backupPath;
7185
- }
7186
7172
  function upsertHook2(hooksJson, event, command, subcommand) {
7187
7173
  hooksJson.hooks ??= {};
7188
7174
  const list = hooksJson.hooks[event] ??= [];
@@ -7404,21 +7390,10 @@ function maskTomlStringsAndComments(raw) {
7404
7390
  }
7405
7391
  return output;
7406
7392
  }
7407
- function writeConfigWithBackup(path, text) {
7408
- const dir = dirname3(path);
7409
- if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
7410
- let backupPath = "";
7411
- if (existsSync2(path)) {
7412
- backupPath = `${path}.caveat-backup-${Date.now()}`;
7413
- copyFileSync2(path, backupPath);
7414
- }
7415
- writeFileSync2(path, text, "utf-8");
7416
- return backupPath;
7417
- }
7418
7393
  function installCodexHooks(opts) {
7419
7394
  const hooksPath = join4(opts.codexHome, "hooks.json");
7420
7395
  const configPath = join4(opts.codexHome, "config.toml");
7421
- const rawConfig = existsSync2(configPath) ? readFileSync3(configPath, "utf-8") : "";
7396
+ const rawConfig = existsSync3(configPath) ? readFileSync3(configPath, "utf-8") : "";
7422
7397
  const enabled = enableCodexHooksFeature(rawConfig);
7423
7398
  if (enabled.status === "blocked") {
7424
7399
  return {
@@ -7458,7 +7433,7 @@ function installCodexHooks(opts) {
7458
7433
  if (backup) backupPath = backup;
7459
7434
  }
7460
7435
  if (enabled.changed) {
7461
- const backup = writeConfigWithBackup(configPath, enabled.text);
7436
+ const backup = writeFileWithBackup(configPath, enabled.text);
7462
7437
  if (backup) configBackupPath = backup;
7463
7438
  }
7464
7439
  }
@@ -7582,9 +7557,9 @@ function askOnce(question) {
7582
7557
  }
7583
7558
 
7584
7559
  // src/nodePath.ts
7585
- import { existsSync as existsSync3, realpathSync as realpathSync3 } from "node:fs";
7560
+ import { existsSync as existsSync4, realpathSync as realpathSync2 } from "node:fs";
7586
7561
  import { delimiter, join as join5 } from "node:path";
7587
- var defaultRealpath = (path) => realpathSync3.native(path);
7562
+ var defaultRealpath = (path) => realpathSync2.native(path);
7588
7563
  function safeRealpath(path, realpath = defaultRealpath) {
7589
7564
  try {
7590
7565
  return realpath(path);
@@ -7596,12 +7571,12 @@ function resolveHookNodePath({
7596
7571
  env = process.env,
7597
7572
  execPath = process.execPath,
7598
7573
  platform = process.platform,
7599
- exists = existsSync3,
7574
+ exists = existsSync4,
7600
7575
  realpath = defaultRealpath
7601
7576
  } = {}) {
7602
7577
  const execRealpath = safeRealpath(execPath, realpath);
7603
7578
  const pathEnv = env.PATH ?? env.Path ?? "";
7604
- const names = platform === "win32" ? ["node.exe", "node.cmd", "node.bat", "node"] : ["node"];
7579
+ const names = nodeExecutableNames(platform);
7605
7580
  for (const dir of pathEnv.split(delimiter).filter(Boolean)) {
7606
7581
  for (const name of names) {
7607
7582
  const candidate = join5(dir, name);
@@ -7616,12 +7591,12 @@ function resolveHookNodePath({
7616
7591
  }
7617
7592
 
7618
7593
  // src/commands/publish.ts
7619
- import { existsSync as existsSync4 } from "node:fs";
7594
+ import { existsSync as existsSync5 } from "node:fs";
7620
7595
  import { join as join7 } from "node:path";
7621
7596
 
7622
7597
  // src/commands/codexSidecarAdvisory.ts
7623
7598
  import { spawnSync as spawnSync3 } from "node:child_process";
7624
- import { mkdtempSync, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
7599
+ import { mkdtempSync, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
7625
7600
  import { tmpdir } from "node:os";
7626
7601
  import { join as join6 } from "node:path";
7627
7602
  var DEFAULT_HOOK_SIDECAR_TIMEOUT_MS = 12e4;
@@ -7684,7 +7659,7 @@ function runCodexSidecarAdvisory(input) {
7684
7659
  args.push("--save-result", resultFile);
7685
7660
  if (input.additionalContext) {
7686
7661
  const additionalContextFile = join6(resultDir, "hook-signal.json");
7687
- writeFileSync3(additionalContextFile, JSON.stringify({ context: [input.additionalContext] }) + "\n", { encoding: "utf-8", mode: 384, flag: "wx" });
7662
+ writeFileSync2(additionalContextFile, JSON.stringify({ context: [input.additionalContext] }) + "\n", { encoding: "utf-8", mode: 384, flag: "wx" });
7688
7663
  args.push("--additional-context-file", additionalContextFile);
7689
7664
  }
7690
7665
  const nodeCli = process.env.CAVEAT_CODEX_SIDECAR_NODE_CLI;
@@ -7779,7 +7754,7 @@ async function runPublish(ctx, opts, dependencies = {}) {
7779
7754
  const isTty = dependencies.isTty ?? (() => Boolean(process.stdin.isTTY));
7780
7755
  const confirm = dependencies.confirm ?? askOnce;
7781
7756
  const projectRoot = process.cwd();
7782
- const hasCodexSidecarConfig = dependencies.hasCodexSidecarConfig ?? ((root) => existsSync4(join7(root, ".codex-sidecar.yml")));
7757
+ const hasCodexSidecarConfig = dependencies.hasCodexSidecarConfig ?? ((root) => existsSync5(join7(root, ".codex-sidecar.yml")));
7783
7758
  const publishAdvisory = hasCodexSidecarConfig(projectRoot) ? (changes) => {
7784
7759
  try {
7785
7760
  return dependencies.runCodexSidecarAdvisory?.(changes, projectRoot) ?? formatCodexSidecarAdvisory(runCodexSidecarAdvisory({
@@ -7838,22 +7813,22 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }, depende
7838
7813
  let codexHookState = "not-installed";
7839
7814
  ensureUserConfig(ctx.userConfigPath);
7840
7815
  ctx.logger.info(`user config: ${ctx.userConfigPath}`);
7841
- if (!existsSync5(ctx.paths.knowledgeRepo)) {
7842
- mkdirSync3(ctx.paths.knowledgeRepo, { recursive: true });
7843
- mkdirSync3(ctx.paths.entriesDir, { recursive: true });
7816
+ if (!existsSync6(ctx.paths.knowledgeRepo)) {
7817
+ mkdirSync2(ctx.paths.knowledgeRepo, { recursive: true });
7818
+ mkdirSync2(ctx.paths.entriesDir, { recursive: true });
7844
7819
  ctx.logger.info(`knowledge repo scaffolded: ${ctx.paths.knowledgeRepo}`);
7845
7820
  } else {
7846
7821
  ctx.logger.info(`knowledge repo: ${ctx.paths.knowledgeRepo}`);
7847
7822
  }
7848
7823
  migrateLegacyCommunityDir(ctx);
7849
7824
  const gitignorePath = join8(ctx.paths.knowledgeRepo, ".gitignore");
7850
- if (!existsSync5(gitignorePath)) {
7851
- writeFileSync4(gitignorePath, KNOWLEDGE_GITIGNORE, "utf-8");
7825
+ if (!existsSync6(gitignorePath)) {
7826
+ writeFileSync3(gitignorePath, KNOWLEDGE_GITIGNORE, "utf-8");
7852
7827
  ctx.logger.info(`.gitignore created: ${gitignorePath}`);
7853
7828
  }
7854
7829
  if (!opts.dryRun) {
7855
- const dbDir = dirname4(ctx.paths.dbPath);
7856
- if (!existsSync5(dbDir)) mkdirSync3(dbDir, { recursive: true });
7830
+ const dbDir = dirname3(ctx.paths.dbPath);
7831
+ if (!existsSync6(dbDir)) mkdirSync2(dbDir, { recursive: true });
7857
7832
  const keyProvider = createKeyserverKeyProvider({ caveatHome: ctx.caveatHome });
7858
7833
  const failures = await prewarmSealedKeys({ paths: ctx.paths, keyProvider });
7859
7834
  for (const failure of failures) {
@@ -8031,7 +8006,7 @@ function reportEnvironmentSummary(ctx, publishTarget, codexHookState, dryRun) {
8031
8006
  const prefix = dryRun ? "[dry-run] would have " : "";
8032
8007
  const isRepo = gitOutput(["-C", ctx.paths.knowledgeRepo, "rev-parse", "--is-inside-work-tree"]) === "true";
8033
8008
  const remote = isRepo ? gitOutput(["-C", ctx.paths.knowledgeRepo, "config", "--get", "remote.origin.url"]) : null;
8034
- const communityCount = existsSync5(ctx.paths.communityDir) ? readdirSync(ctx.paths.communityDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).length : 0;
8009
+ const communityCount = existsSync6(ctx.paths.communityDir) ? readdirSync(ctx.paths.communityDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).length : 0;
8035
8010
  ctx.logger.info(`${prefix}environment summary:`);
8036
8011
  ctx.logger.info(` own git: ${isRepo ? "repository" : "not initialized"}`);
8037
8012
  ctx.logger.info(` private remote: ${remote || "not configured"}`);
@@ -8046,14 +8021,14 @@ function migrateLegacyCommunityDir(ctx) {
8046
8021
  const legacy = join8(ctx.paths.knowledgeRepo, "community");
8047
8022
  const current = ctx.paths.communityDir;
8048
8023
  if (legacy === current) return;
8049
- if (!existsSync5(legacy)) return;
8050
- if (existsSync5(current)) {
8024
+ if (!existsSync6(legacy)) return;
8025
+ if (existsSync6(current)) {
8051
8026
  ctx.logger.warn(
8052
8027
  `legacy community dir still exists at ${legacy} \u2014 remove manually (new location in use)`
8053
8028
  );
8054
8029
  return;
8055
8030
  }
8056
- mkdirSync3(current, { recursive: true });
8031
+ mkdirSync2(current, { recursive: true });
8057
8032
  for (const entry of readdirSync(legacy, { withFileTypes: true })) {
8058
8033
  if (!entry.isDirectory()) continue;
8059
8034
  renameSync(join8(legacy, entry.name), join8(current, entry.name));
@@ -8118,11 +8093,11 @@ function reportInstallResult(ctx, result, dryRun) {
8118
8093
  }
8119
8094
 
8120
8095
  // src/commands/indexCmd.ts
8121
- import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "node:fs";
8122
- import { dirname as dirname5 } from "node:path";
8096
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3 } from "node:fs";
8097
+ import { dirname as dirname4 } from "node:path";
8123
8098
  async function runIndex(ctx, opts) {
8124
- const dbDir = dirname5(ctx.paths.dbPath);
8125
- if (!existsSync6(dbDir)) mkdirSync4(dbDir, { recursive: true });
8099
+ const dbDir = dirname4(ctx.paths.dbPath);
8100
+ if (!existsSync7(dbDir)) mkdirSync3(dbDir, { recursive: true });
8126
8101
  const keyProvider = createKeyserverKeyProvider({ caveatHome: ctx.caveatHome });
8127
8102
  const failures = await prewarmSealedKeys({ paths: ctx.paths, keyProvider });
8128
8103
  for (const failure of failures) {
@@ -8306,7 +8281,7 @@ function runStats(ctx) {
8306
8281
 
8307
8282
  // src/commands/serve.ts
8308
8283
  async function runServe(opts) {
8309
- const { startServer } = await import("./server-FUWTRVRZ.js");
8284
+ const { startServer } = await import("./server-GBVHOF67.js");
8310
8285
  const { port, host } = startServer({ port: opts.port });
8311
8286
  process.stdout.write(`[caveat] web portal: http://${host}:${port}/
8312
8287
  `);
@@ -32546,12 +32521,12 @@ function handleListRecent(ctx, args) {
32546
32521
  }
32547
32522
 
32548
32523
  // ../mcp/dist/tools/pull.js
32549
- import { existsSync as existsSync7 } from "node:fs";
32524
+ import { existsSync as existsSync8 } from "node:fs";
32550
32525
  var pullInputShape = {};
32551
32526
  async function handlePull(ctx, _args = {}) {
32552
32527
  const pulled = [];
32553
32528
  const indexed = [];
32554
- if (existsSync7(ctx.paths.communityDir)) {
32529
+ if (existsSync8(ctx.paths.communityDir)) {
32555
32530
  const results = await communityPull({
32556
32531
  communityDir: ctx.paths.communityDir,
32557
32532
  logger: ctx.logger
@@ -32707,28 +32682,28 @@ async function runMcpServer() {
32707
32682
  import { spawn as spawn2 } from "node:child_process";
32708
32683
  import {
32709
32684
  chmodSync,
32710
- existsSync as existsSync9,
32685
+ existsSync as existsSync11,
32711
32686
  lstatSync,
32712
32687
  mkdtempSync as mkdtempSync2,
32713
32688
  mkdirSync as mkdirSync5,
32714
32689
  readdirSync as readdirSync2,
32715
- readFileSync as readFileSync5,
32716
- realpathSync as realpathSync4,
32690
+ readFileSync as readFileSync6,
32691
+ realpathSync as realpathSync3,
32717
32692
  rmSync as rmSync2,
32718
32693
  writeFileSync as writeFileSync5
32719
32694
  } from "node:fs";
32720
32695
  import { tmpdir as tmpdir2 } from "node:os";
32721
- import { basename, dirname as dirname6, join as join11 } from "node:path";
32722
- import { createHash, randomBytes } from "node:crypto";
32696
+ import { basename, dirname as dirname5, join as join12 } from "node:path";
32697
+ import { randomBytes } from "node:crypto";
32723
32698
 
32724
32699
  // src/autoReindexTrigger.ts
32725
32700
  import { spawn } from "node:child_process";
32726
- import { existsSync as existsSync8 } from "node:fs";
32701
+ import { existsSync as existsSync9 } from "node:fs";
32727
32702
  import { join as join10 } from "node:path";
32728
32703
  function maybeTriggerAutoReindex(ctx) {
32729
32704
  if (process.env.CAVEAT_INDEX_AUTOSYNC === "off") return;
32730
- if (!existsSync8(ctx.paths.dbPath)) return;
32731
- if (existsSync8(join10(ctx.caveatHome, "index", ".reindex-lock"))) return;
32705
+ if (!existsSync9(ctx.paths.dbPath)) return;
32706
+ if (existsSync9(join10(ctx.caveatHome, "index", ".reindex-lock"))) return;
32732
32707
  const current = computeEntriesDigest(ctx.paths);
32733
32708
  const marker = readDigestMarker(ctx.caveatHome);
32734
32709
  if (marker?.digest === current.digest && marker.fileCount === current.fileCount) return;
@@ -32753,18 +32728,22 @@ function maybeTriggerAutoSync(ctx, debounceMs = AUTO_SYNC_DEBOUNCE_MS) {
32753
32728
  });
32754
32729
  }
32755
32730
 
32756
- // src/commands/hookCmd.ts
32757
- var silentLogger = {
32758
- info: () => {
32759
- },
32760
- warn: () => {
32761
- },
32762
- error: (m) => process.stderr.write(`[caveat:hook] ${m}
32731
+ // src/hookShared.ts
32732
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
32733
+ import { join as join11 } from "node:path";
32734
+ import { createHash } from "node:crypto";
32735
+ var MAX_CONTEXT_BLOCKS = 3;
32736
+ var STOP_REMINDER_PREFIX = "[caveat] \u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u3067\u5916\u90E8\u4ED5\u69D8\u306E\u7F60\u306B\u5F53\u305F\u3063\u305F\u53EF\u80FD\u6027\u3092\u793A\u3059\u30B7\u30B0\u30CA\u30EB:";
32737
+ function hookSilentLogger(host) {
32738
+ return {
32739
+ info: () => {
32740
+ },
32741
+ warn: () => {
32742
+ },
32743
+ error: (m) => process.stderr.write(`[${host.stderrTag}] ${m}
32763
32744
  `)
32764
- };
32765
- var CLAUDE_MAX_CONTEXT_BLOCKS = 3;
32766
- var CLAUDE_STOP_REMINDER_PREFIX = "[caveat] \u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u3067\u5916\u90E8\u4ED5\u69D8\u306E\u7F60\u306B\u5F53\u305F\u3063\u305F\u53EF\u80FD\u6027\u3092\u793A\u3059\u30B7\u30B0\u30CA\u30EB:";
32767
- var CLAUDE_STOP_STATE_DIR = "claude-stop-state";
32745
+ };
32746
+ }
32768
32747
  async function readStdin() {
32769
32748
  const chunks = [];
32770
32749
  for await (const chunk of process.stdin) {
@@ -32772,34 +32751,32 @@ async function readStdin() {
32772
32751
  }
32773
32752
  return Buffer.concat(chunks).toString("utf-8");
32774
32753
  }
32775
- function parsePayload(raw) {
32754
+ function errorMessage4(err) {
32755
+ return err instanceof Error ? err.message : String(err);
32756
+ }
32757
+ function reportHookError(host, phase, err) {
32758
+ observeRuntimeError(host.errorCode, { version: CAVEAT_VERSION });
32759
+ process.stderr.write(`[${host.stderrTag}] ${phase}: ${errorMessage4(err)}
32760
+ `);
32761
+ }
32762
+ function parsePayload(host, raw) {
32776
32763
  if (!raw) return {};
32777
32764
  try {
32778
32765
  return JSON.parse(raw);
32779
32766
  } catch (err) {
32780
- observeRuntimeError("CAVEAT.CLAUDE_HOOK_FAILED", { version: CAVEAT_VERSION });
32781
- const msg = err instanceof Error ? err.message : String(err);
32782
- process.stderr.write(`[caveat:hook] json parse error: ${msg}
32783
- `);
32767
+ reportHookError(host, "json parse error", err);
32784
32768
  return {};
32785
32769
  }
32786
32770
  }
32787
- function getSessionId(payload) {
32788
- const v = payload.session_id ?? payload.sessionId;
32789
- return typeof v === "string" && v.length > 0 ? v : "_unknown";
32790
- }
32791
- function buildContextSafely() {
32771
+ function buildContextSafely(host) {
32792
32772
  try {
32793
- return buildContext(silentLogger);
32773
+ return buildContext(hookSilentLogger(host));
32794
32774
  } catch (err) {
32795
- observeRuntimeError("CAVEAT.CLAUDE_HOOK_FAILED", { version: CAVEAT_VERSION });
32796
- const msg = err instanceof Error ? err.message : String(err);
32797
- process.stderr.write(`[caveat:hook] context error: ${msg}
32798
- `);
32775
+ reportHookError(host, "context error", err);
32799
32776
  return null;
32800
32777
  }
32801
32778
  }
32802
- function searchCaveatsSafely(input) {
32779
+ function searchCaveatsSafely(host, input) {
32803
32780
  const inputs = Array.isArray(input) ? input : [input];
32804
32781
  const queryForLog = inputs.map((item) => item.surface === "user_prompt" ? item.topicText || item.failureText : item.failureText).filter(Boolean).join("\n");
32805
32782
  if (inputs.length === 0 || inputs.every((item) => !item.topicText && !item.failureText)) return [];
@@ -32807,8 +32784,8 @@ function searchCaveatsSafely(input) {
32807
32784
  let caveatHome;
32808
32785
  let hits;
32809
32786
  try {
32810
- const ctx = buildContextSafely();
32811
- if (!ctx || !existsSync9(ctx.paths.dbPath)) return [];
32787
+ const ctx = buildContextSafely(host);
32788
+ if (!ctx || !existsSync10(ctx.paths.dbPath)) return [];
32812
32789
  caveatHome = ctx.caveatHome;
32813
32790
  db = openDb({ path: ctx.paths.dbPath });
32814
32791
  const searchOptions = {
@@ -32816,29 +32793,20 @@ function searchCaveatsSafely(input) {
32816
32793
  };
32817
32794
  hits = inputs.length === 1 ? findCaveatsForHook(db, inputs[0], searchOptions) : findCaveatsForHookSegments(db, inputs, searchOptions);
32818
32795
  } catch (err) {
32819
- observeRuntimeError("CAVEAT.CLAUDE_HOOK_FAILED", { version: CAVEAT_VERSION });
32820
- const msg = err instanceof Error ? err.message : String(err);
32821
- process.stderr.write(`[caveat:hook] search error: ${msg}
32822
- `);
32796
+ reportHookError(host, "search error", err);
32823
32797
  return [];
32824
32798
  }
32825
32799
  if (hits.length > 0) {
32826
32800
  try {
32827
32801
  markHit(db, hits);
32828
32802
  } catch (err) {
32829
- observeRuntimeError("CAVEAT.CLAUDE_HOOK_FAILED", { version: CAVEAT_VERSION });
32830
- const msg = err instanceof Error ? err.message : String(err);
32831
- process.stderr.write(`[caveat:hook] markHit error: ${msg}
32832
- `);
32803
+ reportHookError(host, "markHit error", err);
32833
32804
  }
32834
32805
  } else {
32835
32806
  try {
32836
- logHookQueryMiss({ caveatHome, agent: "claude", surface: inputs[0].surface, query: queryForLog });
32807
+ logHookQueryMiss({ caveatHome, agent: host.agent, surface: inputs[0].surface, query: queryForLog });
32837
32808
  } catch (err) {
32838
- observeRuntimeError("CAVEAT.CLAUDE_HOOK_FAILED", { version: CAVEAT_VERSION });
32839
- const msg = err instanceof Error ? err.message : String(err);
32840
- process.stderr.write(`[caveat:hook] query log error: ${msg}
32841
- `);
32809
+ reportHookError(host, "query log error", err);
32842
32810
  }
32843
32811
  }
32844
32812
  try {
@@ -32847,50 +32815,37 @@ function searchCaveatsSafely(input) {
32847
32815
  db?.close();
32848
32816
  }
32849
32817
  }
32850
- function loadSignalsSafely(path) {
32851
- try {
32852
- return readSessionSignals(path);
32853
- } catch (err) {
32854
- const msg = err instanceof Error ? err.message : String(err);
32855
- process.stderr.write(`[caveat:hook] transcript read error: ${msg}
32856
- `);
32857
- return null;
32858
- }
32859
- }
32860
- function systemReminderOutput(text) {
32861
- return `<system-reminder>${text.replace(/</g, "\u2039").replace(/>/g, "\u203A")}</system-reminder>`;
32862
- }
32863
- function claudePendingCleanupFailureText() {
32864
- return "[caveat:hook] pending reminder cleanup failed";
32818
+ function pendingCleanupFailureText(host) {
32819
+ return `[${host.stderrTag}] pending reminder cleanup failed`;
32865
32820
  }
32866
- function drainForSession(sessionId) {
32867
- const ctx = buildContextSafely();
32821
+ function drainForSession(host, sessionId) {
32822
+ const ctx = buildContextSafely(host);
32868
32823
  if (!ctx) return [];
32869
32824
  const local = drainPendingRemindersDetailed(ctx.caveatHome, sessionId);
32870
32825
  const global = drainPendingRemindersDetailed(ctx.caveatHome, "_global");
32871
32826
  for (const _failure of [...local.cleanupFailures, ...global.cleanupFailures]) {
32872
- process.stderr.write(`${claudePendingCleanupFailureText()}
32827
+ process.stderr.write(`${pendingCleanupFailureText(host)}
32873
32828
  `);
32874
32829
  }
32875
32830
  return [...local.reminders, ...global.reminders];
32876
32831
  }
32877
- function claudeContextDedupeKey(text) {
32878
- if (text.startsWith(CLAUDE_STOP_REMINDER_PREFIX)) return "claude-stop-reminder";
32832
+ function contextDedupeKey(host, text) {
32833
+ if (text.startsWith(STOP_REMINDER_PREFIX)) return host.stopDedupeKey;
32879
32834
  return text.trim();
32880
32835
  }
32881
- function compactClaudeContexts(contexts) {
32836
+ function compactContexts(host, contexts) {
32882
32837
  const selected = [];
32883
32838
  const seen = /* @__PURE__ */ new Set();
32884
32839
  for (let i = contexts.length - 1; i >= 0; i -= 1) {
32885
32840
  const text = contexts[i]?.trim();
32886
32841
  if (!text) continue;
32887
- const key = claudeContextDedupeKey(text);
32842
+ const key = contextDedupeKey(host, text);
32888
32843
  if (seen.has(key)) continue;
32889
32844
  seen.add(key);
32890
32845
  selected.push(text);
32891
32846
  }
32892
32847
  selected.reverse();
32893
- const limited = selected.slice(-CLAUDE_MAX_CONTEXT_BLOCKS);
32848
+ const limited = selected.slice(-MAX_CONTEXT_BLOCKS);
32894
32849
  const omitted = selected.length - limited.length;
32895
32850
  if (omitted > 0) {
32896
32851
  limited.push(
@@ -32899,7 +32854,7 @@ function compactClaudeContexts(contexts) {
32899
32854
  }
32900
32855
  return limited;
32901
32856
  }
32902
- function sanitizeClaudeStateId(raw) {
32857
+ function sanitizeStateId(raw) {
32903
32858
  const clean = raw.replace(/[^A-Za-z0-9_-]/g, "");
32904
32859
  return clean.length > 0 ? clean : "_unknown";
32905
32860
  }
@@ -32915,59 +32870,59 @@ function stopSignalKey(signals, related) {
32915
32870
  });
32916
32871
  return createHash("sha256").update(body).digest("hex");
32917
32872
  }
32918
- function stopStatePath(caveatHome, sessionId) {
32919
- return join11(caveatHome, CLAUDE_STOP_STATE_DIR, `${sanitizeClaudeStateId(sessionId)}.txt`);
32873
+ function stopStatePath(host, caveatHome, sessionId) {
32874
+ return join11(caveatHome, host.stopStateDir, `${sanitizeStateId(sessionId)}.txt`);
32920
32875
  }
32921
- function wasStopReminderQueued(caveatHome, sessionId, key) {
32922
- const path = stopStatePath(caveatHome, sessionId);
32876
+ function wasStopReminderQueued(host, caveatHome, sessionId, key) {
32877
+ const path = stopStatePath(host, caveatHome, sessionId);
32923
32878
  try {
32924
32879
  return readFileSync5(path, "utf-8") === key;
32925
32880
  } catch {
32926
32881
  return false;
32927
32882
  }
32928
32883
  }
32929
- function markStopReminderQueued(caveatHome, sessionId, key) {
32930
- const path = stopStatePath(caveatHome, sessionId);
32931
- mkdirSync5(join11(caveatHome, CLAUDE_STOP_STATE_DIR), { recursive: true });
32932
- writeFileSync5(path, key, "utf-8");
32884
+ function markStopReminderQueued(host, caveatHome, sessionId, key) {
32885
+ const path = stopStatePath(host, caveatHome, sessionId);
32886
+ mkdirSync4(join11(caveatHome, host.stopStateDir), { recursive: true });
32887
+ writeFileSync4(path, key, "utf-8");
32933
32888
  }
32934
- function queueStopForSession(sessionId, signals, related) {
32935
- const ctx = buildContextSafely();
32889
+ function queueStopForSession(host, sessionId, signals, related, buildText) {
32890
+ const ctx = buildContextSafely(host);
32936
32891
  if (!ctx) return;
32937
32892
  const key = stopSignalKey(signals, related);
32938
- if (wasStopReminderQueued(ctx.caveatHome, sessionId, key)) return;
32893
+ if (wasStopReminderQueued(host, ctx.caveatHome, sessionId, key)) return;
32939
32894
  let result;
32940
32895
  try {
32941
32896
  result = buildAndPublishPendingReminder(ctx.caveatHome, sessionId, buildPendingSemanticKey({
32942
- agent: "claude",
32897
+ agent: host.agent,
32943
32898
  surface: "stop",
32944
32899
  refs: related,
32945
32900
  stopSignalDigest: key
32946
- }), () => buildStopReminder(signals, related));
32901
+ }), buildText);
32947
32902
  } catch {
32948
- process.stderr.write("[caveat:hook] pending reminder build or publish failed\n");
32903
+ process.stderr.write(`[${host.stderrTag}] pending reminder build or publish failed
32904
+ `);
32949
32905
  return;
32950
32906
  }
32951
32907
  if (!result.ran) return;
32952
32908
  try {
32953
- markStopReminderQueued(ctx.caveatHome, sessionId, key);
32909
+ markStopReminderQueued(host, ctx.caveatHome, sessionId, key);
32954
32910
  } catch (err) {
32955
- const msg = err instanceof Error ? err.message : String(err);
32956
- process.stderr.write(`[caveat:hook] pending reminder write error: ${msg}
32911
+ process.stderr.write(`[${host.stderrTag}] pending reminder write error: ${errorMessage4(err)}
32957
32912
  `);
32958
32913
  }
32959
32914
  }
32960
32915
  function extractToolResponseText(response) {
32961
32916
  if (typeof response === "string") return response;
32962
32917
  if (Array.isArray(response)) {
32963
- const parts = [];
32964
- for (const item of response) {
32965
- if (typeof item === "string") parts.push(item);
32966
- else if (item !== null && typeof item === "object" && typeof item.text === "string") {
32967
- parts.push(item.text);
32918
+ return response.map((item) => {
32919
+ if (typeof item === "string") return item;
32920
+ if (item !== null && typeof item === "object") {
32921
+ const text = item.text;
32922
+ return typeof text === "string" ? text : "";
32968
32923
  }
32969
- }
32970
- return parts.join(" ");
32924
+ return "";
32925
+ }).filter(Boolean).join(" ");
32971
32926
  }
32972
32927
  if (response !== null && typeof response === "object") {
32973
32928
  const r = response;
@@ -32980,6 +32935,32 @@ function extractToolResponseText(response) {
32980
32935
  }
32981
32936
  return "";
32982
32937
  }
32938
+
32939
+ // src/commands/hookCmd.ts
32940
+ var CLAUDE_HOST = {
32941
+ agent: "claude",
32942
+ stderrTag: "caveat:hook",
32943
+ errorCode: "CAVEAT.CLAUDE_HOOK_FAILED",
32944
+ stopStateDir: "claude-stop-state",
32945
+ stopDedupeKey: "claude-stop-reminder"
32946
+ };
32947
+ var silentLogger = hookSilentLogger(CLAUDE_HOST);
32948
+ function getSessionId(payload) {
32949
+ const v = payload.session_id ?? payload.sessionId;
32950
+ return typeof v === "string" && v.length > 0 ? v : "_unknown";
32951
+ }
32952
+ function loadSignalsSafely(path) {
32953
+ try {
32954
+ return readSessionSignals(path);
32955
+ } catch (err) {
32956
+ process.stderr.write(`[caveat:hook] transcript read error: ${errorMessage4(err)}
32957
+ `);
32958
+ return null;
32959
+ }
32960
+ }
32961
+ function systemReminderOutput(text) {
32962
+ return `<system-reminder>${text.replace(/</g, "\u2039").replace(/>/g, "\u203A")}</system-reminder>`;
32963
+ }
32983
32964
  function toolTopicText(payload) {
32984
32965
  const parts = [];
32985
32966
  const toolName = payload.tool_name ?? payload.toolName;
@@ -33014,15 +32995,14 @@ function spawnWorker(job) {
33014
32995
  try {
33015
32996
  root = workerRoot();
33016
32997
  sweepStaleWorkerDirs(Date.now(), root);
33017
- workDir = mkdtempSync2(join11(root, "job-"));
32998
+ workDir = mkdtempSync2(join12(root, "job-"));
33018
32999
  chmodSync(workDir, 448);
33019
- workFile = join11(workDir, `${randomBytes(4).toString("hex")}.json`);
33000
+ workFile = join12(workDir, `${randomBytes(4).toString("hex")}.json`);
33020
33001
  writeFileSync5(workFile, JSON.stringify({ ...job, schemaVersion: "caveat-worker-job/v2" }), { encoding: "utf-8", mode: 384, flag: "wx" });
33021
33002
  } catch (err) {
33022
- const msg = err instanceof Error ? err.message : String(err);
33023
- process.stderr.write(`[caveat:hook] worker writefile error: ${msg}
33003
+ process.stderr.write(`[caveat:hook] worker writefile error: ${errorMessage4(err)}
33024
33004
  `);
33025
- cleanupWorkerDir(workDir, workDir ? dirname6(workDir) : void 0);
33005
+ cleanupWorkerDir(workDir, workDir ? dirname5(workDir) : void 0);
33026
33006
  return;
33027
33007
  }
33028
33008
  const cliScript = process.argv[1];
@@ -33038,8 +33018,7 @@ function spawnWorker(job) {
33038
33018
  );
33039
33019
  child.unref();
33040
33020
  } catch (err) {
33041
- const msg = err instanceof Error ? err.message : String(err);
33042
- process.stderr.write(`[caveat:hook] worker spawn error: ${msg}
33021
+ process.stderr.write(`[caveat:hook] worker spawn error: ${errorMessage4(err)}
33043
33022
  `);
33044
33023
  try {
33045
33024
  cleanupWorkerDir(workDir, root);
@@ -33050,11 +33029,11 @@ function spawnWorker(job) {
33050
33029
  async function runWorker(workFile) {
33051
33030
  let raw;
33052
33031
  try {
33053
- raw = readFileSync5(workFile, "utf-8");
33032
+ raw = readFileSync6(workFile, "utf-8");
33054
33033
  } catch {
33055
33034
  process.exit(0);
33056
33035
  }
33057
- cleanupWorkerDir(dirname6(workFile), dirname6(dirname6(workFile)));
33036
+ cleanupWorkerDir(dirname5(workFile), dirname5(dirname5(workFile)));
33058
33037
  let job;
33059
33038
  try {
33060
33039
  job = JSON.parse(raw);
@@ -33062,13 +33041,13 @@ async function runWorker(workFile) {
33062
33041
  process.exit(0);
33063
33042
  }
33064
33043
  if (!job.failureText || !job.sessionId) process.exit(0);
33065
- const hits = searchCaveatsSafely({
33044
+ const hits = searchCaveatsSafely(CLAUDE_HOST, {
33066
33045
  topicText: job.topicText,
33067
33046
  failureText: job.failureText,
33068
33047
  surface: "tool_error"
33069
33048
  });
33070
33049
  if (hits.length === 0) process.exit(0);
33071
- const ctx = buildContextSafely();
33050
+ const ctx = buildContextSafely(CLAUDE_HOST);
33072
33051
  if (!ctx) process.exit(0);
33073
33052
  let result;
33074
33053
  try {
@@ -33095,11 +33074,11 @@ function isOwnedWorkerDir(path, root = workerRoot()) {
33095
33074
  try {
33096
33075
  const inputStat = lstatSync(path);
33097
33076
  if (inputStat.isSymbolicLink()) return false;
33098
- const tmpRoot = realpathSync4(root);
33099
- const resolved = realpathSync4(path);
33077
+ const tmpRoot = realpathSync3(root);
33078
+ const resolved = realpathSync3(path);
33100
33079
  const stat = lstatSync(resolved);
33101
33080
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
33102
- return dirname6(resolved) === tmpRoot && basename(resolved).startsWith("job-") && stat.isDirectory() && !stat.isSymbolicLink() && hasPrivateOwnership(stat, uid);
33081
+ return dirname5(resolved) === tmpRoot && basename(resolved).startsWith("job-") && stat.isDirectory() && !stat.isSymbolicLink() && hasPrivateOwnership(stat, uid);
33103
33082
  } catch {
33104
33083
  return false;
33105
33084
  }
@@ -33114,7 +33093,7 @@ function sweepStaleWorkerDirs(now = Date.now(), root = workerRoot()) {
33114
33093
  }
33115
33094
  for (const entry of entries) {
33116
33095
  if (!entry.startsWith("job-")) continue;
33117
- const path = join11(root, entry);
33096
+ const path = join12(root, entry);
33118
33097
  try {
33119
33098
  if (!isOwnedWorkerDir(path, root)) continue;
33120
33099
  const stat = lstatSync(path);
@@ -33126,32 +33105,32 @@ function sweepStaleWorkerDirs(now = Date.now(), root = workerRoot()) {
33126
33105
  }
33127
33106
  }
33128
33107
  function workerRoot(base = tmpdir2()) {
33129
- const root = join11(base, WORKER_ROOT);
33108
+ const root = join12(base, WORKER_ROOT);
33130
33109
  mkdirSync5(root, { recursive: true, mode: 448 });
33131
33110
  const stat = lstatSync(root);
33132
33111
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
33133
33112
  if (!stat.isDirectory() || stat.isSymbolicLink() || !hasPrivateOwnership(stat, uid)) throw new Error("worker root is unsafe");
33134
- const marker = join11(root, WORKER_MARKER);
33113
+ const marker = join12(root, WORKER_MARKER);
33135
33114
  try {
33136
33115
  writeFileSync5(marker, "caveat-worker/v1\n", { mode: 384, flag: "wx" });
33137
33116
  } catch (error51) {
33138
33117
  if (!(error51 && typeof error51 === "object" && "code" in error51 && error51.code === "EEXIST")) throw error51;
33139
33118
  }
33140
33119
  const markerStat = lstatSync(marker);
33141
- if (!markerStat.isFile() || markerStat.isSymbolicLink() || !hasPrivateOwnership(markerStat, uid) || readFileSync5(marker, "utf-8") !== "caveat-worker/v1\n") throw new Error("worker root marker is invalid");
33120
+ if (!markerStat.isFile() || markerStat.isSymbolicLink() || !hasPrivateOwnership(markerStat, uid) || readFileSync6(marker, "utf-8") !== "caveat-worker/v1\n") throw new Error("worker root marker is invalid");
33142
33121
  return root;
33143
33122
  }
33144
33123
  function isStaleWorkerJobDir(path) {
33145
33124
  const entries = readdirSync2(path);
33146
33125
  if (entries.length !== 1 || !entries[0].endsWith(".json")) return false;
33147
- const file2 = join11(path, entries[0]);
33126
+ const file2 = join12(path, entries[0]);
33148
33127
  const stat = lstatSync(file2);
33149
33128
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
33150
33129
  if (!stat.isFile() || stat.isSymbolicLink() || !hasPrivateOwnership(stat, uid)) return false;
33151
- return isKnownStaleWorkerJob(JSON.parse(readFileSync5(file2, "utf-8")));
33130
+ return isKnownStaleWorkerJob(JSON.parse(readFileSync6(file2, "utf-8")));
33152
33131
  }
33153
33132
  function hasPrivateOwnership(stat, uid) {
33154
- return process.platform === "win32" || (stat.mode & 63) === 0 && (uid === void 0 || stat.uid === uid);
33133
+ return isPrivateOwnerStat(stat, uid);
33155
33134
  }
33156
33135
  function isWorkerJob(value) {
33157
33136
  if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
@@ -33170,23 +33149,19 @@ function isKnownStaleWorkerJob(value) {
33170
33149
  }
33171
33150
  function writeLastReindex(caveatHome, value) {
33172
33151
  try {
33173
- writeFileSync5(join11(caveatHome, "index", ".last-reindex.json"), JSON.stringify(value), "utf-8");
33152
+ writeFileSync5(join12(caveatHome, "index", ".last-reindex.json"), JSON.stringify(value), "utf-8");
33174
33153
  } catch (err) {
33175
- const msg = err instanceof Error ? err.message : String(err);
33176
- process.stderr.write(`[caveat:hook] reindex status write error: ${msg}
33154
+ process.stderr.write(`[caveat:hook] reindex status write error: ${errorMessage4(err)}
33177
33155
  `);
33178
33156
  }
33179
33157
  }
33180
- function errorMessage4(err) {
33181
- return err instanceof Error ? err.message : String(err);
33182
- }
33183
33158
  async function runReindexWorker() {
33184
33159
  if (process.env.CAVEAT_INDEX_AUTOSYNC === "off") {
33185
33160
  process.stderr.write("[caveat:hook] auto reindex disabled by CAVEAT_INDEX_AUTOSYNC=off\n");
33186
33161
  return;
33187
33162
  }
33188
- const ctx = buildContextSafely();
33189
- if (!ctx || !existsSync9(ctx.paths.dbPath)) {
33163
+ const ctx = buildContextSafely(CLAUDE_HOST);
33164
+ if (!ctx || !existsSync11(ctx.paths.dbPath)) {
33190
33165
  process.stderr.write("[caveat:hook] auto reindex skipped: index database does not exist\n");
33191
33166
  return;
33192
33167
  }
@@ -33211,7 +33186,7 @@ async function runReindexWorker() {
33211
33186
  perSource: result.perSource
33212
33187
  });
33213
33188
  } catch (err) {
33214
- const msg = err instanceof Error ? err.message : String(err);
33189
+ const msg = errorMessage4(err);
33215
33190
  process.stderr.write(`[caveat:hook] reindex error: ${msg}
33216
33191
  `);
33217
33192
  writeLastReindex(ctx.caveatHome, {
@@ -33224,8 +33199,7 @@ async function runReindexWorker() {
33224
33199
  try {
33225
33200
  releaseReindexLock(lock);
33226
33201
  } catch (err) {
33227
- const msg = err instanceof Error ? err.message : String(err);
33228
- process.stderr.write(`[caveat:hook] reindex lock release error: ${msg}
33202
+ process.stderr.write(`[caveat:hook] reindex lock release error: ${errorMessage4(err)}
33229
33203
  `);
33230
33204
  }
33231
33205
  }
@@ -33235,7 +33209,7 @@ async function runAutoSyncWorker() {
33235
33209
  process.stderr.write("[caveat:hook] auto sync disabled by CAVEAT_AUTO_SYNC=off\n");
33236
33210
  return;
33237
33211
  }
33238
- const ctx = buildContextSafely();
33212
+ const ctx = buildContextSafely(CLAUDE_HOST);
33239
33213
  if (!ctx) return;
33240
33214
  try {
33241
33215
  await runAutoSync({
@@ -33254,7 +33228,7 @@ function buildToolErrorReminder(job, hits) {
33254
33228
  const mode = hookCodexSidecarMode();
33255
33229
  if (mode === "off") return base;
33256
33230
  const projectRoot = process.cwd();
33257
- const hasSidecarConfig = existsSync9(join11(projectRoot, ".codex-sidecar.yml"));
33231
+ const hasSidecarConfig = existsSync11(join12(projectRoot, ".codex-sidecar.yml"));
33258
33232
  if (mode === "auto" && !hasSidecarConfig) return base;
33259
33233
  const advisory = runCodexSidecarAdvisory({
33260
33234
  searchText: job.failureText,
@@ -33290,7 +33264,7 @@ function buildStopReminder(signals, related) {
33290
33264
  const mode = hookCodexSidecarMode();
33291
33265
  if (mode === "off") return base;
33292
33266
  const projectRoot = process.cwd();
33293
- const hasSidecarConfig = existsSync9(join11(projectRoot, ".codex-sidecar.yml"));
33267
+ const hasSidecarConfig = existsSync11(join12(projectRoot, ".codex-sidecar.yml"));
33294
33268
  if (mode === "auto" && !hasSidecarConfig) return base;
33295
33269
  const advisory = runCodexSidecarAdvisory({
33296
33270
  searchText: struggleSearchText(signals),
@@ -33347,21 +33321,20 @@ async function runHook(name, arg) {
33347
33321
  try {
33348
33322
  raw = await readStdin();
33349
33323
  } catch (err) {
33350
- const msg = err instanceof Error ? err.message : String(err);
33351
- process.stderr.write(`[caveat:hook] stdin read error: ${msg}
33324
+ process.stderr.write(`[caveat:hook] stdin read error: ${errorMessage4(err)}
33352
33325
  `);
33353
33326
  process.exit(0);
33354
33327
  }
33355
- const payload = parsePayload(raw);
33328
+ const payload = parsePayload(CLAUDE_HOST, raw);
33356
33329
  const sessionId = getSessionId(payload);
33357
- const contexts = name === "stop" ? [] : drainForSession(sessionId);
33330
+ const contexts = name === "stop" ? [] : drainForSession(CLAUDE_HOST, sessionId);
33358
33331
  if (name === "user-prompt-submit") {
33359
33332
  const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
33360
- const hits = searchCaveatsSafely({ topicText: prompt, failureText: prompt, surface: "user_prompt" });
33333
+ const hits = searchCaveatsSafely(CLAUDE_HOST, { topicText: prompt, failureText: prompt, surface: "user_prompt" });
33361
33334
  if (hits.length > 0) {
33362
33335
  contexts.push(userPromptSubmitReminderText(hits));
33363
33336
  }
33364
- const compacted = compactClaudeContexts(contexts);
33337
+ const compacted = compactContexts(CLAUDE_HOST, contexts);
33365
33338
  if (compacted.length > 0) {
33366
33339
  process.stdout.write(`${systemReminderOutput(compacted.join("\n\n"))}
33367
33340
  `);
@@ -33369,7 +33342,7 @@ async function runHook(name, arg) {
33369
33342
  process.exit(0);
33370
33343
  }
33371
33344
  if (name === "post-tool-use") {
33372
- const compacted = compactClaudeContexts(contexts);
33345
+ const compacted = compactContexts(CLAUDE_HOST, contexts);
33373
33346
  if (compacted.length > 0) {
33374
33347
  process.stdout.write(`${systemReminderOutput(compacted.join("\n\n"))}
33375
33348
  `);
@@ -33391,27 +33364,24 @@ async function runHook(name, arg) {
33391
33364
  process.exit(0);
33392
33365
  }
33393
33366
  if (name === "stop") {
33394
- const ctx = buildContextSafely();
33367
+ const ctx = buildContextSafely(CLAUDE_HOST);
33395
33368
  if (ctx) {
33396
33369
  try {
33397
33370
  maybeSweepPendingDirs(ctx.caveatHome);
33398
33371
  } catch (err) {
33399
- const msg = err instanceof Error ? err.message : String(err);
33400
- process.stderr.write(`[caveat:hook] pending sweep error: ${msg}
33372
+ process.stderr.write(`[caveat:hook] pending sweep error: ${errorMessage4(err)}
33401
33373
  `);
33402
33374
  }
33403
33375
  try {
33404
33376
  maybeTriggerAutoReindex(ctx);
33405
33377
  } catch (err) {
33406
- const msg = err instanceof Error ? err.message : String(err);
33407
- process.stderr.write(`[caveat:hook] auto reindex trigger error: ${msg}
33378
+ process.stderr.write(`[caveat:hook] auto reindex trigger error: ${errorMessage4(err)}
33408
33379
  `);
33409
33380
  }
33410
33381
  try {
33411
33382
  maybeTriggerAutoSync(ctx);
33412
33383
  } catch (err) {
33413
- const msg = err instanceof Error ? err.message : String(err);
33414
- process.stderr.write(`[caveat:hook] auto sync trigger error: ${msg}
33384
+ process.stderr.write(`[caveat:hook] auto sync trigger error: ${errorMessage4(err)}
33415
33385
  `);
33416
33386
  }
33417
33387
  }
@@ -33419,12 +33389,12 @@ async function runHook(name, arg) {
33419
33389
  const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
33420
33390
  const signals = transcriptPath ? loadSignalsSafely(transcriptPath) : null;
33421
33391
  if (!signals || !hasAnyStruggleSignal(signals)) process.exit(0);
33422
- const related = searchCaveatsSafely(signals.errorSnippets.map((failureText) => ({
33392
+ const related = searchCaveatsSafely(CLAUDE_HOST, signals.errorSnippets.map((failureText) => ({
33423
33393
  topicText: "",
33424
33394
  failureText,
33425
33395
  surface: "stop"
33426
33396
  })));
33427
- queueStopForSession(sessionId, signals, related);
33397
+ queueStopForSession(CLAUDE_HOST, sessionId, signals, related, () => buildStopReminder(signals, related));
33428
33398
  process.exit(0);
33429
33399
  }
33430
33400
  process.stderr.write(`[caveat:hook] unknown hook name: ${name}
@@ -33434,105 +33404,22 @@ async function runHook(name, arg) {
33434
33404
 
33435
33405
  // src/commands/codexHookCmd.ts
33436
33406
  import { spawn as spawn3, spawnSync as spawnSync5 } from "node:child_process";
33437
- import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync6, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
33407
+ import { existsSync as existsSync12, readFileSync as readFileSync7, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
33438
33408
  import { homedir as homedir3, tmpdir as tmpdir3 } from "node:os";
33439
- import { join as join12 } from "node:path";
33440
- import { createHash as createHash2, randomBytes as randomBytes2 } from "node:crypto";
33441
- var silentLogger2 = {
33442
- info: () => {
33443
- },
33444
- warn: () => {
33445
- },
33446
- error: (m) => process.stderr.write(`[caveat:codex-hook] ${m}
33447
- `)
33409
+ import { join as join13 } from "node:path";
33410
+ import { randomBytes as randomBytes2 } from "node:crypto";
33411
+ var CODEX_HOST = {
33412
+ agent: "codex",
33413
+ stderrTag: "caveat:codex-hook",
33414
+ errorCode: "CAVEAT.CODEX_HOOK_FAILED",
33415
+ stopStateDir: "codex-stop-state",
33416
+ stopDedupeKey: "codex-stop-reminder"
33448
33417
  };
33449
- var CODEX_MAX_CONTEXT_BLOCKS = 3;
33450
- var CODEX_STOP_REMINDER_PREFIX = "[caveat] \u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u3067\u5916\u90E8\u4ED5\u69D8\u306E\u7F60\u306B\u5F53\u305F\u3063\u305F\u53EF\u80FD\u6027\u3092\u793A\u3059\u30B7\u30B0\u30CA\u30EB:";
33451
- var CODEX_STOP_STATE_DIR = "codex-stop-state";
33452
- async function readStdin2() {
33453
- const chunks = [];
33454
- for await (const chunk of process.stdin) {
33455
- chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
33456
- }
33457
- return Buffer.concat(chunks).toString("utf-8");
33458
- }
33459
- function parsePayload2(raw) {
33460
- if (!raw) return {};
33461
- try {
33462
- return JSON.parse(raw);
33463
- } catch (err) {
33464
- observeRuntimeError("CAVEAT.CODEX_HOOK_FAILED", { version: CAVEAT_VERSION });
33465
- const msg = err instanceof Error ? err.message : String(err);
33466
- process.stderr.write(`[caveat:codex-hook] json parse error: ${msg}
33467
- `);
33468
- return {};
33469
- }
33470
- }
33471
- function buildContextSafely2() {
33472
- try {
33473
- return buildContext(silentLogger2);
33474
- } catch (err) {
33475
- observeRuntimeError("CAVEAT.CODEX_HOOK_FAILED", { version: CAVEAT_VERSION });
33476
- const msg = err instanceof Error ? err.message : String(err);
33477
- process.stderr.write(`[caveat:codex-hook] context error: ${msg}
33478
- `);
33479
- return null;
33480
- }
33481
- }
33482
- function searchCaveatsSafely2(input) {
33483
- const inputs = Array.isArray(input) ? input : [input];
33484
- const queryForLog = inputs.map((item) => item.surface === "user_prompt" ? item.topicText || item.failureText : item.failureText).filter(Boolean).join("\n");
33485
- if (inputs.length === 0 || inputs.every((item) => !item.topicText && !item.failureText)) return [];
33486
- let db;
33487
- let caveatHome;
33488
- let hits;
33489
- try {
33490
- const ctx = buildContextSafely2();
33491
- if (!ctx || !existsSync10(ctx.paths.dbPath)) return [];
33492
- caveatHome = ctx.caveatHome;
33493
- db = openDb({ path: ctx.paths.dbPath });
33494
- const searchOptions = {
33495
- selfIdentity: defaultSelfIdentityTokens()
33496
- };
33497
- hits = inputs.length === 1 ? findCaveatsForHook(db, inputs[0], searchOptions) : findCaveatsForHookSegments(db, inputs, searchOptions);
33498
- } catch (err) {
33499
- observeRuntimeError("CAVEAT.CODEX_HOOK_FAILED", { version: CAVEAT_VERSION });
33500
- const msg = err instanceof Error ? err.message : String(err);
33501
- process.stderr.write(`[caveat:codex-hook] search error: ${msg}
33502
- `);
33503
- return [];
33504
- }
33505
- if (hits.length > 0) {
33506
- try {
33507
- markHit(db, hits);
33508
- } catch (err) {
33509
- observeRuntimeError("CAVEAT.CODEX_HOOK_FAILED", { version: CAVEAT_VERSION });
33510
- const msg = err instanceof Error ? err.message : String(err);
33511
- process.stderr.write(`[caveat:codex-hook] markHit error: ${msg}
33512
- `);
33513
- }
33514
- } else {
33515
- try {
33516
- logHookQueryMiss({ caveatHome, agent: "codex", surface: inputs[0].surface, query: queryForLog });
33517
- } catch (err) {
33518
- observeRuntimeError("CAVEAT.CODEX_HOOK_FAILED", { version: CAVEAT_VERSION });
33519
- const msg = err instanceof Error ? err.message : String(err);
33520
- process.stderr.write(`[caveat:codex-hook] query log error: ${msg}
33521
- `);
33522
- }
33523
- }
33524
- try {
33525
- return hits;
33526
- } finally {
33527
- db?.close();
33528
- }
33529
- }
33530
33418
  function loadSignalsSafely2(path) {
33531
33419
  try {
33532
33420
  return readCodexSessionSignals(path);
33533
33421
  } catch (err) {
33534
- const msg = err instanceof Error ? err.message : String(err);
33535
- process.stderr.write(`[caveat:codex-hook] transcript read error: ${msg}
33422
+ process.stderr.write(`[caveat:codex-hook] transcript read error: ${errorMessage4(err)}
33536
33423
  `);
33537
33424
  return null;
33538
33425
  }
@@ -33541,29 +33428,6 @@ function codexSessionId(payload) {
33541
33428
  const v = payload.session_id ?? payload.sessionId;
33542
33429
  return typeof v === "string" && v.length > 0 ? v : null;
33543
33430
  }
33544
- function extractToolResponseText2(response) {
33545
- if (typeof response === "string") return response;
33546
- if (Array.isArray(response)) {
33547
- return response.map((item) => {
33548
- if (typeof item === "string") return item;
33549
- if (item !== null && typeof item === "object") {
33550
- const text = item.text;
33551
- return typeof text === "string" ? text : "";
33552
- }
33553
- return "";
33554
- }).filter(Boolean).join(" ");
33555
- }
33556
- if (response !== null && typeof response === "object") {
33557
- const r = response;
33558
- if (typeof r.content === "string") return r.content;
33559
- if (Array.isArray(r.content)) return extractToolResponseText2(r.content);
33560
- if (typeof r.output === "string") return r.output;
33561
- if (typeof r.stdout === "string" || typeof r.stderr === "string") {
33562
- return [r.stdout, r.stderr].filter((x) => typeof x === "string").join(" ");
33563
- }
33564
- }
33565
- return "";
33566
- }
33567
33431
  function numericExitCode(v) {
33568
33432
  return typeof v === "number" && Number.isInteger(v) ? v : null;
33569
33433
  }
@@ -33572,10 +33436,10 @@ function processExitCodeFromText(text) {
33572
33436
  return m ? Number(m[1]) : null;
33573
33437
  }
33574
33438
  function transcriptToolOutput(transcriptPath, toolUseId) {
33575
- if (!transcriptPath || !toolUseId || !existsSync10(transcriptPath)) return null;
33439
+ if (!transcriptPath || !toolUseId || !existsSync12(transcriptPath)) return null;
33576
33440
  let raw = "";
33577
33441
  try {
33578
- raw = readFileSync6(transcriptPath, "utf-8");
33442
+ raw = readFileSync7(transcriptPath, "utf-8");
33579
33443
  } catch {
33580
33444
  return null;
33581
33445
  }
@@ -33629,7 +33493,7 @@ function isCodexToolError(payload) {
33629
33493
  const exit2 = numericExitCode(r.exit_code ?? r.exitCode);
33630
33494
  if (exit2 !== null) return exit2 !== 0;
33631
33495
  }
33632
- const responseExit = processExitCodeFromText(extractToolResponseText2(resp));
33496
+ const responseExit = processExitCodeFromText(extractToolResponseText(resp));
33633
33497
  if (responseExit !== null) return responseExit !== 0;
33634
33498
  const transcriptExit = transcriptExitCode(payload);
33635
33499
  if (transcriptExit !== null) return transcriptExit !== 0;
@@ -33640,7 +33504,7 @@ function buildCodexPostToolUseWorkerJob(payload) {
33640
33504
  if (!sessionId) return null;
33641
33505
  const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : void 0;
33642
33506
  const toolUseId = typeof payload.tool_use_id === "string" ? payload.tool_use_id : void 0;
33643
- const responseText = extractToolResponseText2(payload.tool_response ?? payload.toolResponse);
33507
+ const responseText = extractToolResponseText(payload.tool_response ?? payload.toolResponse);
33644
33508
  const inputText = toolInputText(payload.tool_input);
33645
33509
  const transcriptOutput = transcriptPath && toolUseId ? transcriptToolOutput(transcriptPath, toolUseId) : null;
33646
33510
  const topicText = inputText.trim();
@@ -33670,101 +33534,6 @@ function codexContextOutput(text, eventName = "UserPromptSubmit") {
33670
33534
  }
33671
33535
  });
33672
33536
  }
33673
- function codexPendingCleanupFailureText() {
33674
- return "[caveat:codex-hook] pending reminder cleanup failed";
33675
- }
33676
- function drainForSession2(sessionId) {
33677
- const ctx = buildContextSafely2();
33678
- if (!ctx) return [];
33679
- const local = drainPendingRemindersDetailed(ctx.caveatHome, sessionId);
33680
- const global = drainPendingRemindersDetailed(ctx.caveatHome, "_global");
33681
- for (const _failure of [...local.cleanupFailures, ...global.cleanupFailures]) {
33682
- process.stderr.write(`${codexPendingCleanupFailureText()}
33683
- `);
33684
- }
33685
- return [...local.reminders, ...global.reminders];
33686
- }
33687
- function codexContextDedupeKey(text) {
33688
- if (text.startsWith(CODEX_STOP_REMINDER_PREFIX)) return "codex-stop-reminder";
33689
- return text.trim();
33690
- }
33691
- function compactCodexContexts(contexts) {
33692
- const selected = [];
33693
- const seen = /* @__PURE__ */ new Set();
33694
- for (let i = contexts.length - 1; i >= 0; i -= 1) {
33695
- const text = contexts[i]?.trim();
33696
- if (!text) continue;
33697
- const key = codexContextDedupeKey(text);
33698
- if (seen.has(key)) continue;
33699
- seen.add(key);
33700
- selected.push(text);
33701
- }
33702
- selected.reverse();
33703
- const limited = selected.slice(-CODEX_MAX_CONTEXT_BLOCKS);
33704
- const omitted = selected.length - limited.length;
33705
- if (omitted > 0) {
33706
- limited.push(`[caveat] pending reminder ${omitted} \u4EF6\u3092\u91CD\u8907\u307E\u305F\u306F\u4E0A\u9650\u306B\u3088\u308A\u7701\u7565\u3057\u307E\u3057\u305F\u3002`);
33707
- }
33708
- return limited;
33709
- }
33710
- function sanitizeCodexStateId(raw) {
33711
- const clean = raw.replace(/[^A-Za-z0-9_-]/g, "");
33712
- return clean.length > 0 ? clean : "_unknown";
33713
- }
33714
- function stopSignalKey2(signals, related) {
33715
- const body = JSON.stringify({
33716
- toolFailureCount: signals.toolFailureCount,
33717
- fileEditCounts: signals.fileEditCounts.map((e) => [e.path, e.count]),
33718
- webSearchCount: signals.webSearchCount,
33719
- webFetchCount: signals.webFetchCount,
33720
- bashRetryCount: signals.bashRetryCount,
33721
- searchQueries: signals.searchQueries,
33722
- related: related.map((h) => [h.source, h.id])
33723
- });
33724
- return createHash2("sha256").update(body).digest("hex");
33725
- }
33726
- function stopStatePath2(caveatHome, sessionId) {
33727
- return join12(caveatHome, CODEX_STOP_STATE_DIR, `${sanitizeCodexStateId(sessionId)}.txt`);
33728
- }
33729
- function wasStopReminderQueued2(caveatHome, sessionId, key) {
33730
- const path = stopStatePath2(caveatHome, sessionId);
33731
- try {
33732
- return readFileSync6(path, "utf-8") === key;
33733
- } catch {
33734
- return false;
33735
- }
33736
- }
33737
- function markStopReminderQueued2(caveatHome, sessionId, key) {
33738
- const path = stopStatePath2(caveatHome, sessionId);
33739
- mkdirSync6(join12(caveatHome, CODEX_STOP_STATE_DIR), { recursive: true });
33740
- writeFileSync6(path, key, "utf-8");
33741
- }
33742
- function queueStopForSession2(sessionId, signals, related) {
33743
- const ctx = buildContextSafely2();
33744
- if (!ctx) return;
33745
- const key = stopSignalKey2(signals, related);
33746
- if (wasStopReminderQueued2(ctx.caveatHome, sessionId, key)) return;
33747
- let result;
33748
- try {
33749
- result = buildAndPublishPendingReminder(ctx.caveatHome, sessionId, buildPendingSemanticKey({
33750
- agent: "codex",
33751
- surface: "stop",
33752
- refs: related,
33753
- stopSignalDigest: key
33754
- }), () => stopReminderText(signals, related));
33755
- } catch {
33756
- process.stderr.write("[caveat:codex-hook] pending reminder build or publish failed\n");
33757
- return;
33758
- }
33759
- if (!result.ran) return;
33760
- try {
33761
- markStopReminderQueued2(ctx.caveatHome, sessionId, key);
33762
- } catch (err) {
33763
- const msg = err instanceof Error ? err.message : String(err);
33764
- process.stderr.write(`[caveat:codex-hook] pending reminder write error: ${msg}
33765
- `);
33766
- }
33767
- }
33768
33537
  async function waitForTranscriptOutput(transcriptPath, toolUseId) {
33769
33538
  const deadline = Date.now() + 2e3;
33770
33539
  while (Date.now() <= deadline) {
@@ -33790,13 +33559,13 @@ async function processCodexWorkerJob(job, opts = { waitForTranscript: true }) {
33790
33559
  }
33791
33560
  }
33792
33561
  if (!knownError && job.allowSymptomOnly !== true) return;
33793
- const hits = searchCaveatsSafely2({
33562
+ const hits = searchCaveatsSafely(CODEX_HOST, {
33794
33563
  topicText: job.topicText,
33795
33564
  failureText,
33796
33565
  surface: "tool_error"
33797
33566
  });
33798
33567
  if (hits.length === 0) return;
33799
- const ctx = buildContextSafely2();
33568
+ const ctx = buildContextSafely(CODEX_HOST);
33800
33569
  if (!ctx) return;
33801
33570
  let result;
33802
33571
  try {
@@ -33814,7 +33583,7 @@ async function processCodexWorkerJob(job, opts = { waitForTranscript: true }) {
33814
33583
  async function runCodexWorker(workFile) {
33815
33584
  let raw;
33816
33585
  try {
33817
- raw = readFileSync6(workFile, "utf-8");
33586
+ raw = readFileSync7(workFile, "utf-8");
33818
33587
  } catch {
33819
33588
  process.exit(0);
33820
33589
  }
@@ -33831,7 +33600,7 @@ async function runCodexWorker(workFile) {
33831
33600
  await processCodexWorkerJob(job);
33832
33601
  process.exit(0);
33833
33602
  }
33834
- function runDiagnostics(codexHome = process.env.CODEX_HOME ?? join12(homedir3(), ".codex")) {
33603
+ function runDiagnostics(codexHome = process.env.CODEX_HOME ?? join13(homedir3(), ".codex")) {
33835
33604
  const features = spawnSync5("codex", ["features", "list"], {
33836
33605
  encoding: "utf-8",
33837
33606
  maxBuffer: 1024 * 1024,
@@ -33869,24 +33638,23 @@ async function runCodexHook(name, arg) {
33869
33638
  }
33870
33639
  let raw = "";
33871
33640
  try {
33872
- raw = await readStdin2();
33641
+ raw = await readStdin();
33873
33642
  } catch (err) {
33874
- const msg = err instanceof Error ? err.message : String(err);
33875
- process.stderr.write(`[caveat:codex-hook] stdin read error: ${msg}
33643
+ process.stderr.write(`[caveat:codex-hook] stdin read error: ${errorMessage4(err)}
33876
33644
  `);
33877
33645
  process.exit(0);
33878
33646
  }
33879
- const payload = parsePayload2(raw);
33647
+ const payload = parsePayload(CODEX_HOST, raw);
33880
33648
  const sessionId = codexSessionId(payload);
33881
33649
  if (!sessionId) process.stderr.write("[caveat:codex-hook] missing session_id; pending drain disabled\n");
33882
33650
  if (name === "user-prompt-submit") {
33883
- const contexts = sessionId ? drainForSession2(sessionId) : [];
33651
+ const contexts = sessionId ? drainForSession(CODEX_HOST, sessionId) : [];
33884
33652
  const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
33885
- const hits = searchCaveatsSafely2({ topicText: prompt, failureText: prompt, surface: "user_prompt" });
33653
+ const hits = searchCaveatsSafely(CODEX_HOST, { topicText: prompt, failureText: prompt, surface: "user_prompt" });
33886
33654
  if (hits.length > 0) {
33887
33655
  contexts.push(userPromptSubmitReminderText(hits));
33888
33656
  }
33889
- const compacted = compactCodexContexts(contexts);
33657
+ const compacted = compactContexts(CODEX_HOST, contexts);
33890
33658
  if (compacted.length > 0) {
33891
33659
  process.stdout.write(`${codexContextOutput(compacted.join("\n\n"))}
33892
33660
  `);
@@ -33900,39 +33668,36 @@ async function runCodexHook(name, arg) {
33900
33668
  }
33901
33669
  if (name === "stop") {
33902
33670
  try {
33903
- const ctx = buildContextSafely2();
33671
+ const ctx = buildContextSafely(CODEX_HOST);
33904
33672
  if (ctx) {
33905
33673
  try {
33906
33674
  maybeSweepPendingDirs(ctx.caveatHome);
33907
33675
  } catch (err) {
33908
- const msg = err instanceof Error ? err.message : String(err);
33909
- process.stderr.write(`[caveat:codex-hook] pending sweep error: ${msg}
33676
+ process.stderr.write(`[caveat:codex-hook] pending sweep error: ${errorMessage4(err)}
33910
33677
  `);
33911
33678
  }
33912
33679
  maybeTriggerAutoReindex(ctx);
33913
33680
  try {
33914
33681
  maybeTriggerAutoSync(ctx);
33915
33682
  } catch (err) {
33916
- const msg = err instanceof Error ? err.message : String(err);
33917
- process.stderr.write(`[caveat:codex-hook] auto sync trigger error: ${msg}
33683
+ process.stderr.write(`[caveat:codex-hook] auto sync trigger error: ${errorMessage4(err)}
33918
33684
  `);
33919
33685
  }
33920
33686
  }
33921
33687
  } catch (err) {
33922
- const msg = err instanceof Error ? err.message : String(err);
33923
- process.stderr.write(`[caveat:codex-hook] auto reindex trigger error: ${msg}
33688
+ process.stderr.write(`[caveat:codex-hook] auto reindex trigger error: ${errorMessage4(err)}
33924
33689
  `);
33925
33690
  }
33926
33691
  if (payload.stop_hook_active === true) process.exit(0);
33927
33692
  const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
33928
33693
  const signals = transcriptPath ? loadSignalsSafely2(transcriptPath) : null;
33929
33694
  if (!signals || !hasAnyStruggleSignal(signals)) process.exit(0);
33930
- const related = searchCaveatsSafely2(signals.errorSnippets.map((failureText) => ({
33695
+ const related = searchCaveatsSafely(CODEX_HOST, signals.errorSnippets.map((failureText) => ({
33931
33696
  topicText: "",
33932
33697
  failureText,
33933
33698
  surface: "stop"
33934
33699
  })));
33935
- if (sessionId) queueStopForSession2(sessionId, signals, related);
33700
+ if (sessionId) queueStopForSession(CODEX_HOST, sessionId, signals, related, () => stopReminderText(signals, related));
33936
33701
  process.exit(0);
33937
33702
  }
33938
33703
  process.stderr.write(`[caveat:codex-hook] unknown hook name: ${name}
@@ -33941,9 +33706,9 @@ async function runCodexHook(name, arg) {
33941
33706
  }
33942
33707
 
33943
33708
  // src/commands/pull.ts
33944
- import { existsSync as existsSync11 } from "node:fs";
33709
+ import { existsSync as existsSync13 } from "node:fs";
33945
33710
  async function runPull(ctx) {
33946
- const hasCommunityDir = existsSync11(ctx.paths.communityDir);
33711
+ const hasCommunityDir = existsSync13(ctx.paths.communityDir);
33947
33712
  if (!hasCommunityDir) {
33948
33713
  ctx.logger.info(
33949
33714
  "no community repos yet \u2014 add one with `caveat community add <github-url>`."
@@ -34043,9 +33808,9 @@ async function runSync(ctx, opts, dependencies = {}) {
34043
33808
 
34044
33809
  // src/commands/codexSidecar.ts
34045
33810
  import { spawnSync as spawnSync6 } from "node:child_process";
34046
- import { closeSync, constants as constants3, fstatSync, lstatSync as lstatSync2, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync3, openSync, readSync as readSync2, realpathSync as realpathSync5, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "node:fs";
33811
+ import { closeSync, constants as constants3, fstatSync, lstatSync as lstatSync2, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync3, openSync, readSync as readSync2, realpathSync as realpathSync4, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "node:fs";
34047
33812
  import { tmpdir as tmpdir4 } from "node:os";
34048
- import { basename as basename2, dirname as dirname7, join as join13 } from "node:path";
33813
+ import { basename as basename2, dirname as dirname6, join as join14 } from "node:path";
34049
33814
  import { cwd, exit } from "node:process";
34050
33815
  function runCodexSidecarDiagnostics(logger, opts) {
34051
33816
  const plan = buildCodexSidecarDiagnosticsCommand({
@@ -34077,8 +33842,8 @@ function runCodexSidecarWithCaveats(ctx, workflow, prompt, opts) {
34077
33842
  process.stdout.write(JSON.stringify({ status: "skipped", decision }, null, 2) + "\n");
34078
33843
  exit(0);
34079
33844
  }
34080
- const contextDir = mkdtempSync3(join13(tmpdir4(), "caveat-sidecar-context-"));
34081
- const contextFile = join13(contextDir, "context.json");
33845
+ const contextDir = mkdtempSync3(join14(tmpdir4(), "caveat-sidecar-context-"));
33846
+ const contextFile = join14(contextDir, "context.json");
34082
33847
  let status2 = 1;
34083
33848
  try {
34084
33849
  const blocks = collectCaveatContextBlocks(ctx, {
@@ -34143,16 +33908,16 @@ function readHookSignalAdditionalContextFile(path, testProbe) {
34143
33908
  return [block];
34144
33909
  }
34145
33910
  function assertPrivateRegular(stat, uid, platform) {
34146
- const privateOwner = platform === "win32" || (stat.mode & 63) === 0 && (uid === void 0 || stat.uid === uid);
33911
+ const privateOwner = isPrivateOwnerStat(stat, uid, platform);
34147
33912
  if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_ADDITIONAL_CONTEXT_BYTES || !privateOwner) throw new Error("additional context file must be a private owner-only regular file within 4096 bytes");
34148
33913
  }
34149
33914
  function assertWindowsPrivateTempContainer(path, platform) {
34150
- if (platform !== "win32") return;
34151
- const parent = dirname7(path);
33915
+ if (!isWindows(platform)) return;
33916
+ const parent = dirname6(path);
34152
33917
  const parentStat = lstatSync2(parent);
34153
- const resolvedParent = realpathSync5(parent);
34154
- const resolvedTemp = realpathSync5(tmpdir4());
34155
- if (parentStat.isSymbolicLink() || !parentStat.isDirectory() || dirname7(resolvedParent) !== resolvedTemp || !basename2(resolvedParent).startsWith("caveat-")) {
33918
+ const resolvedParent = realpathSync4(parent);
33919
+ const resolvedTemp = realpathSync4(tmpdir4());
33920
+ if (parentStat.isSymbolicLink() || !parentStat.isDirectory() || dirname6(resolvedParent) !== resolvedTemp || !basename2(resolvedParent).startsWith("caveat-")) {
34156
33921
  throw new Error("additional context file must be inside a reserved per-user Caveat temporary directory");
34157
33922
  }
34158
33923
  }
@@ -34241,7 +34006,7 @@ function executePlan(logger, command, args, options = {}) {
34241
34006
  }
34242
34007
  function saveStructuredResult(path, stdout) {
34243
34008
  const parsed = JSON.parse(stdout);
34244
- mkdirSync7(dirname7(path), { recursive: true });
34009
+ mkdirSync6(dirname6(path), { recursive: true });
34245
34010
  writeFileSync7(path, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
34246
34011
  }
34247
34012
  function shellDisplayQuote(value) {
@@ -34330,8 +34095,8 @@ function runCommunityRemove(ctx, handle, opts) {
34330
34095
 
34331
34096
  // src/commands/factoryDiagnostics.ts
34332
34097
  import { execFileSync } from "node:child_process";
34333
- import { existsSync as existsSync12, readFileSync as readFileSync7 } from "node:fs";
34334
- import { join as join14 } from "node:path";
34098
+ import { existsSync as existsSync14, readFileSync as readFileSync8 } from "node:fs";
34099
+ import { join as join15 } from "node:path";
34335
34100
  import { DatabaseSync } from "node:sqlite";
34336
34101
  import { parse as parseToml2 } from "smol-toml";
34337
34102
  var status = (ok, reason) => ({ status: ok ? "ready" : "not_ready", reason_code: ok ? "ready" : reason });
@@ -34343,10 +34108,10 @@ function isRecord2(value) {
34343
34108
  return typeof value === "object" && value !== null && !Array.isArray(value);
34344
34109
  }
34345
34110
  function claudeRegistration(home, nodePath, cliScriptPath) {
34346
- const path = join14(home, ".claude.json");
34347
- if (!existsSync12(path)) return status(false, "not_registered");
34111
+ const path = join15(home, ".claude.json");
34112
+ if (!existsSync14(path)) return status(false, "not_registered");
34348
34113
  try {
34349
- const value = JSON.parse(readFileSync7(path, "utf8"));
34114
+ const value = JSON.parse(readFileSync8(path, "utf8"));
34350
34115
  if (!isRecord2(value) || !isRecord2(value.mcpServers)) return status(false, "not_registered");
34351
34116
  return status(isCaveatClaudeMcpRegistration(value.mcpServers.caveat, nodePath, cliScriptPath), "not_registered");
34352
34117
  } catch {
@@ -34355,7 +34120,7 @@ function claudeRegistration(home, nodePath, cliScriptPath) {
34355
34120
  }
34356
34121
  function claudeHooks(home, nodePath, cliScriptPath) {
34357
34122
  try {
34358
- const settings = JSON.parse(readFileSync7(join14(home, ".claude", "settings.json"), "utf8"));
34123
+ const settings = JSON.parse(readFileSync8(join15(home, ".claude", "settings.json"), "utf8"));
34359
34124
  const present = (event, subcommand) => settings.hooks?.[event]?.some((entry) => entry.hooks?.some((item) => typeof item.command === "string" && isCanonicalCaveatClaudeHookCommand(item.command, subcommand, nodePath, cliScriptPath))) ?? false;
34360
34125
  return { user_prompt_submit: hook(present("UserPromptSubmit", "user-prompt-submit")), post_tool_use: hook(present("PostToolUse", "post-tool-use")), post_tool_use_failure: hook(present("PostToolUseFailure", "post-tool-use")), stop: hook(present("Stop", "stop")) };
34361
34126
  } catch {
@@ -34377,7 +34142,7 @@ function strictSearchSchema(db) {
34377
34142
  return columns.map((column) => `${column.name}:${column.hidden}`).join(",") === "id:0,title:0,body:0,tags:0,entries_fts:1,rank:1";
34378
34143
  }
34379
34144
  function database(path) {
34380
- if (!existsSync12(path)) return { status: "not_ready", reason_code: "missing", schema_version: null, supported_schema_version: 3, migration_status: "unverified" };
34145
+ if (!existsSync14(path)) return { status: "not_ready", reason_code: "missing", schema_version: null, supported_schema_version: 3, migration_status: "unverified" };
34381
34146
  try {
34382
34147
  const db = new DatabaseSync(path, { readOnly: true });
34383
34148
  try {
@@ -34402,10 +34167,10 @@ function database(path) {
34402
34167
  }
34403
34168
  }
34404
34169
  function codexFeature(codexHome) {
34405
- const path = join14(codexHome, "config.toml");
34406
- if (!existsSync12(path)) return status(false, "feature_disabled");
34170
+ const path = join15(codexHome, "config.toml");
34171
+ if (!existsSync14(path)) return status(false, "feature_disabled");
34407
34172
  try {
34408
- const config2 = parseToml2(readFileSync7(path, "utf8"));
34173
+ const config2 = parseToml2(readFileSync8(path, "utf8"));
34409
34174
  if (!isRecord2(config2.features)) return status(false, "feature_disabled");
34410
34175
  const features = config2.features;
34411
34176
  return status(features.hooks === true && features.codex_hooks === void 0, "feature_disabled");
@@ -34414,7 +34179,7 @@ function codexFeature(codexHome) {
34414
34179
  }
34415
34180
  }
34416
34181
  function codexHooks(codexHome, nodePath, cliScriptPath) {
34417
- const value = JSON.parse(readFileSync7(join14(codexHome, "hooks.json"), "utf8"));
34182
+ const value = JSON.parse(readFileSync8(join15(codexHome, "hooks.json"), "utf8"));
34418
34183
  if (!isRecord2(value) || !isRecord2(value.hooks)) throw Error("config_unreadable");
34419
34184
  const hooks = value.hooks;
34420
34185
  const present = (event, subcommand) => Array.isArray(hooks[event]) && hooks[event].some((entry) => isRecord2(entry) && Array.isArray(entry.hooks) && entry.hooks.some((item) => isCanonicalCaveatCodexHookEntry(item, subcommand, nodePath, cliScriptPath)));
@@ -34449,7 +34214,7 @@ function sync(own) {
34449
34214
  return unverified("upstream_unavailable");
34450
34215
  }
34451
34216
  }
34452
- function factoryDiagnostics(ctx, codexHome = process.env.CODEX_HOME ?? join14(ctx.userHome, ".codex")) {
34217
+ function factoryDiagnostics(ctx, codexHome = process.env.CODEX_HOME ?? join15(ctx.userHome, ".codex")) {
34453
34218
  const nodePath = process.execPath;
34454
34219
  const cliScriptPath = process.argv[1] ?? "";
34455
34220
  const db = database(ctx.paths.dbPath);