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/README.md +1 -2
- package/dist/{chunk-NZD3FHU6.js → chunk-RO2ZVJMX.js} +91 -295
- package/dist/chunk-RO2ZVJMX.js.map +1 -0
- package/dist/index.js +657 -663
- package/dist/index.js.map +1 -1
- package/dist/{server-VOEJV4WH.js → server-INQI6ANK.js} +2 -2
- package/package.json +3 -3
- package/dist/chunk-NZD3FHU6.js.map +0 -1
- /package/dist/{server-VOEJV4WH.js.map → server-INQI6ANK.js.map} +0 -0
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-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
543
|
-
this.code = optimizeExpr(this.code, names,
|
|
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,
|
|
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,
|
|
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,
|
|
629
|
+
optimizeNames(names, constants4) {
|
|
631
630
|
var _a3;
|
|
632
|
-
this.else = (_a3 = this.else) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names,
|
|
633
|
-
if (!(super.optimizeNames(names,
|
|
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,
|
|
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,
|
|
659
|
-
if (!super.optimizeNames(names,
|
|
657
|
+
optimizeNames(names, constants4) {
|
|
658
|
+
if (!super.optimizeNames(names, constants4))
|
|
660
659
|
return;
|
|
661
|
-
this.iteration = optimizeExpr(this.iteration, names,
|
|
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,
|
|
698
|
-
if (!super.optimizeNames(names,
|
|
696
|
+
optimizeNames(names, constants4) {
|
|
697
|
+
if (!super.optimizeNames(names, constants4))
|
|
699
698
|
return;
|
|
700
|
-
this.iterable = optimizeExpr(this.iterable, names,
|
|
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,
|
|
741
|
+
optimizeNames(names, constants4) {
|
|
743
742
|
var _a3, _b;
|
|
744
|
-
super.optimizeNames(names,
|
|
745
|
-
(_a3 = this.catch) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names,
|
|
746
|
-
(_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names,
|
|
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,
|
|
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 =
|
|
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 &&
|
|
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
|
|
6891
|
-
import { existsSync as
|
|
6892
|
-
import { dirname as dirname3, join as
|
|
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 (!
|
|
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 =
|
|
8037
|
-
if (!
|
|
8038
|
-
if (!opts.dryRun)
|
|
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 (!
|
|
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 =
|
|
8087
|
-
if (
|
|
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 ${
|
|
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 (
|
|
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",
|
|
8240
|
-
["grok",
|
|
8241
|
-
["cursor",
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
8127
|
+
const legacy = join8(ctx.paths.knowledgeRepo, "community");
|
|
8286
8128
|
const current = ctx.paths.communityDir;
|
|
8287
8129
|
if (legacy === current) return;
|
|
8288
|
-
if (!
|
|
8289
|
-
if (
|
|
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(
|
|
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
|
|
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 (!
|
|
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-
|
|
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
|
|
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 =
|
|
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
|
|
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 (
|
|
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
|
|
32953
|
-
lstatSync,
|
|
32954
|
-
mkdtempSync
|
|
32955
|
-
mkdirSync as
|
|
32794
|
+
existsSync as existsSync15,
|
|
32795
|
+
lstatSync as lstatSync2,
|
|
32796
|
+
mkdtempSync,
|
|
32797
|
+
mkdirSync as mkdirSync9,
|
|
32956
32798
|
readdirSync as readdirSync2,
|
|
32957
|
-
readFileSync as
|
|
32799
|
+
readFileSync as readFileSync10,
|
|
32958
32800
|
realpathSync as realpathSync3,
|
|
32959
|
-
rmSync as
|
|
32960
|
-
writeFileSync as
|
|
32801
|
+
rmSync as rmSync4,
|
|
32802
|
+
writeFileSync as writeFileSync7
|
|
32961
32803
|
} from "node:fs";
|
|
32962
|
-
import { tmpdir
|
|
32963
|
-
import { basename, dirname as dirname5, join as
|
|
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
|
|
32969
|
-
import { join as
|
|
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 (!
|
|
32973
|
-
if (
|
|
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/
|
|
32999
|
-
import {
|
|
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
|
-
|
|
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
|
|
32991
|
+
function peekForSession(host, sessionId) {
|
|
33089
32992
|
const ctx = buildContextSafely(host);
|
|
33090
|
-
if (!ctx) return [];
|
|
33091
|
-
const local =
|
|
33092
|
-
const global =
|
|
33093
|
-
for (const _failure of [...local.
|
|
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
|
-
|
|
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
|
|
33100
|
-
if (
|
|
33101
|
-
|
|
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
|
|
33104
|
-
|
|
33105
|
-
|
|
33106
|
-
|
|
33107
|
-
|
|
33108
|
-
|
|
33109
|
-
|
|
33110
|
-
|
|
33111
|
-
|
|
33112
|
-
|
|
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
|
-
|
|
33131
|
-
|
|
33132
|
-
|
|
33133
|
-
|
|
33134
|
-
|
|
33135
|
-
|
|
33136
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
33470
|
+
workDir = mkdtempSync(join15(root, "job-"));
|
|
33266
33471
|
chmodSync(workDir, 448);
|
|
33267
|
-
workFile =
|
|
33268
|
-
|
|
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 =
|
|
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
|
-
}), () =>
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
33568
|
+
const path = join15(root, entry);
|
|
33364
33569
|
try {
|
|
33365
33570
|
if (!isOwnedWorkerDir(path, root)) continue;
|
|
33366
|
-
const stat =
|
|
33571
|
+
const stat = lstatSync2(path);
|
|
33367
33572
|
if (now - stat.mtimeMs <= WORKER_STALE_MS || !isStaleWorkerJobDir(path)) continue;
|
|
33368
|
-
|
|
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 =
|
|
33375
|
-
const root =
|
|
33376
|
-
|
|
33377
|
-
const stat =
|
|
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 =
|
|
33585
|
+
const marker = join15(root, WORKER_MARKER);
|
|
33381
33586
|
try {
|
|
33382
|
-
|
|
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 =
|
|
33387
|
-
if (!markerStat.isFile() || markerStat.isSymbolicLink() || !hasPrivateOwnership(markerStat, uid) ||
|
|
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 =
|
|
33394
|
-
const stat =
|
|
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(
|
|
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
|
-
|
|
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 || !
|
|
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
|
|
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
|
-
|
|
33605
|
-
|
|
33606
|
-
|
|
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
|
|
33613
|
-
if (
|
|
33614
|
-
|
|
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
|
|
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, () =>
|
|
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
|
|
33675
|
-
import { homedir as homedir4, tmpdir as
|
|
33676
|
-
import { join as
|
|
33677
|
-
import { randomBytes as
|
|
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 || !
|
|
33863
|
+
if (!transcriptPath || !toolUseId || !existsSync16(transcriptPath)) return null;
|
|
33707
33864
|
let raw = "";
|
|
33708
33865
|
try {
|
|
33709
|
-
raw =
|
|
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
|
|
33761
|
-
if (
|
|
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
|
|
33821
|
-
if (
|
|
33822
|
-
if (
|
|
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 =
|
|
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 ??
|
|
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
|
|
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
|
-
|
|
33925
|
-
|
|
33926
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
34306
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
34118
34307
|
async function runPull(ctx) {
|
|
34119
|
-
const hasCommunityDir =
|
|
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
|
|
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 (!
|
|
34501
|
+
if (!existsSync18(path)) return status(false, "not_registered");
|
|
34520
34502
|
try {
|
|
34521
|
-
const value = JSON.parse(
|
|
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(
|
|
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 (!
|
|
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 (!
|
|
34560
|
+
if (!existsSync18(path)) return status(false, "feature_disabled");
|
|
34579
34561
|
try {
|
|
34580
|
-
const config2 = parseToml2(
|
|
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(
|
|
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((
|
|
34700
|
-
process.stdout.write(`${JSON.stringify(runRuntimeErrors(action,
|
|
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);
|