caveat-cli 0.19.2 → 0.19.3

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-RO2ZVJMX.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-INQI6ANK.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,309 @@ 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
+ title: hit.title,
33280
+ symptom: hit.symptomExcerpt,
33281
+ cause: entry.sections.Cause?.slice(0, 300) ?? "",
33282
+ resolution: entry.sections.Resolution?.slice(0, 600) ?? "",
33283
+ environment: hit.environment
33284
+ }))
33285
+ };
33286
+ const questions = Object.fromEntries(candidates.map((_, index) => [`candidate_${index}`, {
33287
+ type: "noul",
33288
+ instructions: `Does candidates[${index}] directly apply to the unresolved problem shown in the turns and offer a useful resolution? Check the described environment against the task's target environment; currentHostOs is the host OS, which may differ from the target OS.`
33289
+ }]));
33290
+ const response = await ask(key, state, questions);
33291
+ let best = null;
33292
+ let bestScore = MATCH_THRESHOLD;
33293
+ for (let index = 0; index < candidates.length; index++) {
33294
+ const score = noul(response.answers[`candidate_${index}`]);
33295
+ if (score >= MATCH_THRESHOLD && (best === null || score > bestScore)) {
33296
+ best = candidates[index];
33297
+ bestScore = score;
33298
+ }
33299
+ }
33300
+ return best;
33301
+ }
33302
+ function quotePowerShell(value) {
33303
+ return `'${value.replaceAll("'", "''")}'`;
33304
+ }
33305
+ function readThroughlineContext(host, sessionId, projectRoot, transcriptPath) {
33306
+ const sourceId = host === "codex" && !sessionId.startsWith("codex:") ? `codex:${sessionId}` : sessionId;
33307
+ const args = ["caveat-context", "--session", sourceId, "--project", projectRoot, "--host", host, "--transcript", transcriptPath, "--json"];
33308
+ 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 };
33309
+ const result = spawnSync3(invocation.command, invocation.args, { encoding: "utf8", timeout: 1500, maxBuffer: 128 * 1024, windowsHide: true });
33310
+ if (result.error || result.status !== 0) throw new Error("throughline_context_failed");
33311
+ const value = JSON.parse(result.stdout);
33312
+ if (!isRecord(value) || value.schema !== "throughline.caveat_context.v1" || typeof value.status !== "string" || !Array.isArray(value.turns)) {
33313
+ throw new Error("throughline_context_invalid");
33314
+ }
33315
+ return value;
33316
+ }
33317
+ function statePath2(caveatHome, sessionId) {
33318
+ const digest = createHash3("sha256").update(sessionId).digest("hex");
33319
+ return join14(caveatHome, "jev-sessions", `${digest}.json`);
33320
+ }
33321
+ function loadState(caveatHome, sessionId) {
33322
+ const path = statePath2(caveatHome, sessionId);
33323
+ if (!existsSync14(path)) return { lastTurn: null, deliveredIds: [], notifiedTerms: [] };
33324
+ const value = JSON.parse(readFileSync9(path, "utf8"));
33325
+ 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");
33326
+ return value;
33327
+ }
33328
+ function saveState(caveatHome, sessionId, state) {
33329
+ const path = statePath2(caveatHome, sessionId);
33330
+ mkdirSync8(join14(caveatHome, "jev-sessions"), { recursive: true, mode: 448 });
33331
+ const temporary = `${path}.${randomBytes3(6).toString("hex")}.tmp`;
33332
+ try {
33333
+ writeFileSync6(temporary, `${JSON.stringify(state)}
33334
+ `, { encoding: "utf8", mode: 384, flag: "wx" });
33335
+ renameSync4(temporary, path);
33336
+ } finally {
33337
+ rmSync3(temporary, { force: true });
33338
+ }
33339
+ }
33340
+ function reminder(hit) {
33341
+ 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";
33342
+ const resolution = hit.entry.sections.Resolution?.replace(/\s+/g, " ").trim().slice(0, 500) ?? "";
33343
+ 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})
33344
+ \u5BFE\u51E6: ${resolution}
33345
+ \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`;
33346
+ }
33347
+ async function runJevStruggle(ctx, host, payload, deps = {}) {
33348
+ if (!ctx.config.jevEnabled) return null;
33349
+ const sessionId = payload.session_id ?? payload.sessionId;
33350
+ const transcriptPath = payload.transcript_path ?? payload.transcriptPath;
33351
+ if (typeof sessionId !== "string" || !sessionId || typeof transcriptPath !== "string" || !transcriptPath) {
33352
+ throw new Error("jev_hook_context_missing");
33353
+ }
33354
+ const projectRoot = typeof payload.cwd === "string" && payload.cwd ? payload.cwd : process.cwd();
33355
+ const context = (deps.readContext ?? readThroughlineContext)(host, sessionId, projectRoot, transcriptPath);
33356
+ if (["unavailable", "incomplete", "projection_pending"].includes(context.status)) return null;
33357
+ if (context.status !== "ready" || context.turns.length !== 3) throw new Error(`throughline_context_${context.status}`);
33358
+ const latest = context.turns[2];
33359
+ const turnKey = `${latest.originSessionId}:${latest.turnNumber}`;
33360
+ const state = loadState(ctx.caveatHome, sessionId);
33361
+ if (state.lastTurn === turnKey) return null;
33362
+ const key = (deps.readKey ?? readJevKey)(ctx.caveatHome);
33363
+ const ask = deps.ask ?? callJev;
33364
+ const judgment = await judgeStruggle(key, context.turns, ask);
33365
+ state.lastTurn = turnKey;
33366
+ if (!judgment.struggling) {
33367
+ saveState(ctx.caveatHome, sessionId, state);
33368
+ return null;
33369
+ }
33370
+ let candidates = [];
33371
+ if (judgment.term) {
33372
+ if (!existsSync14(ctx.paths.dbPath)) throw new Error("jev_knowledge_index_missing");
33373
+ const db = openDb({ path: ctx.paths.dbPath });
33374
+ try {
33375
+ const taskText = context.turns.map((turn) => `${turn.user}
33376
+ ${turn.assistant}
33377
+ ${turn.thinking}`).join("\n");
33378
+ 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));
33379
+ } finally {
33380
+ db.close();
33381
+ }
33382
+ }
33383
+ const selected = await rankKnowledge(key, context.turns, candidates, ask);
33384
+ const id = selected ? `${selected.hit.source}/${selected.hit.id}` : null;
33385
+ const term = judgment.term ?? "_none";
33386
+ if (id && (state.deliveredIds.includes(id) || wasHitDelivered(ctx.caveatHome, sessionId, id)) || !id && state.notifiedTerms.includes(term)) {
33387
+ saveState(ctx.caveatHome, sessionId, state);
33388
+ return null;
33389
+ }
33390
+ return {
33391
+ text: reminder(selected),
33392
+ ref: id,
33393
+ delivered: () => {
33394
+ const current = loadState(ctx.caveatHome, sessionId);
33395
+ current.lastTurn = turnKey;
33396
+ if (id && !current.deliveredIds.includes(id)) current.deliveredIds.push(id);
33397
+ if (!id && !current.notifiedTerms.includes(term)) current.notifiedTerms.push(term);
33398
+ saveState(ctx.caveatHome, sessionId, current);
33399
+ if (selected) {
33400
+ const db = openDb({ path: ctx.paths.dbPath });
33401
+ try {
33402
+ markHit(db, [selected.hit]);
33403
+ } finally {
33404
+ db.close();
33405
+ }
33406
+ }
33407
+ }
33408
+ };
33409
+ }
33410
+
33206
33411
  // src/commands/hookCmd.ts
33207
33412
  var CLAUDE_HOST = {
33208
33413
  agent: "claude",
@@ -33262,10 +33467,10 @@ function spawnWorker(job) {
33262
33467
  try {
33263
33468
  root = workerRoot();
33264
33469
  sweepStaleWorkerDirs(Date.now(), root);
33265
- workDir = mkdtempSync2(join14(root, "job-"));
33470
+ workDir = mkdtempSync(join15(root, "job-"));
33266
33471
  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" });
33472
+ workFile = join15(workDir, `${randomBytes4(4).toString("hex")}.json`);
33473
+ writeFileSync7(workFile, JSON.stringify({ ...job, schemaVersion: "caveat-worker-job/v2" }), { encoding: "utf-8", mode: 384, flag: "wx" });
33269
33474
  } catch (err) {
33270
33475
  process.stderr.write(`[caveat:hook] worker writefile error: ${errorMessage4(err)}
