caveat-cli 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-Z2XLYCR3.js → chunk-ZNAFNCPW.js} +2517 -1170
- package/dist/chunk-ZNAFNCPW.js.map +1 -0
- package/dist/index.js +1266 -641
- package/dist/index.js.map +1 -1
- package/dist/{server-DCE3ODHI.js → server-SZAVLE56.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-Z2XLYCR3.js.map +0 -1
- /package/dist/{server-DCE3ODHI.js.map → server-SZAVLE56.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
import { createRequire as __caveat_createRequire } from 'node:module';
|
|
3
3
|
const require = __caveat_createRequire(import.meta.url);
|
|
4
4
|
import {
|
|
5
|
+
AUTO_SYNC_DEBOUNCE_MS,
|
|
6
|
+
CAVEAT_AUTO_SYNC_ENV,
|
|
5
7
|
KNOWLEDGE_GITIGNORE,
|
|
8
|
+
PublishScanError,
|
|
9
|
+
SyncError,
|
|
6
10
|
__commonJS,
|
|
7
11
|
__export,
|
|
8
12
|
__toESM,
|
|
@@ -11,6 +15,7 @@ import {
|
|
|
11
15
|
buildCodexSidecarDiagnosticsCommand,
|
|
12
16
|
buildCodexSidecarReadOnlySmokeCommand,
|
|
13
17
|
buildCodexSidecarRunCommand,
|
|
18
|
+
buildHookSignalSidecarContextBlock,
|
|
14
19
|
caveatEntriesToSidecarContextBlocks,
|
|
15
20
|
cleanupStalePendingDirs,
|
|
16
21
|
communityAdd,
|
|
@@ -18,8 +23,10 @@ import {
|
|
|
18
23
|
communityPull,
|
|
19
24
|
communityRemove,
|
|
20
25
|
computeEntriesDigest,
|
|
26
|
+
createKeyserverKeyProvider,
|
|
21
27
|
decideCodexSidecarExecution,
|
|
22
28
|
defaultSelfIdentityTokens,
|
|
29
|
+
drainGlobalPendingReminders,
|
|
23
30
|
drainPendingReminders,
|
|
24
31
|
ensureUserConfig,
|
|
25
32
|
findCaveatHome,
|
|
@@ -30,9 +37,11 @@ import {
|
|
|
30
37
|
listRecent,
|
|
31
38
|
listStale,
|
|
32
39
|
loadConfig,
|
|
40
|
+
logHookQueryMiss,
|
|
33
41
|
markHit,
|
|
34
42
|
maybeSweepPendingDirs,
|
|
35
43
|
openDb,
|
|
44
|
+
prewarmSealedKeys,
|
|
36
45
|
publishOwn,
|
|
37
46
|
readCodexSessionSignals,
|
|
38
47
|
readDigestMarker,
|
|
@@ -41,8 +50,9 @@ import {
|
|
|
41
50
|
recordEntry,
|
|
42
51
|
reindexAllSources,
|
|
43
52
|
releaseReindexLock,
|
|
53
|
+
resetAutoSyncFailureState,
|
|
44
54
|
resolvePaths,
|
|
45
|
-
|
|
55
|
+
runAutoSync,
|
|
46
56
|
search,
|
|
47
57
|
stderrLogger,
|
|
48
58
|
stopReminderText,
|
|
@@ -53,7 +63,7 @@ import {
|
|
|
53
63
|
userPromptSubmitReminderText,
|
|
54
64
|
writeDigestMarker,
|
|
55
65
|
writeUserConfigPatch
|
|
56
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-ZNAFNCPW.js";
|
|
57
67
|
|
|
58
68
|
// ../../node_modules/.pnpm/ajv@8.18.0/node_modules/ajv/dist/compile/codegen/code.js
|
|
59
69
|
var require_code = __commonJS({
|
|
@@ -432,11 +442,11 @@ var require_codegen = __commonJS({
|
|
|
432
442
|
const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
|
|
433
443
|
return `${varKind} ${this.name}${rhs};` + _n;
|
|
434
444
|
}
|
|
435
|
-
optimizeNames(names,
|
|
445
|
+
optimizeNames(names, constants2) {
|
|
436
446
|
if (!names[this.name.str])
|
|
437
447
|
return;
|
|
438
448
|
if (this.rhs)
|
|
439
|
-
this.rhs = optimizeExpr(this.rhs, names,
|
|
449
|
+
this.rhs = optimizeExpr(this.rhs, names, constants2);
|
|
440
450
|
return this;
|
|
441
451
|
}
|
|
442
452
|
get names() {
|
|
@@ -453,10 +463,10 @@ var require_codegen = __commonJS({
|
|
|
453
463
|
render({ _n }) {
|
|
454
464
|
return `${this.lhs} = ${this.rhs};` + _n;
|
|
455
465
|
}
|
|
456
|
-
optimizeNames(names,
|
|
466
|
+
optimizeNames(names, constants2) {
|
|
457
467
|
if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
|
|
458
468
|
return;
|
|
459
|
-
this.rhs = optimizeExpr(this.rhs, names,
|
|
469
|
+
this.rhs = optimizeExpr(this.rhs, names, constants2);
|
|
460
470
|
return this;
|
|
461
471
|
}
|
|
462
472
|
get names() {
|
|
@@ -517,8 +527,8 @@ var require_codegen = __commonJS({
|
|
|
517
527
|
optimizeNodes() {
|
|
518
528
|
return `${this.code}` ? this : void 0;
|
|
519
529
|
}
|
|
520
|
-
optimizeNames(names,
|
|
521
|
-
this.code = optimizeExpr(this.code, names,
|
|
530
|
+
optimizeNames(names, constants2) {
|
|
531
|
+
this.code = optimizeExpr(this.code, names, constants2);
|
|
522
532
|
return this;
|
|
523
533
|
}
|
|
524
534
|
get names() {
|
|
@@ -547,12 +557,12 @@ var require_codegen = __commonJS({
|
|
|
547
557
|
}
|
|
548
558
|
return nodes.length > 0 ? this : void 0;
|
|
549
559
|
}
|
|
550
|
-
optimizeNames(names,
|
|
560
|
+
optimizeNames(names, constants2) {
|
|
551
561
|
const { nodes } = this;
|
|
552
562
|
let i = nodes.length;
|
|
553
563
|
while (i--) {
|
|
554
564
|
const n = nodes[i];
|
|
555
|
-
if (n.optimizeNames(names,
|
|
565
|
+
if (n.optimizeNames(names, constants2))
|
|
556
566
|
continue;
|
|
557
567
|
subtractNames(names, n.names);
|
|
558
568
|
nodes.splice(i, 1);
|
|
@@ -605,12 +615,12 @@ var require_codegen = __commonJS({
|
|
|
605
615
|
return void 0;
|
|
606
616
|
return this;
|
|
607
617
|
}
|
|
608
|
-
optimizeNames(names,
|
|
618
|
+
optimizeNames(names, constants2) {
|
|
609
619
|
var _a3;
|
|
610
|
-
this.else = (_a3 = this.else) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names,
|
|
611
|
-
if (!(super.optimizeNames(names,
|
|
620
|
+
this.else = (_a3 = this.else) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants2);
|
|
621
|
+
if (!(super.optimizeNames(names, constants2) || this.else))
|
|
612
622
|
return;
|
|
613
|
-
this.condition = optimizeExpr(this.condition, names,
|
|
623
|
+
this.condition = optimizeExpr(this.condition, names, constants2);
|
|
614
624
|
return this;
|
|
615
625
|
}
|
|
616
626
|
get names() {
|
|
@@ -633,10 +643,10 @@ var require_codegen = __commonJS({
|
|
|
633
643
|
render(opts) {
|
|
634
644
|
return `for(${this.iteration})` + super.render(opts);
|
|
635
645
|
}
|
|
636
|
-
optimizeNames(names,
|
|
637
|
-
if (!super.optimizeNames(names,
|
|
646
|
+
optimizeNames(names, constants2) {
|
|
647
|
+
if (!super.optimizeNames(names, constants2))
|
|
638
648
|
return;
|
|
639
|
-
this.iteration = optimizeExpr(this.iteration, names,
|
|
649
|
+
this.iteration = optimizeExpr(this.iteration, names, constants2);
|
|
640
650
|
return this;
|
|
641
651
|
}
|
|
642
652
|
get names() {
|
|
@@ -672,10 +682,10 @@ var require_codegen = __commonJS({
|
|
|
672
682
|
render(opts) {
|
|
673
683
|
return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
|
|
674
684
|
}
|
|
675
|
-
optimizeNames(names,
|
|
676
|
-
if (!super.optimizeNames(names,
|
|
685
|
+
optimizeNames(names, constants2) {
|
|
686
|
+
if (!super.optimizeNames(names, constants2))
|
|
677
687
|
return;
|
|
678
|
-
this.iterable = optimizeExpr(this.iterable, names,
|
|
688
|
+
this.iterable = optimizeExpr(this.iterable, names, constants2);
|
|
679
689
|
return this;
|
|
680
690
|
}
|
|
681
691
|
get names() {
|
|
@@ -717,11 +727,11 @@ var require_codegen = __commonJS({
|
|
|
717
727
|
(_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNodes();
|
|
718
728
|
return this;
|
|
719
729
|
}
|
|
720
|
-
optimizeNames(names,
|
|
730
|
+
optimizeNames(names, constants2) {
|
|
721
731
|
var _a3, _b;
|
|
722
|
-
super.optimizeNames(names,
|
|
723
|
-
(_a3 = this.catch) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names,
|
|
724
|
-
(_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names,
|
|
732
|
+
super.optimizeNames(names, constants2);
|
|
733
|
+
(_a3 = this.catch) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants2);
|
|
734
|
+
(_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants2);
|
|
725
735
|
return this;
|
|
726
736
|
}
|
|
727
737
|
get names() {
|
|
@@ -1022,7 +1032,7 @@ var require_codegen = __commonJS({
|
|
|
1022
1032
|
function addExprNames(names, from) {
|
|
1023
1033
|
return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
|
|
1024
1034
|
}
|
|
1025
|
-
function optimizeExpr(expr, names,
|
|
1035
|
+
function optimizeExpr(expr, names, constants2) {
|
|
1026
1036
|
if (expr instanceof code_1.Name)
|
|
1027
1037
|
return replaceName(expr);
|
|
1028
1038
|
if (!canOptimize(expr))
|
|
@@ -1037,14 +1047,14 @@ var require_codegen = __commonJS({
|
|
|
1037
1047
|
return items;
|
|
1038
1048
|
}, []));
|
|
1039
1049
|
function replaceName(n) {
|
|
1040
|
-
const c =
|
|
1050
|
+
const c = constants2[n.str];
|
|
1041
1051
|
if (c === void 0 || names[n.str] !== 1)
|
|
1042
1052
|
return n;
|
|
1043
1053
|
delete names[n.str];
|
|
1044
1054
|
return c;
|
|
1045
1055
|
}
|
|
1046
1056
|
function canOptimize(e) {
|
|
1047
|
-
return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 &&
|
|
1057
|
+
return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants2[c.str] !== void 0);
|
|
1048
1058
|
}
|
|
1049
1059
|
}
|
|
1050
1060
|
function subtractNames(names, from) {
|
|
@@ -6865,8 +6875,9 @@ var stdoutLogger = {
|
|
|
6865
6875
|
};
|
|
6866
6876
|
|
|
6867
6877
|
// src/commands/init.ts
|
|
6868
|
-
import {
|
|
6869
|
-
import {
|
|
6878
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
6879
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readdirSync, renameSync, rmdirSync, writeFileSync as writeFileSync4 } from "node:fs";
|
|
6880
|
+
import { dirname as dirname4, join as join8 } from "node:path";
|
|
6870
6881
|
|
|
6871
6882
|
// src/claudeInstall.ts
|
|
6872
6883
|
import { spawnSync } from "node:child_process";
|
|
@@ -7061,9 +7072,328 @@ function uninstallClaudeIntegration(opts) {
|
|
|
7061
7072
|
};
|
|
7062
7073
|
}
|
|
7063
7074
|
|
|
7075
|
+
// src/codexHookInstall.ts
|
|
7076
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
7077
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
7078
|
+
function quote2(p) {
|
|
7079
|
+
return p.includes(" ") ? `"${p}"` : p;
|
|
7080
|
+
}
|
|
7081
|
+
function hookCommand2(nodePath, cliScriptPath, event) {
|
|
7082
|
+
return `${quote2(nodePath)} ${quote2(cliScriptPath)} codex-hook ${event}`;
|
|
7083
|
+
}
|
|
7084
|
+
function eventCommandFragment(event) {
|
|
7085
|
+
return `codex-hook ${event}`;
|
|
7086
|
+
}
|
|
7087
|
+
function isSameHookCommand2(actual, expected) {
|
|
7088
|
+
return actual === expected || actual.endsWith(` ${expected}`);
|
|
7089
|
+
}
|
|
7090
|
+
function isCaveatCodexHookCommand(actual, event) {
|
|
7091
|
+
const lower = actual.toLowerCase();
|
|
7092
|
+
return lower.includes("caveat") && actual.includes(eventCommandFragment(event));
|
|
7093
|
+
}
|
|
7094
|
+
function hasCaveatHook(hooksJson, event, fragment) {
|
|
7095
|
+
return hooksJson.hooks?.[event]?.some(
|
|
7096
|
+
(entry) => entry.hooks?.some((h) => h.command.includes(fragment))
|
|
7097
|
+
) ?? false;
|
|
7098
|
+
}
|
|
7099
|
+
function readHooks(path) {
|
|
7100
|
+
if (!existsSync2(path)) return {};
|
|
7101
|
+
return JSON.parse(readFileSync3(path, "utf-8"));
|
|
7102
|
+
}
|
|
7103
|
+
function writeJsonWithBackup(path, value) {
|
|
7104
|
+
const dir = dirname3(path);
|
|
7105
|
+
if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
|
|
7106
|
+
let backupPath = "";
|
|
7107
|
+
if (existsSync2(path)) {
|
|
7108
|
+
backupPath = `${path}.caveat-backup-${Date.now()}`;
|
|
7109
|
+
copyFileSync2(path, backupPath);
|
|
7110
|
+
}
|
|
7111
|
+
writeFileSync2(path, `${JSON.stringify(value, null, 2)}
|
|
7112
|
+
`, "utf-8");
|
|
7113
|
+
return backupPath;
|
|
7114
|
+
}
|
|
7115
|
+
function upsertHook2(hooksJson, event, command, subcommand) {
|
|
7116
|
+
hooksJson.hooks ??= {};
|
|
7117
|
+
const list = hooksJson.hooks[event] ??= [];
|
|
7118
|
+
for (const entry of list) {
|
|
7119
|
+
for (const hook of entry.hooks ?? []) {
|
|
7120
|
+
if (isSameHookCommand2(hook.command, command)) return "unchanged";
|
|
7121
|
+
if (isCaveatCodexHookCommand(hook.command, subcommand)) {
|
|
7122
|
+
hook.command = command;
|
|
7123
|
+
hook.timeoutSec = 5;
|
|
7124
|
+
hook.async = false;
|
|
7125
|
+
hook.statusMessage = null;
|
|
7126
|
+
return "added";
|
|
7127
|
+
}
|
|
7128
|
+
}
|
|
7129
|
+
}
|
|
7130
|
+
list.push({
|
|
7131
|
+
hooks: [
|
|
7132
|
+
{
|
|
7133
|
+
type: "command",
|
|
7134
|
+
command,
|
|
7135
|
+
timeoutSec: 5,
|
|
7136
|
+
async: false,
|
|
7137
|
+
statusMessage: null
|
|
7138
|
+
}
|
|
7139
|
+
]
|
|
7140
|
+
});
|
|
7141
|
+
return "added";
|
|
7142
|
+
}
|
|
7143
|
+
function removeHook2(hooksJson, event, command, subcommand) {
|
|
7144
|
+
const list = hooksJson.hooks?.[event];
|
|
7145
|
+
if (!list) return false;
|
|
7146
|
+
const before = list.length;
|
|
7147
|
+
const filtered = list.filter(
|
|
7148
|
+
(entry) => !entry.hooks?.some(
|
|
7149
|
+
(h) => isSameHookCommand2(h.command, command) || isCaveatCodexHookCommand(h.command, subcommand)
|
|
7150
|
+
)
|
|
7151
|
+
);
|
|
7152
|
+
if (filtered.length === before) return false;
|
|
7153
|
+
hooksJson.hooks[event] = filtered;
|
|
7154
|
+
return true;
|
|
7155
|
+
}
|
|
7156
|
+
function enableCodexHooksFeature(raw) {
|
|
7157
|
+
const lines = raw.split(/\r?\n/);
|
|
7158
|
+
const unsupportedFeatureShape = lines.find(
|
|
7159
|
+
(line) => /^\s*(?:["']features["']|features)\s*(?:\.|=)/.test(line) || /^\s*\[\[?\s*(?:["']features["']|features\.)/.test(line)
|
|
7160
|
+
);
|
|
7161
|
+
if (unsupportedFeatureShape) {
|
|
7162
|
+
return {
|
|
7163
|
+
status: "blocked",
|
|
7164
|
+
text: raw,
|
|
7165
|
+
changed: false,
|
|
7166
|
+
reason: "config.toml uses a non-canonical features table shape that Caveat will not rewrite safely"
|
|
7167
|
+
};
|
|
7168
|
+
}
|
|
7169
|
+
const featureHeaders = lines.map((line, index) => /^\s*\[features]\s*(?:#.*)?$/.test(line) ? index : -1).filter((index) => index >= 0);
|
|
7170
|
+
if (featureHeaders.length > 1) {
|
|
7171
|
+
return {
|
|
7172
|
+
status: "blocked",
|
|
7173
|
+
text: raw,
|
|
7174
|
+
changed: false,
|
|
7175
|
+
reason: "config.toml contains more than one [features] table"
|
|
7176
|
+
};
|
|
7177
|
+
}
|
|
7178
|
+
const featuresStart = featureHeaders[0] ?? -1;
|
|
7179
|
+
if (featuresStart === -1) {
|
|
7180
|
+
const prefix = raw.trimEnd();
|
|
7181
|
+
const text = `${prefix}${prefix ? "\n\n" : ""}[features]
|
|
7182
|
+
codex_hooks = true
|
|
7183
|
+
`;
|
|
7184
|
+
return { status: "enabled", text, changed: true };
|
|
7185
|
+
}
|
|
7186
|
+
const assignments = [];
|
|
7187
|
+
for (let i = featuresStart + 1; i < lines.length; i += 1) {
|
|
7188
|
+
if (/^\s*\[\[?.+?\]?]\s*(?:#.*)?$/.test(lines[i])) {
|
|
7189
|
+
break;
|
|
7190
|
+
}
|
|
7191
|
+
const assignment = /^\s*codex_hooks\s*=\s*([^#]*?)\s*(?:#.*)?$/.exec(lines[i]);
|
|
7192
|
+
if (assignment) {
|
|
7193
|
+
assignments.push({ index: i, value: assignment[1].trim() });
|
|
7194
|
+
} else if (/^\s*["']codex_hooks["']\s*=/.test(lines[i])) {
|
|
7195
|
+
return {
|
|
7196
|
+
status: "blocked",
|
|
7197
|
+
text: raw,
|
|
7198
|
+
changed: false,
|
|
7199
|
+
reason: "[features].codex_hooks uses a quoted key that Caveat will not rewrite safely"
|
|
7200
|
+
};
|
|
7201
|
+
}
|
|
7202
|
+
}
|
|
7203
|
+
if (assignments.length > 1) {
|
|
7204
|
+
return {
|
|
7205
|
+
status: "blocked",
|
|
7206
|
+
text: raw,
|
|
7207
|
+
changed: false,
|
|
7208
|
+
reason: "[features].codex_hooks is defined more than once"
|
|
7209
|
+
};
|
|
7210
|
+
}
|
|
7211
|
+
const existing = assignments[0];
|
|
7212
|
+
if (existing) {
|
|
7213
|
+
if (existing.value === "true") return { status: "enabled", text: raw, changed: false };
|
|
7214
|
+
return {
|
|
7215
|
+
status: "blocked",
|
|
7216
|
+
text: raw,
|
|
7217
|
+
changed: false,
|
|
7218
|
+
reason: `[features].codex_hooks is explicitly ${existing.value || "set to a non-true value"}`
|
|
7219
|
+
};
|
|
7220
|
+
}
|
|
7221
|
+
lines.splice(featuresStart + 1, 0, "codex_hooks = true");
|
|
7222
|
+
return { status: "enabled", text: `${lines.join("\n").trimEnd()}
|
|
7223
|
+
`, changed: true };
|
|
7224
|
+
}
|
|
7225
|
+
function writeConfigWithBackup(path, text) {
|
|
7226
|
+
const dir = dirname3(path);
|
|
7227
|
+
if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
|
|
7228
|
+
let backupPath = "";
|
|
7229
|
+
if (existsSync2(path)) {
|
|
7230
|
+
backupPath = `${path}.caveat-backup-${Date.now()}`;
|
|
7231
|
+
copyFileSync2(path, backupPath);
|
|
7232
|
+
}
|
|
7233
|
+
writeFileSync2(path, text, "utf-8");
|
|
7234
|
+
return backupPath;
|
|
7235
|
+
}
|
|
7236
|
+
function installCodexHooks(opts) {
|
|
7237
|
+
const hooksPath = join4(opts.codexHome, "hooks.json");
|
|
7238
|
+
const configPath = join4(opts.codexHome, "config.toml");
|
|
7239
|
+
const rawConfig = existsSync2(configPath) ? readFileSync3(configPath, "utf-8") : "";
|
|
7240
|
+
const enabled = enableCodexHooksFeature(rawConfig);
|
|
7241
|
+
if (enabled.status === "blocked") {
|
|
7242
|
+
return {
|
|
7243
|
+
hooks: { userPromptSubmit: "skipped", postToolUse: "skipped", stop: "skipped" },
|
|
7244
|
+
feature: "blocked",
|
|
7245
|
+
blockedReason: enabled.reason
|
|
7246
|
+
};
|
|
7247
|
+
}
|
|
7248
|
+
const hooksJson = readHooks(hooksPath);
|
|
7249
|
+
const userPromptSubmit = upsertHook2(
|
|
7250
|
+
hooksJson,
|
|
7251
|
+
"UserPromptSubmit",
|
|
7252
|
+
hookCommand2(opts.nodePath, opts.cliScriptPath, "user-prompt-submit"),
|
|
7253
|
+
"user-prompt-submit"
|
|
7254
|
+
);
|
|
7255
|
+
const postToolUse = upsertHook2(
|
|
7256
|
+
hooksJson,
|
|
7257
|
+
"PostToolUse",
|
|
7258
|
+
hookCommand2(opts.nodePath, opts.cliScriptPath, "post-tool-use"),
|
|
7259
|
+
"post-tool-use"
|
|
7260
|
+
);
|
|
7261
|
+
const stop = upsertHook2(
|
|
7262
|
+
hooksJson,
|
|
7263
|
+
"Stop",
|
|
7264
|
+
hookCommand2(opts.nodePath, opts.cliScriptPath, "stop"),
|
|
7265
|
+
"stop"
|
|
7266
|
+
);
|
|
7267
|
+
const anyHookAdded = userPromptSubmit === "added" || postToolUse === "added" || stop === "added";
|
|
7268
|
+
let backupPath;
|
|
7269
|
+
let configBackupPath;
|
|
7270
|
+
if (opts.dryRun) {
|
|
7271
|
+
opts.logger.info(`[dry-run] would update ${hooksPath}`);
|
|
7272
|
+
opts.logger.info(`[dry-run] would ensure [features].codex_hooks = true in ${configPath}`);
|
|
7273
|
+
} else {
|
|
7274
|
+
if (anyHookAdded) {
|
|
7275
|
+
const backup = writeJsonWithBackup(hooksPath, hooksJson);
|
|
7276
|
+
if (backup) backupPath = backup;
|
|
7277
|
+
}
|
|
7278
|
+
if (enabled.changed) {
|
|
7279
|
+
const backup = writeConfigWithBackup(configPath, enabled.text);
|
|
7280
|
+
if (backup) configBackupPath = backup;
|
|
7281
|
+
}
|
|
7282
|
+
}
|
|
7283
|
+
return {
|
|
7284
|
+
hooks: { userPromptSubmit, postToolUse, stop },
|
|
7285
|
+
feature: enabled.changed ? "enabled" : "unchanged",
|
|
7286
|
+
backupPath,
|
|
7287
|
+
configBackupPath
|
|
7288
|
+
};
|
|
7289
|
+
}
|
|
7290
|
+
function uninstallCodexHooks(opts) {
|
|
7291
|
+
const hooksPath = join4(opts.codexHome, "hooks.json");
|
|
7292
|
+
const hooksJson = readHooks(hooksPath);
|
|
7293
|
+
const userPromptSubmitRemoved = removeHook2(
|
|
7294
|
+
hooksJson,
|
|
7295
|
+
"UserPromptSubmit",
|
|
7296
|
+
hookCommand2(opts.nodePath, opts.cliScriptPath, "user-prompt-submit"),
|
|
7297
|
+
"user-prompt-submit"
|
|
7298
|
+
);
|
|
7299
|
+
const postToolUseRemoved = removeHook2(
|
|
7300
|
+
hooksJson,
|
|
7301
|
+
"PostToolUse",
|
|
7302
|
+
hookCommand2(opts.nodePath, opts.cliScriptPath, "post-tool-use"),
|
|
7303
|
+
"post-tool-use"
|
|
7304
|
+
);
|
|
7305
|
+
const stopRemoved = removeHook2(
|
|
7306
|
+
hooksJson,
|
|
7307
|
+
"Stop",
|
|
7308
|
+
hookCommand2(opts.nodePath, opts.cliScriptPath, "stop"),
|
|
7309
|
+
"stop"
|
|
7310
|
+
);
|
|
7311
|
+
let backupPath;
|
|
7312
|
+
if (opts.dryRun) {
|
|
7313
|
+
opts.logger.info(`[dry-run] would remove Caveat Codex hooks from ${hooksPath}`);
|
|
7314
|
+
} else if (userPromptSubmitRemoved || postToolUseRemoved || stopRemoved) {
|
|
7315
|
+
const backup = writeJsonWithBackup(hooksPath, hooksJson);
|
|
7316
|
+
if (backup) backupPath = backup;
|
|
7317
|
+
}
|
|
7318
|
+
return {
|
|
7319
|
+
hooks: {
|
|
7320
|
+
userPromptSubmit: userPromptSubmitRemoved ? "added" : "unchanged",
|
|
7321
|
+
postToolUse: postToolUseRemoved ? "added" : "unchanged",
|
|
7322
|
+
stop: stopRemoved ? "added" : "unchanged"
|
|
7323
|
+
},
|
|
7324
|
+
feature: "unchanged",
|
|
7325
|
+
backupPath
|
|
7326
|
+
};
|
|
7327
|
+
}
|
|
7328
|
+
function detectCodexHookInstallation(codexHome) {
|
|
7329
|
+
const hooksPath = join4(codexHome, "hooks.json");
|
|
7330
|
+
const hooksJson = readHooks(hooksPath);
|
|
7331
|
+
const hooks = {
|
|
7332
|
+
userPromptSubmit: hasCaveatHook(
|
|
7333
|
+
hooksJson,
|
|
7334
|
+
"UserPromptSubmit",
|
|
7335
|
+
eventCommandFragment("user-prompt-submit")
|
|
7336
|
+
),
|
|
7337
|
+
postToolUse: hasCaveatHook(hooksJson, "PostToolUse", eventCommandFragment("post-tool-use")),
|
|
7338
|
+
stop: hasCaveatHook(hooksJson, "Stop", eventCommandFragment("stop"))
|
|
7339
|
+
};
|
|
7340
|
+
const count = Object.values(hooks).filter(Boolean).length;
|
|
7341
|
+
return {
|
|
7342
|
+
installation: count === 0 ? "not-installed" : count === 3 ? "installed" : "partial",
|
|
7343
|
+
hooksPath,
|
|
7344
|
+
hooks
|
|
7345
|
+
};
|
|
7346
|
+
}
|
|
7347
|
+
|
|
7348
|
+
// src/ghSetup.ts
|
|
7349
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
7350
|
+
import { readSync } from "node:fs";
|
|
7351
|
+
function runGh(args) {
|
|
7352
|
+
const result = spawnSync2("gh", args, { encoding: "utf-8" });
|
|
7353
|
+
return { status: result.status, stdout: typeof result.stdout === "string" ? result.stdout : "", stderr: typeof result.stderr === "string" ? result.stderr : "" };
|
|
7354
|
+
}
|
|
7355
|
+
function commandError(result, fallback) {
|
|
7356
|
+
return new Error((result.stderr || result.stdout || fallback).trim());
|
|
7357
|
+
}
|
|
7358
|
+
function defaultGitHubRepoUrl(opts) {
|
|
7359
|
+
const version2 = opts.ghRunner(["--version"]);
|
|
7360
|
+
if (version2.status !== 0) {
|
|
7361
|
+
throw new Error(
|
|
7362
|
+
`gh is not available. create a ${opts.visibility} repository named ${opts.repositoryName} on GitHub, then run:
|
|
7363
|
+
${opts.retryCommand}
|
|
7364
|
+
# or install gh and retry`
|
|
7365
|
+
);
|
|
7366
|
+
}
|
|
7367
|
+
const user = opts.ghRunner(["api", "user", "-q", ".login"]);
|
|
7368
|
+
if (user.status !== 0 || !user.stdout.trim()) {
|
|
7369
|
+
throw new Error(`gh is not authenticated; run \`gh auth login\`, then retry \`${opts.retryCommand}\``);
|
|
7370
|
+
}
|
|
7371
|
+
const login = user.stdout.trim();
|
|
7372
|
+
const qualifiedName = `${login}/${opts.repositoryName}`;
|
|
7373
|
+
const view = opts.ghRunner(["repo", "view", qualifiedName]);
|
|
7374
|
+
if (view.status !== 0) {
|
|
7375
|
+
const question = `create ${opts.visibility} repo github.com/${qualifiedName}? [y/N]`;
|
|
7376
|
+
if (!opts.yes) {
|
|
7377
|
+
if (!opts.isTty()) {
|
|
7378
|
+
throw new Error(`${question}; rerun with --yes to approve non-interactively`);
|
|
7379
|
+
}
|
|
7380
|
+
if (!opts.confirm(question)) throw new Error(`${opts.visibility} repository creation cancelled`);
|
|
7381
|
+
}
|
|
7382
|
+
const create = opts.ghRunner(["repo", "create", opts.repositoryName, `--${opts.visibility}`]);
|
|
7383
|
+
if (create.status !== 0) throw commandError(create, "gh repo create failed");
|
|
7384
|
+
}
|
|
7385
|
+
return `https://github.com/${qualifiedName}.git`;
|
|
7386
|
+
}
|
|
7387
|
+
function askOnce(question) {
|
|
7388
|
+
process.stdout.write(`${question} `);
|
|
7389
|
+
const buf = Buffer.alloc(256);
|
|
7390
|
+
const bytes = readSync(0, buf, 0, buf.length, null);
|
|
7391
|
+
return /^(?:y|yes)$/i.test(buf.toString("utf-8", 0, bytes).trim());
|
|
7392
|
+
}
|
|
7393
|
+
|
|
7064
7394
|
// src/nodePath.ts
|
|
7065
|
-
import { existsSync as
|
|
7066
|
-
import { delimiter, join as
|
|
7395
|
+
import { existsSync as existsSync3, realpathSync } from "node:fs";
|
|
7396
|
+
import { delimiter, join as join5 } from "node:path";
|
|
7067
7397
|
var defaultRealpath = (path) => realpathSync.native(path);
|
|
7068
7398
|
function safeRealpath(path, realpath = defaultRealpath) {
|
|
7069
7399
|
try {
|
|
@@ -7076,7 +7406,7 @@ function resolveHookNodePath({
|
|
|
7076
7406
|
env = process.env,
|
|
7077
7407
|
execPath = process.execPath,
|
|
7078
7408
|
platform = process.platform,
|
|
7079
|
-
exists =
|
|
7409
|
+
exists = existsSync3,
|
|
7080
7410
|
realpath = defaultRealpath
|
|
7081
7411
|
} = {}) {
|
|
7082
7412
|
const execRealpath = safeRealpath(execPath, realpath);
|
|
@@ -7084,7 +7414,7 @@ function resolveHookNodePath({
|
|
|
7084
7414
|
const names = platform === "win32" ? ["node.exe", "node.cmd", "node.bat", "node"] : ["node"];
|
|
7085
7415
|
for (const dir of pathEnv.split(delimiter).filter(Boolean)) {
|
|
7086
7416
|
for (const name of names) {
|
|
7087
|
-
const candidate =
|
|
7417
|
+
const candidate = join5(dir, name);
|
|
7088
7418
|
if (!exists(candidate)) continue;
|
|
7089
7419
|
const candidateRealpath = safeRealpath(candidate, realpath);
|
|
7090
7420
|
if (execRealpath && candidateRealpath && candidateRealpath === execRealpath) {
|
|
@@ -7095,29 +7425,241 @@ function resolveHookNodePath({
|
|
|
7095
7425
|
return execPath;
|
|
7096
7426
|
}
|
|
7097
7427
|
|
|
7428
|
+
// src/commands/publish.ts
|
|
7429
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
7430
|
+
import { join as join7 } from "node:path";
|
|
7431
|
+
|
|
7432
|
+
// src/commands/codexSidecarAdvisory.ts
|
|
7433
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
7434
|
+
import { mkdtempSync, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
7435
|
+
import { tmpdir } from "node:os";
|
|
7436
|
+
import { join as join6 } from "node:path";
|
|
7437
|
+
function compactFailureMessage(message) {
|
|
7438
|
+
const singleLine = message.replace(/\s+/g, " ").trim();
|
|
7439
|
+
if (!singleLine) return "unknown error";
|
|
7440
|
+
return singleLine.length > 220 ? `${singleLine.slice(0, 220)}...` : singleLine;
|
|
7441
|
+
}
|
|
7442
|
+
function formatCodexSidecarAdvisoryUnavailable(error51) {
|
|
7443
|
+
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
7444
|
+
return `[caveat:codex-sidecar] advisory unavailable: ${compactFailureMessage(message)}`;
|
|
7445
|
+
}
|
|
7446
|
+
function sidecarFailureDetail(output) {
|
|
7447
|
+
const protocol = output.match(/PROTOCOL_ERROR:[^"\r\n]+/);
|
|
7448
|
+
if (protocol) return compactFailureMessage(protocol[0]);
|
|
7449
|
+
const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
7450
|
+
const diagnostic = lines.find(
|
|
7451
|
+
(line) => !line.startsWith("[caveat] [codex-sidecar] codex-sidecar ")
|
|
7452
|
+
);
|
|
7453
|
+
return compactFailureMessage(diagnostic ?? output);
|
|
7454
|
+
}
|
|
7455
|
+
function runCodexSidecarAdvisory(input) {
|
|
7456
|
+
const cliScript = process.argv[1];
|
|
7457
|
+
if (!cliScript) {
|
|
7458
|
+
return { status: "failed", message: "current Caveat CLI script path is unavailable" };
|
|
7459
|
+
}
|
|
7460
|
+
const args = [
|
|
7461
|
+
...process.execArgv,
|
|
7462
|
+
"--disable-warning=ExperimentalWarning",
|
|
7463
|
+
cliScript,
|
|
7464
|
+
"codex-sidecar",
|
|
7465
|
+
"run",
|
|
7466
|
+
"explore",
|
|
7467
|
+
input.prompt,
|
|
7468
|
+
"--preset",
|
|
7469
|
+
"advisory",
|
|
7470
|
+
"--query",
|
|
7471
|
+
input.searchText,
|
|
7472
|
+
"--limit",
|
|
7473
|
+
String(Math.max(1, input.limit)),
|
|
7474
|
+
"--host-agent",
|
|
7475
|
+
"claude",
|
|
7476
|
+
"--availability",
|
|
7477
|
+
"operational"
|
|
7478
|
+
];
|
|
7479
|
+
let resultDir;
|
|
7480
|
+
let advisory;
|
|
7481
|
+
try {
|
|
7482
|
+
resultDir = mkdtempSync(join6(tmpdir(), "caveat-codex-advisory-"));
|
|
7483
|
+
const resultFile = join6(resultDir, "result.json");
|
|
7484
|
+
args.push("--save-result", resultFile);
|
|
7485
|
+
if (input.additionalContext) {
|
|
7486
|
+
const additionalContextFile = join6(resultDir, "hook-signal.json");
|
|
7487
|
+
writeFileSync3(additionalContextFile, JSON.stringify({ context: [input.additionalContext] }) + "\n", { encoding: "utf-8", mode: 384, flag: "wx" });
|
|
7488
|
+
args.push("--additional-context-file", additionalContextFile);
|
|
7489
|
+
}
|
|
7490
|
+
const nodeCli = process.env.CAVEAT_CODEX_SIDECAR_NODE_CLI;
|
|
7491
|
+
if (nodeCli) {
|
|
7492
|
+
args.push("--node-cli", nodeCli);
|
|
7493
|
+
} else {
|
|
7494
|
+
const command = process.env.CAVEAT_CODEX_SIDECAR_COMMAND;
|
|
7495
|
+
if (command) args.push("--command", command);
|
|
7496
|
+
}
|
|
7497
|
+
const result = spawnSync3(process.execPath, args, {
|
|
7498
|
+
cwd: input.projectRoot,
|
|
7499
|
+
encoding: "utf-8",
|
|
7500
|
+
timeout: Number(process.env.CAVEAT_HOOK_CODEX_SIDECAR_TIMEOUT_MS ?? 12e4),
|
|
7501
|
+
maxBuffer: 10 * 1024 * 1024
|
|
7502
|
+
});
|
|
7503
|
+
if (result.error) {
|
|
7504
|
+
advisory = { status: "failed", message: compactFailureMessage(result.error.message) };
|
|
7505
|
+
} else if (result.status !== 0) {
|
|
7506
|
+
const detail = (result.stderr || result.stdout || `exit ${result.status}`).trim();
|
|
7507
|
+
advisory = {
|
|
7508
|
+
status: "failed",
|
|
7509
|
+
message: `sidecar command failed: ${sidecarFailureDetail(detail)}`
|
|
7510
|
+
};
|
|
7511
|
+
} else {
|
|
7512
|
+
const parsed = JSON.parse(readFileSync4(resultFile, "utf-8"));
|
|
7513
|
+
if (parsed.status !== "ok" || typeof parsed.summary !== "string") {
|
|
7514
|
+
advisory = {
|
|
7515
|
+
status: "failed",
|
|
7516
|
+
message: compactFailureMessage(`unexpected SidecarResult status: ${String(parsed.status)}`)
|
|
7517
|
+
};
|
|
7518
|
+
} else {
|
|
7519
|
+
advisory = {
|
|
7520
|
+
status: "ok",
|
|
7521
|
+
summary: parsed.summary,
|
|
7522
|
+
...typeof parsed.rawEventLogRef === "string" ? { rawEventLogRef: parsed.rawEventLogRef } : {}
|
|
7523
|
+
};
|
|
7524
|
+
}
|
|
7525
|
+
}
|
|
7526
|
+
} catch (err) {
|
|
7527
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
7528
|
+
advisory = {
|
|
7529
|
+
status: "failed",
|
|
7530
|
+
message: compactFailureMessage(`invalid SidecarResult JSON: ${msg}`)
|
|
7531
|
+
};
|
|
7532
|
+
} finally {
|
|
7533
|
+
if (resultDir) {
|
|
7534
|
+
try {
|
|
7535
|
+
rmSync(resultDir, { recursive: true });
|
|
7536
|
+
} catch (err) {
|
|
7537
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
7538
|
+
advisory = {
|
|
7539
|
+
status: "failed",
|
|
7540
|
+
message: compactFailureMessage(`sidecar advisory cleanup failed: ${msg}`)
|
|
7541
|
+
};
|
|
7542
|
+
}
|
|
7543
|
+
}
|
|
7544
|
+
}
|
|
7545
|
+
return advisory;
|
|
7546
|
+
}
|
|
7547
|
+
function formatCodexSidecarAdvisory(advisory) {
|
|
7548
|
+
if (advisory.status === "ok") {
|
|
7549
|
+
return [
|
|
7550
|
+
"[caveat:codex-sidecar] Codex advisory:",
|
|
7551
|
+
advisory.summary,
|
|
7552
|
+
advisory.rawEventLogRef ? `rawEventLogRef: ${advisory.rawEventLogRef}` : ""
|
|
7553
|
+
].filter(Boolean).join("\n");
|
|
7554
|
+
}
|
|
7555
|
+
return `[caveat:codex-sidecar] advisory unavailable: ${advisory.message}`;
|
|
7556
|
+
}
|
|
7557
|
+
|
|
7558
|
+
// src/commands/publish.ts
|
|
7559
|
+
var HTTPS_TARGET_RE = /^https:\/\/github\.com\/[^/]+\/[^/]+?(?:\.git)?\/?$/;
|
|
7560
|
+
var SCP_TARGET_RE = /^git@github\.com:[^/]+\/[^/]+?(?:\.git)?$/;
|
|
7561
|
+
function validatePublishTarget(url2) {
|
|
7562
|
+
if (!HTTPS_TARGET_RE.test(url2) && !SCP_TARGET_RE.test(url2)) throw new Error("invalid publish target: use https://github.com/<org>/<repo>(.git) or git@github.com:<org>/<repo>(.git)");
|
|
7563
|
+
return url2;
|
|
7564
|
+
}
|
|
7565
|
+
async function defaultPublicRepoUrl(opts, deps) {
|
|
7566
|
+
return defaultGitHubRepoUrl({
|
|
7567
|
+
repositoryName: "Caveat-Public",
|
|
7568
|
+
visibility: "public",
|
|
7569
|
+
yes: opts.yes,
|
|
7570
|
+
retryCommand: "caveat publish --init <url>",
|
|
7571
|
+
...deps
|
|
7572
|
+
});
|
|
7573
|
+
}
|
|
7574
|
+
async function runPublish(ctx, opts, dependencies = {}) {
|
|
7575
|
+
try {
|
|
7576
|
+
const ghRunner = dependencies.ghRunner ?? runGh;
|
|
7577
|
+
const isTty = dependencies.isTty ?? (() => Boolean(process.stdin.isTTY));
|
|
7578
|
+
const confirm = dependencies.confirm ?? askOnce;
|
|
7579
|
+
const projectRoot = process.cwd();
|
|
7580
|
+
const hasCodexSidecarConfig = dependencies.hasCodexSidecarConfig ?? ((root) => existsSync4(join7(root, ".codex-sidecar.yml")));
|
|
7581
|
+
const publishAdvisory = hasCodexSidecarConfig(projectRoot) ? (changes) => {
|
|
7582
|
+
try {
|
|
7583
|
+
return dependencies.runCodexSidecarAdvisory?.(changes, projectRoot) ?? formatCodexSidecarAdvisory(runCodexSidecarAdvisory({
|
|
7584
|
+
searchText: changes.lines.join("\n"),
|
|
7585
|
+
limit: Math.max(changes.lines.length, 1),
|
|
7586
|
+
projectRoot,
|
|
7587
|
+
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."
|
|
7588
|
+
}));
|
|
7589
|
+
} catch (err) {
|
|
7590
|
+
return formatCodexSidecarAdvisoryUnavailable(err);
|
|
7591
|
+
}
|
|
7592
|
+
} : void 0;
|
|
7593
|
+
let target = ctx.config.publishTarget;
|
|
7594
|
+
if (opts.init !== void 0) {
|
|
7595
|
+
target = validatePublishTarget(opts.init);
|
|
7596
|
+
writeUserConfigPatch(ctx.userConfigPath, { publishTarget: target });
|
|
7597
|
+
}
|
|
7598
|
+
if (!target) {
|
|
7599
|
+
target = await defaultPublicRepoUrl(opts, { ghRunner, isTty, confirm });
|
|
7600
|
+
writeUserConfigPatch(ctx.userConfigPath, { publishTarget: target });
|
|
7601
|
+
}
|
|
7602
|
+
const result = await publishOwn({
|
|
7603
|
+
paths: ctx.paths,
|
|
7604
|
+
config: { ...ctx.config, publishTarget: target },
|
|
7605
|
+
logger: ctx.logger,
|
|
7606
|
+
confirmImpl: confirm,
|
|
7607
|
+
isTty,
|
|
7608
|
+
dryRun: opts.dryRun,
|
|
7609
|
+
yes: opts.yes,
|
|
7610
|
+
allow: opts.allow,
|
|
7611
|
+
saveAllow: opts.save,
|
|
7612
|
+
advisory: publishAdvisory
|
|
7613
|
+
});
|
|
7614
|
+
if (result.dryRun) ctx.logger.info(`[dry-run] ${result.fileCount} public entry(ies) ready to publish`);
|
|
7615
|
+
else if (result.changed) ctx.logger.info(`published ${result.fileCount} public entry(ies)`);
|
|
7616
|
+
} catch (err) {
|
|
7617
|
+
if (err instanceof PublishScanError) {
|
|
7618
|
+
for (const finding of err.findings) {
|
|
7619
|
+
ctx.logger.error(`${finding.relPath}:${finding.line} ${finding.rule} ${finding.excerpt}`);
|
|
7620
|
+
ctx.logger.error(` --allow ${finding.matchDigest}`);
|
|
7621
|
+
}
|
|
7622
|
+
process.exitCode = 1;
|
|
7623
|
+
return;
|
|
7624
|
+
}
|
|
7625
|
+
ctx.logger.error(err instanceof Error ? err.message : String(err));
|
|
7626
|
+
process.exitCode = 1;
|
|
7627
|
+
}
|
|
7628
|
+
}
|
|
7629
|
+
|
|
7098
7630
|
// src/commands/init.ts
|
|
7099
|
-
async function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
|
|
7631
|
+
async function runInit(ctx, opts = { skipClaude: false, dryRun: false }, dependencies = {}) {
|
|
7632
|
+
const isTty = dependencies.isTty ?? (() => Boolean(process.stdin.isTTY));
|
|
7633
|
+
const confirm = dependencies.confirm ?? askOnce;
|
|
7634
|
+
const ghRunner = dependencies.ghRunner ?? runGh;
|
|
7635
|
+
let publishTarget = ctx.config.publishTarget;
|
|
7636
|
+
let codexHookState = "not-installed";
|
|
7100
7637
|
ensureUserConfig(ctx.userConfigPath);
|
|
7101
7638
|
ctx.logger.info(`user config: ${ctx.userConfigPath}`);
|
|
7102
|
-
if (!
|
|
7103
|
-
|
|
7104
|
-
|
|
7639
|
+
if (!existsSync5(ctx.paths.knowledgeRepo)) {
|
|
7640
|
+
mkdirSync3(ctx.paths.knowledgeRepo, { recursive: true });
|
|
7641
|
+
mkdirSync3(ctx.paths.entriesDir, { recursive: true });
|
|
7105
7642
|
ctx.logger.info(`knowledge repo scaffolded: ${ctx.paths.knowledgeRepo}`);
|
|
7106
7643
|
} else {
|
|
7107
7644
|
ctx.logger.info(`knowledge repo: ${ctx.paths.knowledgeRepo}`);
|
|
7108
7645
|
}
|
|
7109
7646
|
migrateLegacyCommunityDir(ctx);
|
|
7110
|
-
const gitignorePath =
|
|
7111
|
-
if (!
|
|
7112
|
-
|
|
7647
|
+
const gitignorePath = join8(ctx.paths.knowledgeRepo, ".gitignore");
|
|
7648
|
+
if (!existsSync5(gitignorePath)) {
|
|
7649
|
+
writeFileSync4(gitignorePath, KNOWLEDGE_GITIGNORE, "utf-8");
|
|
7113
7650
|
ctx.logger.info(`.gitignore created: ${gitignorePath}`);
|
|
7114
7651
|
}
|
|
7115
7652
|
if (!opts.dryRun) {
|
|
7116
|
-
const dbDir =
|
|
7117
|
-
if (!
|
|
7653
|
+
const dbDir = dirname4(ctx.paths.dbPath);
|
|
7654
|
+
if (!existsSync5(dbDir)) mkdirSync3(dbDir, { recursive: true });
|
|
7655
|
+
const keyProvider = createKeyserverKeyProvider({ caveatHome: ctx.caveatHome });
|
|
7656
|
+
const failures = await prewarmSealedKeys({ paths: ctx.paths, keyProvider });
|
|
7657
|
+
for (const failure of failures) {
|
|
7658
|
+
ctx.logger.warn(`${failure.source}: sealed key prewarm failed: ${errorMessage(failure.error)}`);
|
|
7659
|
+
}
|
|
7118
7660
|
const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
|
|
7119
7661
|
try {
|
|
7120
|
-
reindexAllSources({ db, paths: ctx.paths, logger: ctx.logger });
|
|
7662
|
+
reindexAllSources({ db, paths: ctx.paths, logger: ctx.logger, keyProvider });
|
|
7121
7663
|
writeDigestMarker(ctx.caveatHome, computeEntriesDigest(ctx.paths));
|
|
7122
7664
|
} finally {
|
|
7123
7665
|
db.close();
|
|
@@ -7140,39 +7682,179 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
|
|
|
7140
7682
|
ctx.logger.info(
|
|
7141
7683
|
"tip: subscribe to a group repo with `caveat community add <github-url>`, then `caveat pull`."
|
|
7142
7684
|
);
|
|
7685
|
+
let syncRequested = opts.sync !== void 0 && opts.sync !== false;
|
|
7686
|
+
let syncNudgeAccepted = false;
|
|
7687
|
+
if (opts.sync === void 0 && isTty()) {
|
|
7688
|
+
syncNudgeAccepted = confirm("private \u540C\u671F\u3092\u4ECA\u3059\u3050\u8A2D\u5B9A\u3059\u308B\uFF1F [y/N]");
|
|
7689
|
+
syncRequested = syncNudgeAccepted;
|
|
7690
|
+
}
|
|
7691
|
+
if (syncRequested) {
|
|
7692
|
+
try {
|
|
7693
|
+
if (opts.dryRun) {
|
|
7694
|
+
const target = typeof opts.sync === "string" ? opts.sync : "<user>/Caveat-Private";
|
|
7695
|
+
ctx.logger.info(`[dry-run] would initialize private sync: ${target}`);
|
|
7696
|
+
} else {
|
|
7697
|
+
const url2 = typeof opts.sync === "string" ? opts.sync : defaultGitHubRepoUrl({
|
|
7698
|
+
repositoryName: "Caveat-Private",
|
|
7699
|
+
visibility: "private",
|
|
7700
|
+
yes: (opts.yes ?? false) || syncNudgeAccepted,
|
|
7701
|
+
retryCommand: "caveat init --sync <url>",
|
|
7702
|
+
ghRunner,
|
|
7703
|
+
isTty,
|
|
7704
|
+
confirm
|
|
7705
|
+
});
|
|
7706
|
+
const result = await initOwnSync({
|
|
7707
|
+
ownDir: ctx.paths.knowledgeRepo,
|
|
7708
|
+
url: url2,
|
|
7709
|
+
caveatHome: ctx.caveatHome,
|
|
7710
|
+
paths: ctx.paths,
|
|
7711
|
+
logger: ctx.logger,
|
|
7712
|
+
// file: remotes are governed by local filesystem permissions and have
|
|
7713
|
+
// no anonymous HTTP surface for the remote-visibility probe to inspect.
|
|
7714
|
+
trustRemotePrivate: isFileRemote(url2)
|
|
7715
|
+
});
|
|
7716
|
+
ctx.logger.info(
|
|
7717
|
+
result.remoteWasEmpty ? `initialized private sync remote: ${url2}` : `checked out existing private remote: ${url2} (branch ${result.branch})`
|
|
7718
|
+
);
|
|
7719
|
+
}
|
|
7720
|
+
} catch (err) {
|
|
7721
|
+
if (err instanceof SyncError && err.code === "OWN_REPO_EXISTS") {
|
|
7722
|
+
ctx.logger.info("private sync already configured (own repo exists); skipped");
|
|
7723
|
+
} else {
|
|
7724
|
+
ctx.logger.warn(`private sync setup skipped: ${errorMessage(err)}`);
|
|
7725
|
+
}
|
|
7726
|
+
}
|
|
7727
|
+
}
|
|
7728
|
+
let publishRequested = opts.publishTarget !== void 0 && opts.publishTarget !== false;
|
|
7729
|
+
let publishNudgeAccepted = false;
|
|
7730
|
+
if (opts.publishTarget === void 0 && isTty()) {
|
|
7731
|
+
publishNudgeAccepted = confirm("\u516C\u958B repo\uFF08\u5C01\u7DD8\u30DF\u30E9\u30FC\uFF09\u3082\u8A2D\u5B9A\u3059\u308B\uFF1F [y/N]");
|
|
7732
|
+
publishRequested = publishNudgeAccepted;
|
|
7733
|
+
}
|
|
7734
|
+
if (publishRequested) {
|
|
7735
|
+
try {
|
|
7736
|
+
const requested = typeof opts.publishTarget === "string" ? validatePublishTarget(opts.publishTarget) : publishTarget;
|
|
7737
|
+
const target = requested ?? (opts.dryRun ? "<user>/Caveat-Public" : defaultGitHubRepoUrl({
|
|
7738
|
+
repositoryName: "Caveat-Public",
|
|
7739
|
+
visibility: "public",
|
|
7740
|
+
yes: (opts.yes ?? false) || publishNudgeAccepted,
|
|
7741
|
+
retryCommand: "caveat init --publish-target <url>",
|
|
7742
|
+
ghRunner,
|
|
7743
|
+
isTty,
|
|
7744
|
+
confirm
|
|
7745
|
+
}));
|
|
7746
|
+
if (opts.dryRun) {
|
|
7747
|
+
ctx.logger.info(`[dry-run] would configure publish target: ${target}`);
|
|
7748
|
+
publishTarget = target;
|
|
7749
|
+
} else if (target === publishTarget) {
|
|
7750
|
+
ctx.logger.info(`publish target already configured: ${target}`);
|
|
7751
|
+
} else {
|
|
7752
|
+
writeUserConfigPatch(ctx.userConfigPath, { publishTarget: target });
|
|
7753
|
+
publishTarget = target;
|
|
7754
|
+
ctx.logger.info(`publish target configured: ${target}`);
|
|
7755
|
+
}
|
|
7756
|
+
} catch (err) {
|
|
7757
|
+
ctx.logger.warn(`publish target setup skipped: ${errorMessage(err)}`);
|
|
7758
|
+
}
|
|
7759
|
+
}
|
|
7143
7760
|
if (opts.skipClaude) {
|
|
7144
7761
|
ctx.logger.info("Claude Code integration skipped (--skip-claude)");
|
|
7145
|
-
|
|
7762
|
+
} else {
|
|
7763
|
+
const cliScriptPath = process.argv[1];
|
|
7764
|
+
if (!cliScriptPath) {
|
|
7765
|
+
ctx.logger.warn("cannot determine CLI script path; skipping Claude integration");
|
|
7766
|
+
} else {
|
|
7767
|
+
const result = installClaudeIntegration({
|
|
7768
|
+
claudeDir: join8(ctx.userHome, ".claude"),
|
|
7769
|
+
cliScriptPath,
|
|
7770
|
+
nodePath: resolveHookNodePath(),
|
|
7771
|
+
dryRun: opts.dryRun,
|
|
7772
|
+
logger: ctx.logger
|
|
7773
|
+
});
|
|
7774
|
+
reportInstallResult(ctx, result, opts.dryRun);
|
|
7775
|
+
}
|
|
7146
7776
|
}
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
ctx.logger.
|
|
7150
|
-
|
|
7777
|
+
if (opts.skipCodexHook) {
|
|
7778
|
+
codexHookState = "skipped";
|
|
7779
|
+
ctx.logger.info("Codex hook integration skipped (--skip-codex-hook)");
|
|
7780
|
+
} else if ((dependencies.codexAvailable ?? detectCodexAvailability)()) {
|
|
7781
|
+
const cliScriptPath = process.argv[1];
|
|
7782
|
+
if (!cliScriptPath) {
|
|
7783
|
+
codexHookState = "skipped";
|
|
7784
|
+
ctx.logger.warn("cannot determine CLI script path; skipping Codex hook integration");
|
|
7785
|
+
} else {
|
|
7786
|
+
const codexHome = join8(ctx.userHome, ".codex");
|
|
7787
|
+
const result = installCodexHooks({
|
|
7788
|
+
codexHome,
|
|
7789
|
+
cliScriptPath,
|
|
7790
|
+
nodePath: resolveHookNodePath(),
|
|
7791
|
+
dryRun: opts.dryRun,
|
|
7792
|
+
logger: ctx.logger
|
|
7793
|
+
});
|
|
7794
|
+
if (result.feature === "blocked") {
|
|
7795
|
+
codexHookState = "skipped";
|
|
7796
|
+
ctx.logger.warn(
|
|
7797
|
+
`${result.blockedReason}; preserving explicit consent. Set \`codex_hooks = true\` in ${join8(codexHome, "config.toml")}, then rerun \`caveat init\`.`
|
|
7798
|
+
);
|
|
7799
|
+
} else if (opts.dryRun) {
|
|
7800
|
+
codexHookState = "skipped";
|
|
7801
|
+
ctx.logger.info("[dry-run] would install Codex hooks");
|
|
7802
|
+
} else {
|
|
7803
|
+
codexHookState = detectCodexHookInstallation(codexHome).installation;
|
|
7804
|
+
}
|
|
7805
|
+
}
|
|
7151
7806
|
}
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7807
|
+
reportEnvironmentSummary(ctx, publishTarget, codexHookState, opts.dryRun);
|
|
7808
|
+
}
|
|
7809
|
+
function detectCodexAvailability() {
|
|
7810
|
+
const result = spawnSync4("codex --version", {
|
|
7811
|
+
shell: true,
|
|
7812
|
+
encoding: "utf-8",
|
|
7813
|
+
stdio: "ignore"
|
|
7158
7814
|
});
|
|
7159
|
-
|
|
7815
|
+
return !result.error && result.status === 0;
|
|
7816
|
+
}
|
|
7817
|
+
function isFileRemote(url2) {
|
|
7818
|
+
try {
|
|
7819
|
+
return new URL(url2).protocol === "file:";
|
|
7820
|
+
} catch {
|
|
7821
|
+
return false;
|
|
7822
|
+
}
|
|
7823
|
+
}
|
|
7824
|
+
function gitOutput(args) {
|
|
7825
|
+
const result = spawnSync4("git", args, { encoding: "utf-8" });
|
|
7826
|
+
return result.status === 0 && typeof result.stdout === "string" ? result.stdout.trim() : null;
|
|
7827
|
+
}
|
|
7828
|
+
function reportEnvironmentSummary(ctx, publishTarget, codexHookState, dryRun) {
|
|
7829
|
+
const prefix = dryRun ? "[dry-run] would have " : "";
|
|
7830
|
+
const isRepo = gitOutput(["-C", ctx.paths.knowledgeRepo, "rev-parse", "--is-inside-work-tree"]) === "true";
|
|
7831
|
+
const remote = isRepo ? gitOutput(["-C", ctx.paths.knowledgeRepo, "config", "--get", "remote.origin.url"]) : null;
|
|
7832
|
+
const communityCount = existsSync5(ctx.paths.communityDir) ? readdirSync(ctx.paths.communityDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).length : 0;
|
|
7833
|
+
ctx.logger.info(`${prefix}environment summary:`);
|
|
7834
|
+
ctx.logger.info(` own git: ${isRepo ? "repository" : "not initialized"}`);
|
|
7835
|
+
ctx.logger.info(` private remote: ${remote || "not configured"}`);
|
|
7836
|
+
ctx.logger.info(` publish target: ${publishTarget || "not configured"}`);
|
|
7837
|
+
ctx.logger.info(` community sources: ${communityCount}`);
|
|
7838
|
+
ctx.logger.info(` codex hook: ${codexHookState}`);
|
|
7839
|
+
}
|
|
7840
|
+
function errorMessage(err) {
|
|
7841
|
+
return err instanceof Error ? err.message : String(err);
|
|
7160
7842
|
}
|
|
7161
7843
|
function migrateLegacyCommunityDir(ctx) {
|
|
7162
|
-
const legacy =
|
|
7844
|
+
const legacy = join8(ctx.paths.knowledgeRepo, "community");
|
|
7163
7845
|
const current = ctx.paths.communityDir;
|
|
7164
7846
|
if (legacy === current) return;
|
|
7165
|
-
if (!
|
|
7166
|
-
if (
|
|
7847
|
+
if (!existsSync5(legacy)) return;
|
|
7848
|
+
if (existsSync5(current)) {
|
|
7167
7849
|
ctx.logger.warn(
|
|
7168
7850
|
`legacy community dir still exists at ${legacy} \u2014 remove manually (new location in use)`
|
|
7169
7851
|
);
|
|
7170
7852
|
return;
|
|
7171
7853
|
}
|
|
7172
|
-
|
|
7854
|
+
mkdirSync3(current, { recursive: true });
|
|
7173
7855
|
for (const entry of readdirSync(legacy, { withFileTypes: true })) {
|
|
7174
7856
|
if (!entry.isDirectory()) continue;
|
|
7175
|
-
renameSync(
|
|
7857
|
+
renameSync(join8(legacy, entry.name), join8(current, entry.name));
|
|
7176
7858
|
}
|
|
7177
7859
|
try {
|
|
7178
7860
|
rmdirSync(legacy);
|
|
@@ -7187,7 +7869,7 @@ function runUninstall(ctx, opts) {
|
|
|
7187
7869
|
process.exit(1);
|
|
7188
7870
|
}
|
|
7189
7871
|
const result = uninstallClaudeIntegration({
|
|
7190
|
-
claudeDir:
|
|
7872
|
+
claudeDir: join8(ctx.userHome, ".claude"),
|
|
7191
7873
|
cliScriptPath,
|
|
7192
7874
|
nodePath: resolveHookNodePath(),
|
|
7193
7875
|
dryRun: opts.dryRun,
|
|
@@ -7234,18 +7916,23 @@ function reportInstallResult(ctx, result, dryRun) {
|
|
|
7234
7916
|
}
|
|
7235
7917
|
|
|
7236
7918
|
// src/commands/indexCmd.ts
|
|
7237
|
-
import { existsSync as
|
|
7238
|
-
import { dirname as
|
|
7239
|
-
function runIndex(ctx, opts) {
|
|
7240
|
-
const dbDir =
|
|
7241
|
-
if (!
|
|
7919
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "node:fs";
|
|
7920
|
+
import { dirname as dirname5 } from "node:path";
|
|
7921
|
+
async function runIndex(ctx, opts) {
|
|
7922
|
+
const dbDir = dirname5(ctx.paths.dbPath);
|
|
7923
|
+
if (!existsSync6(dbDir)) mkdirSync4(dbDir, { recursive: true });
|
|
7924
|
+
const keyProvider = createKeyserverKeyProvider({ caveatHome: ctx.caveatHome });
|
|
7925
|
+
const failures = await prewarmSealedKeys({ paths: ctx.paths, keyProvider });
|
|
7926
|
+
for (const failure of failures) {
|
|
7927
|
+
ctx.logger.warn(`${failure.source}: sealed key prewarm failed: ${errorMessage2(failure.error)}`);
|
|
7928
|
+
}
|
|
7242
7929
|
const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
|
|
7243
7930
|
try {
|
|
7244
7931
|
if (opts.full) {
|
|
7245
7932
|
ctx.logger.info("full rebuild: DELETE FROM entries");
|
|
7246
7933
|
rebuildAll(db);
|
|
7247
7934
|
}
|
|
7248
|
-
const result = reindexAllSources({ db, paths: ctx.paths, logger: ctx.logger });
|
|
7935
|
+
const result = reindexAllSources({ db, paths: ctx.paths, logger: ctx.logger, keyProvider });
|
|
7249
7936
|
for (const [source, scan] of Object.entries(result.perSource)) {
|
|
7250
7937
|
if (source === "own") {
|
|
7251
7938
|
ctx.logger.info(`own: +${scan.added} ~${scan.updated} -${scan.deleted}`);
|
|
@@ -7258,6 +7945,9 @@ function runIndex(ctx, opts) {
|
|
|
7258
7945
|
db.close();
|
|
7259
7946
|
}
|
|
7260
7947
|
}
|
|
7948
|
+
function errorMessage2(err) {
|
|
7949
|
+
return err instanceof Error ? err.message : String(err);
|
|
7950
|
+
}
|
|
7261
7951
|
|
|
7262
7952
|
// src/commands/search.ts
|
|
7263
7953
|
function runSearch(ctx, opts) {
|
|
@@ -7405,7 +8095,7 @@ function runStats(ctx) {
|
|
|
7405
8095
|
|
|
7406
8096
|
// src/commands/serve.ts
|
|
7407
8097
|
async function runServe(opts) {
|
|
7408
|
-
const { startServer } = await import("./server-
|
|
8098
|
+
const { startServer } = await import("./server-SZAVLE56.js");
|
|
7409
8099
|
const { port, host } = startServer({ port: opts.port });
|
|
7410
8100
|
process.stdout.write(`[caveat] web portal: http://${host}:${port}/
|
|
7411
8101
|
`);
|
|
@@ -7784,15 +8474,15 @@ var makeIssue = (params) => {
|
|
|
7784
8474
|
message: issueData.message
|
|
7785
8475
|
};
|
|
7786
8476
|
}
|
|
7787
|
-
let
|
|
8477
|
+
let errorMessage6 = "";
|
|
7788
8478
|
const maps = errorMaps.filter((m) => !!m).slice().reverse();
|
|
7789
8479
|
for (const map2 of maps) {
|
|
7790
|
-
|
|
8480
|
+
errorMessage6 = map2(fullIssue, { data, defaultError: errorMessage6 }).message;
|
|
7791
8481
|
}
|
|
7792
8482
|
return {
|
|
7793
8483
|
...issueData,
|
|
7794
8484
|
path: fullPath,
|
|
7795
|
-
message:
|
|
8485
|
+
message: errorMessage6
|
|
7796
8486
|
};
|
|
7797
8487
|
};
|
|
7798
8488
|
function addIssueToContext(ctx, issueData) {
|
|
@@ -27584,19 +28274,19 @@ var getRefs = (options) => {
|
|
|
27584
28274
|
};
|
|
27585
28275
|
|
|
27586
28276
|
// ../../node_modules/.pnpm/zod-to-json-schema@3.25.2_zod@4.4.3/node_modules/zod-to-json-schema/dist/esm/errorMessages.js
|
|
27587
|
-
function addErrorMessage(res, key,
|
|
28277
|
+
function addErrorMessage(res, key, errorMessage6, refs) {
|
|
27588
28278
|
if (!refs?.errorMessages)
|
|
27589
28279
|
return;
|
|
27590
|
-
if (
|
|
28280
|
+
if (errorMessage6) {
|
|
27591
28281
|
res.errorMessage = {
|
|
27592
28282
|
...res.errorMessage,
|
|
27593
|
-
[key]:
|
|
28283
|
+
[key]: errorMessage6
|
|
27594
28284
|
};
|
|
27595
28285
|
}
|
|
27596
28286
|
}
|
|
27597
|
-
function setResponseValueAndErrors(res, key, value,
|
|
28287
|
+
function setResponseValueAndErrors(res, key, value, errorMessage6, refs) {
|
|
27598
28288
|
res[key] = value;
|
|
27599
|
-
addErrorMessage(res, key,
|
|
28289
|
+
addErrorMessage(res, key, errorMessage6, refs);
|
|
27600
28290
|
}
|
|
27601
28291
|
|
|
27602
28292
|
// ../../node_modules/.pnpm/zod-to-json-schema@3.25.2_zod@4.4.3/node_modules/zod-to-json-schema/dist/esm/getRelativePath.js
|
|
@@ -28907,8 +29597,8 @@ var Protocol = class {
|
|
|
28907
29597
|
if (queuedMessage.type === "response") {
|
|
28908
29598
|
resolver(message);
|
|
28909
29599
|
} else {
|
|
28910
|
-
const
|
|
28911
|
-
const error51 = new McpError(
|
|
29600
|
+
const errorMessage6 = message;
|
|
29601
|
+
const error51 = new McpError(errorMessage6.error.code, errorMessage6.error.message, errorMessage6.error.data);
|
|
28912
29602
|
resolver(error51);
|
|
28913
29603
|
}
|
|
28914
29604
|
} else {
|
|
@@ -30208,23 +30898,23 @@ var Server = class extends Protocol {
|
|
|
30208
30898
|
const wrappedHandler = async (request, extra) => {
|
|
30209
30899
|
const validatedRequest = safeParse2(CallToolRequestSchema, request);
|
|
30210
30900
|
if (!validatedRequest.success) {
|
|
30211
|
-
const
|
|
30212
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call request: ${
|
|
30901
|
+
const errorMessage6 = validatedRequest.error instanceof Error ? validatedRequest.error.message : String(validatedRequest.error);
|
|
30902
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call request: ${errorMessage6}`);
|
|
30213
30903
|
}
|
|
30214
30904
|
const { params } = validatedRequest.data;
|
|
30215
30905
|
const result = await Promise.resolve(handler(request, extra));
|
|
30216
30906
|
if (params.task) {
|
|
30217
30907
|
const taskValidationResult = safeParse2(CreateTaskResultSchema, result);
|
|
30218
30908
|
if (!taskValidationResult.success) {
|
|
30219
|
-
const
|
|
30220
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${
|
|
30909
|
+
const errorMessage6 = taskValidationResult.error instanceof Error ? taskValidationResult.error.message : String(taskValidationResult.error);
|
|
30910
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage6}`);
|
|
30221
30911
|
}
|
|
30222
30912
|
return taskValidationResult.data;
|
|
30223
30913
|
}
|
|
30224
30914
|
const validationResult = safeParse2(CallToolResultSchema, result);
|
|
30225
30915
|
if (!validationResult.success) {
|
|
30226
|
-
const
|
|
30227
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call result: ${
|
|
30916
|
+
const errorMessage6 = validationResult.error instanceof Error ? validationResult.error.message : String(validationResult.error);
|
|
30917
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call result: ${errorMessage6}`);
|
|
30228
30918
|
}
|
|
30229
30919
|
return validationResult.data;
|
|
30230
30920
|
};
|
|
@@ -30718,12 +31408,12 @@ var McpServer = class {
|
|
|
30718
31408
|
* @param errorMessage - The error message.
|
|
30719
31409
|
* @returns The tool error result.
|
|
30720
31410
|
*/
|
|
30721
|
-
createToolError(
|
|
31411
|
+
createToolError(errorMessage6) {
|
|
30722
31412
|
return {
|
|
30723
31413
|
content: [
|
|
30724
31414
|
{
|
|
30725
31415
|
type: "text",
|
|
30726
|
-
text:
|
|
31416
|
+
text: errorMessage6
|
|
30727
31417
|
}
|
|
30728
31418
|
],
|
|
30729
31419
|
isError: true
|
|
@@ -30741,8 +31431,8 @@ var McpServer = class {
|
|
|
30741
31431
|
const parseResult = await safeParseAsync2(schemaToParse, args);
|
|
30742
31432
|
if (!parseResult.success) {
|
|
30743
31433
|
const error51 = "error" in parseResult ? parseResult.error : "Unknown error";
|
|
30744
|
-
const
|
|
30745
|
-
throw new McpError(ErrorCode.InvalidParams, `Input validation error: Invalid arguments for tool ${toolName}: ${
|
|
31434
|
+
const errorMessage6 = getParseErrorMessage(error51);
|
|
31435
|
+
throw new McpError(ErrorCode.InvalidParams, `Input validation error: Invalid arguments for tool ${toolName}: ${errorMessage6}`);
|
|
30746
31436
|
}
|
|
30747
31437
|
return parseResult.data;
|
|
30748
31438
|
}
|
|
@@ -30766,8 +31456,8 @@ var McpServer = class {
|
|
|
30766
31456
|
const parseResult = await safeParseAsync2(outputObj, result.structuredContent);
|
|
30767
31457
|
if (!parseResult.success) {
|
|
30768
31458
|
const error51 = "error" in parseResult ? parseResult.error : "Unknown error";
|
|
30769
|
-
const
|
|
30770
|
-
throw new McpError(ErrorCode.InvalidParams, `Output validation error: Invalid structured content for tool ${toolName}: ${
|
|
31459
|
+
const errorMessage6 = getParseErrorMessage(error51);
|
|
31460
|
+
throw new McpError(ErrorCode.InvalidParams, `Output validation error: Invalid structured content for tool ${toolName}: ${errorMessage6}`);
|
|
30771
31461
|
}
|
|
30772
31462
|
}
|
|
30773
31463
|
/**
|
|
@@ -30979,8 +31669,8 @@ var McpServer = class {
|
|
|
30979
31669
|
const parseResult = await safeParseAsync2(argsObj, request.params.arguments);
|
|
30980
31670
|
if (!parseResult.success) {
|
|
30981
31671
|
const error51 = "error" in parseResult ? parseResult.error : "Unknown error";
|
|
30982
|
-
const
|
|
30983
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for prompt ${request.params.name}: ${
|
|
31672
|
+
const errorMessage6 = getParseErrorMessage(error51);
|
|
31673
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for prompt ${request.params.name}: ${errorMessage6}`);
|
|
30984
31674
|
}
|
|
30985
31675
|
const args = parseResult.data;
|
|
30986
31676
|
const cb = prompt.callback;
|
|
@@ -31477,11 +32167,11 @@ var StdioServerTransport = class {
|
|
|
31477
32167
|
|
|
31478
32168
|
// ../mcp/dist/context.js
|
|
31479
32169
|
import { homedir as homedir2 } from "node:os";
|
|
31480
|
-
import { join as
|
|
32170
|
+
import { join as join9 } from "node:path";
|
|
31481
32171
|
function buildMcpContext(overrides = {}) {
|
|
31482
32172
|
const userHome = overrides.userHome ?? homedir2();
|
|
31483
32173
|
const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
|
|
31484
|
-
const userConfigPath =
|
|
32174
|
+
const userConfigPath = join9(userHome, ".caveatrc.json");
|
|
31485
32175
|
const logger = overrides.logger ?? stderrLogger;
|
|
31486
32176
|
const config2 = loadConfig(userConfigPath);
|
|
31487
32177
|
const paths = resolvePaths(caveatHome, config2.knowledgeRepo, userHome);
|
|
@@ -31494,7 +32184,9 @@ var sourceFilter = external_exports.enum(["own", "community", "all"]);
|
|
|
31494
32184
|
var confidenceSchema = external_exports.enum(["confirmed", "reproduced", "tentative"]);
|
|
31495
32185
|
var visibilityFilter = external_exports.enum(["public", "private", "all"]);
|
|
31496
32186
|
var searchInputShape = {
|
|
31497
|
-
query: external_exports.string().describe(
|
|
32187
|
+
query: external_exports.string().describe(
|
|
32188
|
+
"FTS query (3+ chars for trigram). Empty string lists without text filter. If a search returns 0 hits, retry with synonyms and Japanese/English paraphrases. Use plain tokens only; FTS operators are not supported."
|
|
32189
|
+
),
|
|
31498
32190
|
filters: external_exports.object({
|
|
31499
32191
|
tags: external_exports.array(external_exports.string()).optional(),
|
|
31500
32192
|
confidence: external_exports.array(confidenceSchema).optional(),
|
|
@@ -31543,7 +32235,9 @@ var outcomeSchema = external_exports.enum(["resolved", "impossible"]);
|
|
|
31543
32235
|
var visibilitySchema = external_exports.enum(["public", "private"]);
|
|
31544
32236
|
var recordInputShape = {
|
|
31545
32237
|
title: external_exports.string().min(1),
|
|
31546
|
-
symptom: external_exports.string().min(1)
|
|
32238
|
+
symptom: external_exports.string().min(1).describe(
|
|
32239
|
+
"Describe the observed failure. Preserve raw errors and exact error strings verbatim; when a stable translation is known, also include the main symptom keywords in Japanese and English to improve retrieval. Do not force a full translation or guess at uncertain wording."
|
|
32240
|
+
),
|
|
31547
32241
|
cause: external_exports.string().optional(),
|
|
31548
32242
|
resolution: external_exports.string().optional(),
|
|
31549
32243
|
evidence: external_exports.string().optional(),
|
|
@@ -31592,14 +32286,19 @@ var patchFrontmatterSchema = external_exports.object({
|
|
|
31592
32286
|
});
|
|
31593
32287
|
var updateInputShape = {
|
|
31594
32288
|
id: external_exports.string(),
|
|
31595
|
-
source: external_exports.string().optional(),
|
|
32289
|
+
source: external_exports.string().optional().describe("Only own entries may be updated. community entries are subscriptions; edit them upstream."),
|
|
31596
32290
|
patch: external_exports.object({
|
|
31597
32291
|
frontmatter: patchFrontmatterSchema.optional(),
|
|
31598
|
-
sections: external_exports.record(external_exports.string(), external_exports.string()).optional()
|
|
32292
|
+
sections: external_exports.record(external_exports.string(), external_exports.string()).optional().describe(
|
|
32293
|
+
"Patch H2 sections by heading. When changing the Symptom section, preserve raw errors and exact error strings verbatim; when a stable translation is known, also include the main symptom keywords in Japanese and English to improve retrieval. Do not force a full translation or guess at uncertain wording."
|
|
32294
|
+
)
|
|
31599
32295
|
})
|
|
31600
32296
|
};
|
|
31601
32297
|
function handleUpdate(ctx, args) {
|
|
31602
32298
|
const source = args.source ?? "own";
|
|
32299
|
+
if (source !== "own") {
|
|
32300
|
+
throw new Error("community \u30A8\u30F3\u30C8\u30EA\u306F\u8CFC\u8AAD\u7269\u3067\u3059; \u7DE8\u96C6\u306F\u4E0A\u6D41\u3067\u884C\u3063\u3066\u304F\u3060\u3055\u3044");
|
|
32301
|
+
}
|
|
31603
32302
|
return updateEntry(args.id, args.patch, {
|
|
31604
32303
|
db: ctx.db,
|
|
31605
32304
|
entriesRoot: ctx.paths.entriesDir,
|
|
@@ -31616,13 +32315,12 @@ function handleListRecent(ctx, args) {
|
|
|
31616
32315
|
}
|
|
31617
32316
|
|
|
31618
32317
|
// ../mcp/dist/tools/pull.js
|
|
31619
|
-
import { existsSync as
|
|
31620
|
-
import { join as join7 } from "node:path";
|
|
32318
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
31621
32319
|
var pullInputShape = {};
|
|
31622
32320
|
async function handlePull(ctx, _args = {}) {
|
|
31623
32321
|
const pulled = [];
|
|
31624
32322
|
const indexed = [];
|
|
31625
|
-
if (
|
|
32323
|
+
if (existsSync7(ctx.paths.communityDir)) {
|
|
31626
32324
|
const results = await communityPull({
|
|
31627
32325
|
communityDir: ctx.paths.communityDir,
|
|
31628
32326
|
logger: ctx.logger
|
|
@@ -31631,23 +32329,25 @@ async function handlePull(ctx, _args = {}) {
|
|
|
31631
32329
|
pulled.push({ handle: r.handle, status: r.status, message: r.message });
|
|
31632
32330
|
}
|
|
31633
32331
|
}
|
|
31634
|
-
|
|
31635
|
-
|
|
31636
|
-
|
|
31637
|
-
|
|
32332
|
+
const keyProvider = createKeyserverKeyProvider({ caveatHome: ctx.caveatHome });
|
|
32333
|
+
const failures = await prewarmSealedKeys({ paths: ctx.paths, keyProvider });
|
|
32334
|
+
for (const failure of failures) {
|
|
32335
|
+
ctx.logger.warn(`${failure.source}: sealed key prewarm failed: ${errorMessage3(failure.error)}`);
|
|
31638
32336
|
}
|
|
31639
|
-
|
|
31640
|
-
|
|
31641
|
-
|
|
31642
|
-
|
|
31643
|
-
|
|
31644
|
-
|
|
31645
|
-
|
|
31646
|
-
|
|
31647
|
-
}
|
|
32337
|
+
const result = reindexAllSources({
|
|
32338
|
+
db: ctx.db,
|
|
32339
|
+
paths: ctx.paths,
|
|
32340
|
+
logger: ctx.logger,
|
|
32341
|
+
keyProvider
|
|
32342
|
+
});
|
|
32343
|
+
for (const [source, scan] of Object.entries(result.perSource)) {
|
|
32344
|
+
indexed.push({ source, ...scan });
|
|
31648
32345
|
}
|
|
31649
32346
|
return { pulled, indexed };
|
|
31650
32347
|
}
|
|
32348
|
+
function errorMessage3(err) {
|
|
32349
|
+
return err instanceof Error ? err.message : String(err);
|
|
32350
|
+
}
|
|
31651
32351
|
|
|
31652
32352
|
// ../mcp/dist/registerTools.js
|
|
31653
32353
|
function jsonResult(data) {
|
|
@@ -31665,7 +32365,7 @@ function registerAllTools(server, ctx) {
|
|
|
31665
32365
|
"caveat_search",
|
|
31666
32366
|
{
|
|
31667
32367
|
title: "caveat_search",
|
|
31668
|
-
description: 'Search the "caveats" knowledge base \u2014 records of time-wasting traps
|
|
32368
|
+
description: 'Search the "caveats" knowledge base \u2014 records of time-wasting traps someone already diagnosed, including external-spec issues (GPU/driver/CUDA versions, native-module builds, IDE/shell quirks, platform-specific behavior, library version incompatibilities) and reusable repo-specific context. Call this FIRST when a problem smells like a known trap \u2014 before reading stack traces top-to-bottom or trying fixes. Query: 3+ chars, plain tokens only (no OR/NEAR/other FTS5 operators). If it returns 0 hits, retry with synonyms and Japanese/English paraphrases. Returns summary rows including `{id, source}` \u2014 pass BOTH to caveat_get for the full body.',
|
|
31669
32369
|
inputSchema: searchInputShape
|
|
31670
32370
|
},
|
|
31671
32371
|
async (args) => jsonResult(handleSearch(ctx, args))
|
|
@@ -31683,7 +32383,7 @@ function registerAllTools(server, ctx) {
|
|
|
31683
32383
|
"caveat_record",
|
|
31684
32384
|
{
|
|
31685
32385
|
title: "caveat_record",
|
|
31686
|
-
description: "Create a new caveat: a record of
|
|
32386
|
+
description: "Create a new caveat: a reusable record of a time-wasting trap (wrong driver, version mismatch, platform bug, IDE quirk, native-module issue, or repo-specific context) so future sessions can find it via caveat_search. REQUIRED BEFORE CALLING: run caveat_search first to avoid duplicates. Set visibility using this binary criterion: `public` if a third party could reproduce it; `private` if it is repo/workflow-specific or depends on intentional local design; when unclear, prefer `private`; follow an explicit user visibility choice. Qualifies: specific symptom + diagnosed cause (or `outcome: impossible` verdict) + environment fingerprint. Does NOT qualify: ordinary project-internal logic bugs, user preferences, session summaries, or ephemeral task notes; reusable repo-specific traps may be `private`. Auto-fills source_session and environment defaults; source_project is left null by design (shared knowledge must not leak per-user project names).",
|
|
31687
32387
|
inputSchema: recordInputShape
|
|
31688
32388
|
},
|
|
31689
32389
|
async (args) => jsonResult(handleRecord(ctx, args))
|
|
@@ -31692,7 +32392,7 @@ function registerAllTools(server, ctx) {
|
|
|
31692
32392
|
"caveat_update",
|
|
31693
32393
|
{
|
|
31694
32394
|
title: "caveat_update",
|
|
31695
|
-
description: "Patch an existing caveat \u2014 use when newer evidence extends or corrects one that already exists. Frontmatter shallow-merges, but array fields (tags etc.) REPLACE rather than append \u2014 to add one tag, read the current list first, then patch with the full new array. Sections match by case-insensitive H2 heading. Immutable keys: id, created_at, source_session, source_project. Common uses: bump `last_verified` after re-confirming, add a resolution when it was `tentative`, flip `outcome` to `impossible`.",
|
|
32395
|
+
description: "Patch an existing caveat \u2014 use when newer evidence extends or corrects one that already exists. Frontmatter shallow-merges, but array fields (tags etc.) REPLACE rather than append \u2014 to add one tag, read the current list first, then patch with the full new array. Sections match by case-insensitive H2 heading. When changing `Symptom`, preserve raw errors verbatim and add stable Japanese/English symptom keywords when known; do not force or guess translations. Immutable keys: id, created_at, source_session, source_project. Common uses: bump `last_verified` after re-confirming, add a resolution when it was `tentative`, flip `outcome` to `impossible`.",
|
|
31696
32396
|
inputSchema: updateInputShape
|
|
31697
32397
|
},
|
|
31698
32398
|
async (args) => jsonResult(handleUpdate(ctx, args))
|
|
@@ -31754,28 +32454,31 @@ async function runMcpServer() {
|
|
|
31754
32454
|
}
|
|
31755
32455
|
|
|
31756
32456
|
// src/commands/hookCmd.ts
|
|
31757
|
-
import { spawn as
|
|
32457
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
31758
32458
|
import {
|
|
31759
|
-
|
|
31760
|
-
|
|
31761
|
-
|
|
31762
|
-
|
|
31763
|
-
|
|
31764
|
-
|
|
31765
|
-
|
|
32459
|
+
chmodSync,
|
|
32460
|
+
existsSync as existsSync10,
|
|
32461
|
+
lstatSync,
|
|
32462
|
+
mkdtempSync as mkdtempSync2,
|
|
32463
|
+
mkdirSync as mkdirSync5,
|
|
32464
|
+
readdirSync as readdirSync2,
|
|
32465
|
+
readFileSync as readFileSync5,
|
|
32466
|
+
realpathSync as realpathSync2,
|
|
32467
|
+
rmSync as rmSync2,
|
|
32468
|
+
writeFileSync as writeFileSync5
|
|
31766
32469
|
} from "node:fs";
|
|
31767
|
-
import { tmpdir } from "node:os";
|
|
31768
|
-
import { join as
|
|
32470
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
32471
|
+
import { basename, dirname as dirname6, join as join12 } from "node:path";
|
|
31769
32472
|
import { createHash, randomBytes } from "node:crypto";
|
|
31770
32473
|
|
|
31771
32474
|
// src/autoReindexTrigger.ts
|
|
31772
32475
|
import { spawn } from "node:child_process";
|
|
31773
|
-
import { existsSync as
|
|
31774
|
-
import { join as
|
|
32476
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
32477
|
+
import { join as join10 } from "node:path";
|
|
31775
32478
|
function maybeTriggerAutoReindex(ctx) {
|
|
31776
32479
|
if (process.env.CAVEAT_INDEX_AUTOSYNC === "off") return;
|
|
31777
|
-
if (!
|
|
31778
|
-
if (
|
|
32480
|
+
if (!existsSync8(ctx.paths.dbPath)) return;
|
|
32481
|
+
if (existsSync8(join10(ctx.caveatHome, "index", ".reindex-lock"))) return;
|
|
31779
32482
|
const current = computeEntriesDigest(ctx.paths);
|
|
31780
32483
|
const marker = readDigestMarker(ctx.caveatHome);
|
|
31781
32484
|
if (marker?.digest === current.digest && marker.fileCount === current.fileCount) return;
|
|
@@ -31791,6 +32494,28 @@ function maybeTriggerAutoReindex(ctx) {
|
|
|
31791
32494
|
child.unref();
|
|
31792
32495
|
}
|
|
31793
32496
|
|
|
32497
|
+
// src/autoSyncTrigger.ts
|
|
32498
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
32499
|
+
import { existsSync as existsSync9, statSync } from "node:fs";
|
|
32500
|
+
import { join as join11 } from "node:path";
|
|
32501
|
+
function maybeTriggerAutoSync(ctx) {
|
|
32502
|
+
if (process.env[CAVEAT_AUTO_SYNC_ENV] === "off") return;
|
|
32503
|
+
const statePath = join11(ctx.caveatHome, "sync", ".last-autosync.json");
|
|
32504
|
+
const debounceMs = Number(process.env.CAVEAT_AUTO_SYNC_DEBOUNCE_MS ?? AUTO_SYNC_DEBOUNCE_MS);
|
|
32505
|
+
try {
|
|
32506
|
+
if (existsSync9(statePath) && Date.now() - statSync(statePath).mtimeMs < debounceMs) return;
|
|
32507
|
+
} catch {
|
|
32508
|
+
}
|
|
32509
|
+
const cliScript = process.argv[1];
|
|
32510
|
+
if (!cliScript) throw new Error("current Caveat CLI script path is unavailable");
|
|
32511
|
+
const child = spawn2(
|
|
32512
|
+
process.execPath,
|
|
32513
|
+
[...process.execArgv, "--disable-warning=ExperimentalWarning", cliScript, "hook", "autosync"],
|
|
32514
|
+
{ detached: true, stdio: "ignore", windowsHide: true }
|
|
32515
|
+
);
|
|
32516
|
+
child.unref();
|
|
32517
|
+
}
|
|
32518
|
+
|
|
31794
32519
|
// src/commands/hookCmd.ts
|
|
31795
32520
|
var silentLogger = {
|
|
31796
32521
|
info: () => {
|
|
@@ -31835,31 +32560,44 @@ function buildContextSafely() {
|
|
|
31835
32560
|
return null;
|
|
31836
32561
|
}
|
|
31837
32562
|
}
|
|
31838
|
-
function searchCaveatsFromTextSafely(text) {
|
|
32563
|
+
function searchCaveatsFromTextSafely(text, surface) {
|
|
31839
32564
|
if (!text) return [];
|
|
31840
32565
|
let db;
|
|
32566
|
+
let caveatHome;
|
|
32567
|
+
let hits;
|
|
31841
32568
|
try {
|
|
31842
32569
|
const ctx = buildContextSafely();
|
|
31843
|
-
if (!ctx || !
|
|
32570
|
+
if (!ctx || !existsSync10(ctx.paths.dbPath)) return [];
|
|
32571
|
+
caveatHome = ctx.caveatHome;
|
|
31844
32572
|
db = openDb({ path: ctx.paths.dbPath });
|
|
31845
|
-
|
|
32573
|
+
hits = findCaveatsForPrompt(db, text, {
|
|
31846
32574
|
selfIdentity: defaultSelfIdentityTokens()
|
|
31847
32575
|
});
|
|
31848
|
-
if (hits.length > 0) {
|
|
31849
|
-
try {
|
|
31850
|
-
markHit(db, hits);
|
|
31851
|
-
} catch (err) {
|
|
31852
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
31853
|
-
process.stderr.write(`[caveat:hook] markHit error: ${msg}
|
|
31854
|
-
`);
|
|
31855
|
-
}
|
|
31856
|
-
}
|
|
31857
|
-
return hits;
|
|
31858
32576
|
} catch (err) {
|
|
31859
32577
|
const msg = err instanceof Error ? err.message : String(err);
|
|
31860
32578
|
process.stderr.write(`[caveat:hook] search error: ${msg}
|
|
31861
32579
|
`);
|
|
31862
32580
|
return [];
|
|
32581
|
+
}
|
|
32582
|
+
if (hits.length > 0) {
|
|
32583
|
+
try {
|
|
32584
|
+
markHit(db, hits);
|
|
32585
|
+
} catch (err) {
|
|
32586
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
32587
|
+
process.stderr.write(`[caveat:hook] markHit error: ${msg}
|
|
32588
|
+
`);
|
|
32589
|
+
}
|
|
32590
|
+
} else {
|
|
32591
|
+
try {
|
|
32592
|
+
logHookQueryMiss({ caveatHome, agent: "claude", surface, query: text });
|
|
32593
|
+
} catch (err) {
|
|
32594
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
32595
|
+
process.stderr.write(`[caveat:hook] query log error: ${msg}
|
|
32596
|
+
`);
|
|
32597
|
+
}
|
|
32598
|
+
}
|
|
32599
|
+
try {
|
|
32600
|
+
return hits;
|
|
31863
32601
|
} finally {
|
|
31864
32602
|
db?.close();
|
|
31865
32603
|
}
|
|
@@ -31875,12 +32613,15 @@ function loadSignalsSafely(path) {
|
|
|
31875
32613
|
}
|
|
31876
32614
|
}
|
|
31877
32615
|
function systemReminderOutput(text) {
|
|
31878
|
-
return `<system-reminder>${text}</system-reminder>`;
|
|
32616
|
+
return `<system-reminder>${text.replace(/</g, "\u2039").replace(/>/g, "\u203A")}</system-reminder>`;
|
|
31879
32617
|
}
|
|
31880
32618
|
function drainForSession(sessionId) {
|
|
31881
32619
|
const ctx = buildContextSafely();
|
|
31882
32620
|
if (!ctx) return [];
|
|
31883
|
-
return
|
|
32621
|
+
return [
|
|
32622
|
+
...drainPendingReminders(ctx.caveatHome, sessionId),
|
|
32623
|
+
...drainGlobalPendingReminders(ctx.caveatHome)
|
|
32624
|
+
];
|
|
31884
32625
|
}
|
|
31885
32626
|
function claudeContextDedupeKey(text) {
|
|
31886
32627
|
if (text.startsWith(CLAUDE_STOP_REMINDER_PREFIX)) return "claude-stop-reminder";
|
|
@@ -31924,20 +32665,20 @@ function stopSignalKey(signals, related) {
|
|
|
31924
32665
|
return createHash("sha256").update(body).digest("hex");
|
|
31925
32666
|
}
|
|
31926
32667
|
function stopStatePath(caveatHome, sessionId) {
|
|
31927
|
-
return
|
|
32668
|
+
return join12(caveatHome, CLAUDE_STOP_STATE_DIR, `${sanitizeClaudeStateId(sessionId)}.txt`);
|
|
31928
32669
|
}
|
|
31929
32670
|
function wasStopReminderQueued(caveatHome, sessionId, key) {
|
|
31930
32671
|
const path = stopStatePath(caveatHome, sessionId);
|
|
31931
32672
|
try {
|
|
31932
|
-
return
|
|
32673
|
+
return readFileSync5(path, "utf-8") === key;
|
|
31933
32674
|
} catch {
|
|
31934
32675
|
return false;
|
|
31935
32676
|
}
|
|
31936
32677
|
}
|
|
31937
32678
|
function markStopReminderQueued(caveatHome, sessionId, key) {
|
|
31938
32679
|
const path = stopStatePath(caveatHome, sessionId);
|
|
31939
|
-
|
|
31940
|
-
|
|
32680
|
+
mkdirSync5(join12(caveatHome, CLAUDE_STOP_STATE_DIR), { recursive: true });
|
|
32681
|
+
writeFileSync5(path, key, "utf-8");
|
|
31941
32682
|
}
|
|
31942
32683
|
function queueStopForSession(sessionId, signals, related) {
|
|
31943
32684
|
const ctx = buildContextSafely();
|
|
@@ -31986,23 +32727,34 @@ function isToolError(payload) {
|
|
|
31986
32727
|
if (typeof payload.error === "string" && payload.error.length > 0) return true;
|
|
31987
32728
|
return false;
|
|
31988
32729
|
}
|
|
32730
|
+
var WORKER_STALE_MS = 24 * 60 * 60 * 1e3;
|
|
32731
|
+
var WORKER_ROOT = "caveat-worker-v1";
|
|
32732
|
+
var WORKER_MARKER = ".caveat-worker-schema-v1";
|
|
31989
32733
|
function spawnWorker(job) {
|
|
31990
|
-
|
|
31991
|
-
|
|
31992
|
-
|
|
31993
|
-
);
|
|
32734
|
+
let root;
|
|
32735
|
+
let workDir;
|
|
32736
|
+
let workFile;
|
|
31994
32737
|
try {
|
|
31995
|
-
|
|
32738
|
+
root = workerRoot();
|
|
32739
|
+
sweepStaleWorkerDirs(Date.now(), root);
|
|
32740
|
+
workDir = mkdtempSync2(join12(root, "job-"));
|
|
32741
|
+
chmodSync(workDir, 448);
|
|
32742
|
+
workFile = join12(workDir, `${randomBytes(4).toString("hex")}.json`);
|
|
32743
|
+
writeFileSync5(workFile, JSON.stringify({ ...job, schemaVersion: "caveat-worker-job/v1" }), { encoding: "utf-8", mode: 384, flag: "wx" });
|
|
31996
32744
|
} catch (err) {
|
|
31997
32745
|
const msg = err instanceof Error ? err.message : String(err);
|
|
31998
32746
|
process.stderr.write(`[caveat:hook] worker writefile error: ${msg}
|
|
31999
32747
|
`);
|
|
32748
|
+
cleanupWorkerDir(workDir, workDir ? dirname6(workDir) : void 0);
|
|
32000
32749
|
return;
|
|
32001
32750
|
}
|
|
32002
32751
|
const cliScript = process.argv[1];
|
|
32003
|
-
if (!cliScript)
|
|
32752
|
+
if (!cliScript) {
|
|
32753
|
+
cleanupWorkerDir(workDir, root);
|
|
32754
|
+
return;
|
|
32755
|
+
}
|
|
32004
32756
|
try {
|
|
32005
|
-
const child =
|
|
32757
|
+
const child = spawn3(
|
|
32006
32758
|
process.execPath,
|
|
32007
32759
|
["--disable-warning=ExperimentalWarning", cliScript, "hook", "worker", workFile],
|
|
32008
32760
|
{ detached: true, stdio: "ignore", windowsHide: true }
|
|
@@ -32013,7 +32765,7 @@ function spawnWorker(job) {
|
|
|
32013
32765
|
process.stderr.write(`[caveat:hook] worker spawn error: ${msg}
|
|
32014
32766
|
`);
|
|
32015
32767
|
try {
|
|
32016
|
-
|
|
32768
|
+
cleanupWorkerDir(workDir, root);
|
|
32017
32769
|
} catch {
|
|
32018
32770
|
}
|
|
32019
32771
|
}
|
|
@@ -32021,14 +32773,11 @@ function spawnWorker(job) {
|
|
|
32021
32773
|
async function runWorker(workFile) {
|
|
32022
32774
|
let raw;
|
|
32023
32775
|
try {
|
|
32024
|
-
raw =
|
|
32776
|
+
raw = readFileSync5(workFile, "utf-8");
|
|
32025
32777
|
} catch {
|
|
32026
32778
|
process.exit(0);
|
|
32027
32779
|
}
|
|
32028
|
-
|
|
32029
|
-
unlinkSync(workFile);
|
|
32030
|
-
} catch {
|
|
32031
|
-
}
|
|
32780
|
+
cleanupWorkerDir(dirname6(workFile), dirname6(dirname6(workFile)));
|
|
32032
32781
|
let job;
|
|
32033
32782
|
try {
|
|
32034
32783
|
job = JSON.parse(raw);
|
|
@@ -32036,7 +32785,7 @@ async function runWorker(workFile) {
|
|
|
32036
32785
|
process.exit(0);
|
|
32037
32786
|
}
|
|
32038
32787
|
if (!job.searchText || !job.sessionId) process.exit(0);
|
|
32039
|
-
const hits = searchCaveatsFromTextSafely(job.searchText);
|
|
32788
|
+
const hits = searchCaveatsFromTextSafely(job.searchText, "tool_error");
|
|
32040
32789
|
if (hits.length === 0) process.exit(0);
|
|
32041
32790
|
const ctx = buildContextSafely();
|
|
32042
32791
|
if (!ctx) process.exit(0);
|
|
@@ -32046,22 +32795,101 @@ async function runWorker(workFile) {
|
|
|
32046
32795
|
}
|
|
32047
32796
|
process.exit(0);
|
|
32048
32797
|
}
|
|
32798
|
+
function cleanupWorkerDir(workDir, root) {
|
|
32799
|
+
if (!workDir || !root || !isOwnedWorkerDir(workDir, root)) return;
|
|
32800
|
+
try {
|
|
32801
|
+
rmSync2(workDir, { recursive: true, force: true });
|
|
32802
|
+
} catch {
|
|
32803
|
+
}
|
|
32804
|
+
}
|
|
32805
|
+
function isOwnedWorkerDir(path, root = workerRoot()) {
|
|
32806
|
+
try {
|
|
32807
|
+
const inputStat = lstatSync(path);
|
|
32808
|
+
if (inputStat.isSymbolicLink()) return false;
|
|
32809
|
+
const tmpRoot = realpathSync2(root);
|
|
32810
|
+
const resolved = realpathSync2(path);
|
|
32811
|
+
const stat = lstatSync(resolved);
|
|
32812
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
32813
|
+
return dirname6(resolved) === tmpRoot && basename(resolved).startsWith("job-") && stat.isDirectory() && !stat.isSymbolicLink() && hasPrivateOwnership(stat, uid);
|
|
32814
|
+
} catch {
|
|
32815
|
+
return false;
|
|
32816
|
+
}
|
|
32817
|
+
}
|
|
32818
|
+
function sweepStaleWorkerDirs(now = Date.now(), root = workerRoot()) {
|
|
32819
|
+
let entries;
|
|
32820
|
+
try {
|
|
32821
|
+
entries = readdirSync2(root);
|
|
32822
|
+
} catch {
|
|
32823
|
+
process.stderr.write("[caveat:hook] worker stale cleanup failed\n");
|
|
32824
|
+
return;
|
|
32825
|
+
}
|
|
32826
|
+
for (const entry of entries) {
|
|
32827
|
+
if (!entry.startsWith("job-")) continue;
|
|
32828
|
+
const path = join12(root, entry);
|
|
32829
|
+
try {
|
|
32830
|
+
if (!isOwnedWorkerDir(path, root)) continue;
|
|
32831
|
+
const stat = lstatSync(path);
|
|
32832
|
+
if (now - stat.mtimeMs <= WORKER_STALE_MS || !isStaleWorkerJobDir(path)) continue;
|
|
32833
|
+
rmSync2(path, { recursive: true, force: true });
|
|
32834
|
+
} catch {
|
|
32835
|
+
process.stderr.write("[caveat:hook] worker stale cleanup failed\n");
|
|
32836
|
+
}
|
|
32837
|
+
}
|
|
32838
|
+
}
|
|
32839
|
+
function workerRoot(base = tmpdir2()) {
|
|
32840
|
+
const root = join12(base, WORKER_ROOT);
|
|
32841
|
+
mkdirSync5(root, { recursive: true, mode: 448 });
|
|
32842
|
+
const stat = lstatSync(root);
|
|
32843
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
32844
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || !hasPrivateOwnership(stat, uid)) throw new Error("worker root is unsafe");
|
|
32845
|
+
const marker = join12(root, WORKER_MARKER);
|
|
32846
|
+
try {
|
|
32847
|
+
writeFileSync5(marker, "caveat-worker/v1\n", { mode: 384, flag: "wx" });
|
|
32848
|
+
} catch (error51) {
|
|
32849
|
+
if (!(error51 && typeof error51 === "object" && "code" in error51 && error51.code === "EEXIST")) throw error51;
|
|
32850
|
+
}
|
|
32851
|
+
const markerStat = lstatSync(marker);
|
|
32852
|
+
if (!markerStat.isFile() || markerStat.isSymbolicLink() || !hasPrivateOwnership(markerStat, uid) || readFileSync5(marker, "utf-8") !== "caveat-worker/v1\n") throw new Error("worker root marker is invalid");
|
|
32853
|
+
return root;
|
|
32854
|
+
}
|
|
32855
|
+
function isStaleWorkerJobDir(path) {
|
|
32856
|
+
const entries = readdirSync2(path);
|
|
32857
|
+
if (entries.length !== 1 || !entries[0].endsWith(".json")) return false;
|
|
32858
|
+
const file2 = join12(path, entries[0]);
|
|
32859
|
+
const stat = lstatSync(file2);
|
|
32860
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
32861
|
+
if (!stat.isFile() || stat.isSymbolicLink() || !hasPrivateOwnership(stat, uid)) return false;
|
|
32862
|
+
return isWorkerJob(JSON.parse(readFileSync5(file2, "utf-8")));
|
|
32863
|
+
}
|
|
32864
|
+
function hasPrivateOwnership(stat, uid) {
|
|
32865
|
+
return process.platform === "win32" || (stat.mode & 63) === 0 && (uid === void 0 || stat.uid === uid);
|
|
32866
|
+
}
|
|
32867
|
+
function isWorkerJob(value) {
|
|
32868
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
32869
|
+
const job = value;
|
|
32870
|
+
const keys = Object.keys(job).sort();
|
|
32871
|
+
if (!(keys.length === 3 && keys.join(",") === "schemaVersion,searchText,sessionId") && !(keys.length === 4 && keys.join(",") === "additionalContext,schemaVersion,searchText,sessionId")) return false;
|
|
32872
|
+
return job.schemaVersion === "caveat-worker-job/v1" && typeof job.sessionId === "string" && typeof job.searchText === "string" && (job.additionalContext === void 0 || job.additionalContext !== null && typeof job.additionalContext === "object" && !Array.isArray(job.additionalContext));
|
|
32873
|
+
}
|
|
32049
32874
|
function writeLastReindex(caveatHome, value) {
|
|
32050
32875
|
try {
|
|
32051
|
-
|
|
32876
|
+
writeFileSync5(join12(caveatHome, "index", ".last-reindex.json"), JSON.stringify(value), "utf-8");
|
|
32052
32877
|
} catch (err) {
|
|
32053
32878
|
const msg = err instanceof Error ? err.message : String(err);
|
|
32054
32879
|
process.stderr.write(`[caveat:hook] reindex status write error: ${msg}
|
|
32055
32880
|
`);
|
|
32056
32881
|
}
|
|
32057
32882
|
}
|
|
32058
|
-
function
|
|
32883
|
+
function errorMessage4(err) {
|
|
32884
|
+
return err instanceof Error ? err.message : String(err);
|
|
32885
|
+
}
|
|
32886
|
+
async function runReindexWorker() {
|
|
32059
32887
|
if (process.env.CAVEAT_INDEX_AUTOSYNC === "off") {
|
|
32060
32888
|
process.stderr.write("[caveat:hook] auto reindex disabled by CAVEAT_INDEX_AUTOSYNC=off\n");
|
|
32061
32889
|
return;
|
|
32062
32890
|
}
|
|
32063
32891
|
const ctx = buildContextSafely();
|
|
32064
|
-
if (!ctx || !
|
|
32892
|
+
if (!ctx || !existsSync10(ctx.paths.dbPath)) {
|
|
32065
32893
|
process.stderr.write("[caveat:hook] auto reindex skipped: index database does not exist\n");
|
|
32066
32894
|
return;
|
|
32067
32895
|
}
|
|
@@ -32071,8 +32899,14 @@ function runReindexWorker() {
|
|
|
32071
32899
|
let db;
|
|
32072
32900
|
try {
|
|
32073
32901
|
const digest = computeEntriesDigest(ctx.paths);
|
|
32902
|
+
const keyProvider = createKeyserverKeyProvider({ caveatHome: ctx.caveatHome });
|
|
32903
|
+
const failures = await prewarmSealedKeys({ paths: ctx.paths, keyProvider });
|
|
32904
|
+
for (const failure of failures) {
|
|
32905
|
+
process.stderr.write(`[caveat:hook] ${failure.source}: sealed key prewarm failed: ${errorMessage4(failure.error)}
|
|
32906
|
+
`);
|
|
32907
|
+
}
|
|
32074
32908
|
db = openDb({ path: ctx.paths.dbPath, logger: silentLogger });
|
|
32075
|
-
const result = reindexAllSources({ db, paths: ctx.paths, logger: silentLogger });
|
|
32909
|
+
const result = reindexAllSources({ db, paths: ctx.paths, logger: silentLogger, keyProvider });
|
|
32076
32910
|
writeDigestMarker(ctx.caveatHome, digest);
|
|
32077
32911
|
writeLastReindex(ctx.caveatHome, {
|
|
32078
32912
|
startedAt,
|
|
@@ -32099,12 +32933,31 @@ function runReindexWorker() {
|
|
|
32099
32933
|
}
|
|
32100
32934
|
}
|
|
32101
32935
|
}
|
|
32936
|
+
async function runAutoSyncWorker() {
|
|
32937
|
+
if (process.env[CAVEAT_AUTO_SYNC_ENV] === "off") {
|
|
32938
|
+
process.stderr.write("[caveat:hook] auto sync disabled by CAVEAT_AUTO_SYNC=off\n");
|
|
32939
|
+
return;
|
|
32940
|
+
}
|
|
32941
|
+
const ctx = buildContextSafely();
|
|
32942
|
+
if (!ctx) return;
|
|
32943
|
+
try {
|
|
32944
|
+
await runAutoSync({
|
|
32945
|
+
caveatHome: ctx.caveatHome,
|
|
32946
|
+
ownDir: ctx.paths.knowledgeRepo,
|
|
32947
|
+
paths: ctx.paths,
|
|
32948
|
+
logger: silentLogger
|
|
32949
|
+
});
|
|
32950
|
+
} catch (err) {
|
|
32951
|
+
process.stderr.write(`[caveat:hook] auto sync error: ${errorMessage4(err)}
|
|
32952
|
+
`);
|
|
32953
|
+
}
|
|
32954
|
+
}
|
|
32102
32955
|
function buildToolErrorReminder(job, hits) {
|
|
32103
32956
|
const base = toolErrorReminderText(hits);
|
|
32104
32957
|
const mode = hookCodexSidecarMode();
|
|
32105
32958
|
if (mode === "off") return base;
|
|
32106
32959
|
const projectRoot = process.cwd();
|
|
32107
|
-
const hasSidecarConfig =
|
|
32960
|
+
const hasSidecarConfig = existsSync10(join12(projectRoot, ".codex-sidecar.yml"));
|
|
32108
32961
|
if (mode === "auto" && !hasSidecarConfig) return base;
|
|
32109
32962
|
const advisory = runCodexSidecarAdvisory({
|
|
32110
32963
|
searchText: job.searchText,
|
|
@@ -32114,21 +32967,20 @@ function buildToolErrorReminder(job, hits) {
|
|
|
32114
32967
|
"A Claude Code tool just returned an error.",
|
|
32115
32968
|
"Use the provided Caveat context to give concise next-step advice.",
|
|
32116
32969
|
"Do not tell Claude to search Caveat again unless the context is insufficient."
|
|
32117
|
-
].join(" ")
|
|
32970
|
+
].join(" "),
|
|
32971
|
+
additionalContext: job.additionalContext
|
|
32118
32972
|
});
|
|
32119
32973
|
if (advisory.status === "ok") {
|
|
32120
32974
|
return [
|
|
32121
32975
|
base,
|
|
32122
32976
|
"",
|
|
32123
|
-
|
|
32124
|
-
advisory.summary,
|
|
32125
|
-
advisory.rawEventLogRef ? `rawEventLogRef: ${advisory.rawEventLogRef}` : ""
|
|
32977
|
+
formatCodexSidecarAdvisory(advisory)
|
|
32126
32978
|
].filter(Boolean).join("\n");
|
|
32127
32979
|
}
|
|
32128
32980
|
return [
|
|
32129
32981
|
base,
|
|
32130
32982
|
"",
|
|
32131
|
-
|
|
32983
|
+
formatCodexSidecarAdvisory(advisory)
|
|
32132
32984
|
].join("\n");
|
|
32133
32985
|
}
|
|
32134
32986
|
function hookCodexSidecarMode() {
|
|
@@ -32141,7 +32993,7 @@ function buildStopReminder(signals, related) {
|
|
|
32141
32993
|
const mode = hookCodexSidecarMode();
|
|
32142
32994
|
if (mode === "off") return base;
|
|
32143
32995
|
const projectRoot = process.cwd();
|
|
32144
|
-
const hasSidecarConfig =
|
|
32996
|
+
const hasSidecarConfig = existsSync10(join12(projectRoot, ".codex-sidecar.yml"));
|
|
32145
32997
|
if (mode === "auto" && !hasSidecarConfig) return base;
|
|
32146
32998
|
const advisory = runCodexSidecarAdvisory({
|
|
32147
32999
|
searchText: struggleSearchText(signals),
|
|
@@ -32149,115 +33001,44 @@ function buildStopReminder(signals, related) {
|
|
|
32149
33001
|
projectRoot,
|
|
32150
33002
|
prompt: [
|
|
32151
33003
|
"A Claude Code session is ending after objective struggle signals.",
|
|
32152
|
-
"Use the provided Caveat context and
|
|
33004
|
+
"Use the provided Caveat context and structured hook signal context to advise whether Claude should update an existing caveat or record a new one.",
|
|
32153
33005
|
"Be concise and preserve Caveat visibility rules."
|
|
32154
|
-
].join(" ")
|
|
33006
|
+
].join(" "),
|
|
33007
|
+
additionalContext: buildHookSignalSidecarContextBlock({
|
|
33008
|
+
type: "stop",
|
|
33009
|
+
toolFailureCount: signals.toolFailureCount,
|
|
33010
|
+
reeditedFileCount: signals.fileEditCounts.length,
|
|
33011
|
+
webSearchCount: signals.webSearchCount,
|
|
33012
|
+
webFetchCount: signals.webFetchCount,
|
|
33013
|
+
bashRetryCount: signals.bashRetryCount,
|
|
33014
|
+
durationMinutes: signals.durationMinutes
|
|
33015
|
+
})
|
|
32155
33016
|
});
|
|
32156
33017
|
if (advisory.status === "ok") {
|
|
32157
33018
|
return [
|
|
32158
33019
|
base,
|
|
32159
33020
|
"",
|
|
32160
|
-
|
|
32161
|
-
advisory.summary,
|
|
32162
|
-
advisory.rawEventLogRef ? `rawEventLogRef: ${advisory.rawEventLogRef}` : ""
|
|
33021
|
+
formatCodexSidecarAdvisory(advisory)
|
|
32163
33022
|
].filter(Boolean).join("\n");
|
|
32164
33023
|
}
|
|
32165
33024
|
return [
|
|
32166
33025
|
base,
|
|
32167
33026
|
"",
|
|
32168
|
-
|
|
33027
|
+
formatCodexSidecarAdvisory(advisory)
|
|
32169
33028
|
].join("\n");
|
|
32170
33029
|
}
|
|
32171
|
-
function
|
|
32172
|
-
const singleLine = message.replace(/\s+/g, " ").trim();
|
|
32173
|
-
if (!singleLine) return "unknown error";
|
|
32174
|
-
return singleLine.length > 220 ? `${singleLine.slice(0, 220)}...` : singleLine;
|
|
32175
|
-
}
|
|
32176
|
-
function sidecarFailureDetail(output) {
|
|
32177
|
-
const protocol = output.match(/PROTOCOL_ERROR:[^"\r\n]+/);
|
|
32178
|
-
if (protocol) return compactFailureMessage(protocol[0]);
|
|
32179
|
-
const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
32180
|
-
const diagnostic = lines.find(
|
|
32181
|
-
(line) => !line.startsWith("[caveat] [codex-sidecar] codex-sidecar ")
|
|
32182
|
-
);
|
|
32183
|
-
return compactFailureMessage(diagnostic ?? output);
|
|
32184
|
-
}
|
|
32185
|
-
function runCodexSidecarAdvisory(input) {
|
|
32186
|
-
const cliScript = process.argv[1];
|
|
32187
|
-
if (!cliScript) {
|
|
32188
|
-
return { status: "failed", message: "current Caveat CLI script path is unavailable" };
|
|
32189
|
-
}
|
|
32190
|
-
const args = [
|
|
32191
|
-
...process.execArgv,
|
|
32192
|
-
"--disable-warning=ExperimentalWarning",
|
|
32193
|
-
cliScript,
|
|
32194
|
-
"codex-sidecar",
|
|
32195
|
-
"run",
|
|
32196
|
-
"explore",
|
|
32197
|
-
input.prompt,
|
|
32198
|
-
"--preset",
|
|
32199
|
-
"advisory",
|
|
32200
|
-
"--query",
|
|
32201
|
-
input.searchText,
|
|
32202
|
-
"--limit",
|
|
32203
|
-
String(Math.max(1, input.limit)),
|
|
32204
|
-
"--host-agent",
|
|
32205
|
-
"claude",
|
|
32206
|
-
"--availability",
|
|
32207
|
-
"operational"
|
|
32208
|
-
];
|
|
32209
|
-
const resultDir = mkdtempSync(join9(tmpdir(), "caveat-codex-advisory-"));
|
|
32210
|
-
const resultFile = join9(resultDir, "result.json");
|
|
32211
|
-
args.push("--save-result", resultFile);
|
|
32212
|
-
const nodeCli = process.env.CAVEAT_CODEX_SIDECAR_NODE_CLI;
|
|
32213
|
-
if (nodeCli) {
|
|
32214
|
-
args.push("--node-cli", nodeCli);
|
|
32215
|
-
} else {
|
|
32216
|
-
const command = process.env.CAVEAT_CODEX_SIDECAR_COMMAND;
|
|
32217
|
-
if (command) args.push("--command", command);
|
|
32218
|
-
}
|
|
33030
|
+
async function runHook(name, arg) {
|
|
32219
33031
|
try {
|
|
32220
|
-
|
|
32221
|
-
|
|
32222
|
-
|
|
32223
|
-
timeout: Number(process.env.CAVEAT_HOOK_CODEX_SIDECAR_TIMEOUT_MS ?? 12e4),
|
|
32224
|
-
maxBuffer: 10 * 1024 * 1024
|
|
32225
|
-
});
|
|
32226
|
-
if (result.error) {
|
|
32227
|
-
return { status: "failed", message: compactFailureMessage(result.error.message) };
|
|
32228
|
-
}
|
|
32229
|
-
if (result.status !== 0) {
|
|
32230
|
-
const detail = (result.stderr || result.stdout || `exit ${result.status}`).trim();
|
|
32231
|
-
return {
|
|
32232
|
-
status: "failed",
|
|
32233
|
-
message: `sidecar command failed: ${sidecarFailureDetail(detail)}`
|
|
32234
|
-
};
|
|
32235
|
-
}
|
|
32236
|
-
const parsed = JSON.parse(readFileSync3(resultFile, "utf-8"));
|
|
32237
|
-
if (parsed.status !== "ok" || typeof parsed.summary !== "string") {
|
|
32238
|
-
return {
|
|
32239
|
-
status: "failed",
|
|
32240
|
-
message: compactFailureMessage(`unexpected SidecarResult status: ${String(parsed.status)}`)
|
|
32241
|
-
};
|
|
32242
|
-
}
|
|
32243
|
-
return {
|
|
32244
|
-
status: "ok",
|
|
32245
|
-
summary: parsed.summary,
|
|
32246
|
-
...typeof parsed.rawEventLogRef === "string" ? { rawEventLogRef: parsed.rawEventLogRef } : {}
|
|
32247
|
-
};
|
|
32248
|
-
} catch (err) {
|
|
32249
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
32250
|
-
return {
|
|
32251
|
-
status: "failed",
|
|
32252
|
-
message: compactFailureMessage(`invalid SidecarResult JSON: ${msg}`)
|
|
32253
|
-
};
|
|
32254
|
-
} finally {
|
|
32255
|
-
rmSync(resultDir, { recursive: true, force: true });
|
|
33032
|
+
sweepStaleWorkerDirs();
|
|
33033
|
+
} catch {
|
|
33034
|
+
process.stderr.write("[caveat:hook] worker stale cleanup failed\n");
|
|
32256
33035
|
}
|
|
32257
|
-
}
|
|
32258
|
-
async function runHook(name, arg) {
|
|
32259
33036
|
if (name === "reindex") {
|
|
32260
|
-
runReindexWorker();
|
|
33037
|
+
await runReindexWorker();
|
|
33038
|
+
process.exit(0);
|
|
33039
|
+
}
|
|
33040
|
+
if (name === "autosync") {
|
|
33041
|
+
await runAutoSyncWorker();
|
|
32261
33042
|
process.exit(0);
|
|
32262
33043
|
}
|
|
32263
33044
|
if (name === "worker") {
|
|
@@ -32279,7 +33060,7 @@ async function runHook(name, arg) {
|
|
|
32279
33060
|
const contexts = name === "stop" ? [] : drainForSession(sessionId);
|
|
32280
33061
|
if (name === "user-prompt-submit") {
|
|
32281
33062
|
const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
|
|
32282
|
-
const hits = searchCaveatsFromTextSafely(prompt);
|
|
33063
|
+
const hits = searchCaveatsFromTextSafely(prompt, "user_prompt");
|
|
32283
33064
|
if (hits.length > 0) {
|
|
32284
33065
|
contexts.push(userPromptSubmitReminderText(hits));
|
|
32285
33066
|
}
|
|
@@ -32301,7 +33082,13 @@ async function runHook(name, arg) {
|
|
|
32301
33082
|
payload.tool_response ?? payload.toolResponse ?? payload.error ?? payload
|
|
32302
33083
|
);
|
|
32303
33084
|
if (errText) {
|
|
32304
|
-
|
|
33085
|
+
const failureKind = payload.hook_event_name === "PostToolUseFailure" ? "post-tool-use-failure" : "error-bearing-post-tool-use";
|
|
33086
|
+
const additionalContext = buildHookSignalSidecarContextBlock({
|
|
33087
|
+
type: "tool-error",
|
|
33088
|
+
toolName: payload.tool_name ?? payload.toolName,
|
|
33089
|
+
failureKind
|
|
33090
|
+
});
|
|
33091
|
+
spawnWorker({ sessionId, searchText: errText, ...additionalContext ? { additionalContext } : {} });
|
|
32305
33092
|
}
|
|
32306
33093
|
process.exit(0);
|
|
32307
33094
|
}
|
|
@@ -32320,6 +33107,13 @@ async function runHook(name, arg) {
|
|
|
32320
33107
|
} catch (err) {
|
|
32321
33108
|
const msg = err instanceof Error ? err.message : String(err);
|
|
32322
33109
|
process.stderr.write(`[caveat:hook] auto reindex trigger error: ${msg}
|
|
33110
|
+
`);
|
|
33111
|
+
}
|
|
33112
|
+
try {
|
|
33113
|
+
maybeTriggerAutoSync(ctx);
|
|
33114
|
+
} catch (err) {
|
|
33115
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
33116
|
+
process.stderr.write(`[caveat:hook] auto sync trigger error: ${msg}
|
|
32323
33117
|
`);
|
|
32324
33118
|
}
|
|
32325
33119
|
}
|
|
@@ -32327,7 +33121,7 @@ async function runHook(name, arg) {
|
|
|
32327
33121
|
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
|
|
32328
33122
|
const signals = transcriptPath ? loadSignalsSafely(transcriptPath) : null;
|
|
32329
33123
|
if (!signals || !hasAnyStruggleSignal(signals)) process.exit(0);
|
|
32330
|
-
const related = searchCaveatsFromTextSafely(struggleSearchText(signals));
|
|
33124
|
+
const related = searchCaveatsFromTextSafely(struggleSearchText(signals), "stop");
|
|
32331
33125
|
queueStopForSession(sessionId, signals, related);
|
|
32332
33126
|
process.exit(0);
|
|
32333
33127
|
}
|
|
@@ -32337,236 +33131,11 @@ async function runHook(name, arg) {
|
|
|
32337
33131
|
}
|
|
32338
33132
|
|
|
32339
33133
|
// src/commands/codexHookCmd.ts
|
|
32340
|
-
import { spawn as
|
|
32341
|
-
import { existsSync as
|
|
32342
|
-
import { homedir as homedir3, tmpdir as
|
|
32343
|
-
import { join as
|
|
33134
|
+
import { spawn as spawn4, spawnSync as spawnSync5 } from "node:child_process";
|
|
33135
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync6, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
33136
|
+
import { homedir as homedir3, tmpdir as tmpdir3 } from "node:os";
|
|
33137
|
+
import { join as join13 } from "node:path";
|
|
32344
33138
|
import { createHash as createHash2, randomBytes as randomBytes2 } from "node:crypto";
|
|
32345
|
-
|
|
32346
|
-
// src/codexHookInstall.ts
|
|
32347
|
-
import { copyFileSync as copyFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
32348
|
-
import { dirname as dirname5, join as join10 } from "node:path";
|
|
32349
|
-
function quote2(p) {
|
|
32350
|
-
return p.includes(" ") ? `"${p}"` : p;
|
|
32351
|
-
}
|
|
32352
|
-
function hookCommand2(nodePath, cliScriptPath, event) {
|
|
32353
|
-
return `${quote2(nodePath)} ${quote2(cliScriptPath)} codex-hook ${event}`;
|
|
32354
|
-
}
|
|
32355
|
-
function eventCommandFragment(event) {
|
|
32356
|
-
return `codex-hook ${event}`;
|
|
32357
|
-
}
|
|
32358
|
-
function isSameHookCommand2(actual, expected) {
|
|
32359
|
-
return actual === expected || actual.endsWith(` ${expected}`);
|
|
32360
|
-
}
|
|
32361
|
-
function isCaveatCodexHookCommand(actual, event) {
|
|
32362
|
-
const lower = actual.toLowerCase();
|
|
32363
|
-
return lower.includes("caveat") && actual.includes(eventCommandFragment(event));
|
|
32364
|
-
}
|
|
32365
|
-
function hasCaveatHook(hooksJson, event, fragment) {
|
|
32366
|
-
return hooksJson.hooks?.[event]?.some(
|
|
32367
|
-
(entry) => entry.hooks?.some((h) => h.command.includes(fragment))
|
|
32368
|
-
) ?? false;
|
|
32369
|
-
}
|
|
32370
|
-
function readHooks(path) {
|
|
32371
|
-
if (!existsSync8(path)) return {};
|
|
32372
|
-
return JSON.parse(readFileSync4(path, "utf-8"));
|
|
32373
|
-
}
|
|
32374
|
-
function writeJsonWithBackup(path, value) {
|
|
32375
|
-
const dir = dirname5(path);
|
|
32376
|
-
if (!existsSync8(dir)) mkdirSync5(dir, { recursive: true });
|
|
32377
|
-
let backupPath = "";
|
|
32378
|
-
if (existsSync8(path)) {
|
|
32379
|
-
backupPath = `${path}.caveat-backup-${Date.now()}`;
|
|
32380
|
-
copyFileSync2(path, backupPath);
|
|
32381
|
-
}
|
|
32382
|
-
writeFileSync4(path, `${JSON.stringify(value, null, 2)}
|
|
32383
|
-
`, "utf-8");
|
|
32384
|
-
return backupPath;
|
|
32385
|
-
}
|
|
32386
|
-
function upsertHook2(hooksJson, event, command, subcommand) {
|
|
32387
|
-
hooksJson.hooks ??= {};
|
|
32388
|
-
const list = hooksJson.hooks[event] ??= [];
|
|
32389
|
-
for (const entry of list) {
|
|
32390
|
-
for (const hook of entry.hooks ?? []) {
|
|
32391
|
-
if (isSameHookCommand2(hook.command, command)) return "unchanged";
|
|
32392
|
-
if (isCaveatCodexHookCommand(hook.command, subcommand)) {
|
|
32393
|
-
hook.command = command;
|
|
32394
|
-
hook.timeoutSec = 5;
|
|
32395
|
-
hook.async = false;
|
|
32396
|
-
hook.statusMessage = null;
|
|
32397
|
-
return "added";
|
|
32398
|
-
}
|
|
32399
|
-
}
|
|
32400
|
-
}
|
|
32401
|
-
list.push({
|
|
32402
|
-
hooks: [
|
|
32403
|
-
{
|
|
32404
|
-
type: "command",
|
|
32405
|
-
command,
|
|
32406
|
-
timeoutSec: 5,
|
|
32407
|
-
async: false,
|
|
32408
|
-
statusMessage: null
|
|
32409
|
-
}
|
|
32410
|
-
]
|
|
32411
|
-
});
|
|
32412
|
-
return "added";
|
|
32413
|
-
}
|
|
32414
|
-
function removeHook2(hooksJson, event, command, subcommand) {
|
|
32415
|
-
const list = hooksJson.hooks?.[event];
|
|
32416
|
-
if (!list) return false;
|
|
32417
|
-
const before = list.length;
|
|
32418
|
-
const filtered = list.filter(
|
|
32419
|
-
(entry) => !entry.hooks?.some(
|
|
32420
|
-
(h) => isSameHookCommand2(h.command, command) || isCaveatCodexHookCommand(h.command, subcommand)
|
|
32421
|
-
)
|
|
32422
|
-
);
|
|
32423
|
-
if (filtered.length === before) return false;
|
|
32424
|
-
hooksJson.hooks[event] = filtered;
|
|
32425
|
-
return true;
|
|
32426
|
-
}
|
|
32427
|
-
function enableCodexHooksFeature(raw) {
|
|
32428
|
-
if (/^\s*codex_hooks\s*=\s*true\s*$/m.test(raw)) return { text: raw, changed: false };
|
|
32429
|
-
const lines = raw.split(/\r?\n/);
|
|
32430
|
-
const featuresStart = lines.findIndex((line) => /^\s*\[features]\s*$/.test(line));
|
|
32431
|
-
if (featuresStart === -1) {
|
|
32432
|
-
const prefix = raw.trimEnd();
|
|
32433
|
-
const text = `${prefix}${prefix ? "\n\n" : ""}[features]
|
|
32434
|
-
codex_hooks = true
|
|
32435
|
-
`;
|
|
32436
|
-
return { text, changed: true };
|
|
32437
|
-
}
|
|
32438
|
-
let insertAt = featuresStart + 1;
|
|
32439
|
-
for (let i = featuresStart + 1; i < lines.length; i += 1) {
|
|
32440
|
-
if (/^\s*\[.+]\s*$/.test(lines[i])) {
|
|
32441
|
-
break;
|
|
32442
|
-
}
|
|
32443
|
-
if (/^\s*codex_hooks\s*=/.test(lines[i])) {
|
|
32444
|
-
lines[i] = "codex_hooks = true";
|
|
32445
|
-
return { text: `${lines.join("\n").trimEnd()}
|
|
32446
|
-
`, changed: true };
|
|
32447
|
-
}
|
|
32448
|
-
}
|
|
32449
|
-
lines.splice(insertAt, 0, "codex_hooks = true");
|
|
32450
|
-
return { text: `${lines.join("\n").trimEnd()}
|
|
32451
|
-
`, changed: true };
|
|
32452
|
-
}
|
|
32453
|
-
function writeConfigWithBackup(path, text) {
|
|
32454
|
-
const dir = dirname5(path);
|
|
32455
|
-
if (!existsSync8(dir)) mkdirSync5(dir, { recursive: true });
|
|
32456
|
-
let backupPath = "";
|
|
32457
|
-
if (existsSync8(path)) {
|
|
32458
|
-
backupPath = `${path}.caveat-backup-${Date.now()}`;
|
|
32459
|
-
copyFileSync2(path, backupPath);
|
|
32460
|
-
}
|
|
32461
|
-
writeFileSync4(path, text, "utf-8");
|
|
32462
|
-
return backupPath;
|
|
32463
|
-
}
|
|
32464
|
-
function installCodexHooks(opts) {
|
|
32465
|
-
const hooksPath = join10(opts.codexHome, "hooks.json");
|
|
32466
|
-
const configPath = join10(opts.codexHome, "config.toml");
|
|
32467
|
-
const hooksJson = readHooks(hooksPath);
|
|
32468
|
-
const userPromptSubmit = upsertHook2(
|
|
32469
|
-
hooksJson,
|
|
32470
|
-
"UserPromptSubmit",
|
|
32471
|
-
hookCommand2(opts.nodePath, opts.cliScriptPath, "user-prompt-submit"),
|
|
32472
|
-
"user-prompt-submit"
|
|
32473
|
-
);
|
|
32474
|
-
const postToolUse = upsertHook2(
|
|
32475
|
-
hooksJson,
|
|
32476
|
-
"PostToolUse",
|
|
32477
|
-
hookCommand2(opts.nodePath, opts.cliScriptPath, "post-tool-use"),
|
|
32478
|
-
"post-tool-use"
|
|
32479
|
-
);
|
|
32480
|
-
const stop = upsertHook2(
|
|
32481
|
-
hooksJson,
|
|
32482
|
-
"Stop",
|
|
32483
|
-
hookCommand2(opts.nodePath, opts.cliScriptPath, "stop"),
|
|
32484
|
-
"stop"
|
|
32485
|
-
);
|
|
32486
|
-
const rawConfig = existsSync8(configPath) ? readFileSync4(configPath, "utf-8") : "";
|
|
32487
|
-
const enabled = enableCodexHooksFeature(rawConfig);
|
|
32488
|
-
const anyHookAdded = userPromptSubmit === "added" || postToolUse === "added" || stop === "added";
|
|
32489
|
-
let backupPath;
|
|
32490
|
-
let configBackupPath;
|
|
32491
|
-
if (opts.dryRun) {
|
|
32492
|
-
opts.logger.info(`[dry-run] would update ${hooksPath}`);
|
|
32493
|
-
opts.logger.info(`[dry-run] would ensure [features].codex_hooks = true in ${configPath}`);
|
|
32494
|
-
} else {
|
|
32495
|
-
if (anyHookAdded) {
|
|
32496
|
-
const backup = writeJsonWithBackup(hooksPath, hooksJson);
|
|
32497
|
-
if (backup) backupPath = backup;
|
|
32498
|
-
}
|
|
32499
|
-
if (enabled.changed) {
|
|
32500
|
-
const backup = writeConfigWithBackup(configPath, enabled.text);
|
|
32501
|
-
if (backup) configBackupPath = backup;
|
|
32502
|
-
}
|
|
32503
|
-
}
|
|
32504
|
-
return {
|
|
32505
|
-
hooks: { userPromptSubmit, postToolUse, stop },
|
|
32506
|
-
feature: enabled.changed ? "enabled" : "unchanged",
|
|
32507
|
-
backupPath,
|
|
32508
|
-
configBackupPath
|
|
32509
|
-
};
|
|
32510
|
-
}
|
|
32511
|
-
function uninstallCodexHooks(opts) {
|
|
32512
|
-
const hooksPath = join10(opts.codexHome, "hooks.json");
|
|
32513
|
-
const hooksJson = readHooks(hooksPath);
|
|
32514
|
-
const userPromptSubmitRemoved = removeHook2(
|
|
32515
|
-
hooksJson,
|
|
32516
|
-
"UserPromptSubmit",
|
|
32517
|
-
hookCommand2(opts.nodePath, opts.cliScriptPath, "user-prompt-submit"),
|
|
32518
|
-
"user-prompt-submit"
|
|
32519
|
-
);
|
|
32520
|
-
const postToolUseRemoved = removeHook2(
|
|
32521
|
-
hooksJson,
|
|
32522
|
-
"PostToolUse",
|
|
32523
|
-
hookCommand2(opts.nodePath, opts.cliScriptPath, "post-tool-use"),
|
|
32524
|
-
"post-tool-use"
|
|
32525
|
-
);
|
|
32526
|
-
const stopRemoved = removeHook2(
|
|
32527
|
-
hooksJson,
|
|
32528
|
-
"Stop",
|
|
32529
|
-
hookCommand2(opts.nodePath, opts.cliScriptPath, "stop"),
|
|
32530
|
-
"stop"
|
|
32531
|
-
);
|
|
32532
|
-
let backupPath;
|
|
32533
|
-
if (opts.dryRun) {
|
|
32534
|
-
opts.logger.info(`[dry-run] would remove Caveat Codex hooks from ${hooksPath}`);
|
|
32535
|
-
} else if (userPromptSubmitRemoved || postToolUseRemoved || stopRemoved) {
|
|
32536
|
-
const backup = writeJsonWithBackup(hooksPath, hooksJson);
|
|
32537
|
-
if (backup) backupPath = backup;
|
|
32538
|
-
}
|
|
32539
|
-
return {
|
|
32540
|
-
hooks: {
|
|
32541
|
-
userPromptSubmit: userPromptSubmitRemoved ? "added" : "unchanged",
|
|
32542
|
-
postToolUse: postToolUseRemoved ? "added" : "unchanged",
|
|
32543
|
-
stop: stopRemoved ? "added" : "unchanged"
|
|
32544
|
-
},
|
|
32545
|
-
feature: "unchanged",
|
|
32546
|
-
backupPath
|
|
32547
|
-
};
|
|
32548
|
-
}
|
|
32549
|
-
function detectCodexHookInstallation(codexHome) {
|
|
32550
|
-
const hooksPath = join10(codexHome, "hooks.json");
|
|
32551
|
-
const hooksJson = readHooks(hooksPath);
|
|
32552
|
-
const hooks = {
|
|
32553
|
-
userPromptSubmit: hasCaveatHook(
|
|
32554
|
-
hooksJson,
|
|
32555
|
-
"UserPromptSubmit",
|
|
32556
|
-
eventCommandFragment("user-prompt-submit")
|
|
32557
|
-
),
|
|
32558
|
-
postToolUse: hasCaveatHook(hooksJson, "PostToolUse", eventCommandFragment("post-tool-use")),
|
|
32559
|
-
stop: hasCaveatHook(hooksJson, "Stop", eventCommandFragment("stop"))
|
|
32560
|
-
};
|
|
32561
|
-
const count = Object.values(hooks).filter(Boolean).length;
|
|
32562
|
-
return {
|
|
32563
|
-
installation: count === 0 ? "not-installed" : count === 3 ? "installed" : "partial",
|
|
32564
|
-
hooksPath,
|
|
32565
|
-
hooks
|
|
32566
|
-
};
|
|
32567
|
-
}
|
|
32568
|
-
|
|
32569
|
-
// src/commands/codexHookCmd.ts
|
|
32570
33139
|
var silentLogger2 = {
|
|
32571
33140
|
info: () => {
|
|
32572
33141
|
},
|
|
@@ -32606,31 +33175,44 @@ function buildContextSafely2() {
|
|
|
32606
33175
|
return null;
|
|
32607
33176
|
}
|
|
32608
33177
|
}
|
|
32609
|
-
function searchCaveatsFromTextSafely2(text) {
|
|
33178
|
+
function searchCaveatsFromTextSafely2(text, surface) {
|
|
32610
33179
|
if (!text) return [];
|
|
32611
33180
|
let db;
|
|
33181
|
+
let caveatHome;
|
|
33182
|
+
let hits;
|
|
32612
33183
|
try {
|
|
32613
33184
|
const ctx = buildContextSafely2();
|
|
32614
|
-
if (!ctx || !
|
|
33185
|
+
if (!ctx || !existsSync11(ctx.paths.dbPath)) return [];
|
|
33186
|
+
caveatHome = ctx.caveatHome;
|
|
32615
33187
|
db = openDb({ path: ctx.paths.dbPath });
|
|
32616
|
-
|
|
33188
|
+
hits = findCaveatsForPrompt(db, text, {
|
|
32617
33189
|
selfIdentity: defaultSelfIdentityTokens()
|
|
32618
33190
|
});
|
|
32619
|
-
if (hits.length > 0) {
|
|
32620
|
-
try {
|
|
32621
|
-
markHit(db, hits);
|
|
32622
|
-
} catch (err) {
|
|
32623
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
32624
|
-
process.stderr.write(`[caveat:codex-hook] markHit error: ${msg}
|
|
32625
|
-
`);
|
|
32626
|
-
}
|
|
32627
|
-
}
|
|
32628
|
-
return hits;
|
|
32629
33191
|
} catch (err) {
|
|
32630
33192
|
const msg = err instanceof Error ? err.message : String(err);
|
|
32631
33193
|
process.stderr.write(`[caveat:codex-hook] search error: ${msg}
|
|
32632
33194
|
`);
|
|
32633
33195
|
return [];
|
|
33196
|
+
}
|
|
33197
|
+
if (hits.length > 0) {
|
|
33198
|
+
try {
|
|
33199
|
+
markHit(db, hits);
|
|
33200
|
+
} catch (err) {
|
|
33201
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
33202
|
+
process.stderr.write(`[caveat:codex-hook] markHit error: ${msg}
|
|
33203
|
+
`);
|
|
33204
|
+
}
|
|
33205
|
+
} else {
|
|
33206
|
+
try {
|
|
33207
|
+
logHookQueryMiss({ caveatHome, agent: "codex", surface, query: text });
|
|
33208
|
+
} catch (err) {
|
|
33209
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
33210
|
+
process.stderr.write(`[caveat:codex-hook] query log error: ${msg}
|
|
33211
|
+
`);
|
|
33212
|
+
}
|
|
33213
|
+
}
|
|
33214
|
+
try {
|
|
33215
|
+
return hits;
|
|
32634
33216
|
} finally {
|
|
32635
33217
|
db?.close();
|
|
32636
33218
|
}
|
|
@@ -32680,10 +33262,10 @@ function processExitCodeFromText(text) {
|
|
|
32680
33262
|
return m ? Number(m[1]) : null;
|
|
32681
33263
|
}
|
|
32682
33264
|
function transcriptToolOutput(transcriptPath, toolUseId) {
|
|
32683
|
-
if (!transcriptPath || !toolUseId || !
|
|
33265
|
+
if (!transcriptPath || !toolUseId || !existsSync11(transcriptPath)) return null;
|
|
32684
33266
|
let raw = "";
|
|
32685
33267
|
try {
|
|
32686
|
-
raw =
|
|
33268
|
+
raw = readFileSync6(transcriptPath, "utf-8");
|
|
32687
33269
|
} catch {
|
|
32688
33270
|
return null;
|
|
32689
33271
|
}
|
|
@@ -32779,7 +33361,10 @@ function codexContextOutput(text, eventName = "UserPromptSubmit") {
|
|
|
32779
33361
|
function drainForSession2(sessionId) {
|
|
32780
33362
|
const ctx = buildContextSafely2();
|
|
32781
33363
|
if (!ctx) return [];
|
|
32782
|
-
return
|
|
33364
|
+
return [
|
|
33365
|
+
...drainPendingReminders(ctx.caveatHome, sessionId),
|
|
33366
|
+
...drainGlobalPendingReminders(ctx.caveatHome)
|
|
33367
|
+
];
|
|
32783
33368
|
}
|
|
32784
33369
|
function codexContextDedupeKey(text) {
|
|
32785
33370
|
if (text.startsWith(CODEX_STOP_REMINDER_PREFIX)) return "codex-stop-reminder";
|
|
@@ -32821,20 +33406,20 @@ function stopSignalKey2(signals, related) {
|
|
|
32821
33406
|
return createHash2("sha256").update(body).digest("hex");
|
|
32822
33407
|
}
|
|
32823
33408
|
function stopStatePath2(caveatHome, sessionId) {
|
|
32824
|
-
return
|
|
33409
|
+
return join13(caveatHome, CODEX_STOP_STATE_DIR, `${sanitizeCodexStateId(sessionId)}.txt`);
|
|
32825
33410
|
}
|
|
32826
33411
|
function wasStopReminderQueued2(caveatHome, sessionId, key) {
|
|
32827
33412
|
const path = stopStatePath2(caveatHome, sessionId);
|
|
32828
33413
|
try {
|
|
32829
|
-
return
|
|
33414
|
+
return readFileSync6(path, "utf-8") === key;
|
|
32830
33415
|
} catch {
|
|
32831
33416
|
return false;
|
|
32832
33417
|
}
|
|
32833
33418
|
}
|
|
32834
33419
|
function markStopReminderQueued2(caveatHome, sessionId, key) {
|
|
32835
33420
|
const path = stopStatePath2(caveatHome, sessionId);
|
|
32836
|
-
mkdirSync6(
|
|
32837
|
-
|
|
33421
|
+
mkdirSync6(join13(caveatHome, CODEX_STOP_STATE_DIR), { recursive: true });
|
|
33422
|
+
writeFileSync6(path, key, "utf-8");
|
|
32838
33423
|
}
|
|
32839
33424
|
function queueStopForSession2(sessionId, signals, related) {
|
|
32840
33425
|
const ctx = buildContextSafely2();
|
|
@@ -32875,7 +33460,7 @@ async function processCodexWorkerJob(job, opts = { waitForTranscript: true }) {
|
|
|
32875
33460
|
}
|
|
32876
33461
|
}
|
|
32877
33462
|
if (!knownError && job.allowSymptomOnly !== true) return;
|
|
32878
|
-
const hits = searchCaveatsFromTextSafely2(searchText);
|
|
33463
|
+
const hits = searchCaveatsFromTextSafely2(searchText, "tool_error");
|
|
32879
33464
|
if (hits.length === 0) return;
|
|
32880
33465
|
const ctx = buildContextSafely2();
|
|
32881
33466
|
if (!ctx) return;
|
|
@@ -32887,12 +33472,12 @@ async function processCodexWorkerJob(job, opts = { waitForTranscript: true }) {
|
|
|
32887
33472
|
async function runCodexWorker(workFile) {
|
|
32888
33473
|
let raw;
|
|
32889
33474
|
try {
|
|
32890
|
-
raw =
|
|
33475
|
+
raw = readFileSync6(workFile, "utf-8");
|
|
32891
33476
|
} catch {
|
|
32892
33477
|
process.exit(0);
|
|
32893
33478
|
}
|
|
32894
33479
|
try {
|
|
32895
|
-
|
|
33480
|
+
unlinkSync(workFile);
|
|
32896
33481
|
} catch {
|
|
32897
33482
|
}
|
|
32898
33483
|
let job;
|
|
@@ -32904,8 +33489,8 @@ async function runCodexWorker(workFile) {
|
|
|
32904
33489
|
await processCodexWorkerJob(job);
|
|
32905
33490
|
process.exit(0);
|
|
32906
33491
|
}
|
|
32907
|
-
function runDiagnostics(codexHome = process.env.CODEX_HOME ??
|
|
32908
|
-
const features =
|
|
33492
|
+
function runDiagnostics(codexHome = process.env.CODEX_HOME ?? join13(homedir3(), ".codex")) {
|
|
33493
|
+
const features = spawnSync5("codex", ["features", "list"], {
|
|
32909
33494
|
encoding: "utf-8",
|
|
32910
33495
|
maxBuffer: 1024 * 1024
|
|
32911
33496
|
});
|
|
@@ -32950,7 +33535,7 @@ async function runCodexHook(name, arg) {
|
|
|
32950
33535
|
if (name === "user-prompt-submit") {
|
|
32951
33536
|
const contexts = sessionId ? drainForSession2(sessionId) : [];
|
|
32952
33537
|
const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
|
|
32953
|
-
const hits = searchCaveatsFromTextSafely2(prompt);
|
|
33538
|
+
const hits = searchCaveatsFromTextSafely2(prompt, "user_prompt");
|
|
32954
33539
|
if (hits.length > 0) {
|
|
32955
33540
|
contexts.push(userPromptSubmitReminderText(hits));
|
|
32956
33541
|
}
|
|
@@ -32969,7 +33554,23 @@ async function runCodexHook(name, arg) {
|
|
|
32969
33554
|
if (name === "stop") {
|
|
32970
33555
|
try {
|
|
32971
33556
|
const ctx = buildContextSafely2();
|
|
32972
|
-
if (ctx)
|
|
33557
|
+
if (ctx) {
|
|
33558
|
+
try {
|
|
33559
|
+
maybeSweepPendingDirs(ctx.caveatHome);
|
|
33560
|
+
} catch (err) {
|
|
33561
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
33562
|
+
process.stderr.write(`[caveat:codex-hook] pending sweep error: ${msg}
|
|
33563
|
+
`);
|
|
33564
|
+
}
|
|
33565
|
+
maybeTriggerAutoReindex(ctx);
|
|
33566
|
+
try {
|
|
33567
|
+
maybeTriggerAutoSync(ctx);
|
|
33568
|
+
} catch (err) {
|
|
33569
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
33570
|
+
process.stderr.write(`[caveat:codex-hook] auto sync trigger error: ${msg}
|
|
33571
|
+
`);
|
|
33572
|
+
}
|
|
33573
|
+
}
|
|
32973
33574
|
} catch (err) {
|
|
32974
33575
|
const msg = err instanceof Error ? err.message : String(err);
|
|
32975
33576
|
process.stderr.write(`[caveat:codex-hook] auto reindex trigger error: ${msg}
|
|
@@ -32979,7 +33580,7 @@ async function runCodexHook(name, arg) {
|
|
|
32979
33580
|
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
|
|
32980
33581
|
const signals = transcriptPath ? loadSignalsSafely2(transcriptPath) : null;
|
|
32981
33582
|
if (!signals || !hasAnyStruggleSignal(signals)) process.exit(0);
|
|
32982
|
-
const related = searchCaveatsFromTextSafely2(struggleSearchText(signals));
|
|
33583
|
+
const related = searchCaveatsFromTextSafely2(struggleSearchText(signals), "stop");
|
|
32983
33584
|
if (sessionId) queueStopForSession2(sessionId, signals, related);
|
|
32984
33585
|
process.exit(0);
|
|
32985
33586
|
}
|
|
@@ -32989,9 +33590,9 @@ async function runCodexHook(name, arg) {
|
|
|
32989
33590
|
}
|
|
32990
33591
|
|
|
32991
33592
|
// src/commands/pull.ts
|
|
32992
|
-
import { existsSync as
|
|
33593
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
32993
33594
|
async function runPull(ctx) {
|
|
32994
|
-
const hasCommunityDir =
|
|
33595
|
+
const hasCommunityDir = existsSync12(ctx.paths.communityDir);
|
|
32995
33596
|
if (!hasCommunityDir) {
|
|
32996
33597
|
ctx.logger.info(
|
|
32997
33598
|
"no community repos yet \u2014 add one with `caveat community add <github-url>`."
|
|
@@ -33007,8 +33608,12 @@ async function runPull(ctx) {
|
|
|
33007
33608
|
}
|
|
33008
33609
|
const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
|
|
33009
33610
|
try {
|
|
33010
|
-
|
|
33011
|
-
const
|
|
33611
|
+
const keyProvider = createKeyserverKeyProvider({ caveatHome: ctx.caveatHome });
|
|
33612
|
+
const failures = await prewarmSealedKeys({ paths: ctx.paths, keyProvider });
|
|
33613
|
+
for (const failure of failures) {
|
|
33614
|
+
ctx.logger.warn(`${failure.source}: sealed key prewarm failed: ${errorMessage5(failure.error)}`);
|
|
33615
|
+
}
|
|
33616
|
+
const result = reindexAllSources({ db, paths: ctx.paths, logger: ctx.logger, keyProvider });
|
|
33012
33617
|
for (const [source, scan] of Object.entries(result.perSource)) {
|
|
33013
33618
|
ctx.logger.info(`${source}: +${scan.added}`);
|
|
33014
33619
|
}
|
|
@@ -33017,51 +33622,19 @@ async function runPull(ctx) {
|
|
|
33017
33622
|
db.close();
|
|
33018
33623
|
}
|
|
33019
33624
|
}
|
|
33020
|
-
|
|
33021
|
-
|
|
33022
|
-
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
33023
|
-
import { readSync } from "node:fs";
|
|
33024
|
-
function runGh(args) {
|
|
33025
|
-
const result = spawnSync4("gh", args, { encoding: "utf-8" });
|
|
33026
|
-
return { status: result.status, stdout: typeof result.stdout === "string" ? result.stdout : "", stderr: typeof result.stderr === "string" ? result.stderr : "" };
|
|
33027
|
-
}
|
|
33028
|
-
function commandError(result, fallback) {
|
|
33029
|
-
return new Error((result.stderr || result.stdout || fallback).trim());
|
|
33030
|
-
}
|
|
33031
|
-
function askOnce(question) {
|
|
33032
|
-
process.stdout.write(`${question} `);
|
|
33033
|
-
const buf = Buffer.alloc(256);
|
|
33034
|
-
const bytes = readSync(0, buf, 0, buf.length, null);
|
|
33035
|
-
return /^(?:y|yes)$/i.test(buf.toString("utf-8", 0, bytes).trim());
|
|
33625
|
+
function errorMessage5(err) {
|
|
33626
|
+
return err instanceof Error ? err.message : String(err);
|
|
33036
33627
|
}
|
|
33037
33628
|
|
|
33038
33629
|
// src/commands/sync.ts
|
|
33039
33630
|
async function defaultPrivateRepoUrl(opts, deps) {
|
|
33040
|
-
|
|
33041
|
-
|
|
33042
|
-
|
|
33043
|
-
|
|
33044
|
-
|
|
33045
|
-
|
|
33046
|
-
|
|
33047
|
-
if (user.status !== 0 || !user.stdout.trim()) {
|
|
33048
|
-
throw new Error("gh is not authenticated; run `gh auth login`, then retry `caveat sync --init`");
|
|
33049
|
-
}
|
|
33050
|
-
const login = user.stdout.trim();
|
|
33051
|
-
const name = `${login}/Caveat-Private`;
|
|
33052
|
-
const view = deps.ghRunner(["repo", "view", name]);
|
|
33053
|
-
if (view.status !== 0) {
|
|
33054
|
-
const question = `create private repo github.com/${name}? [y/N]`;
|
|
33055
|
-
if (!opts.yes) {
|
|
33056
|
-
if (!deps.isTty()) {
|
|
33057
|
-
throw new Error(`${question}; rerun with --yes to approve non-interactively`);
|
|
33058
|
-
}
|
|
33059
|
-
if (!deps.confirm(question)) throw new Error("private repository creation cancelled");
|
|
33060
|
-
}
|
|
33061
|
-
const create = deps.ghRunner(["repo", "create", "Caveat-Private", "--private"]);
|
|
33062
|
-
if (create.status !== 0) throw commandError(create, "gh repo create failed");
|
|
33063
|
-
}
|
|
33064
|
-
return `https://github.com/${name}.git`;
|
|
33631
|
+
return defaultGitHubRepoUrl({
|
|
33632
|
+
repositoryName: "Caveat-Private",
|
|
33633
|
+
visibility: "private",
|
|
33634
|
+
yes: opts.yes,
|
|
33635
|
+
retryCommand: "caveat sync --init <url>",
|
|
33636
|
+
...deps
|
|
33637
|
+
});
|
|
33065
33638
|
}
|
|
33066
33639
|
async function runSync(ctx, opts, dependencies = {}) {
|
|
33067
33640
|
try {
|
|
@@ -33100,6 +33673,10 @@ async function runSync(ctx, opts, dependencies = {}) {
|
|
|
33100
33673
|
} else {
|
|
33101
33674
|
const rebased = result.pulled ? ", rebased onto remote" : "";
|
|
33102
33675
|
ctx.logger.info(`synced ${result.branch} \u2192 origin (${result.changedFiles} local change(s)${rebased})`);
|
|
33676
|
+
try {
|
|
33677
|
+
resetAutoSyncFailureState(ctx.caveatHome);
|
|
33678
|
+
} catch {
|
|
33679
|
+
}
|
|
33103
33680
|
}
|
|
33104
33681
|
} catch (err) {
|
|
33105
33682
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -33108,60 +33685,11 @@ async function runSync(ctx, opts, dependencies = {}) {
|
|
|
33108
33685
|
}
|
|
33109
33686
|
}
|
|
33110
33687
|
|
|
33111
|
-
// src/commands/publish.ts
|
|
33112
|
-
var HTTPS_TARGET_RE = /^https:\/\/github\.com\/[^/]+\/[^/]+?(?:\.git)?\/?$/;
|
|
33113
|
-
var SCP_TARGET_RE = /^git@github\.com:[^/]+\/[^/]+?(?:\.git)?$/;
|
|
33114
|
-
function validatePublishTarget(url2) {
|
|
33115
|
-
if (!HTTPS_TARGET_RE.test(url2) && !SCP_TARGET_RE.test(url2)) throw new Error("invalid publish target: use https://github.com/<org>/<repo>(.git) or git@github.com:<org>/<repo>(.git)");
|
|
33116
|
-
return url2;
|
|
33117
|
-
}
|
|
33118
|
-
async function defaultPublicRepoUrl(opts, deps) {
|
|
33119
|
-
const version2 = deps.ghRunner(["--version"]);
|
|
33120
|
-
if (version2.status !== 0) throw new Error("gh is not available. create a public repository named Caveat-Public on GitHub, then run:\n caveat publish --init <url>\n # or install gh and run caveat publish");
|
|
33121
|
-
const user = deps.ghRunner(["api", "user", "-q", ".login"]);
|
|
33122
|
-
if (user.status !== 0 || !user.stdout.trim()) throw new Error("gh is not authenticated; run `gh auth login`, then retry `caveat publish`");
|
|
33123
|
-
const login = user.stdout.trim();
|
|
33124
|
-
const name = `${login}/Caveat-Public`;
|
|
33125
|
-
const view = deps.ghRunner(["repo", "view", name]);
|
|
33126
|
-
if (view.status !== 0) {
|
|
33127
|
-
const question = `create public repo github.com/${name}? [y/N]`;
|
|
33128
|
-
if (!opts.yes) {
|
|
33129
|
-
if (!deps.isTty()) throw new Error(`${question}; rerun with --yes to approve non-interactively`);
|
|
33130
|
-
if (!deps.confirm(question)) throw new Error("public repository creation cancelled");
|
|
33131
|
-
}
|
|
33132
|
-
const create = deps.ghRunner(["repo", "create", "Caveat-Public", "--public"]);
|
|
33133
|
-
if (create.status !== 0) throw commandError(create, "gh repo create failed");
|
|
33134
|
-
}
|
|
33135
|
-
return `https://github.com/${name}.git`;
|
|
33136
|
-
}
|
|
33137
|
-
async function runPublish(ctx, opts, dependencies = {}) {
|
|
33138
|
-
try {
|
|
33139
|
-
const ghRunner = dependencies.ghRunner ?? runGh;
|
|
33140
|
-
const isTty = dependencies.isTty ?? (() => Boolean(process.stdin.isTTY));
|
|
33141
|
-
const confirm = dependencies.confirm ?? askOnce;
|
|
33142
|
-
let target = ctx.config.publishTarget;
|
|
33143
|
-
if (opts.init !== void 0) {
|
|
33144
|
-
target = validatePublishTarget(opts.init);
|
|
33145
|
-
writeUserConfigPatch(ctx.userConfigPath, { publishTarget: target });
|
|
33146
|
-
}
|
|
33147
|
-
if (!target) {
|
|
33148
|
-
target = await defaultPublicRepoUrl(opts, { ghRunner, isTty, confirm });
|
|
33149
|
-
writeUserConfigPatch(ctx.userConfigPath, { publishTarget: target });
|
|
33150
|
-
}
|
|
33151
|
-
const result = await publishOwn({ paths: ctx.paths, config: { ...ctx.config, publishTarget: target }, logger: ctx.logger, confirmImpl: confirm, isTty, dryRun: opts.dryRun, yes: opts.yes });
|
|
33152
|
-
if (result.dryRun) ctx.logger.info(`[dry-run] ${result.fileCount} public entry(ies) ready to publish`);
|
|
33153
|
-
else if (result.changed) ctx.logger.info(`published ${result.fileCount} public entry(ies)`);
|
|
33154
|
-
} catch (err) {
|
|
33155
|
-
ctx.logger.error(err instanceof Error ? err.message : String(err));
|
|
33156
|
-
process.exitCode = 1;
|
|
33157
|
-
}
|
|
33158
|
-
}
|
|
33159
|
-
|
|
33160
33688
|
// src/commands/codexSidecar.ts
|
|
33161
|
-
import { spawnSync as
|
|
33162
|
-
import { mkdirSync as mkdirSync7, mkdtempSync as
|
|
33163
|
-
import { tmpdir as
|
|
33164
|
-
import { dirname as
|
|
33689
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
33690
|
+
import { closeSync, constants, fstatSync, lstatSync as lstatSync2, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync3, openSync, readSync as readSync2, realpathSync as realpathSync3, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "node:fs";
|
|
33691
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
33692
|
+
import { basename as basename2, dirname as dirname7, join as join14 } from "node:path";
|
|
33165
33693
|
import { cwd, exit } from "node:process";
|
|
33166
33694
|
function runCodexSidecarDiagnostics(logger, opts) {
|
|
33167
33695
|
const plan = buildCodexSidecarDiagnosticsCommand({
|
|
@@ -33193,8 +33721,8 @@ function runCodexSidecarWithCaveats(ctx, workflow, prompt, opts) {
|
|
|
33193
33721
|
process.stdout.write(JSON.stringify({ status: "skipped", decision }, null, 2) + "\n");
|
|
33194
33722
|
exit(0);
|
|
33195
33723
|
}
|
|
33196
|
-
const contextDir =
|
|
33197
|
-
const contextFile =
|
|
33724
|
+
const contextDir = mkdtempSync3(join14(tmpdir4(), "caveat-sidecar-context-"));
|
|
33725
|
+
const contextFile = join14(contextDir, "context.json");
|
|
33198
33726
|
let status = 1;
|
|
33199
33727
|
try {
|
|
33200
33728
|
const blocks = collectCaveatContextBlocks(ctx, {
|
|
@@ -33203,7 +33731,8 @@ function runCodexSidecarWithCaveats(ctx, workflow, prompt, opts) {
|
|
|
33203
33731
|
source: opts.source,
|
|
33204
33732
|
visibility: opts.visibility
|
|
33205
33733
|
});
|
|
33206
|
-
|
|
33734
|
+
const additionalBlocks = opts.additionalContextFile ? readHookSignalAdditionalContextFile(opts.additionalContextFile) : [];
|
|
33735
|
+
writeFileSync7(contextFile, JSON.stringify({ context: [...blocks, ...additionalBlocks] }, null, 2) + "\n", "utf-8");
|
|
33207
33736
|
const plan = buildCodexSidecarRunCommand({
|
|
33208
33737
|
workflow,
|
|
33209
33738
|
projectRoot: opts.project ?? cwd(),
|
|
@@ -33214,10 +33743,94 @@ function runCodexSidecarWithCaveats(ctx, workflow, prompt, opts) {
|
|
|
33214
33743
|
});
|
|
33215
33744
|
status = executePlan(ctx.logger, plan.command, plan.args, { saveResult: opts.saveResult });
|
|
33216
33745
|
} finally {
|
|
33217
|
-
|
|
33746
|
+
rmSync3(contextDir, { recursive: true, force: true });
|
|
33218
33747
|
}
|
|
33219
33748
|
exit(status);
|
|
33220
33749
|
}
|
|
33750
|
+
var MAX_ADDITIONAL_CONTEXT_BYTES = 4096;
|
|
33751
|
+
function readHookSignalAdditionalContextFile(path, testProbe) {
|
|
33752
|
+
const platform = testProbe?.platform ?? process.platform;
|
|
33753
|
+
assertWindowsPrivateTempContainer(path, platform);
|
|
33754
|
+
const before = lstatSync2(path);
|
|
33755
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
33756
|
+
assertPrivateRegular(before, uid, platform);
|
|
33757
|
+
testProbe?.afterLstat?.();
|
|
33758
|
+
const fd = openSync(path, constants.O_RDONLY | constants.O_NONBLOCK | (constants.O_NOFOLLOW ?? 0));
|
|
33759
|
+
let raw;
|
|
33760
|
+
try {
|
|
33761
|
+
const opened = fstatSync(fd);
|
|
33762
|
+
assertPrivateRegular(opened, uid, platform);
|
|
33763
|
+
if (opened.dev !== before.dev || opened.ino !== before.ino) throw new Error("additional context file changed during open");
|
|
33764
|
+
const bytes = Buffer.alloc(MAX_ADDITIONAL_CONTEXT_BYTES + 1);
|
|
33765
|
+
let count = 0;
|
|
33766
|
+
while (count < bytes.length) {
|
|
33767
|
+
const read = readSync2(fd, bytes, count, bytes.length - count, count);
|
|
33768
|
+
if (read === 0) break;
|
|
33769
|
+
count += read;
|
|
33770
|
+
}
|
|
33771
|
+
if (count > MAX_ADDITIONAL_CONTEXT_BYTES) throw new Error(`additional context file exceeds ${MAX_ADDITIONAL_CONTEXT_BYTES} bytes`);
|
|
33772
|
+
const after = fstatSync(fd);
|
|
33773
|
+
assertPrivateRegular(after, uid, platform);
|
|
33774
|
+
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");
|
|
33775
|
+
raw = bytes.subarray(0, count).toString("utf-8");
|
|
33776
|
+
} finally {
|
|
33777
|
+
closeSync(fd);
|
|
33778
|
+
}
|
|
33779
|
+
const parsed = JSON.parse(raw);
|
|
33780
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.context) || parsed.context.length !== 1) {
|
|
33781
|
+
throw new Error("additional context file must contain exactly one context block");
|
|
33782
|
+
}
|
|
33783
|
+
const block = parsed.context[0];
|
|
33784
|
+
if (!isHookSignalBlock(block)) {
|
|
33785
|
+
throw new Error("additional context file contains an invalid hook signal block");
|
|
33786
|
+
}
|
|
33787
|
+
return [block];
|
|
33788
|
+
}
|
|
33789
|
+
function assertPrivateRegular(stat, uid, platform) {
|
|
33790
|
+
const privateOwner = platform === "win32" || (stat.mode & 63) === 0 && (uid === void 0 || stat.uid === uid);
|
|
33791
|
+
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");
|
|
33792
|
+
}
|
|
33793
|
+
function assertWindowsPrivateTempContainer(path, platform) {
|
|
33794
|
+
if (platform !== "win32") return;
|
|
33795
|
+
const parent = dirname7(path);
|
|
33796
|
+
const parentStat = lstatSync2(parent);
|
|
33797
|
+
const resolvedParent = realpathSync3(parent);
|
|
33798
|
+
const resolvedTemp = realpathSync3(tmpdir4());
|
|
33799
|
+
if (parentStat.isSymbolicLink() || !parentStat.isDirectory() || dirname7(resolvedParent) !== resolvedTemp || !basename2(resolvedParent).startsWith("caveat-")) {
|
|
33800
|
+
throw new Error("additional context file must be inside a reserved per-user Caveat temporary directory");
|
|
33801
|
+
}
|
|
33802
|
+
}
|
|
33803
|
+
function isRecord(value) {
|
|
33804
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
33805
|
+
}
|
|
33806
|
+
function isHookSignalBlock(value) {
|
|
33807
|
+
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;
|
|
33808
|
+
const data = value.data;
|
|
33809
|
+
if (data.type === "tool-error") {
|
|
33810
|
+
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;
|
|
33811
|
+
const canonical2 = buildHookSignalFromData(data);
|
|
33812
|
+
return canonical2 !== null && JSON.stringify(value) === JSON.stringify(canonical2);
|
|
33813
|
+
}
|
|
33814
|
+
if (!hasExactKeys(data, ["type", "tool_failure_count", "reedited_file_count", "web_search_count", "web_fetch_count", "bash_retry_count", "duration_minutes"])) return false;
|
|
33815
|
+
const canonical = buildHookSignalFromData(data);
|
|
33816
|
+
return canonical !== null && JSON.stringify(value) === JSON.stringify(canonical);
|
|
33817
|
+
}
|
|
33818
|
+
function hasExactKeys(value, keys) {
|
|
33819
|
+
const actual = Object.keys(value).sort();
|
|
33820
|
+
return actual.length === keys.length && actual.every((key, index) => key === keys.slice().sort()[index]);
|
|
33821
|
+
}
|
|
33822
|
+
function buildHookSignalFromData(data) {
|
|
33823
|
+
if (data.type === "tool-error") {
|
|
33824
|
+
const validTools = ["bash", "edit", "write", "read", "glob", "grep", "web-search", "web-fetch", "notebook-edit", "other"];
|
|
33825
|
+
if (!validTools.includes(String(data.tool))) return null;
|
|
33826
|
+
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" };
|
|
33827
|
+
return buildHookSignalSidecarContextBlock({ type: "tool-error", toolName: toolNames[String(data.tool)], failureKind: data.failure_kind });
|
|
33828
|
+
}
|
|
33829
|
+
if (data.type !== "stop") return null;
|
|
33830
|
+
const keys = ["tool_failure_count", "reedited_file_count", "web_search_count", "web_fetch_count", "bash_retry_count", "duration_minutes"];
|
|
33831
|
+
if (!keys.every((key) => typeof data[key] === "number" && Number.isInteger(data[key]) && data[key] >= 0 && data[key] <= 1e4)) return null;
|
|
33832
|
+
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 });
|
|
33833
|
+
}
|
|
33221
33834
|
function runCodexSidecarWorkSmoke(ctx, prompt, opts) {
|
|
33222
33835
|
const plan = buildCodexSidecarRunCommand({
|
|
33223
33836
|
workflow: "work",
|
|
@@ -33258,7 +33871,7 @@ function defaultPreset(workflow) {
|
|
|
33258
33871
|
}
|
|
33259
33872
|
function executePlan(logger, command, args, options = {}) {
|
|
33260
33873
|
logger.info(`[codex-sidecar] ${command} ${args.map(shellDisplayQuote).join(" ")}`);
|
|
33261
|
-
const result =
|
|
33874
|
+
const result = spawnSync6(command, args, { encoding: "utf-8", stdio: "pipe" });
|
|
33262
33875
|
if (result.stdout) process.stdout.write(result.stdout);
|
|
33263
33876
|
if (result.stderr) process.stderr.write(result.stderr);
|
|
33264
33877
|
if (options.saveResult && result.stdout) {
|
|
@@ -33272,8 +33885,8 @@ function executePlan(logger, command, args, options = {}) {
|
|
|
33272
33885
|
}
|
|
33273
33886
|
function saveStructuredResult(path, stdout) {
|
|
33274
33887
|
const parsed = JSON.parse(stdout);
|
|
33275
|
-
mkdirSync7(
|
|
33276
|
-
|
|
33888
|
+
mkdirSync7(dirname7(path), { recursive: true });
|
|
33889
|
+
writeFileSync7(path, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
33277
33890
|
}
|
|
33278
33891
|
function shellDisplayQuote(value) {
|
|
33279
33892
|
return /[\s"'$`]/.test(value) ? JSON.stringify(value) : value;
|
|
@@ -33361,10 +33974,13 @@ function runCommunityRemove(ctx, handle, opts) {
|
|
|
33361
33974
|
|
|
33362
33975
|
// src/index.ts
|
|
33363
33976
|
var program = new Command();
|
|
33977
|
+
function collectRepeatable(value, previous) {
|
|
33978
|
+
return [...previous, value];
|
|
33979
|
+
}
|
|
33364
33980
|
program.name("caveat").description("External spec gotcha knowledge base CLI").version(CAVEAT_VERSION);
|
|
33365
33981
|
program.command("init").description(
|
|
33366
33982
|
"Initialize ~/.caveatrc.json, ~/.caveat/, and register Claude Code integration. Add knowledge sources later with `caveat community add <github-url>`."
|
|
33367
|
-
).option("--skip-claude", "skip Claude Code MCP + hook registration", false).option("--dry-run", "show planned changes without writing", false).option(
|
|
33983
|
+
).option("--skip-claude", "skip Claude Code MCP + hook registration", false).option("--sync [url]", "initialize private sync (optionally with its URL)").option("--publish-target [url]", "configure the sealed public mirror target").option("--yes", "approve creating conventional GitHub repositories", false).option("--skip-codex-hook", "skip Codex hook installation", false).option("--dry-run", "show planned changes without writing", false).option(
|
|
33368
33984
|
"--pending-stale-days <days>",
|
|
33369
33985
|
"sweep `<caveatHome>/pending/<sessionId>/` whose newest entry is older than this (default 7)",
|
|
33370
33986
|
(raw) => {
|
|
@@ -33380,7 +33996,11 @@ program.command("init").description(
|
|
|
33380
33996
|
await runInit(ctx, {
|
|
33381
33997
|
skipClaude: opts.skipClaude,
|
|
33382
33998
|
dryRun: opts.dryRun,
|
|
33383
|
-
pendingStaleDays: opts.pendingStaleDays
|
|
33999
|
+
pendingStaleDays: opts.pendingStaleDays,
|
|
34000
|
+
sync: opts.sync,
|
|
34001
|
+
publishTarget: opts.publishTarget,
|
|
34002
|
+
yes: opts.yes,
|
|
34003
|
+
skipCodexHook: opts.skipCodexHook
|
|
33384
34004
|
});
|
|
33385
34005
|
}
|
|
33386
34006
|
);
|
|
@@ -33388,9 +34008,9 @@ program.command("uninstall").description("Remove Claude Code MCP server and hook
|
|
|
33388
34008
|
const ctx = buildContext(stdoutLogger);
|
|
33389
34009
|
runUninstall(ctx, { dryRun: opts.dryRun });
|
|
33390
34010
|
});
|
|
33391
|
-
program.command("index").description("Index knowledge repo entries into SQLite").option("--full", "full rebuild (DELETE all then rescan)", false).action((opts) => {
|
|
34011
|
+
program.command("index").description("Index knowledge repo entries into SQLite").option("--full", "full rebuild (DELETE all then rescan)", false).action(async (opts) => {
|
|
33392
34012
|
const ctx = buildContext(stdoutLogger);
|
|
33393
|
-
runIndex(ctx, { full: opts.full });
|
|
34013
|
+
await runIndex(ctx, { full: opts.full });
|
|
33394
34014
|
});
|
|
33395
34015
|
program.command("search").description("Search caveats via FTS").argument("<query>", "FTS query (3+ chars for trigram)").option("--source <source>", "own | community | all").option("--tag <tag...>", "filter by tag (repeatable)").option("--confidence <confidence...>", "filter by confidence (repeatable)").action(
|
|
33396
34016
|
(query, opts) => {
|
|
@@ -33432,7 +34052,7 @@ program.command("sync").description("Sync own Caveat entries to a private git re
|
|
|
33432
34052
|
const ctx = buildContext(stdoutLogger);
|
|
33433
34053
|
await runSync(ctx, opts);
|
|
33434
34054
|
});
|
|
33435
|
-
program.command("publish").description("Mirror own public Caveat entries to a public git repository").option("--init <url>", "set the public mirror target").option("--dry-run", "show public mirror changes without committing or pushing", false).option("--yes", "approve creating a default repository or publishing changes", false).action(async (opts) => {
|
|
34055
|
+
program.command("publish").description("Mirror own public Caveat entries to a public git repository").option("--init <url>", "set the public mirror target").option("--dry-run", "show public mirror changes without committing or pushing", false).option("--yes", "approve creating a default repository or publishing changes", false).option("--allow <digest>", "allow a scan finding by its sha256 digest (repeatable)", collectRepeatable, []).option("--save", "persist --allow digests to .caveat-publish-allow.json in the own repo", false).action(async (opts) => {
|
|
33436
34056
|
const ctx = buildContext(stdoutLogger);
|
|
33437
34057
|
await runPublish(ctx, opts);
|
|
33438
34058
|
});
|
|
@@ -33443,7 +34063,7 @@ program.command("mcp-server").description("Run the MCP stdio server (registered
|
|
|
33443
34063
|
await runMcpServer();
|
|
33444
34064
|
});
|
|
33445
34065
|
program.command("hook <name> [arg]").description(
|
|
33446
|
-
"Run a Claude Code hook. name: user-prompt-submit | post-tool-use | stop | worker | reindex"
|
|
34066
|
+
"Run a Claude Code hook. name: user-prompt-submit | post-tool-use | stop | worker | reindex | autosync"
|
|
33447
34067
|
).action(async (name, arg) => {
|
|
33448
34068
|
await runHook(name, arg);
|
|
33449
34069
|
});
|
|
@@ -33465,6 +34085,11 @@ codexHook.command("install").description("Install Caveat hooks into ~/.codex/hoo
|
|
|
33465
34085
|
stdoutLogger.info(`PostToolUse hook: ${result.hooks.postToolUse}`);
|
|
33466
34086
|
stdoutLogger.info(`Stop hook: ${result.hooks.stop}`);
|
|
33467
34087
|
stdoutLogger.info(`codex_hooks feature: ${result.feature}`);
|
|
34088
|
+
if (result.feature === "blocked") {
|
|
34089
|
+
stdoutLogger.warn(
|
|
34090
|
+
`${result.blockedReason}; preserving explicit consent. Set \`codex_hooks = true\` in ${opts.codexHome}/config.toml, then rerun \`caveat codex-hook install\`.`
|
|
34091
|
+
);
|
|
34092
|
+
}
|
|
33468
34093
|
if (result.backupPath) stdoutLogger.info(`hooks.json backed up: ${result.backupPath}`);
|
|
33469
34094
|
if (result.configBackupPath) {
|
|
33470
34095
|
stdoutLogger.info(`config.toml backed up: ${result.configBackupPath}`);
|
|
@@ -33510,7 +34135,7 @@ codexSidecar.command("diagnostics").description("Run codex-sidecar diagnostics f
|
|
|
33510
34135
|
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) => {
|
|
33511
34136
|
runCodexSidecarSmoke(stdoutLogger, opts);
|
|
33512
34137
|
});
|
|
33513
|
-
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").action(
|
|
34138
|
+
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(
|
|
33514
34139
|
(workflow, prompt, opts) => {
|
|
33515
34140
|
const normalized = workflow === "risk" ? "risk-check" : workflow;
|
|
33516
34141
|
if (normalized !== "review" && normalized !== "explore" && normalized !== "opinion" && normalized !== "risk-check") {
|
|
@@ -33527,7 +34152,7 @@ codexSidecar.command("work-smoke [prompt]").description("Run codex_work in an is
|
|
|
33527
34152
|
runCodexSidecarWorkSmoke(ctx, prompt, opts);
|
|
33528
34153
|
}
|
|
33529
34154
|
);
|
|
33530
|
-
var community = program.command("community").description("Manage community caveat repos (shallow clones under <
|
|
34155
|
+
var community = program.command("community").description("Manage community caveat repos (shallow clones under <caveatHome>/community/)");
|
|
33531
34156
|
community.command("add <url>").description("Shallow-clone a GitHub caveat repo into community/<handle>/").action(async (url2) => {
|
|
33532
34157
|
const ctx = buildContext(stdoutLogger);
|
|
33533
34158
|
await runCommunityAdd(ctx, url2);
|