caveat-cli 0.19.2 → 0.19.5

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
@@ -10,15 +10,11 @@ import {
10
10
  __commonJS,
11
11
  __export,
12
12
  __toESM,
13
+ acknowledgePendingReminders,
13
14
  acknowledgeRuntimeErrors,
14
15
  acquireReindexLock,
15
16
  buildAndPublishPendingReminder,
16
- buildCodexSidecarDiagnosticsCommand,
17
- buildCodexSidecarReadOnlySmokeCommand,
18
- buildCodexSidecarRunCommand,
19
- buildHookSignalSidecarContextBlock,
20
17
  buildPendingSemanticKey,
21
- caveatEntriesToSidecarContextBlocks,
22
18
  cleanupStalePendingDirs,
23
19
  communityAdd,
24
20
  communityList,
@@ -27,18 +23,20 @@ import {
27
23
  compactRuntimeErrors,
28
24
  computeEntriesDigest,
29
25
  createKeyserverKeyProvider,
30
- decideCodexSidecarExecution,
31
26
  defaultSelfIdentityTokens,
32
- drainPendingRemindersDetailed,
33
27
  ensureUserConfig,
28
+ environmentAppliesToTask,
29
+ extractPromptCandidates,
34
30
  findCaveatHome,
35
31
  findCaveatsForHook,
36
32
  findCaveatsForHookSegments,
33
+ fingerprint,
37
34
  get,
38
35
  hasAnyStruggleSignal,
39
36
  initOwnSync,
40
37
  isPrivateOwnerStat,
41
38
  isWindows,
39
+ isWindowsEnv,
42
40
  listRecent,
43
41
  listStale,
44
42
  loadConfig,
@@ -48,6 +46,7 @@ import {
48
46
  nodeExecutableNames,
49
47
  observeRuntimeError,
50
48
  openDb,
49
+ peekPendingRemindersDetailed,
51
50
  powershellCallPrefix,
52
51
  prewarmSealedKeys,
53
52
  publishOwn,
@@ -61,13 +60,13 @@ import {
61
60
  resetAutoSyncFailureState,
62
61
  resolvePaths,
63
62
  runAutoSync,
63
+ runWindowsAcl,
64
64
  runtimeErrorsDiagnostics,
65
65
  runtimeErrorsSnapshot,
66
66
  search,
67
67
  setRuntimeErrorStatus,
68
68
  stderrLogger,
69
69
  stopReminderText,
70
- struggleSearchText,
71
70
  syncOwn,
72
71
  toolErrorReminderText,
73
72
  triggerAutoSync,
@@ -75,7 +74,7 @@ import {
75
74
  userPromptSubmitReminderText,
76
75
  writeDigestMarker,
77
76
  writeUserConfigPatch
78
- } from "./chunk-NZD3FHU6.js";
77
+ } from "./chunk-EMGYSGFX.js";
79
78
 
80
79
  // ../../node_modules/.pnpm/ajv@8.18.0/node_modules/ajv/dist/compile/codegen/code.js
81
80
  var require_code = __commonJS({
@@ -454,11 +453,11 @@ var require_codegen = __commonJS({
454
453
  const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
455
454
  return `${varKind} ${this.name}${rhs};` + _n;
456
455
  }
457
- optimizeNames(names, constants5) {
456
+ optimizeNames(names, constants4) {
458
457
  if (!names[this.name.str])
459
458
  return;
460
459
  if (this.rhs)
461
- this.rhs = optimizeExpr(this.rhs, names, constants5);
460
+ this.rhs = optimizeExpr(this.rhs, names, constants4);
462
461
  return this;
463
462
  }
464
463
  get names() {
@@ -475,10 +474,10 @@ var require_codegen = __commonJS({
475
474
  render({ _n }) {
476
475
  return `${this.lhs} = ${this.rhs};` + _n;
477
476
  }
478
- optimizeNames(names, constants5) {
477
+ optimizeNames(names, constants4) {
479
478
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
480
479
  return;
481
- this.rhs = optimizeExpr(this.rhs, names, constants5);
480
+ this.rhs = optimizeExpr(this.rhs, names, constants4);
482
481
  return this;
483
482
  }
484
483
  get names() {
@@ -539,8 +538,8 @@ var require_codegen = __commonJS({
539
538
  optimizeNodes() {
540
539
  return `${this.code}` ? this : void 0;
541
540
  }
542
- optimizeNames(names, constants5) {
543
- this.code = optimizeExpr(this.code, names, constants5);
541
+ optimizeNames(names, constants4) {
542
+ this.code = optimizeExpr(this.code, names, constants4);
544
543
  return this;
545
544
  }
546
545
  get names() {
@@ -569,12 +568,12 @@ var require_codegen = __commonJS({
569
568
  }
570
569
  return nodes.length > 0 ? this : void 0;
571
570
  }
572
- optimizeNames(names, constants5) {
571
+ optimizeNames(names, constants4) {
573
572
  const { nodes } = this;
574
573
  let i = nodes.length;
575
574
  while (i--) {
576
575
  const n = nodes[i];
577
- if (n.optimizeNames(names, constants5))
576
+ if (n.optimizeNames(names, constants4))
578
577
  continue;
579
578
  subtractNames(names, n.names);
580
579
  nodes.splice(i, 1);
@@ -627,12 +626,12 @@ var require_codegen = __commonJS({
627
626
  return void 0;
628
627
  return this;
629
628
  }
630
- optimizeNames(names, constants5) {
629
+ optimizeNames(names, constants4) {
631
630
  var _a3;
632
- this.else = (_a3 = this.else) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants5);
633
- if (!(super.optimizeNames(names, constants5) || this.else))
631
+ this.else = (_a3 = this.else) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants4);
632
+ if (!(super.optimizeNames(names, constants4) || this.else))
634
633
  return;
635
- this.condition = optimizeExpr(this.condition, names, constants5);
634
+ this.condition = optimizeExpr(this.condition, names, constants4);
636
635
  return this;
637
636
  }
638
637
  get names() {
@@ -655,10 +654,10 @@ var require_codegen = __commonJS({
655
654
  render(opts) {
656
655
  return `for(${this.iteration})` + super.render(opts);
657
656
  }
658
- optimizeNames(names, constants5) {
659
- if (!super.optimizeNames(names, constants5))
657
+ optimizeNames(names, constants4) {
658
+ if (!super.optimizeNames(names, constants4))
660
659
  return;
661
- this.iteration = optimizeExpr(this.iteration, names, constants5);
660
+ this.iteration = optimizeExpr(this.iteration, names, constants4);
662
661
  return this;
663
662
  }
664
663
  get names() {
@@ -694,10 +693,10 @@ var require_codegen = __commonJS({
694
693
  render(opts) {
695
694
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
696
695
  }
697
- optimizeNames(names, constants5) {
698
- if (!super.optimizeNames(names, constants5))
696
+ optimizeNames(names, constants4) {
697
+ if (!super.optimizeNames(names, constants4))
699
698
  return;
700
- this.iterable = optimizeExpr(this.iterable, names, constants5);
699
+ this.iterable = optimizeExpr(this.iterable, names, constants4);
701
700
  return this;
702
701
  }
703
702
  get names() {
@@ -739,11 +738,11 @@ var require_codegen = __commonJS({
739
738
  (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNodes();
740
739
  return this;
741
740
  }
742
- optimizeNames(names, constants5) {
741
+ optimizeNames(names, constants4) {
743
742
  var _a3, _b;
744
- super.optimizeNames(names, constants5);
745
- (_a3 = this.catch) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants5);
746
- (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants5);
743
+ super.optimizeNames(names, constants4);
744
+ (_a3 = this.catch) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants4);
745
+ (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants4);
747
746
  return this;
748
747
  }
749
748
  get names() {
@@ -1044,7 +1043,7 @@ var require_codegen = __commonJS({
1044
1043
  function addExprNames(names, from) {
1045
1044
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
1046
1045
  }
1047
- function optimizeExpr(expr, names, constants5) {
1046
+ function optimizeExpr(expr, names, constants4) {
1048
1047
  if (expr instanceof code_1.Name)
1049
1048
  return replaceName(expr);
1050
1049
  if (!canOptimize(expr))
@@ -1059,14 +1058,14 @@ var require_codegen = __commonJS({
1059
1058
  return items;
1060
1059
  }, []));
1061
1060
  function replaceName(n) {
1062
- const c = constants5[n.str];
1061
+ const c = constants4[n.str];
1063
1062
  if (c === void 0 || names[n.str] !== 1)
1064
1063
  return n;
1065
1064
  delete names[n.str];
1066
1065
  return c;
1067
1066
  }
1068
1067
  function canOptimize(e) {
1069
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants5[c.str] !== void 0);
1068
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants4[c.str] !== void 0);
1070
1069
  }
1071
1070
  }
1072
1071
  function subtractNames(names, from) {
@@ -6887,9 +6886,9 @@ var stdoutLogger = {
6887
6886
  };
6888
6887
 
6889
6888
  // src/commands/init.ts
6890
- import { spawnSync as spawnSync3 } from "node:child_process";
6891
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync, renameSync, rmdirSync, writeFileSync as writeFileSync3 } from "node:fs";
6892
- import { dirname as dirname3, join as join10 } from "node:path";
6889
+ import { spawnSync as spawnSync2 } from "node:child_process";
6890
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readdirSync, renameSync, rmdirSync, writeFileSync as writeFileSync2 } from "node:fs";
6891
+ import { dirname as dirname3, join as join8 } from "node:path";
6893
6892
 
6894
6893
  // src/claudeInstall.ts
6895
6894
  import { constants, existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
@@ -7796,148 +7795,6 @@ function resolveHookNodePath({
7796
7795
  return execPath;
7797
7796
  }
7798
7797
 
7799
- // src/commands/publish.ts
7800
- import { existsSync as existsSync7 } from "node:fs";
7801
- import { join as join9 } from "node:path";
7802
-
7803
- // src/commands/codexSidecarAdvisory.ts
7804
- import { spawnSync as spawnSync2 } from "node:child_process";
7805
- import { mkdtempSync, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
7806
- import { tmpdir } from "node:os";
7807
- import { join as join8 } from "node:path";
7808
- var DEFAULT_HOOK_SIDECAR_TIMEOUT_MS = 12e4;
7809
- var MAX_HOOK_SIDECAR_TIMEOUT_MS = 24e4;
7810
- function resolveHookSidecarTimeoutMs(raw) {
7811
- if (raw === void 0) return DEFAULT_HOOK_SIDECAR_TIMEOUT_MS;
7812
- const value = Number(raw);
7813
- if (!Number.isSafeInteger(value) || value < 1 || value > MAX_HOOK_SIDECAR_TIMEOUT_MS) {
7814
- throw new Error(`CAVEAT_HOOK_CODEX_SIDECAR_TIMEOUT_MS must be an integer from 1 to ${MAX_HOOK_SIDECAR_TIMEOUT_MS}`);
7815
- }
7816
- return value;
7817
- }
7818
- function compactFailureMessage(message) {
7819
- const singleLine = message.replace(/\s+/g, " ").trim();
7820
- if (!singleLine) return "unknown error";
7821
- return singleLine.length > 220 ? `${singleLine.slice(0, 220)}...` : singleLine;
7822
- }
7823
- function formatCodexSidecarAdvisoryUnavailable(error51) {
7824
- const message = error51 instanceof Error ? error51.message : String(error51);
7825
- return `[caveat:codex-sidecar] advisory unavailable: ${compactFailureMessage(message)}`;
7826
- }
7827
- function sidecarFailureDetail(output) {
7828
- const protocol = output.match(/PROTOCOL_ERROR:[^"\r\n]+/);
7829
- if (protocol) return compactFailureMessage(protocol[0]);
7830
- const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
7831
- const diagnostic = lines.find(
7832
- (line) => !line.startsWith("[caveat] [codex-sidecar] codex-sidecar ")
7833
- );
7834
- return compactFailureMessage(diagnostic ?? output);
7835
- }
7836
- function runCodexSidecarAdvisory(input) {
7837
- const cliScript = process.argv[1];
7838
- if (!cliScript) {
7839
- return { status: "failed", message: "current Caveat CLI script path is unavailable" };
7840
- }
7841
- const args = [
7842
- ...process.execArgv,
7843
- "--disable-warning=ExperimentalWarning",
7844
- cliScript,
7845
- "codex-sidecar",
7846
- "run",
7847
- "explore",
7848
- input.prompt,
7849
- "--preset",
7850
- "advisory",
7851
- "--query",
7852
- input.searchText,
7853
- "--limit",
7854
- String(Math.max(1, input.limit)),
7855
- "--host-agent",
7856
- "claude",
7857
- "--availability",
7858
- "operational"
7859
- ];
7860
- let resultDir;
7861
- let advisory;
7862
- try {
7863
- resultDir = mkdtempSync(join8(tmpdir(), "caveat-codex-advisory-"));
7864
- const resultFile = join8(resultDir, "result.json");
7865
- args.push("--save-result", resultFile);
7866
- if (input.additionalContext) {
7867
- const additionalContextFile = join8(resultDir, "hook-signal.json");
7868
- writeFileSync2(additionalContextFile, JSON.stringify({ context: [input.additionalContext] }) + "\n", { encoding: "utf-8", mode: 384, flag: "wx" });
7869
- args.push("--additional-context-file", additionalContextFile);
7870
- }
7871
- const nodeCli = process.env.CAVEAT_CODEX_SIDECAR_NODE_CLI;
7872
- if (nodeCli) {
7873
- args.push("--node-cli", nodeCli);
7874
- } else {
7875
- const command = process.env.CAVEAT_CODEX_SIDECAR_COMMAND;
7876
- if (command) args.push("--command", command);
7877
- }
7878
- const result = spawnSync2(process.execPath, args, {
7879
- cwd: input.projectRoot,
7880
- encoding: "utf-8",
7881
- // Keep at least one minute between the longest supported model call and
7882
- // the pending single-flight claim TTL (five minutes).
7883
- timeout: resolveHookSidecarTimeoutMs(process.env.CAVEAT_HOOK_CODEX_SIDECAR_TIMEOUT_MS),
7884
- maxBuffer: 10 * 1024 * 1024
7885
- });
7886
- if (result.error) {
7887
- advisory = { status: "failed", message: compactFailureMessage(result.error.message) };
7888
- } else if (result.status !== 0) {
7889
- const detail = (result.stderr || result.stdout || `exit ${result.status}`).trim();
7890
- advisory = {
7891
- status: "failed",
7892
- message: `sidecar command failed: ${sidecarFailureDetail(detail)}`
7893
- };
7894
- } else {
7895
- const parsed = JSON.parse(readFileSync6(resultFile, "utf-8"));
7896
- if (parsed.status !== "ok" || typeof parsed.summary !== "string") {
7897
- advisory = {
7898
- status: "failed",
7899
- message: compactFailureMessage(`unexpected SidecarResult status: ${String(parsed.status)}`)
7900
- };
7901
- } else {
7902
- advisory = {
7903
- status: "ok",
7904
- summary: parsed.summary,
7905
- ...typeof parsed.rawEventLogRef === "string" ? { rawEventLogRef: parsed.rawEventLogRef } : {}
7906
- };
7907
- }
7908
- }
7909
- } catch (err) {
7910
- const msg = err instanceof Error ? err.message : String(err);
7911
- advisory = {
7912
- status: "failed",
7913
- message: compactFailureMessage(`invalid SidecarResult JSON: ${msg}`)
7914
- };
7915
- } finally {
7916
- if (resultDir) {
7917
- try {
7918
- rmSync(resultDir, { recursive: true });
7919
- } catch (err) {
7920
- const msg = err instanceof Error ? err.message : String(err);
7921
- advisory = {
7922
- status: "failed",
7923
- message: compactFailureMessage(`sidecar advisory cleanup failed: ${msg}`)
7924
- };
7925
- }
7926
- }
7927
- }
7928
- return advisory;
7929
- }
7930
- function formatCodexSidecarAdvisory(advisory) {
7931
- if (advisory.status === "ok") {
7932
- return [
7933
- "[caveat:codex-sidecar] Codex advisory:",
7934
- advisory.summary,
7935
- advisory.rawEventLogRef ? `rawEventLogRef: ${advisory.rawEventLogRef}` : ""
7936
- ].filter(Boolean).join("\n");
7937
- }
7938
- return `[caveat:codex-sidecar] advisory unavailable: ${advisory.message}`;
7939
- }
7940
-
7941
7798
  // src/commands/publish.ts
7942
7799
  var HTTPS_TARGET_RE = /^https:\/\/github\.com\/[^/]+\/[^/]+?(?:\.git)?\/?$/;
7943
7800
  var SCP_TARGET_RE = /^git@github\.com:[^/]+\/[^/]+?(?:\.git)?$/;
@@ -7959,20 +7816,6 @@ async function runPublish(ctx, opts, dependencies = {}) {
7959
7816
  const ghRunner = dependencies.ghRunner ?? runGh;
7960
7817
  const isTty = dependencies.isTty ?? (() => Boolean(process.stdin.isTTY));
7961
7818
  const confirm = dependencies.confirm ?? askOnce;
7962
- const projectRoot = process.cwd();
7963
- const hasCodexSidecarConfig = dependencies.hasCodexSidecarConfig ?? ((root) => existsSync7(join9(root, ".codex-sidecar.yml")));
7964
- const publishAdvisory = hasCodexSidecarConfig(projectRoot) ? (changes) => {
7965
- try {
7966
- return dependencies.runCodexSidecarAdvisory?.(changes, projectRoot) ?? formatCodexSidecarAdvisory(runCodexSidecarAdvisory({
7967
- searchText: changes.lines.join("\n"),
7968
- limit: Math.max(changes.lines.length, 1),
7969
- projectRoot,
7970
- prompt: "A public Caveat publish is pending. Use Caveat context to give concise advice about privacy or secret risk. Advisory only: do not authorize or block this publish."
7971
- }));
7972
- } catch (err) {
7973
- return formatCodexSidecarAdvisoryUnavailable(err);
7974
- }
7975
- } : void 0;
7976
7819
  let target = ctx.config.publishTarget;
7977
7820
  if (opts.init !== void 0) {
7978
7821
  target = validatePublishTarget(opts.init);
@@ -7991,8 +7834,7 @@ async function runPublish(ctx, opts, dependencies = {}) {
7991
7834
  dryRun: opts.dryRun,
7992
7835
  yes: opts.yes,
7993
7836
  allow: opts.allow,
7994
- saveAllow: opts.save,
7995
- advisory: publishAdvisory
7837
+ saveAllow: opts.save
7996
7838
  });
7997
7839
  if (result.dryRun) ctx.logger.info(`[dry-run] ${result.fileCount} public entry(ies) ready to publish`);
7998
7840
  else if (result.changed) ctx.logger.info(`published ${result.fileCount} public entry(ies)`);
@@ -8023,7 +7865,7 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }, depende
8023
7865
  let cursorHookState = "not-installed";
8024
7866
  if (!opts.dryRun) ensureUserConfig(ctx.userConfigPath);
8025
7867
  ctx.logger.info(`user config: ${ctx.userConfigPath}`);
8026
- if (!existsSync8(ctx.paths.knowledgeRepo)) {
7868
+ if (!existsSync7(ctx.paths.knowledgeRepo)) {
8027
7869
  if (!opts.dryRun) {
8028
7870
  mkdirSync3(ctx.paths.knowledgeRepo, { recursive: true });
8029
7871
  mkdirSync3(ctx.paths.entriesDir, { recursive: true });
@@ -8033,14 +7875,14 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }, depende
8033
7875
  ctx.logger.info(`knowledge repo: ${ctx.paths.knowledgeRepo}`);
8034
7876
  }
8035
7877
  if (!opts.dryRun) migrateLegacyCommunityDir(ctx);
8036
- const gitignorePath = join10(ctx.paths.knowledgeRepo, ".gitignore");
8037
- if (!existsSync8(gitignorePath)) {
8038
- if (!opts.dryRun) writeFileSync3(gitignorePath, KNOWLEDGE_GITIGNORE, "utf-8");
7878
+ const gitignorePath = join8(ctx.paths.knowledgeRepo, ".gitignore");
7879
+ if (!existsSync7(gitignorePath)) {
7880
+ if (!opts.dryRun) writeFileSync2(gitignorePath, KNOWLEDGE_GITIGNORE, "utf-8");
8039
7881
  ctx.logger.info(`${opts.dryRun ? "[dry-run] would create .gitignore" : ".gitignore created"}: ${gitignorePath}`);
8040
7882
  }
8041
7883
  if (!opts.dryRun) {
8042
7884
  const dbDir = dirname3(ctx.paths.dbPath);
8043
- if (!existsSync8(dbDir)) mkdirSync3(dbDir, { recursive: true });
7885
+ if (!existsSync7(dbDir)) mkdirSync3(dbDir, { recursive: true });
8044
7886
  const keyProvider = createKeyserverKeyProvider({ caveatHome: ctx.caveatHome });
8045
7887
  const failures = await prewarmSealedKeys({ paths: ctx.paths, keyProvider });
8046
7888
  for (const failure of failures) {
@@ -8083,8 +7925,8 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }, depende
8083
7925
  const target = typeof opts.sync === "string" ? opts.sync : "<user>/Caveat-Private";
8084
7926
  ctx.logger.info(`[dry-run] would initialize private sync: ${target}`);
8085
7927
  } else {
8086
- const ownGit = join10(ctx.paths.knowledgeRepo, ".git");
8087
- if (existsSync8(ownGit)) {
7928
+ const ownGit = join8(ctx.paths.knowledgeRepo, ".git");
7929
+ if (existsSync7(ownGit)) {
8088
7930
  const remote = gitOutput(["-C", ctx.paths.knowledgeRepo, "remote", "get-url", "origin"]);
8089
7931
  const result = await syncOwn({
8090
7932
  ownDir: ctx.paths.knowledgeRepo,
@@ -8196,7 +8038,7 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }, depende
8196
8038
  if (result.feature === "blocked") {
8197
8039
  codexHookState = "skipped";
8198
8040
  ctx.logger.warn(
8199
- `${result.blockedReason}; preserving explicit consent. Set \`hooks = true\` in ${join10(codexHome, "config.toml")}, then rerun \`caveat init\`.`
8041
+ `${result.blockedReason}; preserving explicit consent. Set \`hooks = true\` in ${join8(codexHome, "config.toml")}, then rerun \`caveat init\`.`
8200
8042
  );
8201
8043
  } else if (opts.dryRun) {
8202
8044
  codexHookState = "skipped";
@@ -8209,7 +8051,7 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }, depende
8209
8051
  if (opts.skipCursorHook) {
8210
8052
  cursorHookState = "skipped";
8211
8053
  ctx.logger.info("Cursor hook integration skipped (--skip-cursor-hook)");
8212
- } else if (existsSync8(cursorDir)) {
8054
+ } else if (existsSync7(cursorDir)) {
8213
8055
  const cliScriptPath2 = process.argv[1];
8214
8056
  if (!cliScriptPath2) {
8215
8057
  cursorHookState = "skipped";
@@ -8236,9 +8078,9 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }, depende
8236
8078
  const cliScriptPath = process.argv[1];
8237
8079
  if (!cliScriptPath) throw new Error("mcp_setup_failed: CLI\u306E\u5B9F\u884C\u30D1\u30B9\u3092\u53D6\u5F97\u3067\u304D\u307E\u305B\u3093");
8238
8080
  for (const [client, configPath, available] of [
8239
- ["codex", join10(codexHome, "config.toml"), codexAvailable || existsSync8(codexHome)],
8240
- ["grok", join10(grokHome, "config.toml"), existsSync8(grokHome)],
8241
- ["cursor", join10(cursorDir, "mcp.json"), existsSync8(cursorDir)]
8081
+ ["codex", join8(codexHome, "config.toml"), codexAvailable || existsSync7(codexHome)],
8082
+ ["grok", join8(grokHome, "config.toml"), existsSync7(grokHome)],
8083
+ ["cursor", join8(cursorDir, "mcp.json"), existsSync7(cursorDir)]
8242
8084
  ]) {
8243
8085
  if (!available) continue;
8244
8086
  const action = installMcpClient({ client, configPath, cliScriptPath, nodePath: resolveHookNodePath(), dryRun: opts.dryRun });
@@ -8247,7 +8089,7 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }, depende
8247
8089
  reportEnvironmentSummary(ctx, publishTarget, codexHookState, cursorHookState, opts.dryRun);
8248
8090
  }
8249
8091
  function detectCodexAvailability() {
8250
- const result = spawnSync3("codex --version", {
8092
+ const result = spawnSync2("codex --version", {
8251
8093
  shell: true,
8252
8094
  encoding: "utf-8",
8253
8095
  stdio: "ignore"
@@ -8262,14 +8104,14 @@ function isFileRemote(url2) {
8262
8104
  }
8263
8105
  }
8264
8106
  function gitOutput(args) {
8265
- const result = spawnSync3("git", args, { encoding: "utf-8" });
8107
+ const result = spawnSync2("git", args, { encoding: "utf-8" });
8266
8108
  return result.status === 0 && typeof result.stdout === "string" ? result.stdout.trim() : null;
8267
8109
  }
8268
8110
  function reportEnvironmentSummary(ctx, publishTarget, codexHookState, cursorHookState, dryRun) {
8269
8111
  const prefix = dryRun ? "[dry-run] would have " : "";
8270
8112
  const isRepo = gitOutput(["-C", ctx.paths.knowledgeRepo, "rev-parse", "--is-inside-work-tree"]) === "true";
8271
8113
  const remote = isRepo ? gitOutput(["-C", ctx.paths.knowledgeRepo, "config", "--get", "remote.origin.url"]) : null;
8272
- const communityCount = existsSync8(ctx.paths.communityDir) ? readdirSync(ctx.paths.communityDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).length : 0;
8114
+ const communityCount = existsSync7(ctx.paths.communityDir) ? readdirSync(ctx.paths.communityDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).length : 0;
8273
8115
  ctx.logger.info(`${prefix}environment summary:`);
8274
8116
  ctx.logger.info(` own git: ${isRepo ? "repository" : "not initialized"}`);
8275
8117
  ctx.logger.info(` private remote: ${remote || "not configured"}`);
@@ -8282,11 +8124,11 @@ function errorMessage(err) {
8282
8124
  return err instanceof Error ? err.message : String(err);
8283
8125
  }
8284
8126
  function migrateLegacyCommunityDir(ctx) {
8285
- const legacy = join10(ctx.paths.knowledgeRepo, "community");
8127
+ const legacy = join8(ctx.paths.knowledgeRepo, "community");
8286
8128
  const current = ctx.paths.communityDir;
8287
8129
  if (legacy === current) return;
8288
- if (!existsSync8(legacy)) return;
8289
- if (existsSync8(current)) {
8130
+ if (!existsSync7(legacy)) return;
8131
+ if (existsSync7(current)) {
8290
8132
  ctx.logger.warn(
8291
8133
  `legacy community dir still exists at ${legacy} \u2014 remove manually (new location in use)`
8292
8134
  );
@@ -8295,7 +8137,7 @@ function migrateLegacyCommunityDir(ctx) {
8295
8137
  mkdirSync3(current, { recursive: true });
8296
8138
  for (const entry of readdirSync(legacy, { withFileTypes: true })) {
8297
8139
  if (!entry.isDirectory()) continue;
8298
- renameSync(join10(legacy, entry.name), join10(current, entry.name));
8140
+ renameSync(join8(legacy, entry.name), join8(current, entry.name));
8299
8141
  }
8300
8142
  try {
8301
8143
  rmdirSync(legacy);
@@ -8360,11 +8202,11 @@ function reportInstallResult(ctx, result, dryRun) {
8360
8202
  }
8361
8203
 
8362
8204
  // src/commands/indexCmd.ts
8363
- import { existsSync as existsSync9, mkdirSync as mkdirSync4 } from "node:fs";
8205
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4 } from "node:fs";
8364
8206
  import { dirname as dirname4 } from "node:path";
8365
8207
  async function runIndex(ctx, opts) {
8366
8208
  const dbDir = dirname4(ctx.paths.dbPath);
8367
- if (!existsSync9(dbDir)) mkdirSync4(dbDir, { recursive: true });
8209
+ if (!existsSync8(dbDir)) mkdirSync4(dbDir, { recursive: true });
8368
8210
  const keyProvider = createKeyserverKeyProvider({ caveatHome: ctx.caveatHome });
8369
8211
  const failures = await prewarmSealedKeys({ paths: ctx.paths, keyProvider });
8370
8212
  for (const failure of failures) {
@@ -8548,7 +8390,7 @@ function runStats(ctx) {
8548
8390
 
8549
8391
  // src/commands/serve.ts
8550
8392
  async function runServe(opts) {
8551
- const { startServer } = await import("./server-VOEJV4WH.js");
8393
+ const { startServer } = await import("./server-RSDQNLWE.js");
8552
8394
  const { port, host } = startServer({ port: opts.port });
8553
8395
  process.stdout.write(`[caveat] web portal: http://${host}:${port}/
8554
8396
  `);
@@ -32620,7 +32462,7 @@ var StdioServerTransport = class {
32620
32462
 
32621
32463
  // ../mcp/dist/context.js
32622
32464
  import { homedir as homedir3 } from "node:os";
32623
- import { join as join11 } from "node:path";
32465
+ import { join as join9 } from "node:path";
32624
32466
 
32625
32467
  // ../mcp/dist/autoSyncTrigger.js
32626
32468
  function triggerAutoSyncAfterWrite(caveatHome, logger) {
@@ -32639,7 +32481,7 @@ function triggerAutoSyncAfterWrite(caveatHome, logger) {
32639
32481
  function buildMcpContext(overrides = {}) {
32640
32482
  const userHome = overrides.userHome ?? homedir3();
32641
32483
  const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
32642
- const userConfigPath = join11(userHome, ".caveatrc.json");
32484
+ const userConfigPath = join9(userHome, ".caveatrc.json");
32643
32485
  const logger = overrides.logger ?? stderrLogger;
32644
32486
  const config2 = loadConfig(userConfigPath);
32645
32487
  const paths = resolvePaths(caveatHome, config2.knowledgeRepo, userHome);
@@ -32788,12 +32630,12 @@ function handleListRecent(ctx, args) {
32788
32630
  }
32789
32631
 
32790
32632
  // ../mcp/dist/tools/pull.js
32791
- import { existsSync as existsSync10 } from "node:fs";
32633
+ import { existsSync as existsSync9 } from "node:fs";
32792
32634
  var pullInputShape = {};
32793
32635
  async function handlePull(ctx, _args = {}) {
32794
32636
  const pulled = [];
32795
32637
  const indexed = [];
32796
- if (existsSync10(ctx.paths.communityDir)) {
32638
+ if (existsSync9(ctx.paths.communityDir)) {
32797
32639
  const results = await communityPull({
32798
32640
  communityDir: ctx.paths.communityDir,
32799
32641
  logger: ctx.logger
@@ -32949,28 +32791,28 @@ async function runMcpServer() {
32949
32791
  import { spawn as spawn2 } from "node:child_process";
32950
32792
  import {
32951
32793
  chmodSync,
32952
- existsSync as existsSync13,
32953
- lstatSync,
32954
- mkdtempSync as mkdtempSync2,
32955
- mkdirSync as mkdirSync6,
32794
+ existsSync as existsSync15,
32795
+ lstatSync as lstatSync2,
32796
+ mkdtempSync,
32797
+ mkdirSync as mkdirSync9,
32956
32798
  readdirSync as readdirSync2,
32957
- readFileSync as readFileSync8,
32799
+ readFileSync as readFileSync10,
32958
32800
  realpathSync as realpathSync3,
32959
- rmSync as rmSync2,
32960
- writeFileSync as writeFileSync5
32801
+ rmSync as rmSync4,
32802
+ writeFileSync as writeFileSync7
32961
32803
  } from "node:fs";
32962
- import { tmpdir as tmpdir2 } from "node:os";
32963
- import { basename, dirname as dirname5, join as join14 } from "node:path";
32964
- import { randomBytes } from "node:crypto";
32804
+ import { tmpdir } from "node:os";
32805
+ import { basename, dirname as dirname5, join as join15 } from "node:path";
32806
+ import { randomBytes as randomBytes4 } from "node:crypto";
32965
32807
 
32966
32808
  // src/autoReindexTrigger.ts
32967
32809
  import { spawn } from "node:child_process";
32968
- import { existsSync as existsSync11 } from "node:fs";
32969
- import { join as join12 } from "node:path";
32810
+ import { existsSync as existsSync10 } from "node:fs";
32811
+ import { join as join10 } from "node:path";
32970
32812
  function maybeTriggerAutoReindex(ctx) {
32971
32813
  if (process.env.CAVEAT_INDEX_AUTOSYNC === "off") return;
32972
- if (!existsSync11(ctx.paths.dbPath)) return;
32973
- if (existsSync11(join12(ctx.caveatHome, "index", ".reindex-lock"))) return;
32814
+ if (!existsSync10(ctx.paths.dbPath)) return;
32815
+ if (existsSync10(join10(ctx.caveatHome, "index", ".reindex-lock"))) return;
32974
32816
  const current = computeEntriesDigest(ctx.paths);
32975
32817
  const marker = readDigestMarker(ctx.caveatHome);
32976
32818
  if (marker?.digest === current.digest && marker.fileCount === current.fileCount) return;
@@ -32995,11 +32837,69 @@ function maybeTriggerAutoSync(ctx, debounceMs = AUTO_SYNC_DEBOUNCE_MS) {
32995
32837
  });
32996
32838
  }
32997
32839
 
32998
- // src/hookShared.ts
32999
- import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
32840
+ // src/jevStruggle.ts
32841
+ import { spawnSync as spawnSync3 } from "node:child_process";
32842
+ import { createHash as createHash3, randomBytes as randomBytes3 } from "node:crypto";
32843
+ import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as readFileSync9, renameSync as renameSync4, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
32844
+ import { join as join14 } from "node:path";
32845
+
32846
+ // src/jevCredential.ts
32847
+ import { randomBytes } from "node:crypto";
32848
+ import { existsSync as existsSync11, lstatSync, mkdirSync as mkdirSync5, readFileSync as readFileSync6, renameSync as renameSync2, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
32849
+ import { join as join11 } from "node:path";
32850
+ var KEY_FILE = "typesafe.key";
32851
+ function jevKeyPath(caveatHome) {
32852
+ return join11(caveatHome, "credentials", KEY_FILE);
32853
+ }
32854
+ function assertPrivate(path, directory) {
32855
+ const info = lstatSync(path);
32856
+ if (info.isSymbolicLink() || (directory ? !info.isDirectory() : !info.isFile())) throw new Error("jev_credential_unsafe");
32857
+ if (isWindowsEnv(process.env)) runWindowsAcl(path, directory, false);
32858
+ else if (!isPrivateOwnerStat(info, typeof process.getuid === "function" ? process.getuid() : void 0)) {
32859
+ throw new Error("jev_credential_unsafe");
32860
+ }
32861
+ }
32862
+ function readJevKey(caveatHome) {
32863
+ const directory = join11(caveatHome, "credentials");
32864
+ const path = jevKeyPath(caveatHome);
32865
+ assertPrivate(directory, true);
32866
+ assertPrivate(path, false);
32867
+ const key = readFileSync6(path, "utf8").trim();
32868
+ if (!key || /[\r\n]/u.test(key)) throw new Error("jev_credential_invalid");
32869
+ return key;
32870
+ }
32871
+ function writeJevKey(caveatHome, value) {
32872
+ const key = value.trim();
32873
+ if (!key || /[\r\n]/u.test(key)) throw new Error("jev_credential_invalid");
32874
+ const directory = join11(caveatHome, "credentials");
32875
+ const created = !existsSync11(directory);
32876
+ mkdirSync5(directory, { recursive: true, mode: 448 });
32877
+ if (created && isWindowsEnv(process.env)) runWindowsAcl(directory, true, true);
32878
+ assertPrivate(directory, true);
32879
+ const path = jevKeyPath(caveatHome);
32880
+ if (existsSync11(path)) assertPrivate(path, false);
32881
+ const temporary = join11(directory, `.typesafe-${randomBytes(6).toString("hex")}.tmp`);
32882
+ try {
32883
+ writeFileSync3(temporary, `${key}
32884
+ `, { encoding: "utf8", mode: 384, flag: "wx" });
32885
+ if (isWindowsEnv(process.env)) runWindowsAcl(temporary, false, true);
32886
+ assertPrivate(temporary, false);
32887
+ renameSync2(temporary, path);
32888
+ assertPrivate(path, false);
32889
+ } finally {
32890
+ rmSync(temporary, { force: true });
32891
+ }
32892
+ }
32893
+
32894
+ // src/hookDelivery.ts
32895
+ import { createHash as createHash2, randomBytes as randomBytes2 } from "node:crypto";
32896
+ import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
33000
32897
  import { join as join13 } from "node:path";
32898
+
32899
+ // src/hookShared.ts
32900
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
32901
+ import { join as join12 } from "node:path";
33001
32902
  import { createHash } from "node:crypto";
33002
- var MAX_CONTEXT_BLOCKS = 3;
33003
32903
  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:";
33004
32904
  function hookSilentLogger(host) {
33005
32905
  return {
@@ -33058,7 +32958,10 @@ function searchCaveatsSafely(host, input) {
33058
32958
  const searchOptions = {
33059
32959
  selfIdentity: defaultSelfIdentityTokens()
33060
32960
  };
33061
- hits = inputs.length === 1 ? findCaveatsForHook(db, inputs[0], searchOptions) : findCaveatsForHookSegments(db, inputs, searchOptions);
32961
+ const found = inputs.length === 1 ? findCaveatsForHook(db, inputs[0], searchOptions) : findCaveatsForHookSegments(db, inputs, searchOptions);
32962
+ const taskText = inputs.map(({ topicText, failureText }) => `${topicText}
32963
+ ${failureText}`).join("\n");
32964
+ hits = found.filter((hit) => environmentAppliesToTask(hit.environment, taskText, fingerprint().os));
33062
32965
  } catch (err) {
33063
32966
  reportHookError(host, "search error", err);
33064
32967
  return [];
@@ -33085,41 +32988,39 @@ function searchCaveatsSafely(host, input) {
33085
32988
  function pendingCleanupFailureText(host) {
33086
32989
  return `[${host.stderrTag}] pending reminder cleanup failed`;
33087
32990
  }
33088
- function drainForSession(host, sessionId) {
32991
+ function peekForSession(host, sessionId) {
33089
32992
  const ctx = buildContextSafely(host);
33090
- if (!ctx) return [];
33091
- const local = drainPendingRemindersDetailed(ctx.caveatHome, sessionId);
33092
- const global = drainPendingRemindersDetailed(ctx.caveatHome, "_global");
33093
- for (const _failure of [...local.cleanupFailures, ...global.cleanupFailures]) {
32993
+ if (!ctx) return { contexts: [], paths: [], caveatHome: null };
32994
+ const local = peekPendingRemindersDetailed(ctx.caveatHome, sessionId);
32995
+ const global = peekPendingRemindersDetailed(ctx.caveatHome, "_global");
32996
+ for (const _failure of [...local.readFailures, ...global.readFailures]) {
33094
32997
  process.stderr.write(`${pendingCleanupFailureText(host)}
33095
32998
  `);
33096
32999
  }
33097
- return [...local.reminders, ...global.reminders];
33000
+ const reminders = [...local.reminders, ...global.reminders];
33001
+ return { contexts: reminders.map(({ text }) => text), paths: reminders.map(({ path }) => path), caveatHome: ctx.caveatHome };
33098
33002
  }
33099
- function contextDedupeKey(host, text) {
33100
- if (text.startsWith(STOP_REMINDER_PREFIX)) return host.stopDedupeKey;
33101
- return text.trim();
33003
+ function acknowledgeSessionReminders(host, peeked) {
33004
+ if (!peeked.caveatHome || peeked.paths.length === 0) return;
33005
+ try {
33006
+ acknowledgePendingReminders(peeked.caveatHome, peeked.paths);
33007
+ } catch (error51) {
33008
+ process.stderr.write(`[${host.stderrTag}] pending reminder cleanup failed: ${errorMessage4(error51)}
33009
+ `);
33010
+ }
33102
33011
  }
33103
- function compactContexts(host, contexts) {
33104
- const selected = [];
33105
- const seen = /* @__PURE__ */ new Set();
33106
- for (let i = contexts.length - 1; i >= 0; i -= 1) {
33107
- const text = contexts[i]?.trim();
33108
- if (!text) continue;
33109
- const key = contextDedupeKey(host, text);
33110
- if (seen.has(key)) continue;
33111
- seen.add(key);
33112
- selected.push(text);
33113
- }
33114
- selected.reverse();
33115
- const limited = selected.slice(-MAX_CONTEXT_BLOCKS);
33116
- const omitted = selected.length - limited.length;
33117
- if (omitted > 0) {
33118
- limited.push(
33119
- `[caveat] pending reminder ${omitted} \u4EF6\u3092\u91CD\u8907\u307E\u305F\u306F\u4E0A\u9650\u306B\u3088\u308A\u7701\u7565\u3057\u307E\u3057\u305F\u3002`
33120
- );
33012
+ async function emitHookContext(host, output) {
33013
+ try {
33014
+ await new Promise((resolve, reject) => process.stdout.write(
33015
+ output,
33016
+ (error51) => error51 ? reject(error51) : resolve()
33017
+ ));
33018
+ return true;
33019
+ } catch (error51) {
33020
+ process.stderr.write(`[${host.stderrTag}] stdout write failed: ${errorMessage4(error51)}
33021
+ `);
33022
+ return false;
33121
33023
  }
33122
- return limited;
33123
33024
  }
33124
33025
  function sanitizeStateId(raw) {
33125
33026
  const clean = raw.replace(/[^A-Za-z0-9_-]/g, "");
@@ -33127,18 +33028,19 @@ function sanitizeStateId(raw) {
33127
33028
  }
33128
33029
  function stopSignalKey(signals, related) {
33129
33030
  const body = JSON.stringify({
33130
- toolFailureCount: signals.toolFailureCount,
33131
- fileEditCounts: signals.fileEditCounts.map((e) => [e.path, e.count]),
33132
- webSearchCount: signals.webSearchCount,
33133
- webFetchCount: signals.webFetchCount,
33134
- bashRetryCount: signals.bashRetryCount,
33135
- searchQueries: signals.searchQueries,
33136
- related: related.map((h) => [h.source, h.id])
33031
+ signalKinds: [
33032
+ signals.toolFailureCount > 0 && "tool_failure",
33033
+ signals.fileEditCounts.length > 0 && "reedit",
33034
+ signals.webSearchCount > 0 && "web_search",
33035
+ signals.webFetchCount > 0 && "web_fetch",
33036
+ signals.bashRetryCount > 0 && "bash_retry"
33037
+ ].filter(Boolean),
33038
+ related: related.map((h) => [h.source, h.id]).sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)))
33137
33039
  });
33138
33040
  return createHash("sha256").update(body).digest("hex");
33139
33041
  }
33140
33042
  function stopStatePath(host, caveatHome, sessionId) {
33141
- return join13(caveatHome, host.stopStateDir, `${sanitizeStateId(sessionId)}.txt`);
33043
+ return join12(caveatHome, host.stopStateDir, `${sanitizeStateId(sessionId)}.txt`);
33142
33044
  }
33143
33045
  function wasStopReminderQueued(host, caveatHome, sessionId, key) {
33144
33046
  const path = stopStatePath(host, caveatHome, sessionId);
@@ -33150,7 +33052,7 @@ function wasStopReminderQueued(host, caveatHome, sessionId, key) {
33150
33052
  }
33151
33053
  function markStopReminderQueued(host, caveatHome, sessionId, key) {
33152
33054
  const path = stopStatePath(host, caveatHome, sessionId);
33153
- mkdirSync5(join13(caveatHome, host.stopStateDir), { recursive: true });
33055
+ mkdirSync6(join12(caveatHome, host.stopStateDir), { recursive: true });
33154
33056
  writeFileSync4(path, key, "utf-8");
33155
33057
  }
33156
33058
  function queueStopForSession(host, sessionId, signals, related, buildText) {
@@ -33203,6 +33105,313 @@ function extractToolResponseText(response) {
33203
33105
  return "";
33204
33106
  }
33205
33107
 
33108
+ // src/hookDelivery.ts
33109
+ function statePath(caveatHome, sessionId) {
33110
+ return join13(caveatHome, "hook-delivery", `${createHash2("sha256").update(sessionId).digest("hex")}.json`);
33111
+ }
33112
+ function readState(caveatHome, sessionId) {
33113
+ const path = statePath(caveatHome, sessionId);
33114
+ if (!existsSync13(path)) return { ids: [], signals: [] };
33115
+ const value = JSON.parse(readFileSync8(path, "utf8"));
33116
+ if (!value || typeof value !== "object" || !("ids" in value) || !("signals" in value) || !Array.isArray(value.ids) || !value.ids.every((item) => typeof item === "string") || !Array.isArray(value.signals) || !value.signals.every((item) => typeof item === "string")) {
33117
+ throw new Error("hook_delivery_state_invalid");
33118
+ }
33119
+ return value;
33120
+ }
33121
+ function writeState(caveatHome, sessionId, state) {
33122
+ const path = statePath(caveatHome, sessionId);
33123
+ mkdirSync7(join13(caveatHome, "hook-delivery"), { recursive: true, mode: 448 });
33124
+ const temporary = `${path}.${randomBytes2(6).toString("hex")}.tmp`;
33125
+ try {
33126
+ writeFileSync5(temporary, `${JSON.stringify(state)}
33127
+ `, { encoding: "utf8", mode: 384, flag: "wx" });
33128
+ renameSync3(temporary, path);
33129
+ } finally {
33130
+ rmSync2(temporary, { force: true });
33131
+ }
33132
+ }
33133
+ function wasHitDelivered(caveatHome, sessionId, ref) {
33134
+ return readState(caveatHome, sessionId).ids.includes(ref);
33135
+ }
33136
+ var SIGNAL_LINES = [
33137
+ [/^- tool failure:/u, "tool_failure", "\u30C4\u30FC\u30EB\u5931\u6557"],
33138
+ [/^- 同一ファイル複数編集:/u, "reedit", "\u540C\u4E00\u30D5\u30A1\u30A4\u30EB\u306E\u518D\u7DE8\u96C6"],
33139
+ [/^- WebSearch:/u, "web_search", "Web\u691C\u7D22"],
33140
+ [/^- WebFetch:/u, "web_fetch", "Web\u53D6\u5F97"],
33141
+ [/^- 同一 Bash コマンドの再実行:/u, "bash_retry", "\u540C\u4E00\u30B3\u30DE\u30F3\u30C9\u306E\u518D\u5B9F\u884C"]
33142
+ ];
33143
+ function prepareSessionDelivery(caveatHome, sessionId, contexts, additionallyDeliveredIds = []) {
33144
+ const state = readState(caveatHome, sessionId);
33145
+ const seenIds = /* @__PURE__ */ new Set([...state.ids, ...additionallyDeliveredIds]);
33146
+ const seenSignals = new Set(state.signals);
33147
+ const addedIds = [];
33148
+ const addedSignals = [];
33149
+ const hitLines = [];
33150
+ const signalLabels = [];
33151
+ const others = [];
33152
+ for (const context of contexts) {
33153
+ const text = context.trim();
33154
+ if (!text) continue;
33155
+ const lines = text.split("\n");
33156
+ const isStop = text.startsWith(STOP_REMINDER_PREFIX);
33157
+ let hasHit = false;
33158
+ for (const line of lines) {
33159
+ const hit = /^\d+\. ([^\s]+) \(([^)]+)\) — (.+)$/u.exec(line);
33160
+ if (hit) {
33161
+ hasHit = true;
33162
+ const ref = `${hit[2]}/${hit[1]}`;
33163
+ if (seenIds.has(ref)) continue;
33164
+ seenIds.add(ref);
33165
+ addedIds.push(ref);
33166
+ hitLines.push(`${hit[1]} (${hit[2]}) \u2014 ${hit[3]}`);
33167
+ }
33168
+ if (isStop) {
33169
+ for (const [pattern, kind, label] of SIGNAL_LINES) {
33170
+ if (pattern.test(line) && !seenSignals.has(kind)) {
33171
+ seenSignals.add(kind);
33172
+ addedSignals.push(kind);
33173
+ signalLabels.push(label);
33174
+ }
33175
+ }
33176
+ }
33177
+ }
33178
+ if (isStop || hasHit) continue;
33179
+ if (!others.includes(text)) others.push(text);
33180
+ }
33181
+ const texts = [...others];
33182
+ if (signalLabels.length > 0) texts.push(`[caveat] \u65B0\u3057\u3044\u82E6\u6226\u30B7\u30B0\u30CA\u30EB: ${signalLabels.join("\u3001")}\u3002\u5FC5\u8981\u306A\u3089Caveat\u3067\u77E5\u898B\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
33183
+ if (hitLines.length > 0) texts.push(`[caveat] \u65B0\u3057\u304F\u8A72\u5F53\u3057\u305F\u7F60:
33184
+ ${hitLines.map((line) => `- ${line}`).join("\n")}
33185
+ \u8A73\u7D30\u306FCaveat\u3067\u53D6\u5F97\u3057\u3001\u9069\u7528\u6761\u4EF6\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
33186
+ return {
33187
+ texts,
33188
+ delivered: () => {
33189
+ if (addedIds.length === 0 && addedSignals.length === 0 && additionallyDeliveredIds.length === 0) return;
33190
+ const latest = readState(caveatHome, sessionId);
33191
+ writeState(caveatHome, sessionId, {
33192
+ ids: [.../* @__PURE__ */ new Set([...latest.ids, ...addedIds, ...additionallyDeliveredIds])],
33193
+ signals: [.../* @__PURE__ */ new Set([...latest.signals, ...addedSignals])]
33194
+ });
33195
+ }
33196
+ };
33197
+ }
33198
+
33199
+ // src/jevStruggle.ts
33200
+ var MODEL = "jev-1.13.0";
33201
+ var API_URL = "https://api.typesafe.ai/v1/systemone";
33202
+ var STRUGGLE_THRESHOLD = 0.85;
33203
+ var MATCH_THRESHOLD = 0.85;
33204
+ var MAX_TERMS = 48;
33205
+ var MAX_CANDIDATES = 20;
33206
+ async function callJev(key, state, questions, fetcher = fetch) {
33207
+ const response = await fetcher(API_URL, {
33208
+ method: "POST",
33209
+ headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
33210
+ body: JSON.stringify({ state, model: MODEL, questions }),
33211
+ signal: AbortSignal.timeout(1500)
33212
+ });
33213
+ if (!response.ok) throw new Error(`jev_http_${response.status}`);
33214
+ const body = await response.json();
33215
+ if (!isRecord(body) || !isRecord(body.answers)) throw new Error("jev_response_invalid");
33216
+ return body;
33217
+ }
33218
+ function isRecord(value) {
33219
+ return typeof value === "object" && value !== null && !Array.isArray(value);
33220
+ }
33221
+ function noul(answer) {
33222
+ if (answer?.type !== "noul" || typeof answer.noul !== "number" || !Number.isFinite(answer.noul) || answer.noul < 0 || answer.noul > 1) {
33223
+ throw new Error("jev_noul_invalid");
33224
+ }
33225
+ return answer.noul;
33226
+ }
33227
+ function choice(answer, allowed) {
33228
+ if (answer?.type !== "choice" || !allowed.has(answer.choice)) throw new Error("jev_choice_invalid");
33229
+ return answer.choice;
33230
+ }
33231
+ function candidateTerms(turns) {
33232
+ const seen = /* @__PURE__ */ new Set();
33233
+ const terms = [];
33234
+ const groups = [...turns].reverse().map((turn) => [turn.user, turn.thinking, turn.assistant].flatMap((part) => extractPromptCandidates(
33235
+ part.replace(/(?<=[A-Za-z0-9])(?=[\u3040-\u30ff\u3400-\u9fff])|(?<=[\u3040-\u30ff\u3400-\u9fff])(?=[A-Za-z0-9])/gu, " ")
33236
+ )));
33237
+ const add = (term) => {
33238
+ const key = term.toLocaleLowerCase();
33239
+ if (seen.has(key) || terms.length >= MAX_TERMS) return;
33240
+ seen.add(key);
33241
+ terms.push(term);
33242
+ };
33243
+ for (const group of groups) {
33244
+ let added = 0;
33245
+ for (const term of group) {
33246
+ const before = terms.length;
33247
+ add(term);
33248
+ if (terms.length > before) added++;
33249
+ if (added === 12) break;
33250
+ }
33251
+ }
33252
+ for (const group of groups) for (const term of group) add(term);
33253
+ return terms;
33254
+ }
33255
+ async function judgeStruggle(key, turns, ask = callJev) {
33256
+ const terms = candidateTerms(turns);
33257
+ const options = Object.fromEntries(terms.map((term, index) => [`term_${index}`, term]));
33258
+ const questions = {
33259
+ repeated_problem: { type: "noul", instructions: "Across these three ordered turns, is the coding agent repeatedly correcting attempts at the same unresolved task or problem? Judge the meaning of its dialogue and Thinking, not the number of tool calls." },
33260
+ clearly_stuck: { type: "noul", instructions: "At the end of turn 3, is the agent clearly struggling with an unresolved problem rather than making a brief, ordinary correction or verification?" }
33261
+ };
33262
+ if (terms.length > 0) questions.search_term = {
33263
+ type: "choice",
33264
+ instructions: "If the agent is struggling, which exact term from these options would best retrieve a useful Caveat knowledge entry about the concrete unresolved problem? Select none when no option identifies it.",
33265
+ criteria: { ...options, none: "No listed term would retrieve useful knowledge about this problem." }
33266
+ };
33267
+ const response = await ask(key, { turns }, questions);
33268
+ const struggling = noul(response.answers.repeated_problem) >= STRUGGLE_THRESHOLD && noul(response.answers.clearly_stuck) >= STRUGGLE_THRESHOLD;
33269
+ if (!struggling || terms.length === 0) return { struggling, term: null };
33270
+ const selected = choice(response.answers.search_term, /* @__PURE__ */ new Set([...Object.keys(options), "none"]));
33271
+ return { struggling, term: selected === "none" ? null : terms[Number(selected.slice(5))] ?? null };
33272
+ }
33273
+ async function rankKnowledge(key, turns, candidates, ask = callJev) {
33274
+ if (candidates.length === 0) return null;
33275
+ const state = {
33276
+ turns,
33277
+ currentHostOs: fingerprint().os,
33278
+ candidates: candidates.map(({ hit, entry }) => {
33279
+ const { applies_to_os: appliesToOs = null, ...recordedEnvironment } = hit.environment;
33280
+ return {
33281
+ title: hit.title,
33282
+ symptom: hit.symptomExcerpt,
33283
+ cause: entry.sections.Cause?.slice(0, 300) ?? "",
33284
+ resolution: entry.sections.Resolution?.slice(0, 600) ?? "",
33285
+ recordedEnvironment,
33286
+ appliesToOs
33287
+ };
33288
+ })
33289
+ };
33290
+ const questions = Object.fromEntries(candidates.map((_, index) => [`candidate_${index}`, {
33291
+ type: "noul",
33292
+ instructions: `Does candidates[${index}] directly apply to the unresolved problem shown in the turns and offer a useful resolution? A recordedEnvironment value is where the entry was observed, not a restriction. Only appliesToOs restricts the target OS; null means any OS. currentHostOs may differ from the task's target OS.`
33293
+ }]));
33294
+ const response = await ask(key, state, questions);
33295
+ let best = null;
33296
+ let bestScore = MATCH_THRESHOLD;
33297
+ for (let index = 0; index < candidates.length; index++) {
33298
+ const score = noul(response.answers[`candidate_${index}`]);
33299
+ if (score >= MATCH_THRESHOLD && (best === null || score > bestScore)) {
33300
+ best = candidates[index];
33301
+ bestScore = score;
33302
+ }
33303
+ }
33304
+ return best;
33305
+ }
33306
+ function quotePowerShell(value) {
33307
+ return `'${value.replaceAll("'", "''")}'`;
33308
+ }
33309
+ function readThroughlineContext(host, sessionId, projectRoot, transcriptPath) {
33310
+ const sourceId = host === "codex" && !sessionId.startsWith("codex:") ? `codex:${sessionId}` : sessionId;
33311
+ const args = ["caveat-context", "--session", sourceId, "--project", projectRoot, "--host", host, "--transcript", transcriptPath, "--json"];
33312
+ const invocation = isWindows() ? { command: "pwsh.exe", args: ["-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(`& throughline.cmd ${args.map(quotePowerShell).join(" ")}; exit $LASTEXITCODE`, "utf16le").toString("base64")] } : { command: "throughline", args };
33313
+ const result = spawnSync3(invocation.command, invocation.args, { encoding: "utf8", timeout: 1500, maxBuffer: 128 * 1024, windowsHide: true });
33314
+ if (result.error || result.status !== 0) throw new Error("throughline_context_failed");
33315
+ const value = JSON.parse(result.stdout);
33316
+ if (!isRecord(value) || value.schema !== "throughline.caveat_context.v1" || typeof value.status !== "string" || !Array.isArray(value.turns)) {
33317
+ throw new Error("throughline_context_invalid");
33318
+ }
33319
+ return value;
33320
+ }
33321
+ function statePath2(caveatHome, sessionId) {
33322
+ const digest = createHash3("sha256").update(sessionId).digest("hex");
33323
+ return join14(caveatHome, "jev-sessions", `${digest}.json`);
33324
+ }
33325
+ function loadState(caveatHome, sessionId) {
33326
+ const path = statePath2(caveatHome, sessionId);
33327
+ if (!existsSync14(path)) return { lastTurn: null, deliveredIds: [], notifiedTerms: [] };
33328
+ const value = JSON.parse(readFileSync9(path, "utf8"));
33329
+ if (!isRecord(value) || value.lastTurn !== null && typeof value.lastTurn !== "string" || !Array.isArray(value.deliveredIds) || !value.deliveredIds.every((item) => typeof item === "string") || !Array.isArray(value.notifiedTerms) || !value.notifiedTerms.every((item) => typeof item === "string")) throw new Error("jev_state_invalid");
33330
+ return value;
33331
+ }
33332
+ function saveState(caveatHome, sessionId, state) {
33333
+ const path = statePath2(caveatHome, sessionId);
33334
+ mkdirSync8(join14(caveatHome, "jev-sessions"), { recursive: true, mode: 448 });
33335
+ const temporary = `${path}.${randomBytes3(6).toString("hex")}.tmp`;
33336
+ try {
33337
+ writeFileSync6(temporary, `${JSON.stringify(state)}
33338
+ `, { encoding: "utf8", mode: 384, flag: "wx" });
33339
+ renameSync4(temporary, path);
33340
+ } finally {
33341
+ rmSync3(temporary, { force: true });
33342
+ }
33343
+ }
33344
+ function reminder(hit) {
33345
+ if (!hit) return "[caveat] \u540C\u3058\u554F\u984C\u3078\u306E\u8A66\u884C\u304C\u7D9A\u304D\u3001\u307E\u3060\u89E3\u6C7A\u3057\u3066\u3044\u306A\u3044\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002\u5FC5\u8981\u306B\u3088\u308ACaveat\u3067\u691C\u7D22\u3057\u3066\u307F\u3066\u304F\u3060\u3055\u3044\u3002";
33346
+ const resolution = hit.entry.sections.Resolution?.replace(/\s+/g, " ").trim().slice(0, 500) ?? "";
33347
+ return `[caveat] \u540C\u3058\u554F\u984C\u3078\u306E\u8A66\u884C\u304C\u7D9A\u3044\u3066\u3044\u307E\u3059\u3002\u95A2\u9023\u77E5\u898B: ${hit.hit.title} (${hit.hit.source}/${hit.hit.id})
33348
+ \u5BFE\u51E6: ${resolution}
33349
+ \u3053\u306E\u56DE\u7B54\u3067\u89E3\u6C7A\u3057\u306A\u3044\u5834\u5408\u3001\u5FC5\u8981\u306B\u3088\u308ACaveat\u3067\u691C\u7D22\u3057\u3066\u307F\u3066\u304F\u3060\u3055\u3044\u3002`;
33350
+ }
33351
+ async function runJevStruggle(ctx, host, payload, deps = {}) {
33352
+ if (!ctx.config.jevEnabled) return null;
33353
+ const sessionId = payload.session_id ?? payload.sessionId;
33354
+ const transcriptPath = payload.transcript_path ?? payload.transcriptPath;
33355
+ if (typeof sessionId !== "string" || !sessionId || typeof transcriptPath !== "string" || !transcriptPath) {
33356
+ throw new Error("jev_hook_context_missing");
33357
+ }
33358
+ const projectRoot = typeof payload.cwd === "string" && payload.cwd ? payload.cwd : process.cwd();
33359
+ const context = (deps.readContext ?? readThroughlineContext)(host, sessionId, projectRoot, transcriptPath);
33360
+ if (["unavailable", "incomplete", "projection_pending"].includes(context.status)) return null;
33361
+ if (context.status !== "ready" || context.turns.length !== 3) throw new Error(`throughline_context_${context.status}`);
33362
+ const latest = context.turns[2];
33363
+ const turnKey = `${latest.originSessionId}:${latest.turnNumber}`;
33364
+ const state = loadState(ctx.caveatHome, sessionId);
33365
+ if (state.lastTurn === turnKey) return null;
33366
+ const key = (deps.readKey ?? readJevKey)(ctx.caveatHome);
33367
+ const ask = deps.ask ?? callJev;
33368
+ const judgment = await judgeStruggle(key, context.turns, ask);
33369
+ state.lastTurn = turnKey;
33370
+ if (!judgment.struggling) {
33371
+ saveState(ctx.caveatHome, sessionId, state);
33372
+ return null;
33373
+ }
33374
+ let candidates = [];
33375
+ if (judgment.term) {
33376
+ if (!existsSync14(ctx.paths.dbPath)) throw new Error("jev_knowledge_index_missing");
33377
+ const db = openDb({ path: ctx.paths.dbPath });
33378
+ try {
33379
+ const taskText = context.turns.map((turn) => `${turn.user}
33380
+ ${turn.assistant}
33381
+ ${turn.thinking}`).join("\n");
33382
+ candidates = search(db, { query: judgment.term, limit: MAX_CANDIDATES }).filter((hit) => environmentAppliesToTask(hit.environment, taskText, fingerprint().os)).map((hit) => ({ hit, entry: get(db, hit.id, hit.source) })).filter((item) => item.entry !== null).filter((item) => Boolean(item.entry.sections.Resolution));
33383
+ } finally {
33384
+ db.close();
33385
+ }
33386
+ }
33387
+ const selected = await rankKnowledge(key, context.turns, candidates, ask);
33388
+ const id = selected ? `${selected.hit.source}/${selected.hit.id}` : null;
33389
+ const term = judgment.term ?? "_none";
33390
+ if (id && (state.deliveredIds.includes(id) || wasHitDelivered(ctx.caveatHome, sessionId, id)) || !id && state.notifiedTerms.includes(term)) {
33391
+ saveState(ctx.caveatHome, sessionId, state);
33392
+ return null;
33393
+ }
33394
+ return {
33395
+ text: reminder(selected),
33396
+ ref: id,
33397
+ delivered: () => {
33398
+ const current = loadState(ctx.caveatHome, sessionId);
33399
+ current.lastTurn = turnKey;
33400
+ if (id && !current.deliveredIds.includes(id)) current.deliveredIds.push(id);
33401
+ if (!id && !current.notifiedTerms.includes(term)) current.notifiedTerms.push(term);
33402
+ saveState(ctx.caveatHome, sessionId, current);
33403
+ if (selected) {
33404
+ const db = openDb({ path: ctx.paths.dbPath });
33405
+ try {
33406
+ markHit(db, [selected.hit]);
33407
+ } finally {
33408
+ db.close();
33409
+ }
33410
+ }
33411
+ }
33412
+ };
33413
+ }
33414
+
33206
33415
  // src/commands/hookCmd.ts
33207
33416
  var CLAUDE_HOST = {
33208
33417
  agent: "claude",
@@ -33262,10 +33471,10 @@ function spawnWorker(job) {
33262
33471
  try {
33263
33472
  root = workerRoot();
33264
33473
  sweepStaleWorkerDirs(Date.now(), root);
33265
- workDir = mkdtempSync2(join14(root, "job-"));
33474
+ workDir = mkdtempSync(join15(root, "job-"));
33266
33475
  chmodSync(workDir, 448);
33267
- workFile = join14(workDir, `${randomBytes(4).toString("hex")}.json`);
33268
- writeFileSync5(workFile, JSON.stringify({ ...job, schemaVersion: "caveat-worker-job/v2" }), { encoding: "utf-8", mode: 384, flag: "wx" });
33476
+ workFile = join15(workDir, `${randomBytes4(4).toString("hex")}.json`);
33477
+ writeFileSync7(workFile, JSON.stringify({ ...job, schemaVersion: "caveat-worker-job/v2" }), { encoding: "utf-8", mode: 384, flag: "wx" });
33269
33478
  } catch (err) {
33270
33479
  process.stderr.write(`[caveat:hook] worker writefile error: ${errorMessage4(err)}
33271
33480
  `);
@@ -33296,7 +33505,7 @@ function spawnWorker(job) {
33296
33505
  async function runWorker(workFile) {
33297
33506
  let raw;
33298
33507
  try {
33299
- raw = readFileSync8(workFile, "utf-8");
33508
+ raw = readFileSync10(workFile, "utf-8");
33300
33509
  } catch {
33301
33510
  process.exit(0);
33302
33511
  }
@@ -33322,7 +33531,7 @@ async function runWorker(workFile) {
33322
33531
  agent: "claude",
33323
33532
  surface: "tool_error",
33324
33533
  refs: hits
33325
- }), () => buildToolErrorReminder(job, hits));
33534
+ }), () => toolErrorReminderText(hits));
33326
33535
  } catch {
33327
33536
  process.stderr.write("[caveat:hook] pending reminder build or publish failed\n");
33328
33537
  process.exit(0);
@@ -33333,17 +33542,17 @@ async function runWorker(workFile) {
33333
33542
  function cleanupWorkerDir(workDir, root) {
33334
33543
  if (!workDir || !root || !isOwnedWorkerDir(workDir, root)) return;
33335
33544
  try {
33336
- rmSync2(workDir, { recursive: true, force: true });
33545
+ rmSync4(workDir, { recursive: true, force: true });
33337
33546
  } catch {
33338
33547
  }
33339
33548
  }
33340
33549
  function isOwnedWorkerDir(path, root = workerRoot()) {
33341
33550
  try {
33342
- const inputStat = lstatSync(path);
33551
+ const inputStat = lstatSync2(path);
33343
33552
  if (inputStat.isSymbolicLink()) return false;
33344
33553
  const tmpRoot = realpathSync3(root);
33345
33554
  const resolved = realpathSync3(path);
33346
- const stat = lstatSync(resolved);
33555
+ const stat = lstatSync2(resolved);
33347
33556
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
33348
33557
  return dirname5(resolved) === tmpRoot && basename(resolved).startsWith("job-") && stat.isDirectory() && !stat.isSymbolicLink() && hasPrivateOwnership(stat, uid);
33349
33558
  } catch {
@@ -33360,41 +33569,41 @@ function sweepStaleWorkerDirs(now = Date.now(), root = workerRoot()) {
33360
33569
  }
33361
33570
  for (const entry of entries) {
33362
33571
  if (!entry.startsWith("job-")) continue;
33363
- const path = join14(root, entry);
33572
+ const path = join15(root, entry);
33364
33573
  try {
33365
33574
  if (!isOwnedWorkerDir(path, root)) continue;
33366
- const stat = lstatSync(path);
33575
+ const stat = lstatSync2(path);
33367
33576
  if (now - stat.mtimeMs <= WORKER_STALE_MS || !isStaleWorkerJobDir(path)) continue;
33368
- rmSync2(path, { recursive: true, force: true });
33577
+ rmSync4(path, { recursive: true, force: true });
33369
33578
  } catch {
33370
33579
  process.stderr.write("[caveat:hook] worker stale cleanup failed\n");
33371
33580
  }
33372
33581
  }
33373
33582
  }
33374
- function workerRoot(base = tmpdir2()) {
33375
- const root = join14(base, WORKER_ROOT);
33376
- mkdirSync6(root, { recursive: true, mode: 448 });
33377
- const stat = lstatSync(root);
33583
+ function workerRoot(base = tmpdir()) {
33584
+ const root = join15(base, WORKER_ROOT);
33585
+ mkdirSync9(root, { recursive: true, mode: 448 });
33586
+ const stat = lstatSync2(root);
33378
33587
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
33379
33588
  if (!stat.isDirectory() || stat.isSymbolicLink() || !hasPrivateOwnership(stat, uid)) throw new Error("worker root is unsafe");
33380
- const marker = join14(root, WORKER_MARKER);
33589
+ const marker = join15(root, WORKER_MARKER);
33381
33590
  try {
33382
- writeFileSync5(marker, "caveat-worker/v1\n", { mode: 384, flag: "wx" });
33591
+ writeFileSync7(marker, "caveat-worker/v1\n", { mode: 384, flag: "wx" });
33383
33592
  } catch (error51) {
33384
33593
  if (!(error51 && typeof error51 === "object" && "code" in error51 && error51.code === "EEXIST")) throw error51;
33385
33594
  }
33386
- const markerStat = lstatSync(marker);
33387
- if (!markerStat.isFile() || markerStat.isSymbolicLink() || !hasPrivateOwnership(markerStat, uid) || readFileSync8(marker, "utf-8") !== "caveat-worker/v1\n") throw new Error("worker root marker is invalid");
33595
+ const markerStat = lstatSync2(marker);
33596
+ if (!markerStat.isFile() || markerStat.isSymbolicLink() || !hasPrivateOwnership(markerStat, uid) || readFileSync10(marker, "utf-8") !== "caveat-worker/v1\n") throw new Error("worker root marker is invalid");
33388
33597
  return root;
33389
33598
  }
33390
33599
  function isStaleWorkerJobDir(path) {
33391
33600
  const entries = readdirSync2(path);
33392
33601
  if (entries.length !== 1 || !entries[0].endsWith(".json")) return false;
33393
- const file2 = join14(path, entries[0]);
33394
- const stat = lstatSync(file2);
33602
+ const file2 = join15(path, entries[0]);
33603
+ const stat = lstatSync2(file2);
33395
33604
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
33396
33605
  if (!stat.isFile() || stat.isSymbolicLink() || !hasPrivateOwnership(stat, uid)) return false;
33397
- return isKnownStaleWorkerJob(JSON.parse(readFileSync8(file2, "utf-8")));
33606
+ return isKnownStaleWorkerJob(JSON.parse(readFileSync10(file2, "utf-8")));
33398
33607
  }
33399
33608
  function hasPrivateOwnership(stat, uid) {
33400
33609
  return isPrivateOwnerStat(stat, uid);
@@ -33416,7 +33625,7 @@ function isKnownStaleWorkerJob(value) {
33416
33625
  }
33417
33626
  function writeLastReindex(caveatHome, value) {
33418
33627
  try {
33419
- writeFileSync5(join14(caveatHome, "index", ".last-reindex.json"), JSON.stringify(value), "utf-8");
33628
+ writeFileSync7(join15(caveatHome, "index", ".last-reindex.json"), JSON.stringify(value), "utf-8");
33420
33629
  } catch (err) {
33421
33630
  process.stderr.write(`[caveat:hook] reindex status write error: ${errorMessage4(err)}
33422
33631
  `);
@@ -33428,7 +33637,7 @@ async function runReindexWorker() {
33428
33637
  return;
33429
33638
  }
33430
33639
  const ctx = buildContextSafely(CLAUDE_HOST);
33431
- if (!ctx || !existsSync13(ctx.paths.dbPath)) {
33640
+ if (!ctx || !existsSync15(ctx.paths.dbPath)) {
33432
33641
  process.stderr.write("[caveat:hook] auto reindex skipped: index database does not exist\n");
33433
33642
  return;
33434
33643
  }
@@ -33490,81 +33699,6 @@ async function runAutoSyncWorker() {
33490
33699
  `);
33491
33700
  }
33492
33701
  }
33493
- function buildToolErrorReminder(job, hits) {
33494
- const base = toolErrorReminderText(hits);
33495
- const mode = hookCodexSidecarMode();
33496
- if (mode === "off") return base;
33497
- const projectRoot = process.cwd();
33498
- const hasSidecarConfig = existsSync13(join14(projectRoot, ".codex-sidecar.yml"));
33499
- if (mode === "auto" && !hasSidecarConfig) return base;
33500
- const advisory = runCodexSidecarAdvisory({
33501
- searchText: job.failureText,
33502
- limit: hits.length,
33503
- projectRoot,
33504
- prompt: [
33505
- "A Claude Code tool just returned an error.",
33506
- "Use the provided Caveat context to give concise next-step advice.",
33507
- "Do not tell Claude to search Caveat again unless the context is insufficient."
33508
- ].join(" "),
33509
- additionalContext: job.additionalContext
33510
- });
33511
- if (advisory.status === "ok") {
33512
- return [
33513
- base,
33514
- "",
33515
- formatCodexSidecarAdvisory(advisory)
33516
- ].filter(Boolean).join("\n");
33517
- }
33518
- return [
33519
- base,
33520
- "",
33521
- formatCodexSidecarAdvisory(advisory)
33522
- ].join("\n");
33523
- }
33524
- function hookCodexSidecarMode() {
33525
- const raw = process.env.CAVEAT_HOOK_CODEX_SIDECAR;
33526
- if (raw === "off" || raw === "auto" || raw === "require") return raw;
33527
- return "auto";
33528
- }
33529
- function buildStopReminder(signals, related) {
33530
- const base = stopReminderText(signals, related);
33531
- const mode = hookCodexSidecarMode();
33532
- if (mode === "off") return base;
33533
- const projectRoot = process.cwd();
33534
- const hasSidecarConfig = existsSync13(join14(projectRoot, ".codex-sidecar.yml"));
33535
- if (mode === "auto" && !hasSidecarConfig) return base;
33536
- const advisory = runCodexSidecarAdvisory({
33537
- searchText: struggleSearchText(signals),
33538
- limit: Math.max(related.length, 1),
33539
- projectRoot,
33540
- prompt: [
33541
- "A Claude Code session is ending after objective struggle signals.",
33542
- "Use the provided Caveat context and structured hook signal context to advise whether Claude should update an existing caveat or record a new one.",
33543
- "Be concise and preserve Caveat visibility rules."
33544
- ].join(" "),
33545
- additionalContext: buildHookSignalSidecarContextBlock({
33546
- type: "stop",
33547
- toolFailureCount: signals.toolFailureCount,
33548
- reeditedFileCount: signals.fileEditCounts.length,
33549
- webSearchCount: signals.webSearchCount,
33550
- webFetchCount: signals.webFetchCount,
33551
- bashRetryCount: signals.bashRetryCount,
33552
- durationMinutes: signals.durationMinutes
33553
- })
33554
- });
33555
- if (advisory.status === "ok") {
33556
- return [
33557
- base,
33558
- "",
33559
- formatCodexSidecarAdvisory(advisory)
33560
- ].filter(Boolean).join("\n");
33561
- }
33562
- return [
33563
- base,
33564
- "",
33565
- formatCodexSidecarAdvisory(advisory)
33566
- ].join("\n");
33567
- }
33568
33702
  async function runHook(name, arg) {
33569
33703
  try {
33570
33704
  sweepStaleWorkerDirs();
@@ -33594,39 +33728,65 @@ async function runHook(name, arg) {
33594
33728
  }
33595
33729
  const payload = parsePayload(CLAUDE_HOST, raw);
33596
33730
  const sessionId = getSessionId(payload);
33597
- const contexts = name === "stop" ? [] : drainForSession(CLAUDE_HOST, sessionId);
33731
+ const pending = name === "stop" ? null : peekForSession(CLAUDE_HOST, sessionId);
33732
+ const contexts = pending?.contexts ?? [];
33598
33733
  if (name === "user-prompt-submit") {
33734
+ const ctx = buildContextSafely(CLAUDE_HOST);
33735
+ let jevNotice = null;
33736
+ if (ctx?.config.jevEnabled) {
33737
+ try {
33738
+ jevNotice = await runJevStruggle(ctx, "claude", payload);
33739
+ if (jevNotice) contexts.push(jevNotice.text);
33740
+ } catch (err) {
33741
+ process.stderr.write(`[caveat:hook] Jev\u5224\u5B9A\u306B\u5931\u6557: ${errorMessage4(err)}
33742
+ `);
33743
+ }
33744
+ }
33599
33745
  const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
33600
33746
  const hits = searchCaveatsSafely(CLAUDE_HOST, { topicText: prompt, failureText: prompt, surface: "user_prompt" });
33601
33747
  if (hits.length > 0) {
33602
33748
  contexts.push(userPromptSubmitReminderText(hits));
33603
33749
  }
33604
- const compacted = compactContexts(CLAUDE_HOST, contexts);
33605
- if (compacted.length > 0) {
33606
- process.stdout.write(`${systemReminderOutput(compacted.join("\n\n"))}
33750
+ if (ctx) {
33751
+ try {
33752
+ const prepared = prepareSessionDelivery(ctx.caveatHome, sessionId, contexts, jevNotice?.ref ? [jevNotice.ref] : []);
33753
+ const sent = prepared.texts.length === 0 || await emitHookContext(CLAUDE_HOST, `${systemReminderOutput(prepared.texts.join("\n\n"))}
33607
33754
  `);
33755
+ if (sent) {
33756
+ prepared.delivered();
33757
+ if (jevNotice && prepared.texts.includes(jevNotice.text)) jevNotice.delivered();
33758
+ if (pending) acknowledgeSessionReminders(CLAUDE_HOST, pending);
33759
+ }
33760
+ } catch (err) {
33761
+ process.stderr.write(`[caveat:hook] \u914D\u9001\u306B\u5931\u6557: ${errorMessage4(err)}
33762
+ `);
33763
+ }
33608
33764
  }
33609
33765
  process.exit(0);
33610
33766
  }
33611
33767
  if (name === "post-tool-use") {
33612
- const compacted = compactContexts(CLAUDE_HOST, contexts);
33613
- if (compacted.length > 0) {
33614
- process.stdout.write(`${systemReminderOutput(compacted.join("\n\n"))}
33768
+ const ctx = buildContextSafely(CLAUDE_HOST);
33769
+ if (ctx) {
33770
+ try {
33771
+ const prepared = prepareSessionDelivery(ctx.caveatHome, sessionId, contexts);
33772
+ const sent = prepared.texts.length === 0 || await emitHookContext(CLAUDE_HOST, `${systemReminderOutput(prepared.texts.join("\n\n"))}
33615
33773
  `);
33774
+ if (sent) {
33775
+ prepared.delivered();
33776
+ if (pending) acknowledgeSessionReminders(CLAUDE_HOST, pending);
33777
+ }
33778
+ } catch (err) {
33779
+ process.stderr.write(`[caveat:hook] \u914D\u9001\u306B\u5931\u6557: ${errorMessage4(err)}
33780
+ `);
33781
+ }
33616
33782
  }
33617
33783
  if (!isToolError(payload)) process.exit(0);
33618
33784
  const errText = extractToolResponseText(
33619
33785
  payload.tool_response ?? payload.toolResponse ?? payload.error ?? payload
33620
33786
  );
33621
33787
  if (errText) {
33622
- const failureKind = payload.hook_event_name === "PostToolUseFailure" ? "post-tool-use-failure" : "error-bearing-post-tool-use";
33623
- const additionalContext = buildHookSignalSidecarContextBlock({
33624
- type: "tool-error",
33625
- toolName: payload.tool_name ?? payload.toolName,
33626
- failureKind
33627
- });
33628
33788
  const topicText = toolTopicText(payload);
33629
- spawnWorker({ sessionId, topicText, failureText: errText, ...additionalContext ? { additionalContext } : {} });
33789
+ spawnWorker({ sessionId, topicText, failureText: errText });
33630
33790
  }
33631
33791
  process.exit(0);
33632
33792
  }
@@ -33653,6 +33813,7 @@ async function runHook(name, arg) {
33653
33813
  }
33654
33814
  }
33655
33815
  if (payload.stop_hook_active === true) process.exit(0);
33816
+ if (ctx?.config.jevEnabled) process.exit(0);
33656
33817
  const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
33657
33818
  const signals = transcriptPath ? loadSignalsSafely(transcriptPath) : null;
33658
33819
  if (!signals || !hasAnyStruggleSignal(signals)) process.exit(0);
@@ -33661,7 +33822,7 @@ async function runHook(name, arg) {
33661
33822
  failureText,
33662
33823
  surface: "stop"
33663
33824
  })));
33664
- queueStopForSession(CLAUDE_HOST, sessionId, signals, related, () => buildStopReminder(signals, related));
33825
+ queueStopForSession(CLAUDE_HOST, sessionId, signals, related, () => stopReminderText(signals, related));
33665
33826
  process.exit(0);
33666
33827
  }
33667
33828
  process.stderr.write(`[caveat:hook] unknown hook name: ${name}
@@ -33671,10 +33832,10 @@ async function runHook(name, arg) {
33671
33832
 
33672
33833
  // src/commands/codexHookCmd.ts
33673
33834
  import { spawn as spawn3, spawnSync as spawnSync4 } from "node:child_process";
33674
- import { existsSync as existsSync14, readFileSync as readFileSync9, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
33675
- import { homedir as homedir4, tmpdir as tmpdir3 } from "node:os";
33676
- import { join as join15 } from "node:path";
33677
- import { randomBytes as randomBytes2 } from "node:crypto";
33835
+ import { existsSync as existsSync16, readFileSync as readFileSync11, unlinkSync, writeFileSync as writeFileSync8 } from "node:fs";
33836
+ import { homedir as homedir4, tmpdir as tmpdir2 } from "node:os";
33837
+ import { join as join16 } from "node:path";
33838
+ import { randomBytes as randomBytes5 } from "node:crypto";
33678
33839
  var CODEX_HOST = {
33679
33840
  agent: "codex",
33680
33841
  stderrTag: "caveat:codex-hook",
@@ -33703,10 +33864,10 @@ function processExitCodeFromText(text) {
33703
33864
  return m ? Number(m[1]) : null;
33704
33865
  }
33705
33866
  function transcriptToolOutput(transcriptPath, toolUseId) {
33706
- if (!transcriptPath || !toolUseId || !existsSync14(transcriptPath)) return null;
33867
+ if (!transcriptPath || !toolUseId || !existsSync16(transcriptPath)) return null;
33707
33868
  let raw = "";
33708
33869
  try {
33709
- raw = readFileSync9(transcriptPath, "utf-8");
33870
+ raw = readFileSync11(transcriptPath, "utf-8");
33710
33871
  } catch {
33711
33872
  return null;
33712
33873
  }
@@ -33749,26 +33910,28 @@ function toolInputText(input) {
33749
33910
  const command = r.command ?? r.cmd;
33750
33911
  return typeof command === "string" ? command : "";
33751
33912
  }
33752
- function isCodexToolError(payload) {
33753
- if (payload.is_error === true) return true;
33754
- const topExit = numericExitCode(payload.exit_code ?? payload.exitCode);
33755
- if (topExit !== null) return topExit !== 0;
33913
+ function codexToolStatus(payload) {
33914
+ if (payload.is_error === true) return "failed";
33756
33915
  const resp = payload.tool_response ?? payload.toolResponse;
33916
+ if (resp !== null && typeof resp === "object" && !Array.isArray(resp) && resp.is_error === true) return "failed";
33917
+ const topExit = numericExitCode(payload.exit_code ?? payload.exitCode);
33918
+ if (topExit !== null) return topExit === 0 ? "succeeded" : "failed";
33757
33919
  if (resp !== null && typeof resp === "object" && !Array.isArray(resp)) {
33758
33920
  const r = resp;
33759
- if (r.is_error === true) return true;
33760
- const exit2 = numericExitCode(r.exit_code ?? r.exitCode);
33761
- if (exit2 !== null) return exit2 !== 0;
33921
+ const exit = numericExitCode(r.exit_code ?? r.exitCode);
33922
+ if (exit !== null) return exit === 0 ? "succeeded" : "failed";
33762
33923
  }
33763
33924
  const responseExit = processExitCodeFromText(extractToolResponseText(resp));
33764
- if (responseExit !== null) return responseExit !== 0;
33925
+ if (responseExit !== null) return responseExit === 0 ? "succeeded" : "failed";
33765
33926
  const transcriptExit = transcriptExitCode(payload);
33766
- if (transcriptExit !== null) return transcriptExit !== 0;
33767
- return false;
33927
+ if (transcriptExit !== null) return transcriptExit === 0 ? "succeeded" : "failed";
33928
+ return "unknown";
33768
33929
  }
33769
33930
  function buildCodexPostToolUseWorkerJob(payload) {
33770
33931
  const sessionId = codexSessionId(payload);
33771
33932
  if (!sessionId) return null;
33933
+ const status2 = codexToolStatus(payload);
33934
+ if (status2 === "succeeded") return null;
33772
33935
  const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : void 0;
33773
33936
  const toolUseId = typeof payload.tool_use_id === "string" ? payload.tool_use_id : void 0;
33774
33937
  const responseText = extractToolResponseText(payload.tool_response ?? payload.toolResponse);
@@ -33776,8 +33939,7 @@ function buildCodexPostToolUseWorkerJob(payload) {
33776
33939
  const transcriptOutput = transcriptPath && toolUseId ? transcriptToolOutput(transcriptPath, toolUseId) : null;
33777
33940
  const topicText = inputText.trim();
33778
33941
  const failureText = [responseText, transcriptOutput ?? ""].map((s) => s.trim()).filter(Boolean).join("\n");
33779
- const knownError = isCodexToolError(payload);
33780
- if (knownError) {
33942
+ if (status2 === "failed") {
33781
33943
  return { sessionId, topicText, failureText, knownError: true, transcriptPath, toolUseId };
33782
33944
  }
33783
33945
  if (transcriptPath && toolUseId && isShellLikeTool(payload) && (topicText || failureText)) {
@@ -33817,9 +33979,9 @@ async function processCodexWorkerJob(job, opts = { waitForTranscript: true }) {
33817
33979
  if (opts.waitForTranscript && job.transcriptPath && job.toolUseId) {
33818
33980
  const transcriptOutput = await waitForTranscriptOutput(job.transcriptPath, job.toolUseId);
33819
33981
  if (transcriptOutput) {
33820
- const exit2 = processExitCodeFromText(transcriptOutput);
33821
- if (exit2 !== null) {
33822
- if (exit2 === 0) return;
33982
+ const exit = processExitCodeFromText(transcriptOutput);
33983
+ if (exit !== null) {
33984
+ if (exit === 0) return;
33823
33985
  knownError = true;
33824
33986
  }
33825
33987
  failureText = [failureText, transcriptOutput].filter(Boolean).join("\n");
@@ -33850,7 +34012,7 @@ async function processCodexWorkerJob(job, opts = { waitForTranscript: true }) {
33850
34012
  async function runCodexWorker(workFile) {
33851
34013
  let raw;
33852
34014
  try {
33853
- raw = readFileSync9(workFile, "utf-8");
34015
+ raw = readFileSync11(workFile, "utf-8");
33854
34016
  } catch {
33855
34017
  process.exit(0);
33856
34018
  }
@@ -33867,7 +34029,7 @@ async function runCodexWorker(workFile) {
33867
34029
  await processCodexWorkerJob(job);
33868
34030
  process.exit(0);
33869
34031
  }
33870
- function runDiagnostics(codexHome = process.env.CODEX_HOME ?? join15(homedir4(), ".codex")) {
34032
+ function runDiagnostics(codexHome = process.env.CODEX_HOME ?? join16(homedir4(), ".codex")) {
33871
34033
  const features = spawnSync4("codex", ["features", "list"], {
33872
34034
  encoding: "utf-8",
33873
34035
  maxBuffer: 1024 * 1024,
@@ -33915,16 +34077,38 @@ async function runCodexHook(name, arg) {
33915
34077
  const sessionId = codexSessionId(payload);
33916
34078
  if (!sessionId) process.stderr.write("[caveat:codex-hook] missing session_id; pending drain disabled\n");
33917
34079
  if (name === "user-prompt-submit") {
33918
- const contexts = sessionId ? drainForSession(CODEX_HOST, sessionId) : [];
34080
+ const pending = sessionId ? peekForSession(CODEX_HOST, sessionId) : null;
34081
+ const contexts = pending?.contexts ?? [];
34082
+ const ctx = buildContextSafely(CODEX_HOST);
34083
+ let jevNotice = null;
34084
+ if (ctx?.config.jevEnabled) {
34085
+ try {
34086
+ jevNotice = await runJevStruggle(ctx, "codex", payload);
34087
+ if (jevNotice) contexts.push(jevNotice.text);
34088
+ } catch (err) {
34089
+ process.stderr.write(`[caveat:codex-hook] Jev\u5224\u5B9A\u306B\u5931\u6557: ${errorMessage4(err)}
34090
+ `);
34091
+ }
34092
+ }
33919
34093
  const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
33920
34094
  const hits = searchCaveatsSafely(CODEX_HOST, { topicText: prompt, failureText: prompt, surface: "user_prompt" });
33921
34095
  if (hits.length > 0) {
33922
34096
  contexts.push(userPromptSubmitReminderText(hits, "native-cli"));
33923
34097
  }
33924
- const compacted = compactContexts(CODEX_HOST, contexts);
33925
- if (compacted.length > 0) {
33926
- process.stdout.write(`${codexContextOutput(compacted.join("\n\n"))}
34098
+ if (ctx) {
34099
+ try {
34100
+ const prepared = prepareSessionDelivery(ctx.caveatHome, sessionId ?? "_unknown", contexts, jevNotice?.ref ? [jevNotice.ref] : []);
34101
+ const sent = prepared.texts.length === 0 || await emitHookContext(CODEX_HOST, `${codexContextOutput(prepared.texts.join("\n\n"))}
34102
+ `);
34103
+ if (sent) {
34104
+ prepared.delivered();
34105
+ if (jevNotice && prepared.texts.includes(jevNotice.text)) jevNotice.delivered();
34106
+ if (pending) acknowledgeSessionReminders(CODEX_HOST, pending);
34107
+ }
34108
+ } catch (err) {
34109
+ process.stderr.write(`[caveat:codex-hook] \u914D\u9001\u306B\u5931\u6557: ${errorMessage4(err)}
33927
34110
  `);
34111
+ }
33928
34112
  }
33929
34113
  process.exit(0);
33930
34114
  }
@@ -33956,6 +34140,7 @@ async function runCodexHook(name, arg) {
33956
34140
  `);
33957
34141
  }
33958
34142
  if (payload.stop_hook_active === true) process.exit(0);
34143
+ if (buildContextSafely(CODEX_HOST)?.config.jevEnabled) process.exit(0);
33959
34144
  const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
33960
34145
  const signals = transcriptPath ? loadSignalsSafely2(transcriptPath) : null;
33961
34146
  if (!signals || !hasAnyStruggleSignal(signals)) process.exit(0);
@@ -33974,7 +34159,7 @@ async function runCodexHook(name, arg) {
33974
34159
 
33975
34160
  // src/commands/cursorHookCmd.ts
33976
34161
  import { homedir as homedir5 } from "node:os";
33977
- import { join as join16 } from "node:path";
34162
+ import { join as join17 } from "node:path";
33978
34163
  var CURSOR_HOST = {
33979
34164
  agent: "cursor",
33980
34165
  stderrTag: "caveat:cursor-hook",
@@ -34014,7 +34199,7 @@ function loadSignalsSafely3(path) {
34014
34199
  return null;
34015
34200
  }
34016
34201
  }
34017
- function runDiagnostics2(cursorDir = join16(homedir5(), ".cursor")) {
34202
+ function runDiagnostics2(cursorDir = join17(homedir5(), ".cursor")) {
34018
34203
  const installation = detectCursorHookInstallation(cursorDir);
34019
34204
  process.stdout.write(`${JSON.stringify({
34020
34205
  installation: installation.installation,
@@ -34039,7 +34224,24 @@ async function runCursorHook(name, arg) {
34039
34224
  }
34040
34225
  const payload = parsePayload(CURSOR_HOST, raw);
34041
34226
  const sessionId = cursorSessionId(payload);
34042
- const contexts = name === "stop" ? [] : drainForSession(CURSOR_HOST, sessionId);
34227
+ const pending = name === "stop" ? null : peekForSession(CURSOR_HOST, sessionId);
34228
+ const contexts = pending?.contexts ?? [];
34229
+ const deliver = async () => {
34230
+ const ctx = buildContextSafely(CURSOR_HOST);
34231
+ if (!ctx) return;
34232
+ try {
34233
+ const prepared = prepareSessionDelivery(ctx.caveatHome, sessionId, contexts);
34234
+ const sent = prepared.texts.length === 0 || await emitHookContext(CURSOR_HOST, `${cursorContextOutput(prepared.texts.join("\n\n"))}
34235
+ `);
34236
+ if (sent) {
34237
+ prepared.delivered();
34238
+ if (pending) acknowledgeSessionReminders(CURSOR_HOST, pending);
34239
+ }
34240
+ } catch (err) {
34241
+ process.stderr.write(`[caveat:cursor-hook] \u914D\u9001\u306B\u5931\u6557: ${errorMessage4(err)}
34242
+ `);
34243
+ }
34244
+ };
34043
34245
  if (name === "user-prompt-submit") {
34044
34246
  const prompt = cursorPrompt(payload);
34045
34247
  const hits = searchCaveatsSafely(CURSOR_HOST, {
@@ -34048,11 +34250,7 @@ async function runCursorHook(name, arg) {
34048
34250
  surface: "user_prompt"
34049
34251
  });
34050
34252
  if (hits.length > 0) contexts.push(userPromptSubmitReminderText(hits, "native-cli"));
34051
- const compacted = compactContexts(CURSOR_HOST, contexts);
34052
- if (compacted.length > 0) {
34053
- process.stdout.write(`${cursorContextOutput(compacted.join("\n\n"))}
34054
- `);
34055
- }
34253
+ await deliver();
34056
34254
  process.exit(0);
34057
34255
  }
34058
34256
  if (name === "post-tool-use") {
@@ -34067,11 +34265,7 @@ async function runCursorHook(name, arg) {
34067
34265
  });
34068
34266
  if (hits.length > 0) contexts.push(toolErrorReminderText(hits, "native-cli"));
34069
34267
  }
34070
- const compacted = compactContexts(CURSOR_HOST, contexts);
34071
- if (compacted.length > 0) {
34072
- process.stdout.write(`${cursorContextOutput(compacted.join("\n\n"))}
34073
- `);
34074
- }
34268
+ await deliver();
34075
34269
  process.exit(0);
34076
34270
  }
34077
34271
  if (name === "stop") {
@@ -34114,9 +34308,9 @@ async function runCursorHook(name, arg) {
34114
34308
  }
34115
34309
 
34116
34310
  // src/commands/pull.ts
34117
- import { existsSync as existsSync15 } from "node:fs";
34311
+ import { existsSync as existsSync17 } from "node:fs";
34118
34312
  async function runPull(ctx) {
34119
- const hasCommunityDir = existsSync15(ctx.paths.communityDir);
34313
+ const hasCommunityDir = existsSync17(ctx.paths.communityDir);
34120
34314
  if (!hasCommunityDir) {
34121
34315
  ctx.logger.info(
34122
34316
  "no community repos yet \u2014 add one with `caveat community add <github-url>`."
@@ -34214,213 +34408,6 @@ async function runSync(ctx, opts, dependencies = {}) {
34214
34408
  }
34215
34409
  }
34216
34410
 
34217
- // src/commands/codexSidecar.ts
34218
- import { spawnSync as spawnSync5 } from "node:child_process";
34219
- import { closeSync, constants as constants4, fstatSync, lstatSync as lstatSync2, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync3, openSync, readSync as readSync2, realpathSync as realpathSync4, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "node:fs";
34220
- import { tmpdir as tmpdir4 } from "node:os";
34221
- import { basename as basename2, dirname as dirname6, join as join17 } from "node:path";
34222
- import { cwd, exit } from "node:process";
34223
- function runCodexSidecarDiagnostics(logger, opts) {
34224
- const plan = buildCodexSidecarDiagnosticsCommand({
34225
- projectRoot: opts.project ?? cwd(),
34226
- preset: opts.preset ?? "review",
34227
- cli: cliSpec(opts)
34228
- });
34229
- exit(executePlan(logger, plan.command, plan.args, { saveResult: opts.saveResult }));
34230
- }
34231
- function runCodexSidecarSmoke(logger, opts) {
34232
- const plan = buildCodexSidecarReadOnlySmokeCommand({
34233
- projectRoot: opts.project ?? cwd(),
34234
- preset: opts.preset === "review" || opts.preset === "opinion" || opts.preset === "risk" ? opts.preset : "explore",
34235
- cli: cliSpec(opts)
34236
- });
34237
- exit(executePlan(logger, plan.command, plan.args, { saveResult: opts.saveResult }));
34238
- }
34239
- function runCodexSidecarWithCaveats(ctx, workflow, prompt, opts) {
34240
- const decision = decideCodexSidecarExecution({
34241
- hostAgent: opts.hostAgent ?? "unknown",
34242
- availability: opts.availability ?? "operational",
34243
- workflow,
34244
- sidecarAgent: opts.sidecarAgent,
34245
- requiresIsolation: opts.requiresIsolation,
34246
- structuredResultRequired: opts.structuredResultRequired,
34247
- explicitSecondPass: opts.explicitSecondPass
34248
- });
34249
- if (!decision.useSidecar) {
34250
- process.stdout.write(JSON.stringify({ status: "skipped", decision }, null, 2) + "\n");
34251
- exit(0);
34252
- }
34253
- const contextDir = mkdtempSync3(join17(tmpdir4(), "caveat-sidecar-context-"));
34254
- const contextFile = join17(contextDir, "context.json");
34255
- let status2 = 1;
34256
- try {
34257
- const blocks = collectCaveatContextBlocks(ctx, {
34258
- query: opts.query ?? prompt ?? "",
34259
- limit: opts.limit ?? 5,
34260
- source: opts.source,
34261
- visibility: opts.visibility
34262
- });
34263
- const additionalBlocks = opts.additionalContextFile ? readHookSignalAdditionalContextFile(opts.additionalContextFile) : [];
34264
- writeFileSync7(contextFile, JSON.stringify({ context: [...blocks, ...additionalBlocks] }, null, 2) + "\n", "utf-8");
34265
- const plan = buildCodexSidecarRunCommand({
34266
- workflow,
34267
- projectRoot: opts.project ?? cwd(),
34268
- preset: opts.preset ?? defaultPreset(workflow),
34269
- prompt,
34270
- contextFile,
34271
- cli: cliSpec(opts)
34272
- });
34273
- status2 = executePlan(ctx.logger, plan.command, plan.args, { saveResult: opts.saveResult });
34274
- } finally {
34275
- rmSync3(contextDir, { recursive: true, force: true });
34276
- }
34277
- exit(status2);
34278
- }
34279
- var MAX_ADDITIONAL_CONTEXT_BYTES = 4096;
34280
- function readHookSignalAdditionalContextFile(path, testProbe) {
34281
- const platform = testProbe?.platform ?? process.platform;
34282
- assertWindowsPrivateTempContainer(path, platform);
34283
- const before = lstatSync2(path);
34284
- const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
34285
- assertPrivateRegular(before, uid, platform);
34286
- testProbe?.afterLstat?.();
34287
- const fd = openSync(path, constants4.O_RDONLY | constants4.O_NONBLOCK | (constants4.O_NOFOLLOW ?? 0));
34288
- let raw;
34289
- try {
34290
- const opened = fstatSync(fd);
34291
- assertPrivateRegular(opened, uid, platform);
34292
- if (opened.dev !== before.dev || opened.ino !== before.ino) throw new Error("additional context file changed during open");
34293
- const bytes = Buffer.alloc(MAX_ADDITIONAL_CONTEXT_BYTES + 1);
34294
- let count = 0;
34295
- while (count < bytes.length) {
34296
- const read = readSync2(fd, bytes, count, bytes.length - count, count);
34297
- if (read === 0) break;
34298
- count += read;
34299
- }
34300
- if (count > MAX_ADDITIONAL_CONTEXT_BYTES) throw new Error(`additional context file exceeds ${MAX_ADDITIONAL_CONTEXT_BYTES} bytes`);
34301
- const after = fstatSync(fd);
34302
- assertPrivateRegular(after, uid, platform);
34303
- if (after.dev !== opened.dev || after.ino !== opened.ino || after.dev !== before.dev || after.ino !== before.ino) throw new Error("additional context file changed during read");
34304
- raw = bytes.subarray(0, count).toString("utf-8");
34305
- } finally {
34306
- closeSync(fd);
34307
- }
34308
- const parsed = JSON.parse(raw);
34309
- if (!isRecord(parsed) || !Array.isArray(parsed.context) || parsed.context.length !== 1) {
34310
- throw new Error("additional context file must contain exactly one context block");
34311
- }
34312
- const block = parsed.context[0];
34313
- if (!isHookSignalBlock(block)) {
34314
- throw new Error("additional context file contains an invalid hook signal block");
34315
- }
34316
- return [block];
34317
- }
34318
- function assertPrivateRegular(stat, uid, platform) {
34319
- const privateOwner = isPrivateOwnerStat(stat, uid, platform);
34320
- 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");
34321
- }
34322
- function assertWindowsPrivateTempContainer(path, platform) {
34323
- if (!isWindows(platform)) return;
34324
- const parent = dirname6(path);
34325
- const parentStat = lstatSync2(parent);
34326
- const resolvedParent = realpathSync4(parent);
34327
- const resolvedTemp = realpathSync4(tmpdir4());
34328
- if (parentStat.isSymbolicLink() || !parentStat.isDirectory() || dirname6(resolvedParent) !== resolvedTemp || !basename2(resolvedParent).startsWith("caveat-")) {
34329
- throw new Error("additional context file must be inside a reserved per-user Caveat temporary directory");
34330
- }
34331
- }
34332
- function isRecord(value) {
34333
- return value !== null && typeof value === "object" && !Array.isArray(value);
34334
- }
34335
- function isHookSignalBlock(value) {
34336
- if (!isRecord(value) || !hasExactKeys(value, ["kind", "source", "trust", "summary", "data"]) || value.kind !== "manual_note" || value.source !== "caveat-hook-signal" || value.trust !== "local" || typeof value.summary !== "string" || !isRecord(value.data)) return false;
34337
- const data = value.data;
34338
- if (data.type === "tool-error") {
34339
- if (!hasExactKeys(data, ["type", "tool", "failure_kind"]) || typeof data.tool !== "string" || data.failure_kind !== "post-tool-use-failure" && data.failure_kind !== "error-bearing-post-tool-use") return false;
34340
- const canonical2 = buildHookSignalFromData(data);
34341
- return canonical2 !== null && JSON.stringify(value) === JSON.stringify(canonical2);
34342
- }
34343
- if (!hasExactKeys(data, ["type", "tool_failure_count", "reedited_file_count", "web_search_count", "web_fetch_count", "bash_retry_count", "duration_minutes"])) return false;
34344
- const canonical = buildHookSignalFromData(data);
34345
- return canonical !== null && JSON.stringify(value) === JSON.stringify(canonical);
34346
- }
34347
- function hasExactKeys(value, keys) {
34348
- const actual = Object.keys(value).sort();
34349
- return actual.length === keys.length && actual.every((key, index) => key === keys.slice().sort()[index]);
34350
- }
34351
- function buildHookSignalFromData(data) {
34352
- if (data.type === "tool-error") {
34353
- const validTools = ["bash", "edit", "write", "read", "glob", "grep", "web-search", "web-fetch", "notebook-edit", "other"];
34354
- if (!validTools.includes(String(data.tool))) return null;
34355
- const toolNames = { bash: "Bash", edit: "Edit", write: "Write", read: "Read", glob: "Glob", grep: "Grep", "web-search": "WebSearch", "web-fetch": "WebFetch", "notebook-edit": "NotebookEdit", other: "other" };
34356
- return buildHookSignalSidecarContextBlock({ type: "tool-error", toolName: toolNames[String(data.tool)], failureKind: data.failure_kind });
34357
- }
34358
- if (data.type !== "stop") return null;
34359
- const keys = ["tool_failure_count", "reedited_file_count", "web_search_count", "web_fetch_count", "bash_retry_count", "duration_minutes"];
34360
- if (!keys.every((key) => typeof data[key] === "number" && Number.isInteger(data[key]) && data[key] >= 0 && data[key] <= 1e4)) return null;
34361
- return buildHookSignalSidecarContextBlock({ type: "stop", toolFailureCount: data.tool_failure_count, reeditedFileCount: data.reedited_file_count, webSearchCount: data.web_search_count, webFetchCount: data.web_fetch_count, bashRetryCount: data.bash_retry_count, durationMinutes: data.duration_minutes });
34362
- }
34363
- function runCodexSidecarWorkSmoke(ctx, prompt, opts) {
34364
- const plan = buildCodexSidecarRunCommand({
34365
- workflow: "work",
34366
- projectRoot: opts.project ?? cwd(),
34367
- preset: opts.preset ?? "work",
34368
- prompt: prompt ?? [
34369
- "Create docs/codex-sidecar-work-smoke.md with one sentence:",
34370
- '"codex_work smoke succeeded in an isolated worktree."',
34371
- "Do not edit any other file."
34372
- ].join(" "),
34373
- preserveWorktree: false,
34374
- cli: cliSpec(opts)
34375
- });
34376
- exit(executePlan(ctx.logger, plan.command, plan.args, { saveResult: opts.saveResult }));
34377
- }
34378
- function cliSpec(opts) {
34379
- if (opts.nodeCli) {
34380
- return { command: process.execPath, argsPrefix: [opts.nodeCli] };
34381
- }
34382
- return { command: opts.command ?? "codex-sidecar" };
34383
- }
34384
- function collectCaveatContextBlocks(ctx, opts) {
34385
- const filters = {};
34386
- if (opts.source) filters.source = opts.source;
34387
- if (opts.visibility) filters.visibility = opts.visibility;
34388
- const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
34389
- try {
34390
- const hits = search(db, { query: opts.query, filters, limit: opts.limit });
34391
- if (hits.length > 0) markHit(db, hits);
34392
- const entries = hits.map((hit) => get(db, hit.id, hit.source)).filter((entry) => entry !== null);
34393
- return caveatEntriesToSidecarContextBlocks(entries);
34394
- } finally {
34395
- db.close();
34396
- }
34397
- }
34398
- function defaultPreset(workflow) {
34399
- return workflow === "risk-check" ? "risk" : workflow;
34400
- }
34401
- function executePlan(logger, command, args, options = {}) {
34402
- logger.info(`[codex-sidecar] ${command} ${args.map(shellDisplayQuote).join(" ")}`);
34403
- const result = spawnSync5(command, args, { encoding: "utf-8", stdio: "pipe" });
34404
- if (result.stdout) process.stdout.write(result.stdout);
34405
- if (result.stderr) process.stderr.write(result.stderr);
34406
- if (options.saveResult && result.stdout) {
34407
- saveStructuredResult(options.saveResult, result.stdout);
34408
- }
34409
- if (result.error) {
34410
- logger.error(`[codex-sidecar] ${result.error.message}`);
34411
- return 1;
34412
- }
34413
- return result.status ?? 1;
34414
- }
34415
- function saveStructuredResult(path, stdout) {
34416
- const parsed = JSON.parse(stdout);
34417
- mkdirSync7(dirname6(path), { recursive: true });
34418
- writeFileSync7(path, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
34419
- }
34420
- function shellDisplayQuote(value) {
34421
- return /[\s"'$`]/.test(value) ? JSON.stringify(value) : value;
34422
- }
34423
-
34424
34411
  // src/commands/community.ts
34425
34412
  async function runCommunityAdd(ctx, url2) {
34426
34413
  try {
@@ -34503,7 +34490,7 @@ function runCommunityRemove(ctx, handle, opts) {
34503
34490
 
34504
34491
  // src/commands/factoryDiagnostics.ts
34505
34492
  import { execFileSync } from "node:child_process";
34506
- import { existsSync as existsSync16, readFileSync as readFileSync10 } from "node:fs";
34493
+ import { existsSync as existsSync18, readFileSync as readFileSync12 } from "node:fs";
34507
34494
  import { join as join18 } from "node:path";
34508
34495
  import { DatabaseSync } from "node:sqlite";
34509
34496
  import { parse as parseToml2 } from "smol-toml";
@@ -34516,9 +34503,9 @@ function isRecord2(value) {
34516
34503
  return typeof value === "object" && value !== null && !Array.isArray(value);
34517
34504
  }
34518
34505
  function claudeRegistration(path, nodePath, cliScriptPath) {
34519
- if (!existsSync16(path)) return status(false, "not_registered");
34506
+ if (!existsSync18(path)) return status(false, "not_registered");
34520
34507
  try {
34521
- const value = JSON.parse(readFileSync10(path, "utf8"));
34508
+ const value = JSON.parse(readFileSync12(path, "utf8"));
34522
34509
  if (!isRecord2(value) || !isRecord2(value.mcpServers)) return status(false, "not_registered");
34523
34510
  return status(isCaveatClaudeMcpRegistration(value.mcpServers.caveat, nodePath, cliScriptPath), "not_registered");
34524
34511
  } catch {
@@ -34527,7 +34514,7 @@ function claudeRegistration(path, nodePath, cliScriptPath) {
34527
34514
  }
34528
34515
  function claudeHooks(claudeDir, nodePath, cliScriptPath) {
34529
34516
  try {
34530
- const settings = JSON.parse(readFileSync10(join18(claudeDir, "settings.json"), "utf8"));
34517
+ const settings = JSON.parse(readFileSync12(join18(claudeDir, "settings.json"), "utf8"));
34531
34518
  const present = (event, subcommand) => settings.hooks?.[event]?.some((entry) => entry.hooks?.some((item) => typeof item.command === "string" && isCanonicalCaveatClaudeHookCommand(item.command, subcommand, nodePath, cliScriptPath))) ?? false;
34532
34519
  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")) };
34533
34520
  } catch {
@@ -34549,7 +34536,7 @@ function strictSearchSchema(db) {
34549
34536
  return columns.map((column) => `${column.name}:${column.hidden}`).join(",") === "id:0,title:0,body:0,tags:0,entries_fts:1,rank:1";
34550
34537
  }
34551
34538
  function database(path) {
34552
- if (!existsSync16(path)) return { status: "not_ready", reason_code: "missing", schema_version: null, supported_schema_version: 3, migration_status: "unverified" };
34539
+ if (!existsSync18(path)) return { status: "not_ready", reason_code: "missing", schema_version: null, supported_schema_version: 3, migration_status: "unverified" };
34553
34540
  try {
34554
34541
  const db = new DatabaseSync(path, { readOnly: true });
34555
34542
  try {
@@ -34575,9 +34562,9 @@ function database(path) {
34575
34562
  }
34576
34563
  function codexFeature(codexHome) {
34577
34564
  const path = join18(codexHome, "config.toml");
34578
- if (!existsSync16(path)) return status(false, "feature_disabled");
34565
+ if (!existsSync18(path)) return status(false, "feature_disabled");
34579
34566
  try {
34580
- const config2 = parseToml2(readFileSync10(path, "utf8"));
34567
+ const config2 = parseToml2(readFileSync12(path, "utf8"));
34581
34568
  if (!isRecord2(config2.features)) return status(false, "feature_disabled");
34582
34569
  const features = config2.features;
34583
34570
  return status(features.hooks === true && (features.codex_hooks === void 0 || features.codex_hooks === true), "feature_disabled");
@@ -34586,7 +34573,7 @@ function codexFeature(codexHome) {
34586
34573
  }
34587
34574
  }
34588
34575
  function codexHooks(codexHome, nodePath, cliScriptPath) {
34589
- const value = JSON.parse(readFileSync10(join18(codexHome, "hooks.json"), "utf8"));
34576
+ const value = JSON.parse(readFileSync12(join18(codexHome, "hooks.json"), "utf8"));
34590
34577
  if (!isRecord2(value) || !isRecord2(value.hooks)) throw Error("config_unreadable");
34591
34578
  const hooks = value.hooks;
34592
34579
  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)));
@@ -34662,6 +34649,32 @@ function parseCursor(value) {
34662
34649
  return parsed;
34663
34650
  }
34664
34651
 
34652
+ // src/commands/jev.ts
34653
+ import { existsSync as existsSync19 } from "node:fs";
34654
+ async function enableJev(ctx) {
34655
+ const key = (await readStdin()).trim();
34656
+ if (!key) throw new Error("Jev API key was not provided on stdin");
34657
+ const response = await callJev(key, { purpose: "Caveat connection check" }, {
34658
+ probe: { type: "noul", instructions: "Is this a connection check?" }
34659
+ });
34660
+ if (response.answers.probe?.type !== "noul") throw new Error("Jev connection check returned an invalid answer");
34661
+ writeJevKey(ctx.caveatHome, key);
34662
+ writeUserConfigPatch(ctx.userConfigPath, { jevEnabled: true });
34663
+ process.stdout.write("Jev\u306B\u3088\u308B\u82E6\u6226\u5224\u5B9A\u3092\u6709\u52B9\u306B\u3057\u307E\u3057\u305F\u3002\n");
34664
+ }
34665
+ function disableJev(ctx) {
34666
+ writeUserConfigPatch(ctx.userConfigPath, { jevEnabled: false });
34667
+ process.stdout.write("Jev\u306B\u3088\u308B\u82E6\u6226\u5224\u5B9A\u3092\u7121\u52B9\u306B\u3057\u307E\u3057\u305F\u3002\n");
34668
+ }
34669
+ function jevStatus(ctx) {
34670
+ process.stdout.write(`${JSON.stringify({
34671
+ schema: "caveat.jev_status.v1",
34672
+ enabled: ctx.config.jevEnabled,
34673
+ credential: existsSync19(jevKeyPath(ctx.caveatHome)) ? "present" : "missing"
34674
+ })}
34675
+ `);
34676
+ }
34677
+
34665
34678
  // src/index.ts
34666
34679
  var program = new Command();
34667
34680
  var agentPaths = resolveAgentConfigPaths();
@@ -34679,6 +34692,16 @@ program.command("factory-diagnostics").description("Emit read-only factory diagn
34679
34692
  if (output.overall.status !== "ready") process.exitCode = 1;
34680
34693
  });
34681
34694
  var runtimeErrors = program.command("runtime-errors").description("Inspect Caveat opt-in runtime errors");
34695
+ var jev = program.command("jev").description("Configure Jev struggle detection");
34696
+ jev.command("enable").requiredOption("--key-stdin", "read the TypeSafe API key from stdin").action(async () => {
34697
+ await enableJev(buildContext(stdoutLogger));
34698
+ });
34699
+ jev.command("disable").action(() => {
34700
+ disableJev(buildContext(stdoutLogger));
34701
+ });
34702
+ jev.command("status").action(() => {
34703
+ jevStatus(buildContext(stdoutLogger));
34704
+ });
34682
34705
  var parseLimit = (value) => {
34683
34706
  const parsed = parseCursor(value);
34684
34707
  if (parsed < 1 || parsed > 256) throw Error("invalid_limit");
@@ -34696,8 +34719,8 @@ runtimeErrors.command("ack <cursor>").requiredOption("--json", "emit JSON").acti
34696
34719
  process.stdout.write(`${JSON.stringify(runRuntimeErrors("ack", cursor))}
34697
34720
  `);
34698
34721
  });
34699
- for (const action of ["resolve", "reopen"]) runtimeErrors.command(`${action} <fingerprint>`).requiredOption("--json", "emit JSON").action((fingerprint) => {
34700
- process.stdout.write(`${JSON.stringify(runRuntimeErrors(action, fingerprint))}
34722
+ for (const action of ["resolve", "reopen"]) runtimeErrors.command(`${action} <fingerprint>`).requiredOption("--json", "emit JSON").action((fingerprint2) => {
34723
+ process.stdout.write(`${JSON.stringify(runRuntimeErrors(action, fingerprint2))}
34701
34724
  `);
34702
34725
  });
34703
34726
  program.command("init").description(
@@ -34902,30 +34925,6 @@ cursorHook.command("post-tool-use").description("Run the Cursor postToolUse / po
34902
34925
  cursorHook.command("stop").description("Run the Cursor stop hook").action(async () => {
34903
34926
  await runCursorHook("stop");
34904
34927
  });
34905
- var codexSidecar = program.command("codex-sidecar").description("Check Codex sidecar availability for the current repository");
34906
- codexSidecar.command("diagnostics").description("Run codex-sidecar diagnostics for this repository").option("--project <path>", "repository root to check").option("--preset <preset>", "codex-sidecar preset", "review").option("--command <command>", "codex-sidecar executable", "codex-sidecar").option("--node-cli <path>", "development path to codex-sidecar CLI JS").option("--save-result <path>", "write structured SidecarResult JSON to this path").action((opts) => {
34907
- runCodexSidecarDiagnostics(stdoutLogger, opts);
34908
- });
34909
- codexSidecar.command("smoke").description("Run a read-only codex_explore smoke through codex-sidecar").option("--project <path>", "repository root to check").option("--preset <preset>", "codex-sidecar read-only preset", "explore").option("--command <command>", "codex-sidecar executable", "codex-sidecar").option("--node-cli <path>", "development path to codex-sidecar CLI JS").option("--save-result <path>", "write structured SidecarResult JSON to this path").action((opts) => {
34910
- runCodexSidecarSmoke(stdoutLogger, opts);
34911
- });
34912
- codexSidecar.command("run <workflow> [prompt]").description("Run read-only codex-sidecar with relevant Caveat entries as context").option("--project <path>", "repository root to check").option("--preset <preset>", "codex-sidecar preset").option("--query <query>", "Caveat search query; defaults to prompt text").option("--limit <n>", "maximum caveat entries to pass", (v) => Number(v), 5).option("--source <source>", "own | community | all", "all").option("--visibility <visibility>", "public | private | all", "all").option("--host-agent <agent>", "claude | codex | automation | unknown", "unknown").option("--availability <level>", "disabled | unavailable | configured | operational | work-capable", "operational").option("--sidecar-agent <agent>", "codex | disabled | auto").option("--requires-isolation", "mark the sidecar call as isolation-bounded", false).option("--structured-result-required", "mark structured SidecarResult as the delegation boundary", false).option("--explicit-second-pass", "mark the sidecar call as an explicit second-pass review", false).option("--command <command>", "codex-sidecar executable", "codex-sidecar").option("--node-cli <path>", "development path to codex-sidecar CLI JS").option("--save-result <path>", "write structured SidecarResult JSON to this path").option("--additional-context-file <path>", "strictly validated bounded hook-signal context file").action(
34913
- (workflow, prompt, opts) => {
34914
- const normalized = workflow === "risk" ? "risk-check" : workflow;
34915
- if (normalized !== "review" && normalized !== "explore" && normalized !== "opinion" && normalized !== "risk-check") {
34916
- process.stderr.write("[caveat:error] workflow must be review | explore | opinion | risk-check\n");
34917
- process.exit(1);
34918
- }
34919
- const ctx = buildContext(stdoutLogger);
34920
- runCodexSidecarWithCaveats(ctx, normalized, prompt, opts);
34921
- }
34922
- );
34923
- codexSidecar.command("work-smoke [prompt]").description("Run codex_work in an isolated worktree and remove it after verification").option("--project <path>", "repository root to check").option("--preset <preset>", "codex-sidecar work preset", "work").option("--command <command>", "codex-sidecar executable", "codex-sidecar").option("--node-cli <path>", "development path to codex-sidecar CLI JS").option("--save-result <path>", "write structured SidecarResult JSON to this path").action(
34924
- (prompt, opts) => {
34925
- const ctx = buildContext(stdoutLogger);
34926
- runCodexSidecarWorkSmoke(ctx, prompt, opts);
34927
- }
34928
- );
34929
34928
  var community = program.command("community").description("Manage community caveat repos (shallow clones under <caveatHome>/community/)");
34930
34929
  community.command("add <url>").description("Shallow-clone a GitHub caveat repo into community/<handle>/").action(async (url2) => {
34931
34930
  const ctx = buildContext(stdoutLogger);