33271
33476
  `);
@@ -33296,7 +33501,7 @@ function spawnWorker(job) {
33296
33501
  async function runWorker(workFile) {
33297
33502
  let raw;
33298
33503
  try {
33299
- raw = readFileSync8(workFile, "utf-8");
33504
+ raw = readFileSync10(workFile, "utf-8");
33300
33505
  } catch {
33301
33506
  process.exit(0);
33302
33507
  }
@@ -33322,7 +33527,7 @@ async function runWorker(workFile) {
33322
33527
  agent: "claude",
33323
33528
  surface: "tool_error",
33324
33529
  refs: hits
33325
- }), () => buildToolErrorReminder(job, hits));
33530
+ }), () => toolErrorReminderText(hits));
33326
33531
  } catch {
33327
33532
  process.stderr.write("[caveat:hook] pending reminder build or publish failed\n");
33328
33533
  process.exit(0);
@@ -33333,17 +33538,17 @@ async function runWorker(workFile) {
33333
33538
  function cleanupWorkerDir(workDir, root) {
33334
33539
  if (!workDir || !root || !isOwnedWorkerDir(workDir, root)) return;
33335
33540
  try {
33336
- rmSync2(workDir, { recursive: true, force: true });
33541
+ rmSync4(workDir, { recursive: true, force: true });
33337
33542
  } catch {
33338
33543
  }
33339
33544
  }
33340
33545
  function isOwnedWorkerDir(path, root = workerRoot()) {
33341
33546
  try {
33342
- const inputStat = lstatSync(path);
33547
+ const inputStat = lstatSync2(path);
33343
33548
  if (inputStat.isSymbolicLink()) return false;
33344
33549
  const tmpRoot = realpathSync3(root);
33345
33550
  const resolved = realpathSync3(path);
33346
- const stat = lstatSync(resolved);
33551
+ const stat = lstatSync2(resolved);
33347
33552
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
33348
33553
  return dirname5(resolved) === tmpRoot && basename(resolved).startsWith("job-") && stat.isDirectory() && !stat.isSymbolicLink() && hasPrivateOwnership(stat, uid);
33349
33554
  } catch {
@@ -33360,41 +33565,41 @@ function sweepStaleWorkerDirs(now = Date.now(), root = workerRoot()) {
33360
33565
  }
33361
33566
  for (const entry of entries) {
33362
33567
  if (!entry.startsWith("job-")) continue;
33363
- const path = join14(root, entry);
33568
+ const path = join15(root, entry);
33364
33569
  try {
33365
33570
  if (!isOwnedWorkerDir(path, root)) continue;
33366
- const stat = lstatSync(path);
33571
+ const stat = lstatSync2(path);
33367
33572
  if (now - stat.mtimeMs <= WORKER_STALE_MS || !isStaleWorkerJobDir(path)) continue;
33368
- rmSync2(path, { recursive: true, force: true });
33573
+ rmSync4(path, { recursive: true, force: true });
33369
33574
  } catch {
33370
33575
  process.stderr.write("[caveat:hook] worker stale cleanup failed\n");
33371
33576
  }
33372
33577
  }
33373
33578
  }
33374
- function workerRoot(base = tmpdir2()) {
33375
- const root = join14(base, WORKER_ROOT);
33376
- mkdirSync6(root, { recursive: true, mode: 448 });
33377
- const stat = lstatSync(root);
33579
+ function workerRoot(base = tmpdir()) {
33580
+ const root = join15(base, WORKER_ROOT);
33581
+ mkdirSync9(root, { recursive: true, mode: 448 });
33582
+ const stat = lstatSync2(root);
33378
33583
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
33379
33584
  if (!stat.isDirectory() || stat.isSymbolicLink() || !hasPrivateOwnership(stat, uid)) throw new Error("worker root is unsafe");
33380
- const marker = join14(root, WORKER_MARKER);
33585
+ const marker = join15(root, WORKER_MARKER);
33381
33586
  try {
33382
- writeFileSync5(marker, "caveat-worker/v1\n", { mode: 384, flag: "wx" });
33587
+ writeFileSync7(marker, "caveat-worker/v1\n", { mode: 384, flag: "wx" });
33383
33588
  } catch (error51) {
33384
33589
  if (!(error51 && typeof error51 === "object" && "code" in error51 && error51.code === "EEXIST")) throw error51;
33385
33590
  }
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");
33591
+ const markerStat = lstatSync2(marker);
33592
+ 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
33593
  return root;
33389
33594
  }
33390
33595
  function isStaleWorkerJobDir(path) {
33391
33596
  const entries = readdirSync2(path);
33392
33597
  if (entries.length !== 1 || !entries[0].endsWith(".json")) return false;
33393
- const file2 = join14(path, entries[0]);
33394
- const stat = lstatSync(file2);
33598
+ const file2 = join15(path, entries[0]);
33599
+ const stat = lstatSync2(file2);
33395
33600
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
33396
33601
  if (!stat.isFile() || stat.isSymbolicLink() || !hasPrivateOwnership(stat, uid)) return false;
33397
- return isKnownStaleWorkerJob(JSON.parse(readFileSync8(file2, "utf-8")));
33602
+ return isKnownStaleWorkerJob(JSON.parse(readFileSync10(file2, "utf-8")));
33398
33603
  }
33399
33604
  function hasPrivateOwnership(stat, uid) {
33400
33605
  return isPrivateOwnerStat(stat, uid);
@@ -33416,7 +33621,7 @@ function isKnownStaleWorkerJob(value) {
33416
33621
  }
33417
33622
  function writeLastReindex(caveatHome, value) {
33418
33623
  try {
33419
- writeFileSync5(join14(caveatHome, "index", ".last-reindex.json"), JSON.stringify(value), "utf-8");
33624
+ writeFileSync7(join15(caveatHome, "index", ".last-reindex.json"), JSON.stringify(value), "utf-8");
33420
33625
  } catch (err) {
33421
33626
  process.stderr.write(`[caveat:hook] reindex status write error: ${errorMessage4(err)}
