caveat-cli 0.19.2 → 0.19.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/dist/{chunk-NZD3FHU6.js → chunk-EMGYSGFX.js} +91 -295
- package/dist/chunk-EMGYSGFX.js.map +1 -0
- package/dist/index.js +672 -673
- package/dist/index.js.map +1 -1
- package/dist/{server-VOEJV4WH.js → server-RSDQNLWE.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-NZD3FHU6.js.map +0 -1
- /package/dist/{server-VOEJV4WH.js.map → server-RSDQNLWE.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-EMGYSGFX.js";
|
|
79
78
|
|
|
80
79
|
// ../../node_modules/.pnpm/ajv@8.18.0/node_modules/ajv/dist/compile/codegen/code.js
|
|
81
80
|
var require_code = __commonJS({
|
|
@@ -454,11 +453,11 @@ var require_codegen = __commonJS({
|
|
|
454
453
|
const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
|
|
455
454
|
return `${varKind} ${this.name}${rhs};` + _n;
|
|
456
455
|
}
|
|
457
|
-
optimizeNames(names,
|
|
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-RSDQNLWE.js");
|
|
8552
8394
|
const { port, host } = startServer({ port: opts.port });
|
|
8553
8395
|
process.stdout.write(`[caveat] web portal: http://${host}:${port}/
|
|
8554
8396
|
`);
|
|
@@ -32620,7 +32462,7 @@ var StdioServerTransport = class {
|
|
|
32620
32462
|
|
|
32621
32463
|
// ../mcp/dist/context.js
|
|
32622
32464
|
import { homedir as homedir3 } from "node:os";
|
|
32623
|
-
import { join as
|
|
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,313 @@ function extractToolResponseText(response) {
|
|
|
33203
33105
|
return "";
|
|
33204
33106
|
}
|
|
33205
33107
|
|
|
33108
|
+
// src/hookDelivery.ts
|
|
33109
|
+
function statePath(caveatHome, sessionId) {
|
|
33110
|
+
return join13(caveatHome, "hook-delivery", `${createHash2("sha256").update(sessionId).digest("hex")}.json`);
|
|
33111
|
+
}
|
|
33112
|
+
function readState(caveatHome, sessionId) {
|
|
33113
|
+
const path = statePath(caveatHome, sessionId);
|
|
33114
|
+
if (!existsSync13(path)) return { ids: [], signals: [] };
|
|
33115
|
+
const value = JSON.parse(readFileSync8(path, "utf8"));
|
|
33116
|
+
if (!value || typeof value !== "object" || !("ids" in value) || !("signals" in value) || !Array.isArray(value.ids) || !value.ids.every((item) => typeof item === "string") || !Array.isArray(value.signals) || !value.signals.every((item) => typeof item === "string")) {
|
|
33117
|
+
throw new Error("hook_delivery_state_invalid");
|
|
33118
|
+
}
|
|
33119
|
+
return value;
|
|
33120
|
+
}
|
|
33121
|
+
function writeState(caveatHome, sessionId, state) {
|
|
33122
|
+
const path = statePath(caveatHome, sessionId);
|
|
33123
|
+
mkdirSync7(join13(caveatHome, "hook-delivery"), { recursive: true, mode: 448 });
|
|
33124
|
+
const temporary = `${path}.${randomBytes2(6).toString("hex")}.tmp`;
|
|
33125
|
+
try {
|
|
33126
|
+
writeFileSync5(temporary, `${JSON.stringify(state)}
|
|
33127
|
+
`, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
33128
|
+
renameSync3(temporary, path);
|
|
33129
|
+
} finally {
|
|
33130
|
+
rmSync2(temporary, { force: true });
|
|
33131
|
+
}
|
|
33132
|
+
}
|
|
33133
|
+
function wasHitDelivered(caveatHome, sessionId, ref) {
|
|
33134
|
+
return readState(caveatHome, sessionId).ids.includes(ref);
|
|
33135
|
+
}
|
|
33136
|
+
var SIGNAL_LINES = [
|
|
33137
|
+
[/^- tool failure:/u, "tool_failure", "\u30C4\u30FC\u30EB\u5931\u6557"],
|
|
33138
|
+
[/^- 同一ファイル複数編集:/u, "reedit", "\u540C\u4E00\u30D5\u30A1\u30A4\u30EB\u306E\u518D\u7DE8\u96C6"],
|
|
33139
|
+
[/^- WebSearch:/u, "web_search", "Web\u691C\u7D22"],
|
|
33140
|
+
[/^- WebFetch:/u, "web_fetch", "Web\u53D6\u5F97"],
|
|
33141
|
+
[/^- 同一 Bash コマンドの再実行:/u, "bash_retry", "\u540C\u4E00\u30B3\u30DE\u30F3\u30C9\u306E\u518D\u5B9F\u884C"]
|
|
33142
|
+
];
|
|
33143
|
+
function prepareSessionDelivery(caveatHome, sessionId, contexts, additionallyDeliveredIds = []) {
|
|
33144
|
+
const state = readState(caveatHome, sessionId);
|
|
33145
|
+
const seenIds = /* @__PURE__ */ new Set([...state.ids, ...additionallyDeliveredIds]);
|
|
33146
|
+
const seenSignals = new Set(state.signals);
|
|
33147
|
+
const addedIds = [];
|
|
33148
|
+
const addedSignals = [];
|
|
33149
|
+
const hitLines = [];
|
|
33150
|
+
const signalLabels = [];
|
|
33151
|
+
const others = [];
|
|
33152
|
+
for (const context of contexts) {
|
|
33153
|
+
const text = context.trim();
|
|
33154
|
+
if (!text) continue;
|
|
33155
|
+
const lines = text.split("\n");
|
|
33156
|
+
const isStop = text.startsWith(STOP_REMINDER_PREFIX);
|
|
33157
|
+
let hasHit = false;
|
|
33158
|
+
for (const line of lines) {
|
|
33159
|
+
const hit = /^\d+\. ([^\s]+) \(([^)]+)\) — (.+)$/u.exec(line);
|
|
33160
|
+
if (hit) {
|
|
33161
|
+
hasHit = true;
|
|
33162
|
+
const ref = `${hit[2]}/${hit[1]}`;
|
|
33163
|
+
if (seenIds.has(ref)) continue;
|
|
33164
|
+
seenIds.add(ref);
|
|
33165
|
+
addedIds.push(ref);
|
|
33166
|
+
hitLines.push(`${hit[1]} (${hit[2]}) \u2014 ${hit[3]}`);
|
|
33167
|
+
}
|
|
33168
|
+
if (isStop) {
|
|
33169
|
+
for (const [pattern, kind, label] of SIGNAL_LINES) {
|
|
33170
|
+
if (pattern.test(line) && !seenSignals.has(kind)) {
|
|
33171
|
+
seenSignals.add(kind);
|
|
33172
|
+
addedSignals.push(kind);
|
|
33173
|
+
signalLabels.push(label);
|
|
33174
|
+
}
|
|
33175
|
+
}
|
|
33176
|
+
}
|
|
33177
|
+
}
|
|
33178
|
+
if (isStop || hasHit) continue;
|
|
33179
|
+
if (!others.includes(text)) others.push(text);
|
|
33180
|
+
}
|
|
33181
|
+
const texts = [...others];
|
|
33182
|
+
if (signalLabels.length > 0) texts.push(`[caveat] \u65B0\u3057\u3044\u82E6\u6226\u30B7\u30B0\u30CA\u30EB: ${signalLabels.join("\u3001")}\u3002\u5FC5\u8981\u306A\u3089Caveat\u3067\u77E5\u898B\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
|
|
33183
|
+
if (hitLines.length > 0) texts.push(`[caveat] \u65B0\u3057\u304F\u8A72\u5F53\u3057\u305F\u7F60:
|
|
33184
|
+
${hitLines.map((line) => `- ${line}`).join("\n")}
|
|
33185
|
+
\u8A73\u7D30\u306FCaveat\u3067\u53D6\u5F97\u3057\u3001\u9069\u7528\u6761\u4EF6\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
|
|
33186
|
+
return {
|
|
33187
|
+
texts,
|
|
33188
|
+
delivered: () => {
|
|
33189
|
+
if (addedIds.length === 0 && addedSignals.length === 0 && additionallyDeliveredIds.length === 0) return;
|
|
33190
|
+
const latest = readState(caveatHome, sessionId);
|
|
33191
|
+
writeState(caveatHome, sessionId, {
|
|
33192
|
+
ids: [.../* @__PURE__ */ new Set([...latest.ids, ...addedIds, ...additionallyDeliveredIds])],
|
|
33193
|
+
signals: [.../* @__PURE__ */ new Set([...latest.signals, ...addedSignals])]
|
|
33194
|
+
});
|
|
33195
|
+
}
|
|
33196
|
+
};
|
|
33197
|
+
}
|
|
33198
|
+
|
|
33199
|
+
// src/jevStruggle.ts
|
|
33200
|
+
var MODEL = "jev-1.13.0";
|
|
33201
|
+
var API_URL = "https://api.typesafe.ai/v1/systemone";
|
|
33202
|
+
var STRUGGLE_THRESHOLD = 0.85;
|
|
33203
|
+
var MATCH_THRESHOLD = 0.85;
|
|
33204
|
+
var MAX_TERMS = 48;
|
|
33205
|
+
var MAX_CANDIDATES = 20;
|
|
33206
|
+
async function callJev(key, state, questions, fetcher = fetch) {
|
|
33207
|
+
const response = await fetcher(API_URL, {
|
|
33208
|
+
method: "POST",
|
|
33209
|
+
headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
|
|
33210
|
+
body: JSON.stringify({ state, model: MODEL, questions }),
|
|
33211
|
+
signal: AbortSignal.timeout(1500)
|
|
33212
|
+
});
|
|
33213
|
+
if (!response.ok) throw new Error(`jev_http_${response.status}`);
|
|
33214
|
+
const body = await response.json();
|
|
33215
|
+
if (!isRecord(body) || !isRecord(body.answers)) throw new Error("jev_response_invalid");
|
|
33216
|
+
return body;
|
|
33217
|
+
}
|
|
33218
|
+
function isRecord(value) {
|
|
33219
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33220
|
+
}
|
|
33221
|
+
function noul(answer) {
|
|
33222
|
+
if (answer?.type !== "noul" || typeof answer.noul !== "number" || !Number.isFinite(answer.noul) || answer.noul < 0 || answer.noul > 1) {
|
|
33223
|
+
throw new Error("jev_noul_invalid");
|
|
33224
|
+
}
|
|
33225
|
+
return answer.noul;
|
|
33226
|
+
}
|
|
33227
|
+
function choice(answer, allowed) {
|
|
33228
|
+
if (answer?.type !== "choice" || !allowed.has(answer.choice)) throw new Error("jev_choice_invalid");
|
|
33229
|
+
return answer.choice;
|
|
33230
|
+
}
|
|
33231
|
+
function candidateTerms(turns) {
|
|
33232
|
+
const seen = /* @__PURE__ */ new Set();
|
|
33233
|
+
const terms = [];
|
|
33234
|
+
const groups = [...turns].reverse().map((turn) => [turn.user, turn.thinking, turn.assistant].flatMap((part) => extractPromptCandidates(
|
|
33235
|
+
part.replace(/(?<=[A-Za-z0-9])(?=[\u3040-\u30ff\u3400-\u9fff])|(?<=[\u3040-\u30ff\u3400-\u9fff])(?=[A-Za-z0-9])/gu, " ")
|
|
33236
|
+
)));
|
|
33237
|
+
const add = (term) => {
|
|
33238
|
+
const key = term.toLocaleLowerCase();
|
|
33239
|
+
if (seen.has(key) || terms.length >= MAX_TERMS) return;
|
|
33240
|
+
seen.add(key);
|
|
33241
|
+
terms.push(term);
|
|
33242
|
+
};
|
|
33243
|
+
for (const group of groups) {
|
|
33244
|
+
let added = 0;
|
|
33245
|
+
for (const term of group) {
|
|
33246
|
+
const before = terms.length;
|
|
33247
|
+
add(term);
|
|
33248
|
+
if (terms.length > before) added++;
|
|
33249
|
+
if (added === 12) break;
|
|
33250
|
+
}
|
|
33251
|
+
}
|
|
33252
|
+
for (const group of groups) for (const term of group) add(term);
|
|
33253
|
+
return terms;
|
|
33254
|
+
}
|
|
33255
|
+
async function judgeStruggle(key, turns, ask = callJev) {
|
|
33256
|
+
const terms = candidateTerms(turns);
|
|
33257
|
+
const options = Object.fromEntries(terms.map((term, index) => [`term_${index}`, term]));
|
|
33258
|
+
const questions = {
|
|
33259
|
+
repeated_problem: { type: "noul", instructions: "Across these three ordered turns, is the coding agent repeatedly correcting attempts at the same unresolved task or problem? Judge the meaning of its dialogue and Thinking, not the number of tool calls." },
|
|
33260
|
+
clearly_stuck: { type: "noul", instructions: "At the end of turn 3, is the agent clearly struggling with an unresolved problem rather than making a brief, ordinary correction or verification?" }
|
|
33261
|
+
};
|
|
33262
|
+
if (terms.length > 0) questions.search_term = {
|
|
33263
|
+
type: "choice",
|
|
33264
|
+
instructions: "If the agent is struggling, which exact term from these options would best retrieve a useful Caveat knowledge entry about the concrete unresolved problem? Select none when no option identifies it.",
|
|
33265
|
+
criteria: { ...options, none: "No listed term would retrieve useful knowledge about this problem." }
|
|
33266
|
+
};
|
|
33267
|
+
const response = await ask(key, { turns }, questions);
|
|
33268
|
+
const struggling = noul(response.answers.repeated_problem) >= STRUGGLE_THRESHOLD && noul(response.answers.clearly_stuck) >= STRUGGLE_THRESHOLD;
|
|
33269
|
+
if (!struggling || terms.length === 0) return { struggling, term: null };
|
|
33270
|
+
const selected = choice(response.answers.search_term, /* @__PURE__ */ new Set([...Object.keys(options), "none"]));
|
|
33271
|
+
return { struggling, term: selected === "none" ? null : terms[Number(selected.slice(5))] ?? null };
|
|
33272
|
+
}
|
|
33273
|
+
async function rankKnowledge(key, turns, candidates, ask = callJev) {
|
|
33274
|
+
if (candidates.length === 0) return null;
|
|
33275
|
+
const state = {
|
|
33276
|
+
turns,
|
|
33277
|
+
currentHostOs: fingerprint().os,
|
|
33278
|
+
candidates: candidates.map(({ hit, entry }) => {
|
|
33279
|
+
const { applies_to_os: appliesToOs = null, ...recordedEnvironment } = hit.environment;
|
|
33280
|
+
return {
|
|
33281
|
+
title: hit.title,
|
|
33282
|
+
symptom: hit.symptomExcerpt,
|
|
33283
|
+
cause: entry.sections.Cause?.slice(0, 300) ?? "",
|
|
33284
|
+
resolution: entry.sections.Resolution?.slice(0, 600) ?? "",
|
|
33285
|
+
recordedEnvironment,
|
|
33286
|
+
appliesToOs
|
|
33287
|
+
};
|
|
33288
|
+
})
|
|
33289
|
+
};
|
|
33290
|
+
const questions = Object.fromEntries(candidates.map((_, index) => [`candidate_${index}`, {
|
|
33291
|
+
type: "noul",
|
|
33292
|
+
instructions: `Does candidates[${index}] directly apply to the unresolved problem shown in the turns and offer a useful resolution? A recordedEnvironment value is where the entry was observed, not a restriction. Only appliesToOs restricts the target OS; null means any OS. currentHostOs may differ from the task's target OS.`
|
|
33293
|
+
}]));
|
|
33294
|
+
const response = await ask(key, state, questions);
|
|
33295
|
+
let best = null;
|
|
33296
|
+
let bestScore = MATCH_THRESHOLD;
|
|
33297
|
+
for (let index = 0; index < candidates.length; index++) {
|
|
33298
|
+
const score = noul(response.answers[`candidate_${index}`]);
|
|
33299
|
+
if (score >= MATCH_THRESHOLD && (best === null || score > bestScore)) {
|
|
33300
|
+
best = candidates[index];
|
|
33301
|
+
bestScore = score;
|
|
33302
|
+
}
|
|
33303
|
+
}
|
|
33304
|
+
return best;
|
|
33305
|
+
}
|
|
33306
|
+
function quotePowerShell(value) {
|
|
33307
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
33308
|
+
}
|
|
33309
|
+
function readThroughlineContext(host, sessionId, projectRoot, transcriptPath) {
|
|
33310
|
+
const sourceId = host === "codex" && !sessionId.startsWith("codex:") ? `codex:${sessionId}` : sessionId;
|
|
33311
|
+
const args = ["caveat-context", "--session", sourceId, "--project", projectRoot, "--host", host, "--transcript", transcriptPath, "--json"];
|
|
33312
|
+
const invocation = isWindows() ? { command: "pwsh.exe", args: ["-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(`& throughline.cmd ${args.map(quotePowerShell).join(" ")}; exit $LASTEXITCODE`, "utf16le").toString("base64")] } : { command: "throughline", args };
|
|
33313
|
+
const result = spawnSync3(invocation.command, invocation.args, { encoding: "utf8", timeout: 1500, maxBuffer: 128 * 1024, windowsHide: true });
|
|
33314
|
+
if (result.error || result.status !== 0) throw new Error("throughline_context_failed");
|
|
33315
|
+
const value = JSON.parse(result.stdout);
|
|
33316
|
+
if (!isRecord(value) || value.schema !== "throughline.caveat_context.v1" || typeof value.status !== "string" || !Array.isArray(value.turns)) {
|
|
33317
|
+
throw new Error("throughline_context_invalid");
|
|
33318
|
+
}
|
|
33319
|
+
return value;
|
|
33320
|
+
}
|
|
33321
|
+
function statePath2(caveatHome, sessionId) {
|
|
33322
|
+
const digest = createHash3("sha256").update(sessionId).digest("hex");
|
|
33323
|
+
return join14(caveatHome, "jev-sessions", `${digest}.json`);
|
|
33324
|
+
}
|
|
33325
|
+
function loadState(caveatHome, sessionId) {
|
|
33326
|
+
const path = statePath2(caveatHome, sessionId);
|
|
33327
|
+
if (!existsSync14(path)) return { lastTurn: null, deliveredIds: [], notifiedTerms: [] };
|
|
33328
|
+
const value = JSON.parse(readFileSync9(path, "utf8"));
|
|
33329
|
+
if (!isRecord(value) || value.lastTurn !== null && typeof value.lastTurn !== "string" || !Array.isArray(value.deliveredIds) || !value.deliveredIds.every((item) => typeof item === "string") || !Array.isArray(value.notifiedTerms) || !value.notifiedTerms.every((item) => typeof item === "string")) throw new Error("jev_state_invalid");
|
|
33330
|
+
return value;
|
|
33331
|
+
}
|
|
33332
|
+
function saveState(caveatHome, sessionId, state) {
|
|
33333
|
+
const path = statePath2(caveatHome, sessionId);
|
|
33334
|
+
mkdirSync8(join14(caveatHome, "jev-sessions"), { recursive: true, mode: 448 });
|
|
33335
|
+
const temporary = `${path}.${randomBytes3(6).toString("hex")}.tmp`;
|
|
33336
|
+
try {
|
|
33337
|
+
writeFileSync6(temporary, `${JSON.stringify(state)}
|
|
33338
|
+
`, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
33339
|
+
renameSync4(temporary, path);
|
|
33340
|
+
} finally {
|
|
33341
|
+
rmSync3(temporary, { force: true });
|
|
33342
|
+
}
|
|
33343
|
+
}
|
|
33344
|
+
function reminder(hit) {
|
|
33345
|
+
if (!hit) return "[caveat] \u540C\u3058\u554F\u984C\u3078\u306E\u8A66\u884C\u304C\u7D9A\u304D\u3001\u307E\u3060\u89E3\u6C7A\u3057\u3066\u3044\u306A\u3044\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002\u5FC5\u8981\u306B\u3088\u308ACaveat\u3067\u691C\u7D22\u3057\u3066\u307F\u3066\u304F\u3060\u3055\u3044\u3002";
|
|
33346
|
+
const resolution = hit.entry.sections.Resolution?.replace(/\s+/g, " ").trim().slice(0, 500) ?? "";
|
|
33347
|
+
return `[caveat] \u540C\u3058\u554F\u984C\u3078\u306E\u8A66\u884C\u304C\u7D9A\u3044\u3066\u3044\u307E\u3059\u3002\u95A2\u9023\u77E5\u898B: ${hit.hit.title} (${hit.hit.source}/${hit.hit.id})
|
|
33348
|
+
\u5BFE\u51E6: ${resolution}
|
|
33349
|
+
\u3053\u306E\u56DE\u7B54\u3067\u89E3\u6C7A\u3057\u306A\u3044\u5834\u5408\u3001\u5FC5\u8981\u306B\u3088\u308ACaveat\u3067\u691C\u7D22\u3057\u3066\u307F\u3066\u304F\u3060\u3055\u3044\u3002`;
|
|
33350
|
+
}
|
|
33351
|
+
async function runJevStruggle(ctx, host, payload, deps = {}) {
|
|
33352
|
+
if (!ctx.config.jevEnabled) return null;
|
|
33353
|
+
const sessionId = payload.session_id ?? payload.sessionId;
|
|
33354
|
+
const transcriptPath = payload.transcript_path ?? payload.transcriptPath;
|
|
33355
|
+
if (typeof sessionId !== "string" || !sessionId || typeof transcriptPath !== "string" || !transcriptPath) {
|
|
33356
|
+
throw new Error("jev_hook_context_missing");
|
|
33357
|
+
}
|
|
33358
|
+
const projectRoot = typeof payload.cwd === "string" && payload.cwd ? payload.cwd : process.cwd();
|
|
33359
|
+
const context = (deps.readContext ?? readThroughlineContext)(host, sessionId, projectRoot, transcriptPath);
|
|
33360
|
+
if (["unavailable", "incomplete", "projection_pending"].includes(context.status)) return null;
|
|
33361
|
+
if (context.status !== "ready" || context.turns.length !== 3) throw new Error(`throughline_context_${context.status}`);
|
|
33362
|
+
const latest = context.turns[2];
|
|
33363
|
+
const turnKey = `${latest.originSessionId}:${latest.turnNumber}`;
|
|
33364
|
+
const state = loadState(ctx.caveatHome, sessionId);
|
|
33365
|
+
if (state.lastTurn === turnKey) return null;
|
|
33366
|
+
const key = (deps.readKey ?? readJevKey)(ctx.caveatHome);
|
|
33367
|
+
const ask = deps.ask ?? callJev;
|
|
33368
|
+
const judgment = await judgeStruggle(key, context.turns, ask);
|
|
33369
|
+
state.lastTurn = turnKey;
|
|
33370
|
+
if (!judgment.struggling) {
|
|
33371
|
+
saveState(ctx.caveatHome, sessionId, state);
|
|
33372
|
+
return null;
|
|
33373
|
+
}
|
|
33374
|
+
let candidates = [];
|
|
33375
|
+
if (judgment.term) {
|
|
33376
|
+
if (!existsSync14(ctx.paths.dbPath)) throw new Error("jev_knowledge_index_missing");
|
|
33377
|
+
const db = openDb({ path: ctx.paths.dbPath });
|
|
33378
|
+
try {
|
|
33379
|
+
const taskText = context.turns.map((turn) => `${turn.user}
|
|
33380
|
+
${turn.assistant}
|
|
33381
|
+
${turn.thinking}`).join("\n");
|
|
33382
|
+
candidates = search(db, { query: judgment.term, limit: MAX_CANDIDATES }).filter((hit) => environmentAppliesToTask(hit.environment, taskText, fingerprint().os)).map((hit) => ({ hit, entry: get(db, hit.id, hit.source) })).filter((item) => item.entry !== null).filter((item) => Boolean(item.entry.sections.Resolution));
|
|
33383
|
+
} finally {
|
|
33384
|
+
db.close();
|
|
33385
|
+
}
|
|
33386
|
+
}
|
|
33387
|
+
const selected = await rankKnowledge(key, context.turns, candidates, ask);
|
|
33388
|
+
const id = selected ? `${selected.hit.source}/${selected.hit.id}` : null;
|
|
33389
|
+
const term = judgment.term ?? "_none";
|
|
33390
|
+
if (id && (state.deliveredIds.includes(id) || wasHitDelivered(ctx.caveatHome, sessionId, id)) || !id && state.notifiedTerms.includes(term)) {
|
|
33391
|
+
saveState(ctx.caveatHome, sessionId, state);
|
|
33392
|
+
return null;
|
|
33393
|
+
}
|
|
33394
|
+
return {
|
|
33395
|
+
text: reminder(selected),
|
|
33396
|
+
ref: id,
|
|
33397
|
+
delivered: () => {
|
|
33398
|
+
const current = loadState(ctx.caveatHome, sessionId);
|
|
33399
|
+
current.lastTurn = turnKey;
|
|
33400
|
+
if (id && !current.deliveredIds.includes(id)) current.deliveredIds.push(id);
|
|
33401
|
+
if (!id && !current.notifiedTerms.includes(term)) current.notifiedTerms.push(term);
|
|
33402
|
+
saveState(ctx.caveatHome, sessionId, current);
|
|
33403
|
+
if (selected) {
|
|
33404
|
+
const db = openDb({ path: ctx.paths.dbPath });
|
|
33405
|
+
try {
|
|
33406
|
+
markHit(db, [selected.hit]);
|
|
33407
|
+
} finally {
|
|
33408
|
+
db.close();
|
|
33409
|
+
}
|
|
33410
|
+
}
|
|
33411
|
+
}
|
|
33412
|
+
};
|
|
33413
|
+
}
|
|
33414
|
+
|
|
33206
33415
|
// src/commands/hookCmd.ts
|
|
33207
33416
|
var CLAUDE_HOST = {
|
|
33208
33417
|
agent: "claude",
|
|
@@ -33262,10 +33471,10 @@ function spawnWorker(job) {
|
|
|
33262
33471
|
try {
|
|
33263
33472
|
root = workerRoot();
|
|
33264
33473
|
sweepStaleWorkerDirs(Date.now(), root);
|
|
33265
|
-
workDir =
|
|
33474
|
+
workDir = mkdtempSync(join15(root, "job-"));
|
|
33266
33475
|
chmodSync(workDir, 448);
|
|
33267
|
-
workFile =
|
|
33268
|
-
|
|
33476
|
+
workFile = join15(workDir, `${randomBytes4(4).toString("hex")}.json`);
|
|
33477
|
+
writeFileSync7(workFile, JSON.stringify({ ...job, schemaVersion: "caveat-worker-job/v2" }), { encoding: "utf-8", mode: 384, flag: "wx" });
|
|
33269
33478
|
} catch (err) {
|
|
33270
33479
|
process.stderr.write(`[caveat:hook] worker writefile error: ${errorMessage4(err)}
|
|
33271
33480
|
`);
|
|
@@ -33296,7 +33505,7 @@ function spawnWorker(job) {
|
|
|
33296
33505
|
async function runWorker(workFile) {
|
|
33297
33506
|
let raw;
|
|
33298
33507
|
try {
|
|
33299
|
-
raw =
|
|
33508
|
+
raw = readFileSync10(workFile, "utf-8");
|
|
33300
33509
|
} catch {
|
|
33301
33510
|
process.exit(0);
|
|
33302
33511
|
}
|
|
@@ -33322,7 +33531,7 @@ async function runWorker(workFile) {
|
|
|
33322
33531
|
agent: "claude",
|
|
33323
33532
|
surface: "tool_error",
|
|
33324
33533
|
refs: hits
|
|
33325
|
-
}), () =>
|
|
33534
|
+
}), () => toolErrorReminderText(hits));
|
|
33326
33535
|
} catch {
|
|
33327
33536
|
process.stderr.write("[caveat:hook] pending reminder build or publish failed\n");
|
|
33328
33537
|
process.exit(0);
|
|
@@ -33333,17 +33542,17 @@ async function runWorker(workFile) {
|
|
|
33333
33542
|
function cleanupWorkerDir(workDir, root) {
|
|
33334
33543
|
if (!workDir || !root || !isOwnedWorkerDir(workDir, root)) return;
|
|
33335
33544
|
try {
|
|
33336
|
-
|
|
33545
|
+
rmSync4(workDir, { recursive: true, force: true });
|
|
33337
33546
|
} catch {
|
|
33338
33547
|
}
|
|
33339
33548
|
}
|
|
33340
33549
|
function isOwnedWorkerDir(path, root = workerRoot()) {
|
|
33341
33550
|
try {
|
|
33342
|
-
const inputStat =
|
|
33551
|
+
const inputStat = lstatSync2(path);
|
|
33343
33552
|
if (inputStat.isSymbolicLink()) return false;
|
|
33344
33553
|
const tmpRoot = realpathSync3(root);
|
|
33345
33554
|
const resolved = realpathSync3(path);
|
|
33346
|
-
const stat =
|
|
33555
|
+
const stat = lstatSync2(resolved);
|
|
33347
33556
|
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
33348
33557
|
return dirname5(resolved) === tmpRoot && basename(resolved).startsWith("job-") && stat.isDirectory() && !stat.isSymbolicLink() && hasPrivateOwnership(stat, uid);
|
|
33349
33558
|
} catch {
|
|
@@ -33360,41 +33569,41 @@ function sweepStaleWorkerDirs(now = Date.now(), root = workerRoot()) {
|
|
|
33360
33569
|
}
|
|
33361
33570
|
for (const entry of entries) {
|
|
33362
33571
|
if (!entry.startsWith("job-")) continue;
|
|
33363
|
-
const path =
|
|
33572
|
+
const path = join15(root, entry);
|
|
33364
33573
|
try {
|
|
33365
33574
|
if (!isOwnedWorkerDir(path, root)) continue;
|
|
33366
|
-
const stat =
|
|
33575
|
+
const stat = lstatSync2(path);
|
|
33367
33576
|
if (now - stat.mtimeMs <= WORKER_STALE_MS || !isStaleWorkerJobDir(path)) continue;
|
|
33368
|
-
|
|
33577
|
+
rmSync4(path, { recursive: true, force: true });
|
|
33369
33578
|
} catch {
|
|
33370
33579
|
process.stderr.write("[caveat:hook] worker stale cleanup failed\n");
|
|
33371
33580
|
}
|
|
33372
33581
|
}
|
|
33373
33582
|
}
|
|
33374
|
-
function workerRoot(base =
|
|
33375
|
-
const root =
|
|
33376
|
-
|
|
33377
|
-
const stat =
|
|
33583
|
+
function workerRoot(base = tmpdir()) {
|
|
33584
|
+
const root = join15(base, WORKER_ROOT);
|
|
33585
|
+
mkdirSync9(root, { recursive: true, mode: 448 });
|
|
33586
|
+
const stat = lstatSync2(root);
|
|
33378
33587
|
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
33379
33588
|
if (!stat.isDirectory() || stat.isSymbolicLink() || !hasPrivateOwnership(stat, uid)) throw new Error("worker root is unsafe");
|
|
33380
|
-
const marker =
|
|
33589
|
+
const marker = join15(root, WORKER_MARKER);
|
|
33381
33590
|
try {
|
|
33382
|
-
|
|
33591
|
+
writeFileSync7(marker, "caveat-worker/v1\n", { mode: 384, flag: "wx" });
|
|
33383
33592
|
} catch (error51) {
|
|
33384
33593
|
if (!(error51 && typeof error51 === "object" && "code" in error51 && error51.code === "EEXIST")) throw error51;
|
|
33385
33594
|
}
|
|
33386
|
-
const markerStat =
|
|
33387
|
-
if (!markerStat.isFile() || markerStat.isSymbolicLink() || !hasPrivateOwnership(markerStat, uid) ||
|
|
33595
|
+
const markerStat = lstatSync2(marker);
|
|
33596
|
+
if (!markerStat.isFile() || markerStat.isSymbolicLink() || !hasPrivateOwnership(markerStat, uid) || readFileSync10(marker, "utf-8") !== "caveat-worker/v1\n") throw new Error("worker root marker is invalid");
|
|
33388
33597
|
return root;
|
|
33389
33598
|
}
|
|
33390
33599
|
function isStaleWorkerJobDir(path) {
|
|
33391
33600
|
const entries = readdirSync2(path);
|
|
33392
33601
|
if (entries.length !== 1 || !entries[0].endsWith(".json")) return false;
|
|
33393
|
-
const file2 =
|
|
33394
|
-
const stat =
|
|
33602
|
+
const file2 = join15(path, entries[0]);
|
|
33603
|
+
const stat = lstatSync2(file2);
|
|
33395
33604
|
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
33396
33605
|
if (!stat.isFile() || stat.isSymbolicLink() || !hasPrivateOwnership(stat, uid)) return false;
|
|
33397
|
-
return isKnownStaleWorkerJob(JSON.parse(
|
|
33606
|
+
return isKnownStaleWorkerJob(JSON.parse(readFileSync10(file2, "utf-8")));
|
|
33398
33607
|
}
|
|
33399
33608
|
function hasPrivateOwnership(stat, uid) {
|
|
33400
33609
|
return isPrivateOwnerStat(stat, uid);
|
|
@@ -33416,7 +33625,7 @@ function isKnownStaleWorkerJob(value) {
|
|
|
33416
33625
|
}
|
|
33417
33626
|
function writeLastReindex(caveatHome, value) {
|
|
33418
33627
|
try {
|
|
33419
|
-
|
|
33628
|
+
writeFileSync7(join15(caveatHome, "index", ".last-reindex.json"), JSON.stringify(value), "utf-8");
|
|
33420
33629
|
} catch (err) {
|
|
33421
33630
|
process.stderr.write(`[caveat:hook] reindex status write error: ${errorMessage4(err)}
|
|
33422
33631
|
`);
|
|
@@ -33428,7 +33637,7 @@ async function runReindexWorker() {
|
|
|
33428
33637
|
return;
|
|
33429
33638
|
}
|
|
33430
33639
|
const ctx = buildContextSafely(CLAUDE_HOST);
|
|
33431
|
-
if (!ctx || !
|
|
33640
|
+
if (!ctx || !existsSync15(ctx.paths.dbPath)) {
|
|
33432
33641
|
process.stderr.write("[caveat:hook] auto reindex skipped: index database does not exist\n");
|
|
33433
33642
|
return;
|
|
33434
33643
|
}
|
|
@@ -33490,81 +33699,6 @@ async function runAutoSyncWorker() {
|
|
|
33490
33699
|
`);
|
|
33491
33700
|
}
|
|
33492
33701
|
}
|
|
33493
|
-
function buildToolErrorReminder(job, hits) {
|
|
33494
|
-
const base = toolErrorReminderText(hits);
|
|
33495
|
-
const mode = hookCodexSidecarMode();
|
|
33496
|
-
if (mode === "off") return base;
|
|
33497
|
-
const projectRoot = process.cwd();
|
|
33498
|
-
const hasSidecarConfig = existsSync13(join14(projectRoot, ".codex-sidecar.yml"));
|
|
33499
|
-
if (mode === "auto" && !hasSidecarConfig) return base;
|
|
33500
|
-
const advisory = runCodexSidecarAdvisory({
|
|
33501
|
-
searchText: job.failureText,
|
|
33502
|
-
limit: hits.length,
|
|
33503
|
-
projectRoot,
|
|
33504
|
-
prompt: [
|
|
33505
|
-
"A Claude Code tool just returned an error.",
|
|
33506
|
-
"Use the provided Caveat context to give concise next-step advice.",
|
|
33507
|
-
"Do not tell Claude to search Caveat again unless the context is insufficient."
|
|
33508
|
-
].join(" "),
|
|
33509
|
-
additionalContext: job.additionalContext
|
|
33510
|
-
});
|
|
33511
|
-
if (advisory.status === "ok") {
|
|
33512
|
-
return [
|
|
33513
|
-
base,
|
|
33514
|
-
"",
|
|
33515
|
-
formatCodexSidecarAdvisory(advisory)
|
|
33516
|
-
].filter(Boolean).join("\n");
|
|
33517
|
-
}
|
|
33518
|
-
return [
|
|
33519
|
-
base,
|
|
33520
|
-
"",
|
|
33521
|
-
formatCodexSidecarAdvisory(advisory)
|
|
33522
|
-
].join("\n");
|
|
33523
|
-
}
|
|
33524
|
-
function hookCodexSidecarMode() {
|
|
33525
|
-
const raw = process.env.CAVEAT_HOOK_CODEX_SIDECAR;
|
|
33526
|
-
if (raw === "off" || raw === "auto" || raw === "require") return raw;
|
|
33527
|
-
return "auto";
|
|
33528
|
-
}
|
|
33529
|
-
function buildStopReminder(signals, related) {
|
|
33530
|
-
const base = stopReminderText(signals, related);
|
|
33531
|
-
const mode = hookCodexSidecarMode();
|
|
33532
|
-
if (mode === "off") return base;
|
|
33533
|
-
const projectRoot = process.cwd();
|
|
33534
|
-
const hasSidecarConfig = existsSync13(join14(projectRoot, ".codex-sidecar.yml"));
|
|
33535
|
-
if (mode === "auto" && !hasSidecarConfig) return base;
|
|
33536
|
-
const advisory = runCodexSidecarAdvisory({
|
|
33537
|
-
searchText: struggleSearchText(signals),
|
|
33538
|
-
limit: Math.max(related.length, 1),
|
|
33539
|
-
projectRoot,
|
|
33540
|
-
prompt: [
|
|
33541
|
-
"A Claude Code session is ending after objective struggle signals.",
|
|
33542
|
-
"Use the provided Caveat context and structured hook signal context to advise whether Claude should update an existing caveat or record a new one.",
|
|
33543
|
-
"Be concise and preserve Caveat visibility rules."
|
|
33544
|
-
].join(" "),
|
|
33545
|
-
additionalContext: buildHookSignalSidecarContextBlock({
|
|
33546
|
-
type: "stop",
|
|
33547
|
-
toolFailureCount: signals.toolFailureCount,
|
|
33548
|
-
reeditedFileCount: signals.fileEditCounts.length,
|
|
33549
|
-
webSearchCount: signals.webSearchCount,
|
|
33550
|
-
webFetchCount: signals.webFetchCount,
|
|
33551
|
-
bashRetryCount: signals.bashRetryCount,
|
|
33552
|
-
durationMinutes: signals.durationMinutes
|
|
33553
|
-
})
|
|
33554
|
-
});
|
|
33555
|
-
if (advisory.status === "ok") {
|
|
33556
|
-
return [
|
|
33557
|
-
base,
|
|
33558
|
-
"",
|
|
33559
|
-
formatCodexSidecarAdvisory(advisory)
|
|
33560
|
-
].filter(Boolean).join("\n");
|
|
33561
|
-
}
|
|
33562
|
-
return [
|
|
33563
|
-
base,
|
|
33564
|
-
"",
|
|
33565
|
-
formatCodexSidecarAdvisory(advisory)
|
|
33566
|
-
].join("\n");
|
|
33567
|
-
}
|
|
33568
33702
|
async function runHook(name, arg) {
|
|
33569
33703
|
try {
|
|
33570
33704
|
sweepStaleWorkerDirs();
|
|
@@ -33594,39 +33728,65 @@ async function runHook(name, arg) {
|
|
|
33594
33728
|
}
|
|
33595
33729
|
const payload = parsePayload(CLAUDE_HOST, raw);
|
|
33596
33730
|
const sessionId = getSessionId(payload);
|
|
33597
|
-
const
|
|
33731
|
+
const pending = name === "stop" ? null : peekForSession(CLAUDE_HOST, sessionId);
|
|
33732
|
+
const contexts = pending?.contexts ?? [];
|
|
33598
33733
|
if (name === "user-prompt-submit") {
|
|
33734
|
+
const ctx = buildContextSafely(CLAUDE_HOST);
|
|
33735
|
+
let jevNotice = null;
|
|
33736
|
+
if (ctx?.config.jevEnabled) {
|
|
33737
|
+
try {
|
|
33738
|
+
jevNotice = await runJevStruggle(ctx, "claude", payload);
|
|
33739
|
+
if (jevNotice) contexts.push(jevNotice.text);
|
|
33740
|
+
} catch (err) {
|
|
33741
|
+
process.stderr.write(`[caveat:hook] Jev\u5224\u5B9A\u306B\u5931\u6557: ${errorMessage4(err)}
|
|
33742
|
+
`);
|
|
33743
|
+
}
|
|
33744
|
+
}
|
|
33599
33745
|
const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
|
|
33600
33746
|
const hits = searchCaveatsSafely(CLAUDE_HOST, { topicText: prompt, failureText: prompt, surface: "user_prompt" });
|
|
33601
33747
|
if (hits.length > 0) {
|
|
33602
33748
|
contexts.push(userPromptSubmitReminderText(hits));
|
|
33603
33749
|
}
|
|
33604
|
-
|
|
33605
|
-
|
|
33606
|
-
|
|
33750
|
+
if (ctx) {
|
|
33751
|
+
try {
|
|
33752
|
+
const prepared = prepareSessionDelivery(ctx.caveatHome, sessionId, contexts, jevNotice?.ref ? [jevNotice.ref] : []);
|
|
33753
|
+
const sent = prepared.texts.length === 0 || await emitHookContext(CLAUDE_HOST, `${systemReminderOutput(prepared.texts.join("\n\n"))}
|
|
33607
33754
|
`);
|
|
33755
|
+
if (sent) {
|
|
33756
|
+
prepared.delivered();
|
|
33757
|
+
if (jevNotice && prepared.texts.includes(jevNotice.text)) jevNotice.delivered();
|
|
33758
|
+
if (pending) acknowledgeSessionReminders(CLAUDE_HOST, pending);
|
|
33759
|
+
}
|
|
33760
|
+
} catch (err) {
|
|
33761
|
+
process.stderr.write(`[caveat:hook] \u914D\u9001\u306B\u5931\u6557: ${errorMessage4(err)}
|
|
33762
|
+
`);
|
|
33763
|
+
}
|
|
33608
33764
|
}
|
|
33609
33765
|
process.exit(0);
|
|
33610
33766
|
}
|
|
33611
33767
|
if (name === "post-tool-use") {
|
|
33612
|
-
const
|
|
33613
|
-
if (
|
|
33614
|
-
|
|
33768
|
+
const ctx = buildContextSafely(CLAUDE_HOST);
|
|
33769
|
+
if (ctx) {
|
|
33770
|
+
try {
|
|
33771
|
+
const prepared = prepareSessionDelivery(ctx.caveatHome, sessionId, contexts);
|
|
33772
|
+
const sent = prepared.texts.length === 0 || await emitHookContext(CLAUDE_HOST, `${systemReminderOutput(prepared.texts.join("\n\n"))}
|
|
33615
33773
|
`);
|
|
33774
|
+
if (sent) {
|
|
33775
|
+
prepared.delivered();
|
|
33776
|
+
if (pending) acknowledgeSessionReminders(CLAUDE_HOST, pending);
|
|
33777
|
+
}
|
|
33778
|
+
} catch (err) {
|
|
33779
|
+
process.stderr.write(`[caveat:hook] \u914D\u9001\u306B\u5931\u6557: ${errorMessage4(err)}
|
|
33780
|
+
`);
|
|
33781
|
+
}
|
|
33616
33782
|
}
|
|
33617
33783
|
if (!isToolError(payload)) process.exit(0);
|
|
33618
33784
|
const errText = extractToolResponseText(
|
|
33619
33785
|
payload.tool_response ?? payload.toolResponse ?? payload.error ?? payload
|
|
33620
33786
|
);
|
|
33621
33787
|
if (errText) {
|
|
33622
|
-
const failureKind = payload.hook_event_name === "PostToolUseFailure" ? "post-tool-use-failure" : "error-bearing-post-tool-use";
|
|
33623
|
-
const additionalContext = buildHookSignalSidecarContextBlock({
|
|
33624
|
-
type: "tool-error",
|
|
33625
|
-
toolName: payload.tool_name ?? payload.toolName,
|
|
33626
|
-
failureKind
|
|
33627
|
-
});
|
|
33628
33788
|
const topicText = toolTopicText(payload);
|
|
33629
|
-
spawnWorker({ sessionId, topicText, failureText: errText
|
|
33789
|
+
spawnWorker({ sessionId, topicText, failureText: errText });
|
|
33630
33790
|
}
|
|
33631
33791
|
process.exit(0);
|
|
33632
33792
|
}
|
|
@@ -33653,6 +33813,7 @@ async function runHook(name, arg) {
|
|
|
33653
33813
|
}
|
|
33654
33814
|
}
|
|
33655
33815
|
if (payload.stop_hook_active === true) process.exit(0);
|
|
33816
|
+
if (ctx?.config.jevEnabled) process.exit(0);
|
|
33656
33817
|
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
|
|
33657
33818
|
const signals = transcriptPath ? loadSignalsSafely(transcriptPath) : null;
|
|
33658
33819
|
if (!signals || !hasAnyStruggleSignal(signals)) process.exit(0);
|
|
@@ -33661,7 +33822,7 @@ async function runHook(name, arg) {
|
|
|
33661
33822
|
failureText,
|
|
33662
33823
|
surface: "stop"
|
|
33663
33824
|
})));
|
|
33664
|
-
queueStopForSession(CLAUDE_HOST, sessionId, signals, related, () =>
|
|
33825
|
+
queueStopForSession(CLAUDE_HOST, sessionId, signals, related, () => stopReminderText(signals, related));
|
|
33665
33826
|
process.exit(0);
|
|
33666
33827
|
}
|
|
33667
33828
|
process.stderr.write(`[caveat:hook] unknown hook name: ${name}
|
|
@@ -33671,10 +33832,10 @@ async function runHook(name, arg) {
|
|
|
33671
33832
|
|
|
33672
33833
|
// src/commands/codexHookCmd.ts
|
|
33673
33834
|
import { spawn as spawn3, spawnSync as spawnSync4 } from "node:child_process";
|
|
33674
|
-
import { existsSync as
|
|
33675
|
-
import { homedir as homedir4, tmpdir as
|
|
33676
|
-
import { join as
|
|
33677
|
-
import { randomBytes as
|
|
33835
|
+
import { existsSync as existsSync16, readFileSync as readFileSync11, unlinkSync, writeFileSync as writeFileSync8 } from "node:fs";
|
|
33836
|
+
import { homedir as homedir4, tmpdir as tmpdir2 } from "node:os";
|
|
33837
|
+
import { join as join16 } from "node:path";
|
|
33838
|
+
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
33678
33839
|
var CODEX_HOST = {
|
|
33679
33840
|
agent: "codex",
|
|
33680
33841
|
stderrTag: "caveat:codex-hook",
|
|
@@ -33703,10 +33864,10 @@ function processExitCodeFromText(text) {
|
|
|
33703
33864
|
return m ? Number(m[1]) : null;
|
|
33704
33865
|
}
|
|
33705
33866
|
function transcriptToolOutput(transcriptPath, toolUseId) {
|
|
33706
|
-
if (!transcriptPath || !toolUseId || !
|
|
33867
|
+
if (!transcriptPath || !toolUseId || !existsSync16(transcriptPath)) return null;
|
|
33707
33868
|
let raw = "";
|
|
33708
33869
|
try {
|
|
33709
|
-
raw =
|
|
33870
|
+
raw = readFileSync11(transcriptPath, "utf-8");
|
|
33710
33871
|
} catch {
|
|
33711
33872
|
return null;
|
|
33712
33873
|
}
|
|
@@ -33749,26 +33910,28 @@ function toolInputText(input) {
|
|
|
33749
33910
|
const command = r.command ?? r.cmd;
|
|
33750
33911
|
return typeof command === "string" ? command : "";
|
|
33751
33912
|
}
|
|
33752
|
-
function
|
|
33753
|
-
if (payload.is_error === true) return
|
|
33754
|
-
const topExit = numericExitCode(payload.exit_code ?? payload.exitCode);
|
|
33755
|
-
if (topExit !== null) return topExit !== 0;
|
|
33913
|
+
function codexToolStatus(payload) {
|
|
33914
|
+
if (payload.is_error === true) return "failed";
|
|
33756
33915
|
const resp = payload.tool_response ?? payload.toolResponse;
|
|
33916
|
+
if (resp !== null && typeof resp === "object" && !Array.isArray(resp) && resp.is_error === true) return "failed";
|
|
33917
|
+
const topExit = numericExitCode(payload.exit_code ?? payload.exitCode);
|
|
33918
|
+
if (topExit !== null) return topExit === 0 ? "succeeded" : "failed";
|
|
33757
33919
|
if (resp !== null && typeof resp === "object" && !Array.isArray(resp)) {
|
|
33758
33920
|
const r = resp;
|
|
33759
|
-
|
|
33760
|
-
|
|
33761
|
-
if (exit2 !== null) return exit2 !== 0;
|
|
33921
|
+
const exit = numericExitCode(r.exit_code ?? r.exitCode);
|
|
33922
|
+
if (exit !== null) return exit === 0 ? "succeeded" : "failed";
|
|
33762
33923
|
}
|
|
33763
33924
|
const responseExit = processExitCodeFromText(extractToolResponseText(resp));
|
|
33764
|
-
if (responseExit !== null) return responseExit
|
|
33925
|
+
if (responseExit !== null) return responseExit === 0 ? "succeeded" : "failed";
|
|
33765
33926
|
const transcriptExit = transcriptExitCode(payload);
|
|
33766
|
-
if (transcriptExit !== null) return transcriptExit
|
|
33767
|
-
return
|
|
33927
|
+
if (transcriptExit !== null) return transcriptExit === 0 ? "succeeded" : "failed";
|
|
33928
|
+
return "unknown";
|
|
33768
33929
|
}
|
|
33769
33930
|
function buildCodexPostToolUseWorkerJob(payload) {
|
|
33770
33931
|
const sessionId = codexSessionId(payload);
|
|
33771
33932
|
if (!sessionId) return null;
|
|
33933
|
+
const status2 = codexToolStatus(payload);
|
|
33934
|
+
if (status2 === "succeeded") return null;
|
|
33772
33935
|
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : void 0;
|
|
33773
33936
|
const toolUseId = typeof payload.tool_use_id === "string" ? payload.tool_use_id : void 0;
|
|
33774
33937
|
const responseText = extractToolResponseText(payload.tool_response ?? payload.toolResponse);
|
|
@@ -33776,8 +33939,7 @@ function buildCodexPostToolUseWorkerJob(payload) {
|
|
|
33776
33939
|
const transcriptOutput = transcriptPath && toolUseId ? transcriptToolOutput(transcriptPath, toolUseId) : null;
|
|
33777
33940
|
const topicText = inputText.trim();
|
|
33778
33941
|
const failureText = [responseText, transcriptOutput ?? ""].map((s) => s.trim()).filter(Boolean).join("\n");
|
|
33779
|
-
|
|
33780
|
-
if (knownError) {
|
|
33942
|
+
if (status2 === "failed") {
|
|
33781
33943
|
return { sessionId, topicText, failureText, knownError: true, transcriptPath, toolUseId };
|
|
33782
33944
|
}
|
|
33783
33945
|
if (transcriptPath && toolUseId && isShellLikeTool(payload) && (topicText || failureText)) {
|
|
@@ -33817,9 +33979,9 @@ async function processCodexWorkerJob(job, opts = { waitForTranscript: true }) {
|
|
|
33817
33979
|
if (opts.waitForTranscript && job.transcriptPath && job.toolUseId) {
|
|
33818
33980
|
const transcriptOutput = await waitForTranscriptOutput(job.transcriptPath, job.toolUseId);
|
|
33819
33981
|
if (transcriptOutput) {
|
|
33820
|
-
const
|
|
33821
|
-
if (
|
|
33822
|
-
if (
|
|
33982
|
+
const exit = processExitCodeFromText(transcriptOutput);
|
|
33983
|
+
if (exit !== null) {
|
|
33984
|
+
if (exit === 0) return;
|
|
33823
33985
|
knownError = true;
|
|
33824
33986
|
}
|
|
33825
33987
|
failureText = [failureText, transcriptOutput].filter(Boolean).join("\n");
|
|
@@ -33850,7 +34012,7 @@ async function processCodexWorkerJob(job, opts = { waitForTranscript: true }) {
|
|
|
33850
34012
|
async function runCodexWorker(workFile) {
|
|
33851
34013
|
let raw;
|
|
33852
34014
|
try {
|
|
33853
|
-
raw =
|
|
34015
|
+
raw = readFileSync11(workFile, "utf-8");
|
|
33854
34016
|
} catch {
|
|
33855
34017
|
process.exit(0);
|
|
33856
34018
|
}
|
|
@@ -33867,7 +34029,7 @@ async function runCodexWorker(workFile) {
|
|
|
33867
34029
|
await processCodexWorkerJob(job);
|
|
33868
34030
|
process.exit(0);
|
|
33869
34031
|
}
|
|
33870
|
-
function runDiagnostics(codexHome = process.env.CODEX_HOME ??
|
|
34032
|
+
function runDiagnostics(codexHome = process.env.CODEX_HOME ?? join16(homedir4(), ".codex")) {
|
|
33871
34033
|
const features = spawnSync4("codex", ["features", "list"], {
|
|
33872
34034
|
encoding: "utf-8",
|
|
33873
34035
|
maxBuffer: 1024 * 1024,
|
|
@@ -33915,16 +34077,38 @@ async function runCodexHook(name, arg) {
|
|
|
33915
34077
|
const sessionId = codexSessionId(payload);
|
|
33916
34078
|
if (!sessionId) process.stderr.write("[caveat:codex-hook] missing session_id; pending drain disabled\n");
|
|
33917
34079
|
if (name === "user-prompt-submit") {
|
|
33918
|
-
const
|
|
34080
|
+
const pending = sessionId ? peekForSession(CODEX_HOST, sessionId) : null;
|
|
34081
|
+
const contexts = pending?.contexts ?? [];
|
|
34082
|
+
const ctx = buildContextSafely(CODEX_HOST);
|
|
34083
|
+
let jevNotice = null;
|
|
34084
|
+
if (ctx?.config.jevEnabled) {
|
|
34085
|
+
try {
|
|
34086
|
+
jevNotice = await runJevStruggle(ctx, "codex", payload);
|
|
34087
|
+
if (jevNotice) contexts.push(jevNotice.text);
|
|
34088
|
+
} catch (err) {
|
|
34089
|
+
process.stderr.write(`[caveat:codex-hook] Jev\u5224\u5B9A\u306B\u5931\u6557: ${errorMessage4(err)}
|
|
34090
|
+
`);
|
|
34091
|
+
}
|
|
34092
|
+
}
|
|
33919
34093
|
const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
|
|
33920
34094
|
const hits = searchCaveatsSafely(CODEX_HOST, { topicText: prompt, failureText: prompt, surface: "user_prompt" });
|
|
33921
34095
|
if (hits.length > 0) {
|
|
33922
34096
|
contexts.push(userPromptSubmitReminderText(hits, "native-cli"));
|
|
33923
34097
|
}
|
|
33924
|
-
|
|
33925
|
-
|
|
33926
|
-
|
|
34098
|
+
if (ctx) {
|
|
34099
|
+
try {
|
|
34100
|
+
const prepared = prepareSessionDelivery(ctx.caveatHome, sessionId ?? "_unknown", contexts, jevNotice?.ref ? [jevNotice.ref] : []);
|
|
34101
|
+
const sent = prepared.texts.length === 0 || await emitHookContext(CODEX_HOST, `${codexContextOutput(prepared.texts.join("\n\n"))}
|
|
34102
|
+
`);
|
|
34103
|
+
if (sent) {
|
|
34104
|
+
prepared.delivered();
|
|
34105
|
+
if (jevNotice && prepared.texts.includes(jevNotice.text)) jevNotice.delivered();
|
|
34106
|
+
if (pending) acknowledgeSessionReminders(CODEX_HOST, pending);
|
|
34107
|
+
}
|
|
34108
|
+
} catch (err) {
|
|
34109
|
+
process.stderr.write(`[caveat:codex-hook] \u914D\u9001\u306B\u5931\u6557: ${errorMessage4(err)}
|
|
33927
34110
|
`);
|
|
34111
|
+
}
|
|
33928
34112
|
}
|
|
33929
34113
|
process.exit(0);
|
|
33930
34114
|
}
|
|
@@ -33956,6 +34140,7 @@ async function runCodexHook(name, arg) {
|
|
|
33956
34140
|
`);
|
|
33957
34141
|
}
|
|
33958
34142
|
if (payload.stop_hook_active === true) process.exit(0);
|
|
34143
|
+
if (buildContextSafely(CODEX_HOST)?.config.jevEnabled) process.exit(0);
|
|
33959
34144
|
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
|
|
33960
34145
|
const signals = transcriptPath ? loadSignalsSafely2(transcriptPath) : null;
|
|
33961
34146
|
if (!signals || !hasAnyStruggleSignal(signals)) process.exit(0);
|
|
@@ -33974,7 +34159,7 @@ async function runCodexHook(name, arg) {
|
|
|
33974
34159
|
|
|
33975
34160
|
// src/commands/cursorHookCmd.ts
|
|
33976
34161
|
import { homedir as homedir5 } from "node:os";
|
|
33977
|
-
import { join as
|
|
34162
|
+
import { join as join17 } from "node:path";
|
|
33978
34163
|
var CURSOR_HOST = {
|
|
33979
34164
|
agent: "cursor",
|
|
33980
34165
|
stderrTag: "caveat:cursor-hook",
|
|
@@ -34014,7 +34199,7 @@ function loadSignalsSafely3(path) {
|
|
|
34014
34199
|
return null;
|
|
34015
34200
|
}
|
|
34016
34201
|
}
|
|
34017
|
-
function runDiagnostics2(cursorDir =
|
|
34202
|
+
function runDiagnostics2(cursorDir = join17(homedir5(), ".cursor")) {
|
|
34018
34203
|
const installation = detectCursorHookInstallation(cursorDir);
|
|
34019
34204
|
process.stdout.write(`${JSON.stringify({
|
|
34020
34205
|
installation: installation.installation,
|
|
@@ -34039,7 +34224,24 @@ async function runCursorHook(name, arg) {
|
|
|
34039
34224
|
}
|
|
34040
34225
|
const payload = parsePayload(CURSOR_HOST, raw);
|
|
34041
34226
|
const sessionId = cursorSessionId(payload);
|
|
34042
|
-
const
|
|
34227
|
+
const pending = name === "stop" ? null : peekForSession(CURSOR_HOST, sessionId);
|
|
34228
|
+
const contexts = pending?.contexts ?? [];
|
|
34229
|
+
const deliver = async () => {
|
|
34230
|
+
const ctx = buildContextSafely(CURSOR_HOST);
|
|
34231
|
+
if (!ctx) return;
|
|
34232
|
+
try {
|
|
34233
|
+
const prepared = prepareSessionDelivery(ctx.caveatHome, sessionId, contexts);
|
|
34234
|
+
const sent = prepared.texts.length === 0 || await emitHookContext(CURSOR_HOST, `${cursorContextOutput(prepared.texts.join("\n\n"))}
|
|
34235
|
+
`);
|
|
34236
|
+
if (sent) {
|
|
34237
|
+
prepared.delivered();
|
|
34238
|
+
if (pending) acknowledgeSessionReminders(CURSOR_HOST, pending);
|
|
34239
|
+
}
|
|
34240
|
+
} catch (err) {
|
|
34241
|
+
process.stderr.write(`[caveat:cursor-hook] \u914D\u9001\u306B\u5931\u6557: ${errorMessage4(err)}
|
|
34242
|
+
`);
|
|
34243
|
+
}
|
|
34244
|
+
};
|
|
34043
34245
|
if (name === "user-prompt-submit") {
|
|
34044
34246
|
const prompt = cursorPrompt(payload);
|
|
34045
34247
|
const hits = searchCaveatsSafely(CURSOR_HOST, {
|
|
@@ -34048,11 +34250,7 @@ async function runCursorHook(name, arg) {
|
|
|
34048
34250
|
surface: "user_prompt"
|
|
34049
34251
|
});
|
|
34050
34252
|
if (hits.length > 0) contexts.push(userPromptSubmitReminderText(hits, "native-cli"));
|
|
34051
|
-
|
|
34052
|
-
if (compacted.length > 0) {
|
|
34053
|
-
process.stdout.write(`${cursorContextOutput(compacted.join("\n\n"))}
|
|
34054
|
-
`);
|
|
34055
|
-
}
|
|
34253
|
+
await deliver();
|
|
34056
34254
|
process.exit(0);
|
|
34057
34255
|
}
|
|
34058
34256
|
if (name === "post-tool-use") {
|
|
@@ -34067,11 +34265,7 @@ async function runCursorHook(name, arg) {
|
|
|
34067
34265
|
});
|
|
34068
34266
|
if (hits.length > 0) contexts.push(toolErrorReminderText(hits, "native-cli"));
|
|
34069
34267
|
}
|
|
34070
|
-
|
|
34071
|
-
if (compacted.length > 0) {
|
|
34072
|
-
process.stdout.write(`${cursorContextOutput(compacted.join("\n\n"))}
|
|
34073
|
-
`);
|
|
34074
|
-
}
|
|
34268
|
+
await deliver();
|
|
34075
34269
|
process.exit(0);
|
|
34076
34270
|
}
|
|
34077
34271
|
if (name === "stop") {
|
|
@@ -34114,9 +34308,9 @@ async function runCursorHook(name, arg) {
|
|
|
34114
34308
|
}
|
|
34115
34309
|
|
|
34116
34310
|
// src/commands/pull.ts
|
|
34117
|
-
import { existsSync as
|
|
34311
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
34118
34312
|
async function runPull(ctx) {
|
|
34119
|
-
const hasCommunityDir =
|
|
34313
|
+
const hasCommunityDir = existsSync17(ctx.paths.communityDir);
|
|
34120
34314
|
if (!hasCommunityDir) {
|
|
34121
34315
|
ctx.logger.info(
|
|
34122
34316
|
"no community repos yet \u2014 add one with `caveat community add <github-url>`."
|
|
@@ -34214,213 +34408,6 @@ async function runSync(ctx, opts, dependencies = {}) {
|
|
|
34214
34408
|
}
|
|
34215
34409
|
}
|
|
34216
34410
|
|
|
34217
|
-
// src/commands/codexSidecar.ts
|
|
34218
|
-
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
34219
|
-
import { closeSync, constants as constants4, fstatSync, lstatSync as lstatSync2, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync3, openSync, readSync as readSync2, realpathSync as realpathSync4, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "node:fs";
|
|
34220
|
-
import { tmpdir as tmpdir4 } from "node:os";
|
|
34221
|
-
import { basename as basename2, dirname as dirname6, join as join17 } from "node:path";
|
|
34222
|
-
import { cwd, exit } from "node:process";
|
|
34223
|
-
function runCodexSidecarDiagnostics(logger, opts) {
|
|
34224
|
-
const plan = buildCodexSidecarDiagnosticsCommand({
|
|
34225
|
-
projectRoot: opts.project ?? cwd(),
|
|
34226
|
-
preset: opts.preset ?? "review",
|
|
34227
|
-
cli: cliSpec(opts)
|
|
34228
|
-
});
|
|
34229
|
-
exit(executePlan(logger, plan.command, plan.args, { saveResult: opts.saveResult }));
|
|
34230
|
-
}
|
|
34231
|
-
function runCodexSidecarSmoke(logger, opts) {
|
|
34232
|
-
const plan = buildCodexSidecarReadOnlySmokeCommand({
|
|
34233
|
-
projectRoot: opts.project ?? cwd(),
|
|
34234
|
-
preset: opts.preset === "review" || opts.preset === "opinion" || opts.preset === "risk" ? opts.preset : "explore",
|
|
34235
|
-
cli: cliSpec(opts)
|
|
34236
|
-
});
|
|
34237
|
-
exit(executePlan(logger, plan.command, plan.args, { saveResult: opts.saveResult }));
|
|
34238
|
-
}
|
|
34239
|
-
function runCodexSidecarWithCaveats(ctx, workflow, prompt, opts) {
|
|
34240
|
-
const decision = decideCodexSidecarExecution({
|
|
34241
|
-
hostAgent: opts.hostAgent ?? "unknown",
|
|
34242
|
-
availability: opts.availability ?? "operational",
|
|
34243
|
-
workflow,
|
|
34244
|
-
sidecarAgent: opts.sidecarAgent,
|
|
34245
|
-
requiresIsolation: opts.requiresIsolation,
|
|
34246
|
-
structuredResultRequired: opts.structuredResultRequired,
|
|
34247
|
-
explicitSecondPass: opts.explicitSecondPass
|
|
34248
|
-
});
|
|
34249
|
-
if (!decision.useSidecar) {
|
|
34250
|
-
process.stdout.write(JSON.stringify({ status: "skipped", decision }, null, 2) + "\n");
|
|
34251
|
-
exit(0);
|
|
34252
|
-
}
|
|
34253
|
-
const contextDir = mkdtempSync3(join17(tmpdir4(), "caveat-sidecar-context-"));
|
|
34254
|
-
const contextFile = join17(contextDir, "context.json");
|
|
34255
|
-
let status2 = 1;
|
|
34256
|
-
try {
|
|
34257
|
-
const blocks = collectCaveatContextBlocks(ctx, {
|
|
34258
|
-
query: opts.query ?? prompt ?? "",
|
|
34259
|
-
limit: opts.limit ?? 5,
|
|
34260
|
-
source: opts.source,
|
|
34261
|
-
visibility: opts.visibility
|
|
34262
|
-
});
|
|
34263
|
-
const additionalBlocks = opts.additionalContextFile ? readHookSignalAdditionalContextFile(opts.additionalContextFile) : [];
|
|
34264
|
-
writeFileSync7(contextFile, JSON.stringify({ context: [...blocks, ...additionalBlocks] }, null, 2) + "\n", "utf-8");
|
|
34265
|
-
const plan = buildCodexSidecarRunCommand({
|
|
34266
|
-
workflow,
|
|
34267
|
-
projectRoot: opts.project ?? cwd(),
|
|
34268
|
-
preset: opts.preset ?? defaultPreset(workflow),
|
|
34269
|
-
prompt,
|
|
34270
|
-
contextFile,
|
|
34271
|
-
cli: cliSpec(opts)
|
|
34272
|
-
});
|
|
34273
|
-
status2 = executePlan(ctx.logger, plan.command, plan.args, { saveResult: opts.saveResult });
|
|
34274
|
-
} finally {
|
|
34275
|
-
rmSync3(contextDir, { recursive: true, force: true });
|
|
34276
|
-
}
|
|
34277
|
-
exit(status2);
|
|
34278
|
-
}
|
|
34279
|
-
var MAX_ADDITIONAL_CONTEXT_BYTES = 4096;
|
|
34280
|
-
function readHookSignalAdditionalContextFile(path, testProbe) {
|
|
34281
|
-
const platform = testProbe?.platform ?? process.platform;
|
|
34282
|
-
assertWindowsPrivateTempContainer(path, platform);
|
|
34283
|
-
const before = lstatSync2(path);
|
|
34284
|
-
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
34285
|
-
assertPrivateRegular(before, uid, platform);
|
|
34286
|
-
testProbe?.afterLstat?.();
|
|
34287
|
-
const fd = openSync(path, constants4.O_RDONLY | constants4.O_NONBLOCK | (constants4.O_NOFOLLOW ?? 0));
|
|
34288
|
-
let raw;
|
|
34289
|
-
try {
|
|
34290
|
-
const opened = fstatSync(fd);
|
|
34291
|
-
assertPrivateRegular(opened, uid, platform);
|
|
34292
|
-
if (opened.dev !== before.dev || opened.ino !== before.ino) throw new Error("additional context file changed during open");
|
|
34293
|
-
const bytes = Buffer.alloc(MAX_ADDITIONAL_CONTEXT_BYTES + 1);
|
|
34294
|
-
let count = 0;
|
|
34295
|
-
while (count < bytes.length) {
|
|
34296
|
-
const read = readSync2(fd, bytes, count, bytes.length - count, count);
|
|
34297
|
-
if (read === 0) break;
|
|
34298
|
-
count += read;
|
|
34299
|
-
}
|
|
34300
|
-
if (count > MAX_ADDITIONAL_CONTEXT_BYTES) throw new Error(`additional context file exceeds ${MAX_ADDITIONAL_CONTEXT_BYTES} bytes`);
|
|
34301
|
-
const after = fstatSync(fd);
|
|
34302
|
-
assertPrivateRegular(after, uid, platform);
|
|
34303
|
-
if (after.dev !== opened.dev || after.ino !== opened.ino || after.dev !== before.dev || after.ino !== before.ino) throw new Error("additional context file changed during read");
|
|
34304
|
-
raw = bytes.subarray(0, count).toString("utf-8");
|
|
34305
|
-
} finally {
|
|
34306
|
-
closeSync(fd);
|
|
34307
|
-
}
|
|
34308
|
-
const parsed = JSON.parse(raw);
|
|
34309
|
-
if (!isRecord(parsed) || !Array.isArray(parsed.context) || parsed.context.length !== 1) {
|
|
34310
|
-
throw new Error("additional context file must contain exactly one context block");
|
|
34311
|
-
}
|
|
34312
|
-
const block = parsed.context[0];
|
|
34313
|
-
if (!isHookSignalBlock(block)) {
|
|
34314
|
-
throw new Error("additional context file contains an invalid hook signal block");
|
|
34315
|
-
}
|
|
34316
|
-
return [block];
|
|
34317
|
-
}
|
|
34318
|
-
function assertPrivateRegular(stat, uid, platform) {
|
|
34319
|
-
const privateOwner = isPrivateOwnerStat(stat, uid, platform);
|
|
34320
|
-
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_ADDITIONAL_CONTEXT_BYTES || !privateOwner) throw new Error("additional context file must be a private owner-only regular file within 4096 bytes");
|
|
34321
|
-
}
|
|
34322
|
-
function assertWindowsPrivateTempContainer(path, platform) {
|
|
34323
|
-
if (!isWindows(platform)) return;
|
|
34324
|
-
const parent = dirname6(path);
|
|
34325
|
-
const parentStat = lstatSync2(parent);
|
|
34326
|
-
const resolvedParent = realpathSync4(parent);
|
|
34327
|
-
const resolvedTemp = realpathSync4(tmpdir4());
|
|
34328
|
-
if (parentStat.isSymbolicLink() || !parentStat.isDirectory() || dirname6(resolvedParent) !== resolvedTemp || !basename2(resolvedParent).startsWith("caveat-")) {
|
|
34329
|
-
throw new Error("additional context file must be inside a reserved per-user Caveat temporary directory");
|
|
34330
|
-
}
|
|
34331
|
-
}
|
|
34332
|
-
function isRecord(value) {
|
|
34333
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
34334
|
-
}
|
|
34335
|
-
function isHookSignalBlock(value) {
|
|
34336
|
-
if (!isRecord(value) || !hasExactKeys(value, ["kind", "source", "trust", "summary", "data"]) || value.kind !== "manual_note" || value.source !== "caveat-hook-signal" || value.trust !== "local" || typeof value.summary !== "string" || !isRecord(value.data)) return false;
|
|
34337
|
-
const data = value.data;
|
|
34338
|
-
if (data.type === "tool-error") {
|
|
34339
|
-
if (!hasExactKeys(data, ["type", "tool", "failure_kind"]) || typeof data.tool !== "string" || data.failure_kind !== "post-tool-use-failure" && data.failure_kind !== "error-bearing-post-tool-use") return false;
|
|
34340
|
-
const canonical2 = buildHookSignalFromData(data);
|
|
34341
|
-
return canonical2 !== null && JSON.stringify(value) === JSON.stringify(canonical2);
|
|
34342
|
-
}
|
|
34343
|
-
if (!hasExactKeys(data, ["type", "tool_failure_count", "reedited_file_count", "web_search_count", "web_fetch_count", "bash_retry_count", "duration_minutes"])) return false;
|
|
34344
|
-
const canonical = buildHookSignalFromData(data);
|
|
34345
|
-
return canonical !== null && JSON.stringify(value) === JSON.stringify(canonical);
|
|
34346
|
-
}
|
|
34347
|
-
function hasExactKeys(value, keys) {
|
|
34348
|
-
const actual = Object.keys(value).sort();
|
|
34349
|
-
return actual.length === keys.length && actual.every((key, index) => key === keys.slice().sort()[index]);
|
|
34350
|
-
}
|
|
34351
|
-
function buildHookSignalFromData(data) {
|
|
34352
|
-
if (data.type === "tool-error") {
|
|
34353
|
-
const validTools = ["bash", "edit", "write", "read", "glob", "grep", "web-search", "web-fetch", "notebook-edit", "other"];
|
|
34354
|
-
if (!validTools.includes(String(data.tool))) return null;
|
|
34355
|
-
const toolNames = { bash: "Bash", edit: "Edit", write: "Write", read: "Read", glob: "Glob", grep: "Grep", "web-search": "WebSearch", "web-fetch": "WebFetch", "notebook-edit": "NotebookEdit", other: "other" };
|
|
34356
|
-
return buildHookSignalSidecarContextBlock({ type: "tool-error", toolName: toolNames[String(data.tool)], failureKind: data.failure_kind });
|
|
34357
|
-
}
|
|
34358
|
-
if (data.type !== "stop") return null;
|
|
34359
|
-
const keys = ["tool_failure_count", "reedited_file_count", "web_search_count", "web_fetch_count", "bash_retry_count", "duration_minutes"];
|
|
34360
|
-
if (!keys.every((key) => typeof data[key] === "number" && Number.isInteger(data[key]) && data[key] >= 0 && data[key] <= 1e4)) return null;
|
|
34361
|
-
return buildHookSignalSidecarContextBlock({ type: "stop", toolFailureCount: data.tool_failure_count, reeditedFileCount: data.reedited_file_count, webSearchCount: data.web_search_count, webFetchCount: data.web_fetch_count, bashRetryCount: data.bash_retry_count, durationMinutes: data.duration_minutes });
|
|
34362
|
-
}
|
|
34363
|
-
function runCodexSidecarWorkSmoke(ctx, prompt, opts) {
|
|
34364
|
-
const plan = buildCodexSidecarRunCommand({
|
|
34365
|
-
workflow: "work",
|
|
34366
|
-
projectRoot: opts.project ?? cwd(),
|
|
34367
|
-
preset: opts.preset ?? "work",
|
|
34368
|
-
prompt: prompt ?? [
|
|
34369
|
-
"Create docs/codex-sidecar-work-smoke.md with one sentence:",
|
|
34370
|
-
'"codex_work smoke succeeded in an isolated worktree."',
|
|
34371
|
-
"Do not edit any other file."
|
|
34372
|
-
].join(" "),
|
|
34373
|
-
preserveWorktree: false,
|
|
34374
|
-
cli: cliSpec(opts)
|
|
34375
|
-
});
|
|
34376
|
-
exit(executePlan(ctx.logger, plan.command, plan.args, { saveResult: opts.saveResult }));
|
|
34377
|
-
}
|
|
34378
|
-
function cliSpec(opts) {
|
|
34379
|
-
if (opts.nodeCli) {
|
|
34380
|
-
return { command: process.execPath, argsPrefix: [opts.nodeCli] };
|
|
34381
|
-
}
|
|
34382
|
-
return { command: opts.command ?? "codex-sidecar" };
|
|
34383
|
-
}
|
|
34384
|
-
function collectCaveatContextBlocks(ctx, opts) {
|
|
34385
|
-
const filters = {};
|
|
34386
|
-
if (opts.source) filters.source = opts.source;
|
|
34387
|
-
if (opts.visibility) filters.visibility = opts.visibility;
|
|
34388
|
-
const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
|
|
34389
|
-
try {
|
|
34390
|
-
const hits = search(db, { query: opts.query, filters, limit: opts.limit });
|
|
34391
|
-
if (hits.length > 0) markHit(db, hits);
|
|
34392
|
-
const entries = hits.map((hit) => get(db, hit.id, hit.source)).filter((entry) => entry !== null);
|
|
34393
|
-
return caveatEntriesToSidecarContextBlocks(entries);
|
|
34394
|
-
} finally {
|
|
34395
|
-
db.close();
|
|
34396
|
-
}
|
|
34397
|
-
}
|
|
34398
|
-
function defaultPreset(workflow) {
|
|
34399
|
-
return workflow === "risk-check" ? "risk" : workflow;
|
|
34400
|
-
}
|
|
34401
|
-
function executePlan(logger, command, args, options = {}) {
|
|
34402
|
-
logger.info(`[codex-sidecar] ${command} ${args.map(shellDisplayQuote).join(" ")}`);
|
|
34403
|
-
const result = spawnSync5(command, args, { encoding: "utf-8", stdio: "pipe" });
|
|
34404
|
-
if (result.stdout) process.stdout.write(result.stdout);
|
|
34405
|
-
if (result.stderr) process.stderr.write(result.stderr);
|
|
34406
|
-
if (options.saveResult && result.stdout) {
|
|
34407
|
-
saveStructuredResult(options.saveResult, result.stdout);
|
|
34408
|
-
}
|
|
34409
|
-
if (result.error) {
|
|
34410
|
-
logger.error(`[codex-sidecar] ${result.error.message}`);
|
|
34411
|
-
return 1;
|
|
34412
|
-
}
|
|
34413
|
-
return result.status ?? 1;
|
|
34414
|
-
}
|
|
34415
|
-
function saveStructuredResult(path, stdout) {
|
|
34416
|
-
const parsed = JSON.parse(stdout);
|
|
34417
|
-
mkdirSync7(dirname6(path), { recursive: true });
|
|
34418
|
-
writeFileSync7(path, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
34419
|
-
}
|
|
34420
|
-
function shellDisplayQuote(value) {
|
|
34421
|
-
return /[\s"'$`]/.test(value) ? JSON.stringify(value) : value;
|
|
34422
|
-
}
|
|
34423
|
-
|
|
34424
34411
|
// src/commands/community.ts
|
|
34425
34412
|
async function runCommunityAdd(ctx, url2) {
|
|
34426
34413
|
try {
|
|
@@ -34503,7 +34490,7 @@ function runCommunityRemove(ctx, handle, opts) {
|
|
|
34503
34490
|
|
|
34504
34491
|
// src/commands/factoryDiagnostics.ts
|
|
34505
34492
|
import { execFileSync } from "node:child_process";
|
|
34506
|
-
import { existsSync as
|
|
34493
|
+
import { existsSync as existsSync18, readFileSync as readFileSync12 } from "node:fs";
|
|
34507
34494
|
import { join as join18 } from "node:path";
|
|
34508
34495
|
import { DatabaseSync } from "node:sqlite";
|
|
34509
34496
|
import { parse as parseToml2 } from "smol-toml";
|
|
@@ -34516,9 +34503,9 @@ function isRecord2(value) {
|
|
|
34516
34503
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
34517
34504
|
}
|
|
34518
34505
|
function claudeRegistration(path, nodePath, cliScriptPath) {
|
|
34519
|
-
if (!
|
|
34506
|
+
if (!existsSync18(path)) return status(false, "not_registered");
|
|
34520
34507
|
try {
|
|
34521
|
-
const value = JSON.parse(
|
|
34508
|
+
const value = JSON.parse(readFileSync12(path, "utf8"));
|
|
34522
34509
|
if (!isRecord2(value) || !isRecord2(value.mcpServers)) return status(false, "not_registered");
|
|
34523
34510
|
return status(isCaveatClaudeMcpRegistration(value.mcpServers.caveat, nodePath, cliScriptPath), "not_registered");
|
|
34524
34511
|
} catch {
|
|
@@ -34527,7 +34514,7 @@ function claudeRegistration(path, nodePath, cliScriptPath) {
|
|
|
34527
34514
|
}
|
|
34528
34515
|
function claudeHooks(claudeDir, nodePath, cliScriptPath) {
|
|
34529
34516
|
try {
|
|
34530
|
-
const settings = JSON.parse(
|
|
34517
|
+
const settings = JSON.parse(readFileSync12(join18(claudeDir, "settings.json"), "utf8"));
|
|
34531
34518
|
const present = (event, subcommand) => settings.hooks?.[event]?.some((entry) => entry.hooks?.some((item) => typeof item.command === "string" && isCanonicalCaveatClaudeHookCommand(item.command, subcommand, nodePath, cliScriptPath))) ?? false;
|
|
34532
34519
|
return { user_prompt_submit: hook(present("UserPromptSubmit", "user-prompt-submit")), post_tool_use: hook(present("PostToolUse", "post-tool-use")), post_tool_use_failure: hook(present("PostToolUseFailure", "post-tool-use")), stop: hook(present("Stop", "stop")) };
|
|
34533
34520
|
} catch {
|
|
@@ -34549,7 +34536,7 @@ function strictSearchSchema(db) {
|
|
|
34549
34536
|
return columns.map((column) => `${column.name}:${column.hidden}`).join(",") === "id:0,title:0,body:0,tags:0,entries_fts:1,rank:1";
|
|
34550
34537
|
}
|
|
34551
34538
|
function database(path) {
|
|
34552
|
-
if (!
|
|
34539
|
+
if (!existsSync18(path)) return { status: "not_ready", reason_code: "missing", schema_version: null, supported_schema_version: 3, migration_status: "unverified" };
|
|
34553
34540
|
try {
|
|
34554
34541
|
const db = new DatabaseSync(path, { readOnly: true });
|
|
34555
34542
|
try {
|
|
@@ -34575,9 +34562,9 @@ function database(path) {
|
|
|
34575
34562
|
}
|
|
34576
34563
|
function codexFeature(codexHome) {
|
|
34577
34564
|
const path = join18(codexHome, "config.toml");
|
|
34578
|
-
if (!
|
|
34565
|
+
if (!existsSync18(path)) return status(false, "feature_disabled");
|
|
34579
34566
|
try {
|
|
34580
|
-
const config2 = parseToml2(
|
|
34567
|
+
const config2 = parseToml2(readFileSync12(path, "utf8"));
|
|
34581
34568
|
if (!isRecord2(config2.features)) return status(false, "feature_disabled");
|
|
34582
34569
|
const features = config2.features;
|
|
34583
34570
|
return status(features.hooks === true && (features.codex_hooks === void 0 || features.codex_hooks === true), "feature_disabled");
|
|
@@ -34586,7 +34573,7 @@ function codexFeature(codexHome) {
|
|
|
34586
34573
|
}
|
|
34587
34574
|
}
|
|
34588
34575
|
function codexHooks(codexHome, nodePath, cliScriptPath) {
|
|
34589
|
-
const value = JSON.parse(
|
|
34576
|
+
const value = JSON.parse(readFileSync12(join18(codexHome, "hooks.json"), "utf8"));
|
|
34590
34577
|
if (!isRecord2(value) || !isRecord2(value.hooks)) throw Error("config_unreadable");
|
|
34591
34578
|
const hooks = value.hooks;
|
|
34592
34579
|
const present = (event, subcommand) => Array.isArray(hooks[event]) && hooks[event].some((entry) => isRecord2(entry) && Array.isArray(entry.hooks) && entry.hooks.some((item) => isCanonicalCaveatCodexHookEntry(item, subcommand, nodePath, cliScriptPath)));
|
|
@@ -34662,6 +34649,32 @@ function parseCursor(value) {
|
|
|
34662
34649
|
return parsed;
|
|
34663
34650
|
}
|
|
34664
34651
|
|
|
34652
|
+
// src/commands/jev.ts
|
|
34653
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
34654
|
+
async function enableJev(ctx) {
|
|
34655
|
+
const key = (await readStdin()).trim();
|
|
34656
|
+
if (!key) throw new Error("Jev API key was not provided on stdin");
|
|
34657
|
+
const response = await callJev(key, { purpose: "Caveat connection check" }, {
|
|
34658
|
+
probe: { type: "noul", instructions: "Is this a connection check?" }
|
|
34659
|
+
});
|
|
34660
|
+
if (response.answers.probe?.type !== "noul") throw new Error("Jev connection check returned an invalid answer");
|
|
34661
|
+
writeJevKey(ctx.caveatHome, key);
|
|
34662
|
+
writeUserConfigPatch(ctx.userConfigPath, { jevEnabled: true });
|
|
34663
|
+
process.stdout.write("Jev\u306B\u3088\u308B\u82E6\u6226\u5224\u5B9A\u3092\u6709\u52B9\u306B\u3057\u307E\u3057\u305F\u3002\n");
|
|
34664
|
+
}
|
|
34665
|
+
function disableJev(ctx) {
|
|
34666
|
+
writeUserConfigPatch(ctx.userConfigPath, { jevEnabled: false });
|
|
34667
|
+
process.stdout.write("Jev\u306B\u3088\u308B\u82E6\u6226\u5224\u5B9A\u3092\u7121\u52B9\u306B\u3057\u307E\u3057\u305F\u3002\n");
|
|
34668
|
+
}
|
|
34669
|
+
function jevStatus(ctx) {
|
|
34670
|
+
process.stdout.write(`${JSON.stringify({
|
|
34671
|
+
schema: "caveat.jev_status.v1",
|
|
34672
|
+
enabled: ctx.config.jevEnabled,
|
|
34673
|
+
credential: existsSync19(jevKeyPath(ctx.caveatHome)) ? "present" : "missing"
|
|
34674
|
+
})}
|
|
34675
|
+
`);
|
|
34676
|
+
}
|
|
34677
|
+
|
|
34665
34678
|
// src/index.ts
|
|
34666
34679
|
var program = new Command();
|
|
34667
34680
|
var agentPaths = resolveAgentConfigPaths();
|
|
@@ -34679,6 +34692,16 @@ program.command("factory-diagnostics").description("Emit read-only factory diagn
|
|
|
34679
34692
|
if (output.overall.status !== "ready") process.exitCode = 1;
|
|
34680
34693
|
});
|
|
34681
34694
|
var runtimeErrors = program.command("runtime-errors").description("Inspect Caveat opt-in runtime errors");
|
|
34695
|
+
var jev = program.command("jev").description("Configure Jev struggle detection");
|
|
34696
|
+
jev.command("enable").requiredOption("--key-stdin", "read the TypeSafe API key from stdin").action(async () => {
|
|
34697
|
+
await enableJev(buildContext(stdoutLogger));
|
|
34698
|
+
});
|
|
34699
|
+
jev.command("disable").action(() => {
|
|
34700
|
+
disableJev(buildContext(stdoutLogger));
|
|
34701
|
+
});
|
|
34702
|
+
jev.command("status").action(() => {
|
|
34703
|
+
jevStatus(buildContext(stdoutLogger));
|
|
34704
|
+
});
|
|
34682
34705
|
var parseLimit = (value) => {
|
|
34683
34706
|
const parsed = parseCursor(value);
|
|
34684
34707
|
if (parsed < 1 || parsed > 256) throw Error("invalid_limit");
|
|
@@ -34696,8 +34719,8 @@ runtimeErrors.command("ack <cursor>").requiredOption("--json", "emit JSON").acti
|
|
|
34696
34719
|
process.stdout.write(`${JSON.stringify(runRuntimeErrors("ack", cursor))}
|
|
34697
34720
|
`);
|
|
34698
34721
|
});
|
|
34699
|
-
for (const action of ["resolve", "reopen"]) runtimeErrors.command(`${action} <fingerprint>`).requiredOption("--json", "emit JSON").action((
|
|
34700
|
-
process.stdout.write(`${JSON.stringify(runRuntimeErrors(action,
|
|
34722
|
+
for (const action of ["resolve", "reopen"]) runtimeErrors.command(`${action} <fingerprint>`).requiredOption("--json", "emit JSON").action((fingerprint2) => {
|
|
34723
|
+
process.stdout.write(`${JSON.stringify(runRuntimeErrors(action, fingerprint2))}
|
|
34701
34724
|
`);
|
|
34702
34725
|
});
|
|
34703
34726
|
program.command("init").description(
|
|
@@ -34902,30 +34925,6 @@ cursorHook.command("post-tool-use").description("Run the Cursor postToolUse / po
|
|
|
34902
34925
|
cursorHook.command("stop").description("Run the Cursor stop hook").action(async () => {
|
|
34903
34926
|
await runCursorHook("stop");
|
|
34904
34927
|
});
|
|
34905
|
-
var codexSidecar = program.command("codex-sidecar").description("Check Codex sidecar availability for the current repository");
|
|
34906
|
-
codexSidecar.command("diagnostics").description("Run codex-sidecar diagnostics for this repository").option("--project <path>", "repository root to check").option("--preset <preset>", "codex-sidecar preset", "review").option("--command <command>", "codex-sidecar executable", "codex-sidecar").option("--node-cli <path>", "development path to codex-sidecar CLI JS").option("--save-result <path>", "write structured SidecarResult JSON to this path").action((opts) => {
|
|
34907
|
-
runCodexSidecarDiagnostics(stdoutLogger, opts);
|
|
34908
|
-
});
|
|
34909
|
-
codexSidecar.command("smoke").description("Run a read-only codex_explore smoke through codex-sidecar").option("--project <path>", "repository root to check").option("--preset <preset>", "codex-sidecar read-only preset", "explore").option("--command <command>", "codex-sidecar executable", "codex-sidecar").option("--node-cli <path>", "development path to codex-sidecar CLI JS").option("--save-result <path>", "write structured SidecarResult JSON to this path").action((opts) => {
|
|
34910
|
-
runCodexSidecarSmoke(stdoutLogger, opts);
|
|
34911
|
-
});
|
|
34912
|
-
codexSidecar.command("run <workflow> [prompt]").description("Run read-only codex-sidecar with relevant Caveat entries as context").option("--project <path>", "repository root to check").option("--preset <preset>", "codex-sidecar preset").option("--query <query>", "Caveat search query; defaults to prompt text").option("--limit <n>", "maximum caveat entries to pass", (v) => Number(v), 5).option("--source <source>", "own | community | all", "all").option("--visibility <visibility>", "public | private | all", "all").option("--host-agent <agent>", "claude | codex | automation | unknown", "unknown").option("--availability <level>", "disabled | unavailable | configured | operational | work-capable", "operational").option("--sidecar-agent <agent>", "codex | disabled | auto").option("--requires-isolation", "mark the sidecar call as isolation-bounded", false).option("--structured-result-required", "mark structured SidecarResult as the delegation boundary", false).option("--explicit-second-pass", "mark the sidecar call as an explicit second-pass review", false).option("--command <command>", "codex-sidecar executable", "codex-sidecar").option("--node-cli <path>", "development path to codex-sidecar CLI JS").option("--save-result <path>", "write structured SidecarResult JSON to this path").option("--additional-context-file <path>", "strictly validated bounded hook-signal context file").action(
|
|
34913
|
-
(workflow, prompt, opts) => {
|
|
34914
|
-
const normalized = workflow === "risk" ? "risk-check" : workflow;
|
|
34915
|
-
if (normalized !== "review" && normalized !== "explore" && normalized !== "opinion" && normalized !== "risk-check") {
|
|
34916
|
-
process.stderr.write("[caveat:error] workflow must be review | explore | opinion | risk-check\n");
|
|
34917
|
-
process.exit(1);
|
|
34918
|
-
}
|
|
34919
|
-
const ctx = buildContext(stdoutLogger);
|
|
34920
|
-
runCodexSidecarWithCaveats(ctx, normalized, prompt, opts);
|
|
34921
|
-
}
|
|
34922
|
-
);
|
|
34923
|
-
codexSidecar.command("work-smoke [prompt]").description("Run codex_work in an isolated worktree and remove it after verification").option("--project <path>", "repository root to check").option("--preset <preset>", "codex-sidecar work preset", "work").option("--command <command>", "codex-sidecar executable", "codex-sidecar").option("--node-cli <path>", "development path to codex-sidecar CLI JS").option("--save-result <path>", "write structured SidecarResult JSON to this path").action(
|
|
34924
|
-
(prompt, opts) => {
|
|
34925
|
-
const ctx = buildContext(stdoutLogger);
|
|
34926
|
-
runCodexSidecarWorkSmoke(ctx, prompt, opts);
|
|
34927
|
-
}
|
|
34928
|
-
);
|
|
34929
34928
|
var community = program.command("community").description("Manage community caveat repos (shallow clones under <caveatHome>/community/)");
|
|
34930
34929
|
community.command("add <url>").description("Shallow-clone a GitHub caveat repo into community/<handle>/").action(async (url2) => {
|
|
34931
34930
|
const ctx = buildContext(stdoutLogger);
|