caveat-cli 0.14.2 → 0.14.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
@@ -40,7 +40,7 @@ import {
40
40
  toolErrorReminderText,
41
41
  updateEntry,
42
42
  userPromptSubmitReminderText
43
- } from "./chunk-CSXD73IX.js";
43
+ } from "./chunk-EH3S6X6X.js";
44
44
 
45
45
  // ../../node_modules/.pnpm/ajv@8.18.0/node_modules/ajv/dist/compile/codegen/code.js
46
46
  var require_code = __commonJS({
@@ -7345,7 +7345,7 @@ function runStats(ctx) {
7345
7345
 
7346
7346
  // src/commands/serve.ts
7347
7347
  async function runServe(opts) {
7348
- const { startServer } = await import("./server-BP54BTEL.js");
7348
+ const { startServer } = await import("./server-P4FQZD2N.js");
7349
7349
  const { port, host } = startServer({ port: opts.port });
7350
7350
  process.stdout.write(`[caveat] web portal: http://${host}:${port}/
7351
7351
  `);
@@ -21846,6 +21846,7 @@ async function runMcpServer() {
21846
21846
  import { spawn, spawnSync as spawnSync2 } from "node:child_process";
21847
21847
  import {
21848
21848
  existsSync as existsSync5,
21849
+ mkdirSync as mkdirSync4,
21849
21850
  mkdtempSync,
21850
21851
  readFileSync as readFileSync3,
21851
21852
  rmSync,
@@ -21854,7 +21855,7 @@ import {
21854
21855
  } from "node:fs";
21855
21856
  import { tmpdir } from "node:os";
21856
21857
  import { join as join8 } from "node:path";
21857
- import { randomBytes } from "node:crypto";
21858
+ import { createHash, randomBytes } from "node:crypto";
21858
21859
  var silentLogger = {
21859
21860
  info: () => {
21860
21861
  },
@@ -21863,6 +21864,9 @@ var silentLogger = {
21863
21864
  error: (m) => process.stderr.write(`[caveat:hook] ${m}
21864
21865
  `)
21865
21866
  };
21867
+ var CLAUDE_MAX_CONTEXT_BLOCKS = 3;
21868
+ 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:";
21869
+ var CLAUDE_STOP_STATE_DIR = "claude-stop-state";
21866
21870
  async function readStdin() {
21867
21871
  const chunks = [];
21868
21872
  for await (const chunk of process.stdin) {
@@ -21934,12 +21938,82 @@ function loadSignalsSafely(path) {
21934
21938
  return null;
21935
21939
  }
21936
21940
  }
21941
+ function systemReminderOutput(text) {
21942
+ return `<system-reminder>${text}</system-reminder>`;
21943
+ }
21937
21944
  function drainForSession(sessionId) {
21945
+ const ctx = buildContextSafely();
21946
+ if (!ctx) return [];
21947
+ return drainPendingReminders(ctx.caveatHome, sessionId);
21948
+ }
21949
+ function claudeContextDedupeKey(text) {
21950
+ if (text.startsWith(CLAUDE_STOP_REMINDER_PREFIX)) return "claude-stop-reminder";
21951
+ return text.trim();
21952
+ }
21953
+ function compactClaudeContexts(contexts) {
21954
+ const selected = [];
21955
+ const seen = /* @__PURE__ */ new Set();
21956
+ for (let i = contexts.length - 1; i >= 0; i -= 1) {
21957
+ const text = contexts[i]?.trim();
21958
+ if (!text) continue;
21959
+ const key = claudeContextDedupeKey(text);
21960
+ if (seen.has(key)) continue;
21961
+ seen.add(key);
21962
+ selected.push(text);
21963
+ }
21964
+ selected.reverse();
21965
+ const limited = selected.slice(-CLAUDE_MAX_CONTEXT_BLOCKS);
21966
+ const omitted = contexts.filter((t) => t.trim().length > 0).length - limited.length;
21967
+ if (omitted > 0) {
21968
+ limited.push(
21969
+ `[caveat] pending reminder ${omitted} \u4EF6\u3092\u91CD\u8907\u307E\u305F\u306F\u4E0A\u9650\u306B\u3088\u308A\u7701\u7565\u3057\u307E\u3057\u305F\u3002`
21970
+ );
21971
+ }
21972
+ return limited;
21973
+ }
21974
+ function sanitizeClaudeStateId(raw) {
21975
+ const clean = raw.replace(/[^A-Za-z0-9_-]/g, "");
21976
+ return clean.length > 0 ? clean : "_unknown";
21977
+ }
21978
+ function stopSignalKey(signals, related) {
21979
+ const body = JSON.stringify({
21980
+ toolFailureCount: signals.toolFailureCount,
21981
+ fileEditCounts: signals.fileEditCounts.map((e) => [e.path, e.count]),
21982
+ webSearchCount: signals.webSearchCount,
21983
+ webFetchCount: signals.webFetchCount,
21984
+ bashRetryCount: signals.bashRetryCount,
21985
+ searchQueries: signals.searchQueries,
21986
+ related: related.map((h) => [h.source, h.id])
21987
+ });
21988
+ return createHash("sha256").update(body).digest("hex");
21989
+ }
21990
+ function stopStatePath(caveatHome, sessionId) {
21991
+ return join8(caveatHome, CLAUDE_STOP_STATE_DIR, `${sanitizeClaudeStateId(sessionId)}.txt`);
21992
+ }
21993
+ function wasStopReminderQueued(caveatHome, sessionId, key) {
21994
+ const path = stopStatePath(caveatHome, sessionId);
21995
+ try {
21996
+ return readFileSync3(path, "utf-8") === key;
21997
+ } catch {
21998
+ return false;
21999
+ }
22000
+ }
22001
+ function markStopReminderQueued(caveatHome, sessionId, key) {
22002
+ const path = stopStatePath(caveatHome, sessionId);
22003
+ mkdirSync4(join8(caveatHome, CLAUDE_STOP_STATE_DIR), { recursive: true });
22004
+ writeFileSync3(path, key, "utf-8");
22005
+ }
22006
+ function queueStopForSession(sessionId, signals, related) {
21938
22007
  const ctx = buildContextSafely();
21939
22008
  if (!ctx) return;
21940
- const reminders = drainPendingReminders(ctx.caveatHome, sessionId);
21941
- for (const text of reminders) {
21942
- process.stdout.write(`<system-reminder>${text}</system-reminder>
22009
+ const key = stopSignalKey(signals, related);
22010
+ if (wasStopReminderQueued(ctx.caveatHome, sessionId, key)) return;
22011
+ try {
22012
+ appendPendingReminder(ctx.caveatHome, sessionId, buildStopReminder(signals, related));
22013
+ markStopReminderQueued(ctx.caveatHome, sessionId, key);
22014
+ } catch (err) {
22015
+ const msg = err instanceof Error ? err.message : String(err);
22016
+ process.stderr.write(`[caveat:hook] pending reminder write error: ${msg}
21943
22017
  `);
21944
22018
  }
21945
22019
  }
@@ -22105,6 +22179,20 @@ function buildStopReminder(signals, related) {
22105
22179
  `[caveat:codex-sidecar] advisory unavailable: ${advisory.message}`
22106
22180
  ].join("\n");
22107
22181
  }
22182
+ function compactFailureMessage(message) {
22183
+ const singleLine = message.replace(/\s+/g, " ").trim();
22184
+ if (!singleLine) return "unknown error";
22185
+ return singleLine.length > 220 ? `${singleLine.slice(0, 220)}...` : singleLine;
22186
+ }
22187
+ function sidecarFailureDetail(output) {
22188
+ const protocol = output.match(/PROTOCOL_ERROR:[^"\r\n]+/);
22189
+ if (protocol) return compactFailureMessage(protocol[0]);
22190
+ const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
22191
+ const diagnostic = lines.find(
22192
+ (line) => !line.startsWith("[caveat] [codex-sidecar] codex-sidecar ")
22193
+ );
22194
+ return compactFailureMessage(diagnostic ?? output);
22195
+ }
22108
22196
  function runCodexSidecarAdvisory(input) {
22109
22197
  const cliScript = process.argv[1];
22110
22198
  if (!cliScript) {
@@ -22144,15 +22232,21 @@ function runCodexSidecarAdvisory(input) {
22144
22232
  maxBuffer: 10 * 1024 * 1024
22145
22233
  });
22146
22234
  if (result.error) {
22147
- return { status: "failed", message: result.error.message };
22235
+ return { status: "failed", message: compactFailureMessage(result.error.message) };
22148
22236
  }
22149
22237
  if (result.status !== 0) {
22150
22238
  const detail = (result.stderr || result.stdout || `exit ${result.status}`).trim();
22151
- return { status: "failed", message: detail.slice(0, 500) };
22239
+ return {
22240
+ status: "failed",
22241
+ message: `sidecar command failed: ${sidecarFailureDetail(detail)}`
22242
+ };
22152
22243
  }
22153
22244
  const parsed = JSON.parse(readFileSync3(resultFile, "utf-8"));
22154
22245
  if (parsed.status !== "ok" || typeof parsed.summary !== "string") {
22155
- return { status: "failed", message: `unexpected SidecarResult status: ${String(parsed.status)}` };
22246
+ return {
22247
+ status: "failed",
22248
+ message: compactFailureMessage(`unexpected SidecarResult status: ${String(parsed.status)}`)
22249
+ };
22156
22250
  }
22157
22251
  return {
22158
22252
  status: "ok",
@@ -22161,7 +22255,10 @@ function runCodexSidecarAdvisory(input) {
22161
22255
  };
22162
22256
  } catch (err) {
22163
22257
  const msg = err instanceof Error ? err.message : String(err);
22164
- return { status: "failed", message: `invalid SidecarResult JSON: ${msg}` };
22258
+ return {
22259
+ status: "failed",
22260
+ message: compactFailureMessage(`invalid SidecarResult JSON: ${msg}`)
22261
+ };
22165
22262
  } finally {
22166
22263
  rmSync(resultDir, { recursive: true, force: true });
22167
22264
  }
@@ -22183,19 +22280,26 @@ async function runHook(name, arg) {
22183
22280
  }
22184
22281
  const payload = parsePayload(raw);
22185
22282
  const sessionId = getSessionId(payload);
22186
- drainForSession(sessionId);
22283
+ const contexts = name === "stop" ? [] : drainForSession(sessionId);
22187
22284
  if (name === "user-prompt-submit") {
22188
22285
  const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
22189
22286
  const hits = searchCaveatsFromTextSafely(prompt);
22190
22287
  if (hits.length > 0) {
22191
- process.stdout.write(
22192
- `<system-reminder>${userPromptSubmitReminderText(hits)}</system-reminder>
22193
- `
22194
- );
22288
+ contexts.push(userPromptSubmitReminderText(hits));
22289
+ }
22290
+ const compacted = compactClaudeContexts(contexts);
22291
+ if (compacted.length > 0) {
22292
+ process.stdout.write(`${systemReminderOutput(compacted.join("\n\n"))}
22293
+ `);
22195
22294
  }
22196
22295
  process.exit(0);
22197
22296
  }
22198
22297
  if (name === "post-tool-use") {
22298
+ const compacted = compactClaudeContexts(contexts);
22299
+ if (compacted.length > 0) {
22300
+ process.stdout.write(`${systemReminderOutput(compacted.join("\n\n"))}
22301
+ `);
22302
+ }
22199
22303
  if (!isToolError(payload)) process.exit(0);
22200
22304
  const errText = extractToolResponseText(
22201
22305
  payload.tool_response ?? payload.toolResponse ?? payload.error ?? payload
@@ -22211,8 +22315,7 @@ async function runHook(name, arg) {
22211
22315
  const signals = transcriptPath ? loadSignalsSafely(transcriptPath) : null;
22212
22316
  if (!signals || !hasAnyStruggleSignal(signals)) process.exit(0);
22213
22317
  const related = searchCaveatsFromTextSafely(struggleSearchText(signals));
22214
- process.stdout.write(`<system-reminder>${buildStopReminder(signals, related)}</system-reminder>
22215
- `);
22318
+ queueStopForSession(sessionId, signals, related);
22216
22319
  process.exit(0);
22217
22320
  }
22218
22321
  process.stderr.write(`[caveat:hook] unknown hook name: ${name}
@@ -22222,13 +22325,13 @@ async function runHook(name, arg) {
22222
22325
 
22223
22326
  // src/commands/codexHookCmd.ts
22224
22327
  import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
22225
- import { existsSync as existsSync7, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "node:fs";
22328
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "node:fs";
22226
22329
  import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
22227
22330
  import { join as join10 } from "node:path";
22228
- import { randomBytes as randomBytes2 } from "node:crypto";
22331
+ import { createHash as createHash2, randomBytes as randomBytes2 } from "node:crypto";
22229
22332
 
22230
22333
  // src/codexHookInstall.ts
22231
- import { copyFileSync as copyFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
22334
+ import { copyFileSync as copyFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
22232
22335
  import { dirname as dirname5, join as join9 } from "node:path";
22233
22336
  function quote2(p) {
22234
22337
  return p.includes(" ") ? `"${p}"` : p;
@@ -22253,7 +22356,7 @@ function readHooks(path) {
22253
22356
  }
22254
22357
  function writeJsonWithBackup(path, value) {
22255
22358
  const dir = dirname5(path);
22256
- if (!existsSync6(dir)) mkdirSync4(dir, { recursive: true });
22359
+ if (!existsSync6(dir)) mkdirSync5(dir, { recursive: true });
22257
22360
  let backupPath = "";
22258
22361
  if (existsSync6(path)) {
22259
22362
  backupPath = `${path}.caveat-backup-${Date.now()}`;
@@ -22322,7 +22425,7 @@ codex_hooks = true
22322
22425
  }
22323
22426
  function writeConfigWithBackup(path, text) {
22324
22427
  const dir = dirname5(path);
22325
- if (!existsSync6(dir)) mkdirSync4(dir, { recursive: true });
22428
+ if (!existsSync6(dir)) mkdirSync5(dir, { recursive: true });
22326
22429
  let backupPath = "";
22327
22430
  if (existsSync6(path)) {
22328
22431
  backupPath = `${path}.caveat-backup-${Date.now()}`;
@@ -22439,6 +22542,9 @@ var silentLogger2 = {
22439
22542
  error: (m) => process.stderr.write(`[caveat:codex-hook] ${m}
22440
22543
  `)
22441
22544
  };
22545
+ var CODEX_MAX_CONTEXT_BLOCKS = 3;
22546
+ 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:";
22547
+ var CODEX_STOP_STATE_DIR = "codex-stop-state";
22442
22548
  async function readStdin2() {
22443
22549
  const chunks = [];
22444
22550
  for await (const chunk of process.stdin) {
@@ -22637,18 +22743,77 @@ function codexContextOutput(text, eventName = "UserPromptSubmit") {
22637
22743
  }
22638
22744
  });
22639
22745
  }
22640
- function codexStopOutput(text) {
22641
- return JSON.stringify({
22642
- decision: "block",
22643
- reason: text
22746
+ function drainForSession2(sessionId) {
22747
+ const ctx = buildContextSafely2();
22748
+ if (!ctx) return [];
22749
+ return drainPendingReminders(ctx.caveatHome, sessionId);
22750
+ }
22751
+ function codexContextDedupeKey(text) {
22752
+ if (text.startsWith(CODEX_STOP_REMINDER_PREFIX)) return "codex-stop-reminder";
22753
+ return text.trim();
22754
+ }
22755
+ function compactCodexContexts(contexts) {
22756
+ const selected = [];
22757
+ const seen = /* @__PURE__ */ new Set();
22758
+ for (let i = contexts.length - 1; i >= 0; i -= 1) {
22759
+ const text = contexts[i]?.trim();
22760
+ if (!text) continue;
22761
+ const key = codexContextDedupeKey(text);
22762
+ if (seen.has(key)) continue;
22763
+ seen.add(key);
22764
+ selected.push(text);
22765
+ }
22766
+ selected.reverse();
22767
+ const limited = selected.slice(-CODEX_MAX_CONTEXT_BLOCKS);
22768
+ const omitted = contexts.filter((t) => t.trim().length > 0).length - limited.length;
22769
+ if (omitted > 0) {
22770
+ 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`);
22771
+ }
22772
+ return limited;
22773
+ }
22774
+ function sanitizeCodexStateId(raw) {
22775
+ const clean = raw.replace(/[^A-Za-z0-9_-]/g, "");
22776
+ return clean.length > 0 ? clean : "_unknown";
22777
+ }
22778
+ function stopSignalKey2(signals, related) {
22779
+ const body = JSON.stringify({
22780
+ toolFailureCount: signals.toolFailureCount,
22781
+ fileEditCounts: signals.fileEditCounts.map((e) => [e.path, e.count]),
22782
+ webSearchCount: signals.webSearchCount,
22783
+ webFetchCount: signals.webFetchCount,
22784
+ bashRetryCount: signals.bashRetryCount,
22785
+ searchQueries: signals.searchQueries,
22786
+ related: related.map((h) => [h.source, h.id])
22644
22787
  });
22788
+ return createHash2("sha256").update(body).digest("hex");
22789
+ }
22790
+ function stopStatePath2(caveatHome, sessionId) {
22791
+ return join10(caveatHome, CODEX_STOP_STATE_DIR, `${sanitizeCodexStateId(sessionId)}.txt`);
22645
22792
  }
22646
- function drainForSession2(sessionId, eventName = "UserPromptSubmit") {
22793
+ function wasStopReminderQueued2(caveatHome, sessionId, key) {
22794
+ const path = stopStatePath2(caveatHome, sessionId);
22795
+ try {
22796
+ return readFileSync5(path, "utf-8") === key;
22797
+ } catch {
22798
+ return false;
22799
+ }
22800
+ }
22801
+ function markStopReminderQueued2(caveatHome, sessionId, key) {
22802
+ const path = stopStatePath2(caveatHome, sessionId);
22803
+ mkdirSync6(join10(caveatHome, CODEX_STOP_STATE_DIR), { recursive: true });
22804
+ writeFileSync5(path, key, "utf-8");
22805
+ }
22806
+ function queueStopForSession2(sessionId, signals, related) {
22647
22807
  const ctx = buildContextSafely2();
22648
22808
  if (!ctx) return;
22649
- const reminders = drainPendingReminders(ctx.caveatHome, sessionId);
22650
- for (const text of reminders) {
22651
- process.stdout.write(`${codexContextOutput(text, eventName)}
22809
+ const key = stopSignalKey2(signals, related);
22810
+ if (wasStopReminderQueued2(ctx.caveatHome, sessionId, key)) return;
22811
+ try {
22812
+ appendPendingReminder(ctx.caveatHome, sessionId, stopReminderText(signals, related));
22813
+ markStopReminderQueued2(ctx.caveatHome, sessionId, key);
22814
+ } catch (err) {
22815
+ const msg = err instanceof Error ? err.message : String(err);
22816
+ process.stderr.write(`[caveat:codex-hook] pending reminder write error: ${msg}
22652
22817
  `);
22653
22818
  }
22654
22819
  }
@@ -22748,13 +22913,17 @@ async function runCodexHook(name, arg) {
22748
22913
  }
22749
22914
  const payload = parsePayload2(raw);
22750
22915
  const sessionId = codexSessionId(payload);
22751
- if (sessionId && name === "user-prompt-submit") drainForSession2(sessionId);
22752
- else if (!sessionId) process.stderr.write("[caveat:codex-hook] missing session_id; pending drain disabled\n");
22916
+ if (!sessionId) process.stderr.write("[caveat:codex-hook] missing session_id; pending drain disabled\n");
22753
22917
  if (name === "user-prompt-submit") {
22918
+ const contexts = sessionId ? drainForSession2(sessionId) : [];
22754
22919
  const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
22755
22920
  const hits = searchCaveatsFromTextSafely2(prompt);
22756
22921
  if (hits.length > 0) {
22757
- process.stdout.write(`${codexContextOutput(userPromptSubmitReminderText(hits))}
22922
+ contexts.push(userPromptSubmitReminderText(hits));
22923
+ }
22924
+ const compacted = compactCodexContexts(contexts);
22925
+ if (compacted.length > 0) {
22926
+ process.stdout.write(`${codexContextOutput(compacted.join("\n\n"))}
22758
22927
  `);
22759
22928
  }
22760
22929
  process.exit(0);
@@ -22770,8 +22939,7 @@ async function runCodexHook(name, arg) {
22770
22939
  const signals = transcriptPath ? loadSignalsSafely2(transcriptPath) : null;
22771
22940
  if (!signals || !hasAnyStruggleSignal(signals)) process.exit(0);
22772
22941
  const related = searchCaveatsFromTextSafely2(struggleSearchText(signals));
22773
- process.stdout.write(`${codexStopOutput(stopReminderText(signals, related))}
22774
- `);
22942
+ if (sessionId) queueStopForSession2(sessionId, signals, related);
22775
22943
  process.exit(0);
22776
22944
  }
22777
22945
  process.stderr.write(`[caveat:codex-hook] unknown hook name: ${name}
@@ -22822,7 +22990,7 @@ async function runPull(ctx) {
22822
22990
 
22823
22991
  // src/commands/codexSidecar.ts
22824
22992
  import { spawnSync as spawnSync4 } from "node:child_process";
22825
- import { mkdirSync as mkdirSync5, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
22993
+ import { mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
22826
22994
  import { tmpdir as tmpdir3 } from "node:os";
22827
22995
  import { dirname as dirname6, join as join12 } from "node:path";
22828
22996
  import { cwd, exit } from "node:process";
@@ -22935,7 +23103,7 @@ function executePlan(logger, command, args, options = {}) {
22935
23103
  }
22936
23104
  function saveStructuredResult(path, stdout) {
22937
23105
  const parsed = JSON.parse(stdout);
22938
- mkdirSync5(dirname6(path), { recursive: true });
23106
+ mkdirSync7(dirname6(path), { recursive: true });
22939
23107
  writeFileSync6(path, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
22940
23108
  }
22941
23109
  function shellDisplayQuote(value) {