33422
33627
  `);
@@ -33428,7 +33633,7 @@ async function runReindexWorker() {
33428
33633
  return;
33429
33634
  }
33430
33635
  const ctx = buildContextSafely(CLAUDE_HOST);
33431
- if (!ctx || !existsSync13(ctx.paths.dbPath)) {
33636
+ if (!ctx || !existsSync15(ctx.paths.dbPath)) {
33432
33637
  process.stderr.write("[caveat:hook] auto reindex skipped: index database does not exist\n");
33433
33638
  return;
33434
33639
  }
@@ -33490,81 +33695,6 @@ async function runAutoSyncWorker() {
33490
33695
  `);
33491
33696
  }
33492
33697
  }
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
33698
  async function runHook(name, arg) {
33569
33699
  try {
33570
33700
  sweepStaleWorkerDirs();
@@ -33594,39 +33724,65 @@ async function runHook(name, arg) {
33594
33724
  }
33595
33725
  const payload = parsePayload(CLAUDE_HOST, raw);
33596
33726
  const sessionId = getSessionId(payload);
33597
- const contexts = name === "stop" ? [] : drainForSession(CLAUDE_HOST, sessionId);
33727
+ const pending = name === "stop" ? null : peekForSession(CLAUDE_HOST, sessionId);
33728
+ const contexts = pending?.contexts ?? [];
33598
33729
  if (name === "user-prompt-submit") {
33730
+ const ctx = buildContextSafely(CLAUDE_HOST);
33731
+ let jevNotice = null;
33732
+ if (ctx?.config.jevEnabled) {
33733
+ try {
33734
+ jevNotice = await runJevStruggle(ctx, "claude", payload);
33735
+ if (jevNotice) contexts.push(jevNotice.text);
33736
+ } catch (err) {
33737
+ process.stderr.write(`[caveat:hook] Jev\u5224\u5B9A\u306B\u5931\u6557: ${errorMessage4(err)}
33738
+ `);
33739
+ }
33740
+ }
33599
33741
  const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
33600
33742
  const hits = searchCaveatsSafely(CLAUDE_HOST, { topicText: prompt, failureText: prompt, surface: "user_prompt" });
33601
33743
  if (hits.length > 0) {
33602
33744
  contexts.push(userPromptSubmitReminderText(hits));
33603
33745
  }
33604
- const compacted = compactContexts(CLAUDE_HOST, contexts);
33605
- if (compacted.length > 0) {
33606
- process.stdout.write(`${systemReminderOutput(compacted.join("\n\n"))}
33746
+ if (ctx) {
33747
+ try {
33748
+ const prepared = prepareSessionDelivery(ctx.caveatHome, sessionId, contexts, jevNotice?.ref ? [jevNotice.ref] : []);
33749
+ const sent = prepared.texts.length === 0 || await emitHookContext(CLAUDE_HOST, `${systemReminderOutput(prepared.texts.join("\n\n"))}
33607
33750
  `);
33751
+ if (sent) {
33752
+ prepared.delivered();
33753
+ if (jevNotice && prepared.texts.includes(jevNotice.text)) jevNotice.delivered();
33754
+ if (pending) acknowledgeSessionReminders(CLAUDE_HOST, pending);
33755
+ }
33756
+ } catch (err) {
33757
+ process.stderr.write(`[caveat:hook] \u914D\u9001\u306B\u5931\u6557: ${errorMessage4(err)}
33758
+ `);
33759
+ }
33608
33760
  }
33609
33761
  process.exit(0);
33610
33762
  }
33611
33763
  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"))}
33764
+ const ctx = buildContextSafely(CLAUDE_HOST);
33765
+ if (ctx) {
33766
+ try {
33767
+ const prepared = prepareSessionDelivery(ctx.caveatHome, sessionId, contexts);
33768
+ const sent = prepared.texts.length === 0 || await emitHookContext(CLAUDE_HOST, `${systemReminderOutput(prepared.texts.join("\n\n"))}
33769
+ `);
33770
+ if (sent) {
33771
+ prepared.delivered();
33772
+ if (pending) acknowledgeSessionReminders(CLAUDE_HOST, pending);
33773
+ }
33774
+ } catch (err) {
33775
+ process.stderr.write(`[caveat:hook] \u914D\u9001\u306B\u5931\u6557: ${errorMessage4(err)}
33615
33776
  `);
33777
+ }
33616
33778
  }
33617
33779
  if (!isToolError(payload)) process.exit(0);
33618
33780
  const errText = extractToolResponseText(
33619
33781
  payload.tool_response ?? payload.toolResponse ?? payload.error ?? payload
33620
33782
  );
33621
33783
  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
33784
  const topicText = toolTopicText(payload);
33629
- spawnWorker({ sessionId, topicText, failureText: errText, ...additionalContext ? { additionalContext } : {} });
33785
+ spawnWorker({ sessionId, topicText, failureText: errText });
33630
33786
  }
33631
33787
  process.exit(0);
33632
33788
  }
@@ -33653,6 +33809,7 @@ async function runHook(name, arg) {
33653
33809
  }
33654
33810
  }
33655
33811
  if (payload.stop_hook_active === true) process.exit(0);
33812
+ if (ctx?.config.jevEnabled) process.exit(0);
33656
33813
  const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
33657
33814
  const signals = transcriptPath ? loadSignalsSafely(transcriptPath) : null;
33658
33815
  if (!signals || !hasAnyStruggleSignal(signals)) process.exit(0);
@@ -33661,7 +33818,7 @@ async function runHook(name, arg) {
33661
33818
  failureText,
33662
33819
  surface: "stop"
33663
33820
  })));
33664
- queueStopForSession(CLAUDE_HOST, sessionId, signals, related, () => buildStopReminder(signals, related));
33821
+ queueStopForSession(CLAUDE_HOST, sessionId, signals, related, () => stopReminderText(signals, related));
33665
33822
  process.exit(0);
33666
33823
  }
33667
33824
  process.stderr.write(`[caveat:hook] unknown hook name: ${name}
@@ -33671,10 +33828,10 @@ async function runHook(name, arg) {
33671
33828
 
33672
33829
  // src/commands/codexHookCmd.ts
33673
33830
  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";
33831
+ import { existsSync as existsSync16, readFileSync as readFileSync11, unlinkSync, writeFileSync as writeFileSync8 } from "node:fs";
33832
+ import { homedir as homedir4, tmpdir as tmpdir2 } from "node:os";
33833
+ import { join as join16 } from "node:path";
33834
+ import { randomBytes as randomBytes5 } from "node:crypto";
33678
33835
  var CODEX_HOST = {
33679
33836
  agent: "codex",
33680
33837
  stderrTag: "caveat:codex-hook",
@@ -33703,10 +33860,10 @@ function processExitCodeFromText(text) {
33703
33860
  return m ? Number(m[1]) : null;
33704
33861
  }
33705
33862
  function transcriptToolOutput(transcriptPath, toolUseId) {
33706
- if (!transcriptPath || !toolUseId || !existsSync14(transcriptPath)) return null;
33863
+ if (!transcriptPath || !toolUseId || !existsSync16(transcriptPath)) return null;
33707
33864
  let raw = "";
33708
33865
  try {
33709
- raw = readFileSync9(transcriptPath, "utf-8");
33866
+ raw = readFileSync11(transcriptPath, "utf-8");
33710
33867
  } catch {
33711
33868
  return null;
33712
33869
  }
@@ -33757,8 +33914,8 @@ function isCodexToolError(payload) {
33757
33914
  if (resp !== null && typeof resp === "object" && !Array.isArray(resp)) {
33758
33915
  const r = resp;
33759
33916
  if (r.is_error === true) return true;
33760
- const exit2 = numericExitCode(r.exit_code ?? r.exitCode);
33761
- if (exit2 !== null) return exit2 !== 0;
33917
+ const exit = numericExitCode(r.exit_code ?? r.exitCode);
33918
+ if (exit !== null) return exit !== 0;
33762
33919
  }
33763
33920
  const responseExit = processExitCodeFromText(extractToolResponseText(resp));
33764
33921
  if (responseExit !== null) return responseExit !== 0;
@@ -33817,9 +33974,9 @@ async function processCodexWorkerJob(job, opts = { waitForTranscript: true }) {
33817
33974
  if (opts.waitForTranscript && job.transcriptPath && job.toolUseId) {
33818
33975
  const transcriptOutput = await waitForTranscriptOutput(job.transcriptPath, job.toolUseId);
33819
33976
  if (transcriptOutput) {
33820
- const exit2 = processExitCodeFromText(transcriptOutput);
33821
- if (exit2 !== null) {
33822
- if (exit2 === 0) return;
33977
+ const exit = processExitCodeFromText(transcriptOutput);
33978
+ if (exit !== null) {
33979
+ if (exit === 0) return;
33823
33980
  knownError = true;
33824
33981
  }
33825
33982
  failureText = [failureText, transcriptOutput].filter(Boolean).join("\n");
@@ -33850,7 +34007,7 @@ async function processCodexWorkerJob(job, opts = { waitForTranscript: true }) {
33850
34007
  async function runCodexWorker(workFile) {
33851
34008
  let raw;
33852
34009
  try {
33853
- raw = readFileSync9(workFile, "utf-8");
34010
+ raw = readFileSync11(workFile, "utf-8");
33854
34011
  } catch {
33855
34012
  process.exit(0);
33856
34013
  }
@@ -33867,7 +34024,7 @@ async function runCodexWorker(workFile) {
33867
34024
  await processCodexWorkerJob(job);
33868
34025
  process.exit(0);
33869
34026
  }
33870
- function runDiagnostics(codexHome = process.env.CODEX_HOME ?? join15(homedir4(), ".codex")) {
34027
+ function runDiagnostics(codexHome = process.env.CODEX_HOME ?? join16(homedir4(), ".codex")) {
33871
34028
  const features = spawnSync4("codex", ["features", "list"], {
33872
34029
  encoding: "utf-8",
33873
34030
  maxBuffer: 1024 * 1024,
@@ -33915,16 +34072,38 @@ async function runCodexHook(name, arg) {
33915
34072
  const sessionId = codexSessionId(payload);
33916
34073
  if (!sessionId) process.stderr.write("[caveat:codex-hook] missing session_id; pending drain disabled\n");
33917
34074
  if (name === "user-prompt-submit") {
33918
- const contexts = sessionId ? drainForSession(CODEX_HOST, sessionId) : [];
34075
+ const pending = sessionId ? peekForSession(CODEX_HOST, sessionId) : null;
34076
+ const contexts = pending?.contexts ?? [];
34077
+ const ctx = buildContextSafely(CODEX_HOST);
34078
+ let jevNotice = null;
34079
+ if (ctx?.config.jevEnabled) {
34080
+ try {
34081
+ jevNotice = await runJevStruggle(ctx, "codex", payload);
34082
+ if (jevNotice) contexts.push(jevNotice.text);
34083
+ } catch (err) {
34084
+ process.stderr.write(`[caveat:codex-hook] Jev\u5224\u5B9A\u306B\u5931\u6557: ${errorMessage4(err)}
34085
+ `);
34086
+ }
34087
+ }
33919
34088
  const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
33920
34089
  const hits = searchCaveatsSafely(CODEX_HOST, { topicText: prompt, failureText: prompt, surface: "user_prompt" });
33921
34090
  if (hits.length > 0) {
33922
34091
  contexts.push(userPromptSubmitReminderText(hits, "native-cli"));
33923
34092
  }
33924
- const compacted = compactContexts(CODEX_HOST, contexts);
33925
- if (compacted.length > 0) {
33926
- process.stdout.write(`${codexContextOutput(compacted.join("\n\n"))}
34093
+ if (ctx) {
34094
+ try {
34095
+ const prepared = prepareSessionDelivery(ctx.caveatHome, sessionId ?? "_unknown", contexts, jevNotice?.ref ? [jevNotice.ref] : []);
34096
+ const sent = prepared.texts.length === 0 || await emitHookContext(CODEX_HOST, `${codexContextOutput(prepared.texts.join("\n\n"))}
34097
+ `);
34098
+ if (sent) {
34099
+ prepared.delivered();
34100
+ if (jevNotice && prepared.texts.includes(jevNotice.text)) jevNotice.delivered();
34101
+ if (pending) acknowledgeSessionReminders(CODEX_HOST, pending);
34102
+ }
34103
+ } catch (err) {
34104
+ process.stderr.write(`[caveat:codex-hook] \u914D\u9001\u306B\u5931\u6557: ${errorMessage4(err)}
33927
34105
  `);
34106
+ }
33928
34107
  }
33929
34108
  process.exit(0);
33930
34109
  }
@@ -33956,6 +34135,7 @@ async function runCodexHook(name, arg) {
33956
34135
  `);
33957
34136
  }
33958
34137
  if (payload.stop_hook_active === true) process.exit(0);
34138
+ if (buildContextSafely(CODEX_HOST)?.config.jevEnabled) process.exit(0);
33959
34139
  const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
33960
34140
  const signals = transcriptPath ? loadSignalsSafely2(transcriptPath) : null;
33961
34141
  if (!signals || !hasAnyStruggleSignal(signals)) process.exit(0);
@@ -33974,7 +34154,7 @@ async function runCodexHook(name, arg) {
33974
34154
 
33975
34155
  // src/commands/cursorHookCmd.ts
33976
34156
  import { homedir as homedir5 } from "node:os";
33977
- import { join as join16 } from "node:path";
34157
+ import { join as join17 } from "node:path";
33978
34158
  var CURSOR_HOST = {
33979
34159
  agent: "cursor",
33980
34160
  stderrTag: "caveat:cursor-hook",
@@ -34014,7 +34194,7 @@ function loadSignalsSafely3(path) {
34014
34194
  return null;
34015
34195
  }
34016
34196
  }
34017
- function runDiagnostics2(cursorDir = join16(homedir5(), ".cursor")) {
34197
+ function runDiagnostics2(cursorDir = join17(homedir5(), ".cursor")) {
34018
34198
  const installation = detectCursorHookInstallation(cursorDir);
34019
34199
  process.stdout.write(`${JSON.stringify({
34020
34200
  installation: installation.installation,
@@ -34039,7 +34219,24 @@ async function runCursorHook(name, arg) {
34039
34219
  }
34040
34220
  const payload = parsePayload(CURSOR_HOST, raw);
34041
34221
  const sessionId = cursorSessionId(payload);
34042
- const contexts = name === "stop" ? [] : drainForSession(CURSOR_HOST, sessionId);
34222
+ const pending = name === "stop" ? null : peekForSession(CURSOR_HOST, sessionId);
34223
+ const contexts = pending?.contexts ?? [];
34224
+ const deliver = async () => {
34225
+ const ctx = buildContextSafely(CURSOR_HOST);
34226
+ if (!ctx) return;
34227
+ try {
34228
+ const prepared = prepareSessionDelivery(ctx.caveatHome, sessionId, contexts);
34229
+ const sent = prepared.texts.length === 0 || await emitHookContext(CURSOR_HOST, `${cursorContextOutput(prepared.texts.join("\n\n"))}
34230
+ `);
34231
+ if (sent) {
34232
+ prepared.delivered();
34233
+ if (pending) acknowledgeSessionReminders(CURSOR_HOST, pending);
34234
+ }
34235
+ } catch (err) {
34236
+ process.stderr.write(`[caveat:cursor-hook] \u914D\u9001\u306B\u5931\u6557: ${errorMessage4(err)}
34237
+ `);
34238
+ }
34239
+ };
34043
34240
  if (name === "user-prompt-submit") {
34044
34241
  const prompt = cursorPrompt(payload);
34045
34242
  const hits = searchCaveatsSafely(CURSOR_HOST, {
@@ -34048,11 +34245,7 @@ async function runCursorHook(name, arg) {
34048
34245
  surface: "user_prompt"
34049
34246
  });
34050
34247
  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
- }
34248
+ await deliver();
34056
34249
  process.exit(0);
34057
34250
  }
34058
34251
  if (name === "post-tool-use") {
@@ -34067,11 +34260,7 @@ async function runCursorHook(name, arg) {
34067
34260
  });
34068
34261
  if (hits.length > 0) contexts.push(toolErrorReminderText(hits, "native-cli"));
34069
34262
  }
34070
- const compacted = compactContexts(CURSOR_HOST, contexts);
34071
- if (compacted.length > 0) {
34072
- process.stdout.write(`${cursorContextOutput(compacted.join("\n\n"))}
34073
- `);
34074
- }
34263
+ await deliver();
34075
34264
  process.exit(0);
34076
34265
  }
34077
34266
  if (name === "stop") {
@@ -34114,9 +34303,9 @@ async function runCursorHook(name, arg) {
34114
34303
  }
34115
34304
 
34116
34305
  // src/commands/pull.ts
34117
- import { existsSync as existsSync15 } from "node:fs";
34306
+ import { existsSync as existsSync17 } from "node:fs";
34118
34307
  async function runPull(ctx) {
34119
- const hasCommunityDir = existsSync15(ctx.paths.communityDir);
34308
+ const hasCommunityDir = existsSync17(ctx.paths.communityDir);
34120
34309
  if (!hasCommunityDir) {
34121
34310
  ctx.logger.info(
34122
34311
  "no community repos yet \u2014 add one with `caveat community add <github-url>`."
@@ -34214,213 +34403,6 @@ async function runSync(ctx, opts, dependencies = {}) {
34214
34403
  }
34215
34404
  }
34216
34405
 
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
34406
  // src/commands/community.ts
34425
34407
  async function runCommunityAdd(ctx, url2) {
34426
34408
  try {
@@ -34503,7 +34485,7 @@ function runCommunityRemove(ctx, handle, opts) {
34503
34485
 
34504
34486
  // src/commands/factoryDiagnostics.ts
34505
34487
  import { execFileSync } from "node:child_process";
34506
- import { existsSync as existsSync16, readFileSync as readFileSync10 } from "node:fs";
34488
+ import { existsSync as existsSync18, readFileSync as readFileSync12 } from "node:fs";
34507
34489
  import { join as join18 } from "node:path";
34508
34490
  import { DatabaseSync } from "node:sqlite";
34509
34491
  import { parse as parseToml2 } from "smol-toml";
@@ -34516,9 +34498,9 @@ function isRecord2(value) {
34516
34498
  return typeof value === "object" && value !== null && !Array.isArray(value);
34517
34499
  }
34518
34500
  function claudeRegistration(path, nodePath, cliScriptPath) {
34519
- if (!existsSync16(path)) return status(false, "not_registered");
34501
+ if (!existsSync18(path)) return status(false, "not_registered");
34520
34502
  try {
34521
- const value = JSON.parse(readFileSync10(path, "utf8"));
34503
+ const value = JSON.parse(readFileSync12(path, "utf8"));
34522
34504
  if (!isRecord2(value) || !isRecord2(value.mcpServers)) return status(false, "not_registered");
34523
34505
  return status(isCaveatClaudeMcpRegistration(value.mcpServers.caveat, nodePath, cliScriptPath), "not_registered");
34524
34506
  } catch {
@@ -34527,7 +34509,7 @@ function claudeRegistration(path, nodePath, cliScriptPath) {
34527
34509
  }
34528
34510
  function claudeHooks(claudeDir, nodePath, cliScriptPath) {
34529
34511
  try {
34530
- const settings = JSON.parse(readFileSync10(join18(claudeDir, "settings.json"), "utf8"));
34512
+ const settings = JSON.parse(readFileSync12(join18(claudeDir, "settings.json"), "utf8"));
34531
34513
  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
34514
  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
34515
  } catch {
@@ -34549,7 +34531,7 @@ function strictSearchSchema(db) {
34549
34531
  return columns.map((column) => `${column.name}:${column.hidden}`).join(",") === "id:0,title:0,body:0,tags:0,entries_fts:1,rank:1";
34550
34532
  }
34551
34533
  function database(path) {
34552
- if (!existsSync16(path)) return { status: "not_ready", reason_code: "missing", schema_version: null, supported_schema_version: 3, migration_status: "unverified" };
34534
+ if (!existsSync18(path)) return { status: "not_ready", reason_code: "missing", schema_version: null, supported_schema_version: 3, migration_status: "unverified" };
34553
34535
  try {
34554
34536
  const db = new DatabaseSync(path, { readOnly: true });
34555
34537
  try {
@@ -34575,9 +34557,9 @@ function database(path) {
34575
34557
  }
34576
34558
  function codexFeature(codexHome) {
34577
34559
  const path = join18(codexHome, "config.toml");
34578
- if (!existsSync16(path)) return status(false, "feature_disabled");
34560
+ if (!existsSync18(path)) return status(false, "feature_disabled");
34579
34561
  try {
34580
- const config2 = parseToml2(readFileSync10(path, "utf8"));
34562
+ const config2 = parseToml2(readFileSync12(path, "utf8"));
34581
34563
  if (!isRecord2(config2.features)) return status(false, "feature_disabled");
34582
34564
  const features = config2.features;
34583
34565
  return status(features.hooks === true && (features.codex_hooks === void 0 || features.codex_hooks === true), "feature_disabled");
@@ -34586,7 +34568,7 @@ function codexFeature(codexHome) {
34586
34568
  }
34587
34569
  }
34588
34570
  function codexHooks(codexHome, nodePath, cliScriptPath) {
34589
- const value = JSON.parse(readFileSync10(join18(codexHome, "hooks.json"), "utf8"));
34571
+ const value = JSON.parse(readFileSync12(join18(codexHome, "hooks.json"), "utf8"));
34590
34572
  if (!isRecord2(value) || !isRecord2(value.hooks)) throw Error("config_unreadable");
34591
34573
  const hooks = value.hooks;
34592
34574
  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 +34644,32 @@ function parseCursor(value) {
34662
34644
  return parsed;
34663
34645
  }
34664
34646
 
34647
+ // src/commands/jev.ts
34648
+ import { existsSync as existsSync19 } from "node:fs";
34649
+ async function enableJev(ctx) {
34650
+ const key = (await readStdin()).trim();
34651
+ if (!key) throw new Error("Jev API key was not provided on stdin");
34652
+ const response = await callJev(key, { purpose: "Caveat connection check" }, {
34653
+ probe: { type: "noul", instructions: "Is this a connection check?" }
34654
+ });
34655
+ if (response.answers.probe?.type !== "noul") throw new Error("Jev connection check returned an invalid answer");
34656
+ writeJevKey(ctx.caveatHome, key);
34657
+ writeUserConfigPatch(ctx.userConfigPath, { jevEnabled: true });
34658
+ process.stdout.write("Jev\u306B\u3088\u308B\u82E6\u6226\u5224\u5B9A\u3092\u6709\u52B9\u306B\u3057\u307E\u3057\u305F\u3002\n");
34659
+ }
34660
+ function disableJev(ctx) {
34661
+ writeUserConfigPatch(ctx.userConfigPath, { jevEnabled: false });
34662
+ process.stdout.write("Jev\u306B\u3088\u308B\u82E6\u6226\u5224\u5B9A\u3092\u7121\u52B9\u306B\u3057\u307E\u3057\u305F\u3002\n");
34663
+ }
34664
+ function jevStatus(ctx) {
34665
+ process.stdout.write(`${JSON.stringify({
34666
+ schema: "caveat.jev_status.v1",
34667
+ enabled: ctx.config.jevEnabled,
34668
+ credential: existsSync19(jevKeyPath(ctx.caveatHome)) ? "present" : "missing"
34669
+ })}
34670
+ `);
34671
+ }
34672
+
34665
34673
  // src/index.ts
34666
34674
  var program = new Command();
34667
34675
  var agentPaths = resolveAgentConfigPaths();
@@ -34679,6 +34687,16 @@ program.command("factory-diagnostics").description("Emit read-only factory diagn
34679
34687
  if (output.overall.status !== "ready") process.exitCode = 1;
34680
34688
  });
34681
34689
  var runtimeErrors = program.command("runtime-errors").description("Inspect Caveat opt-in runtime errors");
34690
+ var jev = program.command("jev").description("Configure Jev struggle detection");
34691
+ jev.command("enable").requiredOption("--key-stdin", "read the TypeSafe API key from stdin").action(async () => {
34692
+ await enableJev(buildContext(stdoutLogger));
34693
+ });
34694
+ jev.command("disable").action(() => {
34695
+ disableJev(buildContext(stdoutLogger));
34696
+ });
34697
+ jev.command("status").action(() => {
34698
+ jevStatus(buildContext(stdoutLogger));
34699
+ });
34682
34700
  var parseLimit = (value) => {
34683
34701
  const parsed = parseCursor(value);
34684
34702
  if (parsed < 1 || parsed > 256) throw Error("invalid_limit");
@@ -34696,8 +34714,8 @@ runtimeErrors.command("ack <cursor>").requiredOption("--json", "emit JSON").acti
34696
34714
  process.stdout.write(`${JSON.stringify(runRuntimeErrors("ack", cursor))}
34697
34715
  `);
34698
34716
  });
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))}
34717
+ for (const action of ["resolve", "reopen"]) runtimeErrors.command(`${action} <fingerprint>`).requiredOption("--json", "emit JSON").action((fingerprint2) => {
34718
+ process.stdout.write(`${JSON.stringify(runRuntimeErrors(action, fingerprint2))}
34701
34719
  `);
34702
34720
  });
34703
34721
  program.command("init").description(
@@ -34902,30 +34920,6 @@ cursorHook.command("post-tool-use").description("Run the Cursor postToolUse / po
34902
34920
  cursorHook.command("stop").description("Run the Cursor stop hook").action(async () => {
34903
34921
  await runCursorHook("stop");
34904
34922
  });
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
34923
  var community = program.command("community").description("Manage community caveat repos (shallow clones under <caveatHome>/community/)");
34930
34924
  community.command("add <url>").description("Shallow-clone a GitHub caveat repo into community/<handle>/").action(async (url2) => {
34931
34925
  const ctx = buildContext(stdoutLogger);