caveat-cli 0.14.10 → 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-ECC5LJZF.js → chunk-ZNAFNCPW.js} +2284 -378
- package/dist/chunk-ZNAFNCPW.js.map +1 -0
- package/dist/index.js +1444 -581
- package/dist/index.js.map +1 -1
- package/dist/{server-YQ4DJ2H7.js → server-SZAVLE56.js} +2 -2
- package/package.json +12 -12
- package/dist/chunk-ECC5LJZF.js.map +0 -1
- /package/dist/{server-YQ4DJ2H7.js.map → server-SZAVLE56.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -2,47 +2,68 @@
|
|
|
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,
|
|
7
|
+
KNOWLEDGE_GITIGNORE,
|
|
8
|
+
PublishScanError,
|
|
9
|
+
SyncError,
|
|
5
10
|
__commonJS,
|
|
6
11
|
__export,
|
|
7
12
|
__toESM,
|
|
13
|
+
acquireReindexLock,
|
|
8
14
|
appendPendingReminder,
|
|
9
15
|
buildCodexSidecarDiagnosticsCommand,
|
|
10
16
|
buildCodexSidecarReadOnlySmokeCommand,
|
|
11
17
|
buildCodexSidecarRunCommand,
|
|
18
|
+
buildHookSignalSidecarContextBlock,
|
|
12
19
|
caveatEntriesToSidecarContextBlocks,
|
|
13
20
|
cleanupStalePendingDirs,
|
|
14
21
|
communityAdd,
|
|
15
22
|
communityList,
|
|
16
23
|
communityPull,
|
|
17
24
|
communityRemove,
|
|
25
|
+
computeEntriesDigest,
|
|
26
|
+
createKeyserverKeyProvider,
|
|
18
27
|
decideCodexSidecarExecution,
|
|
19
28
|
defaultSelfIdentityTokens,
|
|
29
|
+
drainGlobalPendingReminders,
|
|
20
30
|
drainPendingReminders,
|
|
21
31
|
ensureUserConfig,
|
|
22
32
|
findCaveatHome,
|
|
23
33
|
findCaveatsForPrompt,
|
|
24
34
|
get,
|
|
25
35
|
hasAnyStruggleSignal,
|
|
36
|
+
initOwnSync,
|
|
26
37
|
listRecent,
|
|
27
38
|
listStale,
|
|
28
39
|
loadConfig,
|
|
40
|
+
logHookQueryMiss,
|
|
29
41
|
markHit,
|
|
30
42
|
maybeSweepPendingDirs,
|
|
31
43
|
openDb,
|
|
44
|
+
prewarmSealedKeys,
|
|
45
|
+
publishOwn,
|
|
32
46
|
readCodexSessionSignals,
|
|
47
|
+
readDigestMarker,
|
|
33
48
|
readSessionSignals,
|
|
34
49
|
rebuildAll,
|
|
35
50
|
recordEntry,
|
|
51
|
+
reindexAllSources,
|
|
52
|
+
releaseReindexLock,
|
|
53
|
+
resetAutoSyncFailureState,
|
|
36
54
|
resolvePaths,
|
|
37
|
-
|
|
55
|
+
runAutoSync,
|
|
38
56
|
search,
|
|
39
57
|
stderrLogger,
|
|
40
58
|
stopReminderText,
|
|
41
59
|
struggleSearchText,
|
|
60
|
+
syncOwn,
|
|
42
61
|
toolErrorReminderText,
|
|
43
62
|
updateEntry,
|
|
44
|
-
userPromptSubmitReminderText
|
|
45
|
-
|
|
63
|
+
userPromptSubmitReminderText,
|
|
64
|
+
writeDigestMarker,
|
|
65
|
+
writeUserConfigPatch
|
|
66
|
+
} from "./chunk-ZNAFNCPW.js";
|
|
46
67
|
|
|
47
68
|
// ../../node_modules/.pnpm/ajv@8.18.0/node_modules/ajv/dist/compile/codegen/code.js
|
|
48
69
|
var require_code = __commonJS({
|
|
@@ -421,11 +442,11 @@ var require_codegen = __commonJS({
|
|
|
421
442
|
const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
|
|
422
443
|
return `${varKind} ${this.name}${rhs};` + _n;
|
|
423
444
|
}
|
|
424
|
-
optimizeNames(names,
|
|
445
|
+
optimizeNames(names, constants2) {
|
|
425
446
|
if (!names[this.name.str])
|
|
426
447
|
return;
|
|
427
448
|
if (this.rhs)
|
|
428
|
-
this.rhs = optimizeExpr(this.rhs, names,
|
|
449
|
+
this.rhs = optimizeExpr(this.rhs, names, constants2);
|
|
429
450
|
return this;
|
|
430
451
|
}
|
|
431
452
|
get names() {
|
|
@@ -442,10 +463,10 @@ var require_codegen = __commonJS({
|
|
|
442
463
|
render({ _n }) {
|
|
443
464
|
return `${this.lhs} = ${this.rhs};` + _n;
|
|
444
465
|
}
|
|
445
|
-
optimizeNames(names,
|
|
466
|
+
optimizeNames(names, constants2) {
|
|
446
467
|
if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
|
|
447
468
|
return;
|
|
448
|
-
this.rhs = optimizeExpr(this.rhs, names,
|
|
469
|
+
this.rhs = optimizeExpr(this.rhs, names, constants2);
|
|
449
470
|
return this;
|
|
450
471
|
}
|
|
451
472
|
get names() {
|
|
@@ -506,8 +527,8 @@ var require_codegen = __commonJS({
|
|
|
506
527
|
optimizeNodes() {
|
|
507
528
|
return `${this.code}` ? this : void 0;
|
|
508
529
|
}
|
|
509
|
-
optimizeNames(names,
|
|
510
|
-
this.code = optimizeExpr(this.code, names,
|
|
530
|
+
optimizeNames(names, constants2) {
|
|
531
|
+
this.code = optimizeExpr(this.code, names, constants2);
|
|
511
532
|
return this;
|
|
512
533
|
}
|
|
513
534
|
get names() {
|
|
@@ -536,12 +557,12 @@ var require_codegen = __commonJS({
|
|
|
536
557
|
}
|
|
537
558
|
return nodes.length > 0 ? this : void 0;
|
|
538
559
|
}
|
|
539
|
-
optimizeNames(names,
|
|
560
|
+
optimizeNames(names, constants2) {
|
|
540
561
|
const { nodes } = this;
|
|
541
562
|
let i = nodes.length;
|
|
542
563
|
while (i--) {
|
|
543
564
|
const n = nodes[i];
|
|
544
|
-
if (n.optimizeNames(names,
|
|
565
|
+
if (n.optimizeNames(names, constants2))
|
|
545
566
|
continue;
|
|
546
567
|
subtractNames(names, n.names);
|
|
547
568
|
nodes.splice(i, 1);
|
|
@@ -594,12 +615,12 @@ var require_codegen = __commonJS({
|
|
|
594
615
|
return void 0;
|
|
595
616
|
return this;
|
|
596
617
|
}
|
|
597
|
-
optimizeNames(names,
|
|
618
|
+
optimizeNames(names, constants2) {
|
|
598
619
|
var _a3;
|
|
599
|
-
this.else = (_a3 = this.else) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names,
|
|
600
|
-
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))
|
|
601
622
|
return;
|
|
602
|
-
this.condition = optimizeExpr(this.condition, names,
|
|
623
|
+
this.condition = optimizeExpr(this.condition, names, constants2);
|
|
603
624
|
return this;
|
|
604
625
|
}
|
|
605
626
|
get names() {
|
|
@@ -622,10 +643,10 @@ var require_codegen = __commonJS({
|
|
|
622
643
|
render(opts) {
|
|
623
644
|
return `for(${this.iteration})` + super.render(opts);
|
|
624
645
|
}
|
|
625
|
-
optimizeNames(names,
|
|
626
|
-
if (!super.optimizeNames(names,
|
|
646
|
+
optimizeNames(names, constants2) {
|
|
647
|
+
if (!super.optimizeNames(names, constants2))
|
|
627
648
|
return;
|
|
628
|
-
this.iteration = optimizeExpr(this.iteration, names,
|
|
649
|
+
this.iteration = optimizeExpr(this.iteration, names, constants2);
|
|
629
650
|
return this;
|
|
630
651
|
}
|
|
631
652
|
get names() {
|
|
@@ -661,10 +682,10 @@ var require_codegen = __commonJS({
|
|
|
661
682
|
render(opts) {
|
|
662
683
|
return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
|
|
663
684
|
}
|
|
664
|
-
optimizeNames(names,
|
|
665
|
-
if (!super.optimizeNames(names,
|
|
685
|
+
optimizeNames(names, constants2) {
|
|
686
|
+
if (!super.optimizeNames(names, constants2))
|
|
666
687
|
return;
|
|
667
|
-
this.iterable = optimizeExpr(this.iterable, names,
|
|
688
|
+
this.iterable = optimizeExpr(this.iterable, names, constants2);
|
|
668
689
|
return this;
|
|
669
690
|
}
|
|
670
691
|
get names() {
|
|
@@ -706,11 +727,11 @@ var require_codegen = __commonJS({
|
|
|
706
727
|
(_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNodes();
|
|
707
728
|
return this;
|
|
708
729
|
}
|
|
709
|
-
optimizeNames(names,
|
|
730
|
+
optimizeNames(names, constants2) {
|
|
710
731
|
var _a3, _b;
|
|
711
|
-
super.optimizeNames(names,
|
|
712
|
-
(_a3 = this.catch) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names,
|
|
713
|
-
(_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);
|
|
714
735
|
return this;
|
|
715
736
|
}
|
|
716
737
|
get names() {
|
|
@@ -1011,7 +1032,7 @@ var require_codegen = __commonJS({
|
|
|
1011
1032
|
function addExprNames(names, from) {
|
|
1012
1033
|
return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
|
|
1013
1034
|
}
|
|
1014
|
-
function optimizeExpr(expr, names,
|
|
1035
|
+
function optimizeExpr(expr, names, constants2) {
|
|
1015
1036
|
if (expr instanceof code_1.Name)
|
|
1016
1037
|
return replaceName(expr);
|
|
1017
1038
|
if (!canOptimize(expr))
|
|
@@ -1026,14 +1047,14 @@ var require_codegen = __commonJS({
|
|
|
1026
1047
|
return items;
|
|
1027
1048
|
}, []));
|
|
1028
1049
|
function replaceName(n) {
|
|
1029
|
-
const c =
|
|
1050
|
+
const c = constants2[n.str];
|
|
1030
1051
|
if (c === void 0 || names[n.str] !== 1)
|
|
1031
1052
|
return n;
|
|
1032
1053
|
delete names[n.str];
|
|
1033
1054
|
return c;
|
|
1034
1055
|
}
|
|
1035
1056
|
function canOptimize(e) {
|
|
1036
|
-
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);
|
|
1037
1058
|
}
|
|
1038
1059
|
}
|
|
1039
1060
|
function subtractNames(names, from) {
|
|
@@ -6854,8 +6875,9 @@ var stdoutLogger = {
|
|
|
6854
6875
|
};
|
|
6855
6876
|
|
|
6856
6877
|
// src/commands/init.ts
|
|
6857
|
-
import {
|
|
6858
|
-
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";
|
|
6859
6881
|
|
|
6860
6882
|
// src/claudeInstall.ts
|
|
6861
6883
|
import { spawnSync } from "node:child_process";
|
|
@@ -7050,9 +7072,328 @@ function uninstallClaudeIntegration(opts) {
|
|
|
7050
7072
|
};
|
|
7051
7073
|
}
|
|
7052
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
|
+
|
|
7053
7394
|
// src/nodePath.ts
|
|
7054
|
-
import { existsSync as
|
|
7055
|
-
import { delimiter, join as
|
|
7395
|
+
import { existsSync as existsSync3, realpathSync } from "node:fs";
|
|
7396
|
+
import { delimiter, join as join5 } from "node:path";
|
|
7056
7397
|
var defaultRealpath = (path) => realpathSync.native(path);
|
|
7057
7398
|
function safeRealpath(path, realpath = defaultRealpath) {
|
|
7058
7399
|
try {
|
|
@@ -7065,7 +7406,7 @@ function resolveHookNodePath({
|
|
|
7065
7406
|
env = process.env,
|
|
7066
7407
|
execPath = process.execPath,
|
|
7067
7408
|
platform = process.platform,
|
|
7068
|
-
exists =
|
|
7409
|
+
exists = existsSync3,
|
|
7069
7410
|
realpath = defaultRealpath
|
|
7070
7411
|
} = {}) {
|
|
7071
7412
|
const execRealpath = safeRealpath(execPath, realpath);
|
|
@@ -7073,7 +7414,7 @@ function resolveHookNodePath({
|
|
|
7073
7414
|
const names = platform === "win32" ? ["node.exe", "node.cmd", "node.bat", "node"] : ["node"];
|
|
7074
7415
|
for (const dir of pathEnv.split(delimiter).filter(Boolean)) {
|
|
7075
7416
|
for (const name of names) {
|
|
7076
|
-
const candidate =
|
|
7417
|
+
const candidate = join5(dir, name);
|
|
7077
7418
|
if (!exists(candidate)) continue;
|
|
7078
7419
|
const candidateRealpath = safeRealpath(candidate, realpath);
|
|
7079
7420
|
if (execRealpath && candidateRealpath && candidateRealpath === execRealpath) {
|
|
@@ -7084,37 +7425,245 @@ function resolveHookNodePath({
|
|
|
7084
7425
|
return execPath;
|
|
7085
7426
|
}
|
|
7086
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
|
+
|
|
7087
7630
|
// src/commands/init.ts
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
7094
|
-
".obsidian/",
|
|
7095
|
-
""
|
|
7096
|
-
].join("\n");
|
|
7097
|
-
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";
|
|
7098
7637
|
ensureUserConfig(ctx.userConfigPath);
|
|
7099
7638
|
ctx.logger.info(`user config: ${ctx.userConfigPath}`);
|
|
7100
|
-
if (!
|
|
7101
|
-
|
|
7102
|
-
|
|
7639
|
+
if (!existsSync5(ctx.paths.knowledgeRepo)) {
|
|
7640
|
+
mkdirSync3(ctx.paths.knowledgeRepo, { recursive: true });
|
|
7641
|
+
mkdirSync3(ctx.paths.entriesDir, { recursive: true });
|
|
7103
7642
|
ctx.logger.info(`knowledge repo scaffolded: ${ctx.paths.knowledgeRepo}`);
|
|
7104
7643
|
} else {
|
|
7105
7644
|
ctx.logger.info(`knowledge repo: ${ctx.paths.knowledgeRepo}`);
|
|
7106
7645
|
}
|
|
7107
7646
|
migrateLegacyCommunityDir(ctx);
|
|
7108
|
-
const gitignorePath =
|
|
7109
|
-
if (!
|
|
7110
|
-
|
|
7647
|
+
const gitignorePath = join8(ctx.paths.knowledgeRepo, ".gitignore");
|
|
7648
|
+
if (!existsSync5(gitignorePath)) {
|
|
7649
|
+
writeFileSync4(gitignorePath, KNOWLEDGE_GITIGNORE, "utf-8");
|
|
7111
7650
|
ctx.logger.info(`.gitignore created: ${gitignorePath}`);
|
|
7112
7651
|
}
|
|
7113
7652
|
if (!opts.dryRun) {
|
|
7114
|
-
const dbDir =
|
|
7115
|
-
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
|
+
}
|
|
7116
7660
|
const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
|
|
7117
|
-
|
|
7661
|
+
try {
|
|
7662
|
+
reindexAllSources({ db, paths: ctx.paths, logger: ctx.logger, keyProvider });
|
|
7663
|
+
writeDigestMarker(ctx.caveatHome, computeEntriesDigest(ctx.paths));
|
|
7664
|
+
} finally {
|
|
7665
|
+
db.close();
|
|
7666
|
+
}
|
|
7118
7667
|
ctx.logger.info(`db initialized: ${ctx.paths.dbPath}`);
|
|
7119
7668
|
} else {
|
|
7120
7669
|
ctx.logger.info(`[dry-run] db path: ${ctx.paths.dbPath}`);
|
|
@@ -7133,39 +7682,179 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
|
|
|
7133
7682
|
ctx.logger.info(
|
|
7134
7683
|
"tip: subscribe to a group repo with `caveat community add <github-url>`, then `caveat pull`."
|
|
7135
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
|
+
}
|
|
7136
7760
|
if (opts.skipClaude) {
|
|
7137
7761
|
ctx.logger.info("Claude Code integration skipped (--skip-claude)");
|
|
7138
|
-
|
|
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
|
+
}
|
|
7139
7776
|
}
|
|
7140
|
-
|
|
7141
|
-
|
|
7142
|
-
ctx.logger.
|
|
7143
|
-
|
|
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
|
+
}
|
|
7144
7806
|
}
|
|
7145
|
-
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
|
|
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"
|
|
7151
7814
|
});
|
|
7152
|
-
|
|
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);
|
|
7153
7842
|
}
|
|
7154
7843
|
function migrateLegacyCommunityDir(ctx) {
|
|
7155
|
-
const legacy =
|
|
7844
|
+
const legacy = join8(ctx.paths.knowledgeRepo, "community");
|
|
7156
7845
|
const current = ctx.paths.communityDir;
|
|
7157
7846
|
if (legacy === current) return;
|
|
7158
|
-
if (!
|
|
7159
|
-
if (
|
|
7847
|
+
if (!existsSync5(legacy)) return;
|
|
7848
|
+
if (existsSync5(current)) {
|
|
7160
7849
|
ctx.logger.warn(
|
|
7161
7850
|
`legacy community dir still exists at ${legacy} \u2014 remove manually (new location in use)`
|
|
7162
7851
|
);
|
|
7163
7852
|
return;
|
|
7164
7853
|
}
|
|
7165
|
-
|
|
7854
|
+
mkdirSync3(current, { recursive: true });
|
|
7166
7855
|
for (const entry of readdirSync(legacy, { withFileTypes: true })) {
|
|
7167
7856
|
if (!entry.isDirectory()) continue;
|
|
7168
|
-
renameSync(
|
|
7857
|
+
renameSync(join8(legacy, entry.name), join8(current, entry.name));
|
|
7169
7858
|
}
|
|
7170
7859
|
try {
|
|
7171
7860
|
rmdirSync(legacy);
|
|
@@ -7180,7 +7869,7 @@ function runUninstall(ctx, opts) {
|
|
|
7180
7869
|
process.exit(1);
|
|
7181
7870
|
}
|
|
7182
7871
|
const result = uninstallClaudeIntegration({
|
|
7183
|
-
claudeDir:
|
|
7872
|
+
claudeDir: join8(ctx.userHome, ".claude"),
|
|
7184
7873
|
cliScriptPath,
|
|
7185
7874
|
nodePath: resolveHookNodePath(),
|
|
7186
7875
|
dryRun: opts.dryRun,
|
|
@@ -7227,37 +7916,38 @@ function reportInstallResult(ctx, result, dryRun) {
|
|
|
7227
7916
|
}
|
|
7228
7917
|
|
|
7229
7918
|
// src/commands/indexCmd.ts
|
|
7230
|
-
import { existsSync as
|
|
7231
|
-
import { dirname as
|
|
7232
|
-
function runIndex(ctx, opts) {
|
|
7233
|
-
const dbDir =
|
|
7234
|
-
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
|
+
}
|
|
7235
7929
|
const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
|
|
7236
7930
|
try {
|
|
7237
7931
|
if (opts.full) {
|
|
7238
7932
|
ctx.logger.info("full rebuild: DELETE FROM entries");
|
|
7239
7933
|
rebuildAll(db);
|
|
7240
7934
|
}
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
if (existsSync4(ctx.paths.communityDir)) {
|
|
7248
|
-
for (const entry of readdirSync2(ctx.paths.communityDir, { withFileTypes: true })) {
|
|
7249
|
-
if (!entry.isDirectory()) continue;
|
|
7250
|
-
const source = `community/${entry.name}`;
|
|
7251
|
-
const root = join6(ctx.paths.communityDir, entry.name, "entries");
|
|
7252
|
-
if (!existsSync4(root)) continue;
|
|
7253
|
-
const result = scanSource({ db, source, entriesRoot: root });
|
|
7254
|
-
ctx.logger.info(`${source}: +${result.added} ~${result.updated} -${result.deleted}`);
|
|
7935
|
+
const result = reindexAllSources({ db, paths: ctx.paths, logger: ctx.logger, keyProvider });
|
|
7936
|
+
for (const [source, scan] of Object.entries(result.perSource)) {
|
|
7937
|
+
if (source === "own") {
|
|
7938
|
+
ctx.logger.info(`own: +${scan.added} ~${scan.updated} -${scan.deleted}`);
|
|
7939
|
+
} else {
|
|
7940
|
+
ctx.logger.info(`${source}: +${scan.added} ~${scan.updated} -${scan.deleted}`);
|
|
7255
7941
|
}
|
|
7256
7942
|
}
|
|
7943
|
+
writeDigestMarker(ctx.caveatHome, computeEntriesDigest(ctx.paths));
|
|
7257
7944
|
} finally {
|
|
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 join8 } 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,19 +32454,69 @@ async function runMcpServer() {
|
|
|
31754
32454
|
}
|
|
31755
32455
|
|
|
31756
32456
|
// src/commands/hookCmd.ts
|
|
31757
|
-
import { spawn
|
|
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";
|
|
32473
|
+
|
|
32474
|
+
// src/autoReindexTrigger.ts
|
|
32475
|
+
import { spawn } from "node:child_process";
|
|
32476
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
32477
|
+
import { join as join10 } from "node:path";
|
|
32478
|
+
function maybeTriggerAutoReindex(ctx) {
|
|
32479
|
+
if (process.env.CAVEAT_INDEX_AUTOSYNC === "off") return;
|
|
32480
|
+
if (!existsSync8(ctx.paths.dbPath)) return;
|
|
32481
|
+
if (existsSync8(join10(ctx.caveatHome, "index", ".reindex-lock"))) return;
|
|
32482
|
+
const current = computeEntriesDigest(ctx.paths);
|
|
32483
|
+
const marker = readDigestMarker(ctx.caveatHome);
|
|
32484
|
+
if (marker?.digest === current.digest && marker.fileCount === current.fileCount) return;
|
|
32485
|
+
const cliScript = process.argv[1];
|
|
32486
|
+
if (!cliScript) throw new Error("current Caveat CLI script path is unavailable");
|
|
32487
|
+
const child = spawn(
|
|
32488
|
+
process.execPath,
|
|
32489
|
+
// execArgv を継承して親と同じランタイム(例: テストハーネスの --import tsx)で
|
|
32490
|
+
// worker を起動する。素の node だと TS ソース実行のテスト環境で worker が即死する。
|
|
32491
|
+
[...process.execArgv, "--disable-warning=ExperimentalWarning", cliScript, "hook", "reindex"],
|
|
32492
|
+
{ detached: true, stdio: "ignore", windowsHide: true }
|
|
32493
|
+
);
|
|
32494
|
+
child.unref();
|
|
32495
|
+
}
|
|
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
|
+
|
|
32519
|
+
// src/commands/hookCmd.ts
|
|
31770
32520
|
var silentLogger = {
|
|
31771
32521
|
info: () => {
|
|
31772
32522
|
},
|
|
@@ -31810,31 +32560,44 @@ function buildContextSafely() {
|
|
|
31810
32560
|
return null;
|
|
31811
32561
|
}
|
|
31812
32562
|
}
|
|
31813
|
-
function searchCaveatsFromTextSafely(text) {
|
|
32563
|
+
function searchCaveatsFromTextSafely(text, surface) {
|
|
31814
32564
|
if (!text) return [];
|
|
31815
32565
|
let db;
|
|
32566
|
+
let caveatHome;
|
|
32567
|
+
let hits;
|
|
31816
32568
|
try {
|
|
31817
32569
|
const ctx = buildContextSafely();
|
|
31818
|
-
if (!ctx || !
|
|
32570
|
+
if (!ctx || !existsSync10(ctx.paths.dbPath)) return [];
|
|
32571
|
+
caveatHome = ctx.caveatHome;
|
|
31819
32572
|
db = openDb({ path: ctx.paths.dbPath });
|
|
31820
|
-
|
|
32573
|
+
hits = findCaveatsForPrompt(db, text, {
|
|
31821
32574
|
selfIdentity: defaultSelfIdentityTokens()
|
|
31822
32575
|
});
|
|
31823
|
-
if (hits.length > 0) {
|
|
31824
|
-
try {
|
|
31825
|
-
markHit(db, hits);
|
|
31826
|
-
} catch (err) {
|
|
31827
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
31828
|
-
process.stderr.write(`[caveat:hook] markHit error: ${msg}
|
|
31829
|
-
`);
|
|
31830
|
-
}
|
|
31831
|
-
}
|
|
31832
|
-
return hits;
|
|
31833
32576
|
} catch (err) {
|
|
31834
32577
|
const msg = err instanceof Error ? err.message : String(err);
|
|
31835
32578
|
process.stderr.write(`[caveat:hook] search error: ${msg}
|
|
31836
32579
|
`);
|
|
31837
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;
|
|
31838
32601
|
} finally {
|
|
31839
32602
|
db?.close();
|
|
31840
32603
|
}
|
|
@@ -31850,12 +32613,15 @@ function loadSignalsSafely(path) {
|
|
|
31850
32613
|
}
|
|
31851
32614
|
}
|
|
31852
32615
|
function systemReminderOutput(text) {
|
|
31853
|
-
return `<system-reminder>${text}</system-reminder>`;
|
|
32616
|
+
return `<system-reminder>${text.replace(/</g, "\u2039").replace(/>/g, "\u203A")}</system-reminder>`;
|
|
31854
32617
|
}
|
|
31855
32618
|
function drainForSession(sessionId) {
|
|
31856
32619
|
const ctx = buildContextSafely();
|
|
31857
32620
|
if (!ctx) return [];
|
|
31858
|
-
return
|
|
32621
|
+
return [
|
|
32622
|
+
...drainPendingReminders(ctx.caveatHome, sessionId),
|
|
32623
|
+
...drainGlobalPendingReminders(ctx.caveatHome)
|
|
32624
|
+
];
|
|
31859
32625
|
}
|
|
31860
32626
|
function claudeContextDedupeKey(text) {
|
|
31861
32627
|
if (text.startsWith(CLAUDE_STOP_REMINDER_PREFIX)) return "claude-stop-reminder";
|
|
@@ -31899,20 +32665,20 @@ function stopSignalKey(signals, related) {
|
|
|
31899
32665
|
return createHash("sha256").update(body).digest("hex");
|
|
31900
32666
|
}
|
|
31901
32667
|
function stopStatePath(caveatHome, sessionId) {
|
|
31902
|
-
return
|
|
32668
|
+
return join12(caveatHome, CLAUDE_STOP_STATE_DIR, `${sanitizeClaudeStateId(sessionId)}.txt`);
|
|
31903
32669
|
}
|
|
31904
32670
|
function wasStopReminderQueued(caveatHome, sessionId, key) {
|
|
31905
32671
|
const path = stopStatePath(caveatHome, sessionId);
|
|
31906
32672
|
try {
|
|
31907
|
-
return
|
|
32673
|
+
return readFileSync5(path, "utf-8") === key;
|
|
31908
32674
|
} catch {
|
|
31909
32675
|
return false;
|
|
31910
32676
|
}
|
|
31911
32677
|
}
|
|
31912
32678
|
function markStopReminderQueued(caveatHome, sessionId, key) {
|
|
31913
32679
|
const path = stopStatePath(caveatHome, sessionId);
|
|
31914
|
-
|
|
31915
|
-
|
|
32680
|
+
mkdirSync5(join12(caveatHome, CLAUDE_STOP_STATE_DIR), { recursive: true });
|
|
32681
|
+
writeFileSync5(path, key, "utf-8");
|
|
31916
32682
|
}
|
|
31917
32683
|
function queueStopForSession(sessionId, signals, related) {
|
|
31918
32684
|
const ctx = buildContextSafely();
|
|
@@ -31961,23 +32727,34 @@ function isToolError(payload) {
|
|
|
31961
32727
|
if (typeof payload.error === "string" && payload.error.length > 0) return true;
|
|
31962
32728
|
return false;
|
|
31963
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";
|
|
31964
32733
|
function spawnWorker(job) {
|
|
31965
|
-
|
|
31966
|
-
|
|
31967
|
-
|
|
31968
|
-
);
|
|
32734
|
+
let root;
|
|
32735
|
+
let workDir;
|
|
32736
|
+
let workFile;
|
|
31969
32737
|
try {
|
|
31970
|
-
|
|
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" });
|
|
31971
32744
|
} catch (err) {
|
|
31972
32745
|
const msg = err instanceof Error ? err.message : String(err);
|
|
31973
32746
|
process.stderr.write(`[caveat:hook] worker writefile error: ${msg}
|
|
31974
32747
|
`);
|
|
32748
|
+
cleanupWorkerDir(workDir, workDir ? dirname6(workDir) : void 0);
|
|
31975
32749
|
return;
|
|
31976
32750
|
}
|
|
31977
32751
|
const cliScript = process.argv[1];
|
|
31978
|
-
if (!cliScript)
|
|
32752
|
+
if (!cliScript) {
|
|
32753
|
+
cleanupWorkerDir(workDir, root);
|
|
32754
|
+
return;
|
|
32755
|
+
}
|
|
31979
32756
|
try {
|
|
31980
|
-
const child =
|
|
32757
|
+
const child = spawn3(
|
|
31981
32758
|
process.execPath,
|
|
31982
32759
|
["--disable-warning=ExperimentalWarning", cliScript, "hook", "worker", workFile],
|
|
31983
32760
|
{ detached: true, stdio: "ignore", windowsHide: true }
|
|
@@ -31988,7 +32765,7 @@ function spawnWorker(job) {
|
|
|
31988
32765
|
process.stderr.write(`[caveat:hook] worker spawn error: ${msg}
|
|
31989
32766
|
`);
|
|
31990
32767
|
try {
|
|
31991
|
-
|
|
32768
|
+
cleanupWorkerDir(workDir, root);
|
|
31992
32769
|
} catch {
|
|
31993
32770
|
}
|
|
31994
32771
|
}
|
|
@@ -31996,14 +32773,11 @@ function spawnWorker(job) {
|
|
|
31996
32773
|
async function runWorker(workFile) {
|
|
31997
32774
|
let raw;
|
|
31998
32775
|
try {
|
|
31999
|
-
raw =
|
|
32776
|
+
raw = readFileSync5(workFile, "utf-8");
|
|
32000
32777
|
} catch {
|
|
32001
32778
|
process.exit(0);
|
|
32002
32779
|
}
|
|
32003
|
-
|
|
32004
|
-
unlinkSync(workFile);
|
|
32005
|
-
} catch {
|
|
32006
|
-
}
|
|
32780
|
+
cleanupWorkerDir(dirname6(workFile), dirname6(dirname6(workFile)));
|
|
32007
32781
|
let job;
|
|
32008
32782
|
try {
|
|
32009
32783
|
job = JSON.parse(raw);
|
|
@@ -32011,7 +32785,7 @@ async function runWorker(workFile) {
|
|
|
32011
32785
|
process.exit(0);
|
|
32012
32786
|
}
|
|
32013
32787
|
if (!job.searchText || !job.sessionId) process.exit(0);
|
|
32014
|
-
const hits = searchCaveatsFromTextSafely(job.searchText);
|
|
32788
|
+
const hits = searchCaveatsFromTextSafely(job.searchText, "tool_error");
|
|
32015
32789
|
if (hits.length === 0) process.exit(0);
|
|
32016
32790
|
const ctx = buildContextSafely();
|
|
32017
32791
|
if (!ctx) process.exit(0);
|
|
@@ -32021,12 +32795,169 @@ async function runWorker(workFile) {
|
|
|
32021
32795
|
}
|
|
32022
32796
|
process.exit(0);
|
|
32023
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
|
+
}
|
|
32874
|
+
function writeLastReindex(caveatHome, value) {
|
|
32875
|
+
try {
|
|
32876
|
+
writeFileSync5(join12(caveatHome, "index", ".last-reindex.json"), JSON.stringify(value), "utf-8");
|
|
32877
|
+
} catch (err) {
|
|
32878
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
32879
|
+
process.stderr.write(`[caveat:hook] reindex status write error: ${msg}
|
|
32880
|
+
`);
|
|
32881
|
+
}
|
|
32882
|
+
}
|
|
32883
|
+
function errorMessage4(err) {
|
|
32884
|
+
return err instanceof Error ? err.message : String(err);
|
|
32885
|
+
}
|
|
32886
|
+
async function runReindexWorker() {
|
|
32887
|
+
if (process.env.CAVEAT_INDEX_AUTOSYNC === "off") {
|
|
32888
|
+
process.stderr.write("[caveat:hook] auto reindex disabled by CAVEAT_INDEX_AUTOSYNC=off\n");
|
|
32889
|
+
return;
|
|
32890
|
+
}
|
|
32891
|
+
const ctx = buildContextSafely();
|
|
32892
|
+
if (!ctx || !existsSync10(ctx.paths.dbPath)) {
|
|
32893
|
+
process.stderr.write("[caveat:hook] auto reindex skipped: index database does not exist\n");
|
|
32894
|
+
return;
|
|
32895
|
+
}
|
|
32896
|
+
const lock = acquireReindexLock(ctx.caveatHome);
|
|
32897
|
+
if (!lock) return;
|
|
32898
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
32899
|
+
let db;
|
|
32900
|
+
try {
|
|
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
|
+
}
|
|
32908
|
+
db = openDb({ path: ctx.paths.dbPath, logger: silentLogger });
|
|
32909
|
+
const result = reindexAllSources({ db, paths: ctx.paths, logger: silentLogger, keyProvider });
|
|
32910
|
+
writeDigestMarker(ctx.caveatHome, digest);
|
|
32911
|
+
writeLastReindex(ctx.caveatHome, {
|
|
32912
|
+
startedAt,
|
|
32913
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
32914
|
+
perSource: result.perSource
|
|
32915
|
+
});
|
|
32916
|
+
} catch (err) {
|
|
32917
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
32918
|
+
process.stderr.write(`[caveat:hook] reindex error: ${msg}
|
|
32919
|
+
`);
|
|
32920
|
+
writeLastReindex(ctx.caveatHome, {
|
|
32921
|
+
startedAt,
|
|
32922
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
32923
|
+
error: msg
|
|
32924
|
+
});
|
|
32925
|
+
} finally {
|
|
32926
|
+
db?.close();
|
|
32927
|
+
try {
|
|
32928
|
+
releaseReindexLock(lock);
|
|
32929
|
+
} catch (err) {
|
|
32930
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
32931
|
+
process.stderr.write(`[caveat:hook] reindex lock release error: ${msg}
|
|
32932
|
+
`);
|
|
32933
|
+
}
|
|
32934
|
+
}
|
|
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
|
+
}
|
|
32024
32955
|
function buildToolErrorReminder(job, hits) {
|
|
32025
32956
|
const base = toolErrorReminderText(hits);
|
|
32026
32957
|
const mode = hookCodexSidecarMode();
|
|
32027
32958
|
if (mode === "off") return base;
|
|
32028
32959
|
const projectRoot = process.cwd();
|
|
32029
|
-
const hasSidecarConfig =
|
|
32960
|
+
const hasSidecarConfig = existsSync10(join12(projectRoot, ".codex-sidecar.yml"));
|
|
32030
32961
|
if (mode === "auto" && !hasSidecarConfig) return base;
|
|
32031
32962
|
const advisory = runCodexSidecarAdvisory({
|
|
32032
32963
|
searchText: job.searchText,
|
|
@@ -32036,21 +32967,20 @@ function buildToolErrorReminder(job, hits) {
|
|
|
32036
32967
|
"A Claude Code tool just returned an error.",
|
|
32037
32968
|
"Use the provided Caveat context to give concise next-step advice.",
|
|
32038
32969
|
"Do not tell Claude to search Caveat again unless the context is insufficient."
|
|
32039
|
-
].join(" ")
|
|
32970
|
+
].join(" "),
|
|
32971
|
+
additionalContext: job.additionalContext
|
|
32040
32972
|
});
|
|
32041
32973
|
if (advisory.status === "ok") {
|
|
32042
32974
|
return [
|
|
32043
32975
|
base,
|
|
32044
32976
|
"",
|
|
32045
|
-
|
|
32046
|
-
advisory.summary,
|
|
32047
|
-
advisory.rawEventLogRef ? `rawEventLogRef: ${advisory.rawEventLogRef}` : ""
|
|
32977
|
+
formatCodexSidecarAdvisory(advisory)
|
|
32048
32978
|
].filter(Boolean).join("\n");
|
|
32049
32979
|
}
|
|
32050
32980
|
return [
|
|
32051
32981
|
base,
|
|
32052
32982
|
"",
|
|
32053
|
-
|
|
32983
|
+
formatCodexSidecarAdvisory(advisory)
|
|
32054
32984
|
].join("\n");
|
|
32055
32985
|
}
|
|
32056
32986
|
function hookCodexSidecarMode() {
|
|
@@ -32063,7 +32993,7 @@ function buildStopReminder(signals, related) {
|
|
|
32063
32993
|
const mode = hookCodexSidecarMode();
|
|
32064
32994
|
if (mode === "off") return base;
|
|
32065
32995
|
const projectRoot = process.cwd();
|
|
32066
|
-
const hasSidecarConfig =
|
|
32996
|
+
const hasSidecarConfig = existsSync10(join12(projectRoot, ".codex-sidecar.yml"));
|
|
32067
32997
|
if (mode === "auto" && !hasSidecarConfig) return base;
|
|
32068
32998
|
const advisory = runCodexSidecarAdvisory({
|
|
32069
32999
|
searchText: struggleSearchText(signals),
|
|
@@ -32071,113 +33001,46 @@ function buildStopReminder(signals, related) {
|
|
|
32071
33001
|
projectRoot,
|
|
32072
33002
|
prompt: [
|
|
32073
33003
|
"A Claude Code session is ending after objective struggle signals.",
|
|
32074
|
-
"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.",
|
|
32075
33005
|
"Be concise and preserve Caveat visibility rules."
|
|
32076
|
-
].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
|
+
})
|
|
32077
33016
|
});
|
|
32078
33017
|
if (advisory.status === "ok") {
|
|
32079
33018
|
return [
|
|
32080
33019
|
base,
|
|
32081
33020
|
"",
|
|
32082
|
-
|
|
32083
|
-
advisory.summary,
|
|
32084
|
-
advisory.rawEventLogRef ? `rawEventLogRef: ${advisory.rawEventLogRef}` : ""
|
|
33021
|
+
formatCodexSidecarAdvisory(advisory)
|
|
32085
33022
|
].filter(Boolean).join("\n");
|
|
32086
33023
|
}
|
|
32087
33024
|
return [
|
|
32088
33025
|
base,
|
|
32089
33026
|
"",
|
|
32090
|
-
|
|
33027
|
+
formatCodexSidecarAdvisory(advisory)
|
|
32091
33028
|
].join("\n");
|
|
32092
33029
|
}
|
|
32093
|
-
function
|
|
32094
|
-
|
|
32095
|
-
|
|
32096
|
-
|
|
32097
|
-
|
|
32098
|
-
function sidecarFailureDetail(output) {
|
|
32099
|
-
const protocol = output.match(/PROTOCOL_ERROR:[^"\r\n]+/);
|
|
32100
|
-
if (protocol) return compactFailureMessage(protocol[0]);
|
|
32101
|
-
const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
32102
|
-
const diagnostic = lines.find(
|
|
32103
|
-
(line) => !line.startsWith("[caveat] [codex-sidecar] codex-sidecar ")
|
|
32104
|
-
);
|
|
32105
|
-
return compactFailureMessage(diagnostic ?? output);
|
|
32106
|
-
}
|
|
32107
|
-
function runCodexSidecarAdvisory(input) {
|
|
32108
|
-
const cliScript = process.argv[1];
|
|
32109
|
-
if (!cliScript) {
|
|
32110
|
-
return { status: "failed", message: "current Caveat CLI script path is unavailable" };
|
|
33030
|
+
async function runHook(name, arg) {
|
|
33031
|
+
try {
|
|
33032
|
+
sweepStaleWorkerDirs();
|
|
33033
|
+
} catch {
|
|
33034
|
+
process.stderr.write("[caveat:hook] worker stale cleanup failed\n");
|
|
32111
33035
|
}
|
|
32112
|
-
|
|
32113
|
-
|
|
32114
|
-
|
|
32115
|
-
cliScript,
|
|
32116
|
-
"codex-sidecar",
|
|
32117
|
-
"run",
|
|
32118
|
-
"explore",
|
|
32119
|
-
input.prompt,
|
|
32120
|
-
"--preset",
|
|
32121
|
-
"advisory",
|
|
32122
|
-
"--query",
|
|
32123
|
-
input.searchText,
|
|
32124
|
-
"--limit",
|
|
32125
|
-
String(Math.max(1, input.limit)),
|
|
32126
|
-
"--host-agent",
|
|
32127
|
-
"claude",
|
|
32128
|
-
"--availability",
|
|
32129
|
-
"operational"
|
|
32130
|
-
];
|
|
32131
|
-
const resultDir = mkdtempSync(join9(tmpdir(), "caveat-codex-advisory-"));
|
|
32132
|
-
const resultFile = join9(resultDir, "result.json");
|
|
32133
|
-
args.push("--save-result", resultFile);
|
|
32134
|
-
const nodeCli = process.env.CAVEAT_CODEX_SIDECAR_NODE_CLI;
|
|
32135
|
-
if (nodeCli) {
|
|
32136
|
-
args.push("--node-cli", nodeCli);
|
|
32137
|
-
} else {
|
|
32138
|
-
const command = process.env.CAVEAT_CODEX_SIDECAR_COMMAND;
|
|
32139
|
-
if (command) args.push("--command", command);
|
|
33036
|
+
if (name === "reindex") {
|
|
33037
|
+
await runReindexWorker();
|
|
33038
|
+
process.exit(0);
|
|
32140
33039
|
}
|
|
32141
|
-
|
|
32142
|
-
|
|
32143
|
-
|
|
32144
|
-
encoding: "utf-8",
|
|
32145
|
-
timeout: Number(process.env.CAVEAT_HOOK_CODEX_SIDECAR_TIMEOUT_MS ?? 12e4),
|
|
32146
|
-
maxBuffer: 10 * 1024 * 1024
|
|
32147
|
-
});
|
|
32148
|
-
if (result.error) {
|
|
32149
|
-
return { status: "failed", message: compactFailureMessage(result.error.message) };
|
|
32150
|
-
}
|
|
32151
|
-
if (result.status !== 0) {
|
|
32152
|
-
const detail = (result.stderr || result.stdout || `exit ${result.status}`).trim();
|
|
32153
|
-
return {
|
|
32154
|
-
status: "failed",
|
|
32155
|
-
message: `sidecar command failed: ${sidecarFailureDetail(detail)}`
|
|
32156
|
-
};
|
|
32157
|
-
}
|
|
32158
|
-
const parsed = JSON.parse(readFileSync3(resultFile, "utf-8"));
|
|
32159
|
-
if (parsed.status !== "ok" || typeof parsed.summary !== "string") {
|
|
32160
|
-
return {
|
|
32161
|
-
status: "failed",
|
|
32162
|
-
message: compactFailureMessage(`unexpected SidecarResult status: ${String(parsed.status)}`)
|
|
32163
|
-
};
|
|
32164
|
-
}
|
|
32165
|
-
return {
|
|
32166
|
-
status: "ok",
|
|
32167
|
-
summary: parsed.summary,
|
|
32168
|
-
...typeof parsed.rawEventLogRef === "string" ? { rawEventLogRef: parsed.rawEventLogRef } : {}
|
|
32169
|
-
};
|
|
32170
|
-
} catch (err) {
|
|
32171
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
32172
|
-
return {
|
|
32173
|
-
status: "failed",
|
|
32174
|
-
message: compactFailureMessage(`invalid SidecarResult JSON: ${msg}`)
|
|
32175
|
-
};
|
|
32176
|
-
} finally {
|
|
32177
|
-
rmSync(resultDir, { recursive: true, force: true });
|
|
33040
|
+
if (name === "autosync") {
|
|
33041
|
+
await runAutoSyncWorker();
|
|
33042
|
+
process.exit(0);
|
|
32178
33043
|
}
|
|
32179
|
-
}
|
|
32180
|
-
async function runHook(name, arg) {
|
|
32181
33044
|
if (name === "worker") {
|
|
32182
33045
|
if (!arg) process.exit(0);
|
|
32183
33046
|
await runWorker(arg);
|
|
@@ -32197,7 +33060,7 @@ async function runHook(name, arg) {
|
|
|
32197
33060
|
const contexts = name === "stop" ? [] : drainForSession(sessionId);
|
|
32198
33061
|
if (name === "user-prompt-submit") {
|
|
32199
33062
|
const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
|
|
32200
|
-
const hits = searchCaveatsFromTextSafely(prompt);
|
|
33063
|
+
const hits = searchCaveatsFromTextSafely(prompt, "user_prompt");
|
|
32201
33064
|
if (hits.length > 0) {
|
|
32202
33065
|
contexts.push(userPromptSubmitReminderText(hits));
|
|
32203
33066
|
}
|
|
@@ -32219,21 +33082,46 @@ async function runHook(name, arg) {
|
|
|
32219
33082
|
payload.tool_response ?? payload.toolResponse ?? payload.error ?? payload
|
|
32220
33083
|
);
|
|
32221
33084
|
if (errText) {
|
|
32222
|
-
|
|
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 } : {} });
|
|
32223
33092
|
}
|
|
32224
33093
|
process.exit(0);
|
|
32225
33094
|
}
|
|
32226
33095
|
if (name === "stop") {
|
|
32227
|
-
|
|
32228
|
-
|
|
32229
|
-
|
|
32230
|
-
|
|
33096
|
+
const ctx = buildContextSafely();
|
|
33097
|
+
if (ctx) {
|
|
33098
|
+
try {
|
|
33099
|
+
maybeSweepPendingDirs(ctx.caveatHome);
|
|
33100
|
+
} catch (err) {
|
|
33101
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
33102
|
+
process.stderr.write(`[caveat:hook] pending sweep error: ${msg}
|
|
33103
|
+
`);
|
|
33104
|
+
}
|
|
33105
|
+
try {
|
|
33106
|
+
maybeTriggerAutoReindex(ctx);
|
|
33107
|
+
} catch (err) {
|
|
33108
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
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}
|
|
33117
|
+
`);
|
|
33118
|
+
}
|
|
32231
33119
|
}
|
|
32232
33120
|
if (payload.stop_hook_active === true) process.exit(0);
|
|
32233
33121
|
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
|
|
32234
33122
|
const signals = transcriptPath ? loadSignalsSafely(transcriptPath) : null;
|
|
32235
33123
|
if (!signals || !hasAnyStruggleSignal(signals)) process.exit(0);
|
|
32236
|
-
const related = searchCaveatsFromTextSafely(struggleSearchText(signals));
|
|
33124
|
+
const related = searchCaveatsFromTextSafely(struggleSearchText(signals), "stop");
|
|
32237
33125
|
queueStopForSession(sessionId, signals, related);
|
|
32238
33126
|
process.exit(0);
|
|
32239
33127
|
}
|
|
@@ -32243,236 +33131,11 @@ async function runHook(name, arg) {
|
|
|
32243
33131
|
}
|
|
32244
33132
|
|
|
32245
33133
|
// src/commands/codexHookCmd.ts
|
|
32246
|
-
import { spawn as
|
|
32247
|
-
import { existsSync as
|
|
32248
|
-
import { homedir as homedir3, tmpdir as
|
|
32249
|
-
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";
|
|
32250
33138
|
import { createHash as createHash2, randomBytes as randomBytes2 } from "node:crypto";
|
|
32251
|
-
|
|
32252
|
-
// src/codexHookInstall.ts
|
|
32253
|
-
import { copyFileSync as copyFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
32254
|
-
import { dirname as dirname5, join as join10 } from "node:path";
|
|
32255
|
-
function quote2(p) {
|
|
32256
|
-
return p.includes(" ") ? `"${p}"` : p;
|
|
32257
|
-
}
|
|
32258
|
-
function hookCommand2(nodePath, cliScriptPath, event) {
|
|
32259
|
-
return `${quote2(nodePath)} ${quote2(cliScriptPath)} codex-hook ${event}`;
|
|
32260
|
-
}
|
|
32261
|
-
function eventCommandFragment(event) {
|
|
32262
|
-
return `codex-hook ${event}`;
|
|
32263
|
-
}
|
|
32264
|
-
function isSameHookCommand2(actual, expected) {
|
|
32265
|
-
return actual === expected || actual.endsWith(` ${expected}`);
|
|
32266
|
-
}
|
|
32267
|
-
function isCaveatCodexHookCommand(actual, event) {
|
|
32268
|
-
const lower = actual.toLowerCase();
|
|
32269
|
-
return lower.includes("caveat") && actual.includes(eventCommandFragment(event));
|
|
32270
|
-
}
|
|
32271
|
-
function hasCaveatHook(hooksJson, event, fragment) {
|
|
32272
|
-
return hooksJson.hooks?.[event]?.some(
|
|
32273
|
-
(entry) => entry.hooks?.some((h) => h.command.includes(fragment))
|
|
32274
|
-
) ?? false;
|
|
32275
|
-
}
|
|
32276
|
-
function readHooks(path) {
|
|
32277
|
-
if (!existsSync7(path)) return {};
|
|
32278
|
-
return JSON.parse(readFileSync4(path, "utf-8"));
|
|
32279
|
-
}
|
|
32280
|
-
function writeJsonWithBackup(path, value) {
|
|
32281
|
-
const dir = dirname5(path);
|
|
32282
|
-
if (!existsSync7(dir)) mkdirSync5(dir, { recursive: true });
|
|
32283
|
-
let backupPath = "";
|
|
32284
|
-
if (existsSync7(path)) {
|
|
32285
|
-
backupPath = `${path}.caveat-backup-${Date.now()}`;
|
|
32286
|
-
copyFileSync2(path, backupPath);
|
|
32287
|
-
}
|
|
32288
|
-
writeFileSync4(path, `${JSON.stringify(value, null, 2)}
|
|
32289
|
-
`, "utf-8");
|
|
32290
|
-
return backupPath;
|
|
32291
|
-
}
|
|
32292
|
-
function upsertHook2(hooksJson, event, command, subcommand) {
|
|
32293
|
-
hooksJson.hooks ??= {};
|
|
32294
|
-
const list = hooksJson.hooks[event] ??= [];
|
|
32295
|
-
for (const entry of list) {
|
|
32296
|
-
for (const hook of entry.hooks ?? []) {
|
|
32297
|
-
if (isSameHookCommand2(hook.command, command)) return "unchanged";
|
|
32298
|
-
if (isCaveatCodexHookCommand(hook.command, subcommand)) {
|
|
32299
|
-
hook.command = command;
|
|
32300
|
-
hook.timeoutSec = 5;
|
|
32301
|
-
hook.async = false;
|
|
32302
|
-
hook.statusMessage = null;
|
|
32303
|
-
return "added";
|
|
32304
|
-
}
|
|
32305
|
-
}
|
|
32306
|
-
}
|
|
32307
|
-
list.push({
|
|
32308
|
-
hooks: [
|
|
32309
|
-
{
|
|
32310
|
-
type: "command",
|
|
32311
|
-
command,
|
|
32312
|
-
timeoutSec: 5,
|
|
32313
|
-
async: false,
|
|
32314
|
-
statusMessage: null
|
|
32315
|
-
}
|
|
32316
|
-
]
|
|
32317
|
-
});
|
|
32318
|
-
return "added";
|
|
32319
|
-
}
|
|
32320
|
-
function removeHook2(hooksJson, event, command, subcommand) {
|
|
32321
|
-
const list = hooksJson.hooks?.[event];
|
|
32322
|
-
if (!list) return false;
|
|
32323
|
-
const before = list.length;
|
|
32324
|
-
const filtered = list.filter(
|
|
32325
|
-
(entry) => !entry.hooks?.some(
|
|
32326
|
-
(h) => isSameHookCommand2(h.command, command) || isCaveatCodexHookCommand(h.command, subcommand)
|
|
32327
|
-
)
|
|
32328
|
-
);
|
|
32329
|
-
if (filtered.length === before) return false;
|
|
32330
|
-
hooksJson.hooks[event] = filtered;
|
|
32331
|
-
return true;
|
|
32332
|
-
}
|
|
32333
|
-
function enableCodexHooksFeature(raw) {
|
|
32334
|
-
if (/^\s*codex_hooks\s*=\s*true\s*$/m.test(raw)) return { text: raw, changed: false };
|
|
32335
|
-
const lines = raw.split(/\r?\n/);
|
|
32336
|
-
const featuresStart = lines.findIndex((line) => /^\s*\[features]\s*$/.test(line));
|
|
32337
|
-
if (featuresStart === -1) {
|
|
32338
|
-
const prefix = raw.trimEnd();
|
|
32339
|
-
const text = `${prefix}${prefix ? "\n\n" : ""}[features]
|
|
32340
|
-
codex_hooks = true
|
|
32341
|
-
`;
|
|
32342
|
-
return { text, changed: true };
|
|
32343
|
-
}
|
|
32344
|
-
let insertAt = featuresStart + 1;
|
|
32345
|
-
for (let i = featuresStart + 1; i < lines.length; i += 1) {
|
|
32346
|
-
if (/^\s*\[.+]\s*$/.test(lines[i])) {
|
|
32347
|
-
break;
|
|
32348
|
-
}
|
|
32349
|
-
if (/^\s*codex_hooks\s*=/.test(lines[i])) {
|
|
32350
|
-
lines[i] = "codex_hooks = true";
|
|
32351
|
-
return { text: `${lines.join("\n").trimEnd()}
|
|
32352
|
-
`, changed: true };
|
|
32353
|
-
}
|
|
32354
|
-
}
|
|
32355
|
-
lines.splice(insertAt, 0, "codex_hooks = true");
|
|
32356
|
-
return { text: `${lines.join("\n").trimEnd()}
|
|
32357
|
-
`, changed: true };
|
|
32358
|
-
}
|
|
32359
|
-
function writeConfigWithBackup(path, text) {
|
|
32360
|
-
const dir = dirname5(path);
|
|
32361
|
-
if (!existsSync7(dir)) mkdirSync5(dir, { recursive: true });
|
|
32362
|
-
let backupPath = "";
|
|
32363
|
-
if (existsSync7(path)) {
|
|
32364
|
-
backupPath = `${path}.caveat-backup-${Date.now()}`;
|
|
32365
|
-
copyFileSync2(path, backupPath);
|
|
32366
|
-
}
|
|
32367
|
-
writeFileSync4(path, text, "utf-8");
|
|
32368
|
-
return backupPath;
|
|
32369
|
-
}
|
|
32370
|
-
function installCodexHooks(opts) {
|
|
32371
|
-
const hooksPath = join10(opts.codexHome, "hooks.json");
|
|
32372
|
-
const configPath = join10(opts.codexHome, "config.toml");
|
|
32373
|
-
const hooksJson = readHooks(hooksPath);
|
|
32374
|
-
const userPromptSubmit = upsertHook2(
|
|
32375
|
-
hooksJson,
|
|
32376
|
-
"UserPromptSubmit",
|
|
32377
|
-
hookCommand2(opts.nodePath, opts.cliScriptPath, "user-prompt-submit"),
|
|
32378
|
-
"user-prompt-submit"
|
|
32379
|
-
);
|
|
32380
|
-
const postToolUse = upsertHook2(
|
|
32381
|
-
hooksJson,
|
|
32382
|
-
"PostToolUse",
|
|
32383
|
-
hookCommand2(opts.nodePath, opts.cliScriptPath, "post-tool-use"),
|
|
32384
|
-
"post-tool-use"
|
|
32385
|
-
);
|
|
32386
|
-
const stop = upsertHook2(
|
|
32387
|
-
hooksJson,
|
|
32388
|
-
"Stop",
|
|
32389
|
-
hookCommand2(opts.nodePath, opts.cliScriptPath, "stop"),
|
|
32390
|
-
"stop"
|
|
32391
|
-
);
|
|
32392
|
-
const rawConfig = existsSync7(configPath) ? readFileSync4(configPath, "utf-8") : "";
|
|
32393
|
-
const enabled = enableCodexHooksFeature(rawConfig);
|
|
32394
|
-
const anyHookAdded = userPromptSubmit === "added" || postToolUse === "added" || stop === "added";
|
|
32395
|
-
let backupPath;
|
|
32396
|
-
let configBackupPath;
|
|
32397
|
-
if (opts.dryRun) {
|
|
32398
|
-
opts.logger.info(`[dry-run] would update ${hooksPath}`);
|
|
32399
|
-
opts.logger.info(`[dry-run] would ensure [features].codex_hooks = true in ${configPath}`);
|
|
32400
|
-
} else {
|
|
32401
|
-
if (anyHookAdded) {
|
|
32402
|
-
const backup = writeJsonWithBackup(hooksPath, hooksJson);
|
|
32403
|
-
if (backup) backupPath = backup;
|
|
32404
|
-
}
|
|
32405
|
-
if (enabled.changed) {
|
|
32406
|
-
const backup = writeConfigWithBackup(configPath, enabled.text);
|
|
32407
|
-
if (backup) configBackupPath = backup;
|
|
32408
|
-
}
|
|
32409
|
-
}
|
|
32410
|
-
return {
|
|
32411
|
-
hooks: { userPromptSubmit, postToolUse, stop },
|
|
32412
|
-
feature: enabled.changed ? "enabled" : "unchanged",
|
|
32413
|
-
backupPath,
|
|
32414
|
-
configBackupPath
|
|
32415
|
-
};
|
|
32416
|
-
}
|
|
32417
|
-
function uninstallCodexHooks(opts) {
|
|
32418
|
-
const hooksPath = join10(opts.codexHome, "hooks.json");
|
|
32419
|
-
const hooksJson = readHooks(hooksPath);
|
|
32420
|
-
const userPromptSubmitRemoved = removeHook2(
|
|
32421
|
-
hooksJson,
|
|
32422
|
-
"UserPromptSubmit",
|
|
32423
|
-
hookCommand2(opts.nodePath, opts.cliScriptPath, "user-prompt-submit"),
|
|
32424
|
-
"user-prompt-submit"
|
|
32425
|
-
);
|
|
32426
|
-
const postToolUseRemoved = removeHook2(
|
|
32427
|
-
hooksJson,
|
|
32428
|
-
"PostToolUse",
|
|
32429
|
-
hookCommand2(opts.nodePath, opts.cliScriptPath, "post-tool-use"),
|
|
32430
|
-
"post-tool-use"
|
|
32431
|
-
);
|
|
32432
|
-
const stopRemoved = removeHook2(
|
|
32433
|
-
hooksJson,
|
|
32434
|
-
"Stop",
|
|
32435
|
-
hookCommand2(opts.nodePath, opts.cliScriptPath, "stop"),
|
|
32436
|
-
"stop"
|
|
32437
|
-
);
|
|
32438
|
-
let backupPath;
|
|
32439
|
-
if (opts.dryRun) {
|
|
32440
|
-
opts.logger.info(`[dry-run] would remove Caveat Codex hooks from ${hooksPath}`);
|
|
32441
|
-
} else if (userPromptSubmitRemoved || postToolUseRemoved || stopRemoved) {
|
|
32442
|
-
const backup = writeJsonWithBackup(hooksPath, hooksJson);
|
|
32443
|
-
if (backup) backupPath = backup;
|
|
32444
|
-
}
|
|
32445
|
-
return {
|
|
32446
|
-
hooks: {
|
|
32447
|
-
userPromptSubmit: userPromptSubmitRemoved ? "added" : "unchanged",
|
|
32448
|
-
postToolUse: postToolUseRemoved ? "added" : "unchanged",
|
|
32449
|
-
stop: stopRemoved ? "added" : "unchanged"
|
|
32450
|
-
},
|
|
32451
|
-
feature: "unchanged",
|
|
32452
|
-
backupPath
|
|
32453
|
-
};
|
|
32454
|
-
}
|
|
32455
|
-
function detectCodexHookInstallation(codexHome) {
|
|
32456
|
-
const hooksPath = join10(codexHome, "hooks.json");
|
|
32457
|
-
const hooksJson = readHooks(hooksPath);
|
|
32458
|
-
const hooks = {
|
|
32459
|
-
userPromptSubmit: hasCaveatHook(
|
|
32460
|
-
hooksJson,
|
|
32461
|
-
"UserPromptSubmit",
|
|
32462
|
-
eventCommandFragment("user-prompt-submit")
|
|
32463
|
-
),
|
|
32464
|
-
postToolUse: hasCaveatHook(hooksJson, "PostToolUse", eventCommandFragment("post-tool-use")),
|
|
32465
|
-
stop: hasCaveatHook(hooksJson, "Stop", eventCommandFragment("stop"))
|
|
32466
|
-
};
|
|
32467
|
-
const count = Object.values(hooks).filter(Boolean).length;
|
|
32468
|
-
return {
|
|
32469
|
-
installation: count === 0 ? "not-installed" : count === 3 ? "installed" : "partial",
|
|
32470
|
-
hooksPath,
|
|
32471
|
-
hooks
|
|
32472
|
-
};
|
|
32473
|
-
}
|
|
32474
|
-
|
|
32475
|
-
// src/commands/codexHookCmd.ts
|
|
32476
33139
|
var silentLogger2 = {
|
|
32477
33140
|
info: () => {
|
|
32478
33141
|
},
|
|
@@ -32512,31 +33175,44 @@ function buildContextSafely2() {
|
|
|
32512
33175
|
return null;
|
|
32513
33176
|
}
|
|
32514
33177
|
}
|
|
32515
|
-
function searchCaveatsFromTextSafely2(text) {
|
|
33178
|
+
function searchCaveatsFromTextSafely2(text, surface) {
|
|
32516
33179
|
if (!text) return [];
|
|
32517
33180
|
let db;
|
|
33181
|
+
let caveatHome;
|
|
33182
|
+
let hits;
|
|
32518
33183
|
try {
|
|
32519
33184
|
const ctx = buildContextSafely2();
|
|
32520
|
-
if (!ctx || !
|
|
33185
|
+
if (!ctx || !existsSync11(ctx.paths.dbPath)) return [];
|
|
33186
|
+
caveatHome = ctx.caveatHome;
|
|
32521
33187
|
db = openDb({ path: ctx.paths.dbPath });
|
|
32522
|
-
|
|
33188
|
+
hits = findCaveatsForPrompt(db, text, {
|
|
32523
33189
|
selfIdentity: defaultSelfIdentityTokens()
|
|
32524
33190
|
});
|
|
32525
|
-
if (hits.length > 0) {
|
|
32526
|
-
try {
|
|
32527
|
-
markHit(db, hits);
|
|
32528
|
-
} catch (err) {
|
|
32529
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
32530
|
-
process.stderr.write(`[caveat:codex-hook] markHit error: ${msg}
|
|
32531
|
-
`);
|
|
32532
|
-
}
|
|
32533
|
-
}
|
|
32534
|
-
return hits;
|
|
32535
33191
|
} catch (err) {
|
|
32536
33192
|
const msg = err instanceof Error ? err.message : String(err);
|
|
32537
33193
|
process.stderr.write(`[caveat:codex-hook] search error: ${msg}
|
|
32538
33194
|
`);
|
|
32539
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;
|
|
32540
33216
|
} finally {
|
|
32541
33217
|
db?.close();
|
|
32542
33218
|
}
|
|
@@ -32586,10 +33262,10 @@ function processExitCodeFromText(text) {
|
|
|
32586
33262
|
return m ? Number(m[1]) : null;
|
|
32587
33263
|
}
|
|
32588
33264
|
function transcriptToolOutput(transcriptPath, toolUseId) {
|
|
32589
|
-
if (!transcriptPath || !toolUseId || !
|
|
33265
|
+
if (!transcriptPath || !toolUseId || !existsSync11(transcriptPath)) return null;
|
|
32590
33266
|
let raw = "";
|
|
32591
33267
|
try {
|
|
32592
|
-
raw =
|
|
33268
|
+
raw = readFileSync6(transcriptPath, "utf-8");
|
|
32593
33269
|
} catch {
|
|
32594
33270
|
return null;
|
|
32595
33271
|
}
|
|
@@ -32685,7 +33361,10 @@ function codexContextOutput(text, eventName = "UserPromptSubmit") {
|
|
|
32685
33361
|
function drainForSession2(sessionId) {
|
|
32686
33362
|
const ctx = buildContextSafely2();
|
|
32687
33363
|
if (!ctx) return [];
|
|
32688
|
-
return
|
|
33364
|
+
return [
|
|
33365
|
+
...drainPendingReminders(ctx.caveatHome, sessionId),
|
|
33366
|
+
...drainGlobalPendingReminders(ctx.caveatHome)
|
|
33367
|
+
];
|
|
32689
33368
|
}
|
|
32690
33369
|
function codexContextDedupeKey(text) {
|
|
32691
33370
|
if (text.startsWith(CODEX_STOP_REMINDER_PREFIX)) return "codex-stop-reminder";
|
|
@@ -32727,20 +33406,20 @@ function stopSignalKey2(signals, related) {
|
|
|
32727
33406
|
return createHash2("sha256").update(body).digest("hex");
|
|
32728
33407
|
}
|
|
32729
33408
|
function stopStatePath2(caveatHome, sessionId) {
|
|
32730
|
-
return
|
|
33409
|
+
return join13(caveatHome, CODEX_STOP_STATE_DIR, `${sanitizeCodexStateId(sessionId)}.txt`);
|
|
32731
33410
|
}
|
|
32732
33411
|
function wasStopReminderQueued2(caveatHome, sessionId, key) {
|
|
32733
33412
|
const path = stopStatePath2(caveatHome, sessionId);
|
|
32734
33413
|
try {
|
|
32735
|
-
return
|
|
33414
|
+
return readFileSync6(path, "utf-8") === key;
|
|
32736
33415
|
} catch {
|
|
32737
33416
|
return false;
|
|
32738
33417
|
}
|
|
32739
33418
|
}
|
|
32740
33419
|
function markStopReminderQueued2(caveatHome, sessionId, key) {
|
|
32741
33420
|
const path = stopStatePath2(caveatHome, sessionId);
|
|
32742
|
-
mkdirSync6(
|
|
32743
|
-
|
|
33421
|
+
mkdirSync6(join13(caveatHome, CODEX_STOP_STATE_DIR), { recursive: true });
|
|
33422
|
+
writeFileSync6(path, key, "utf-8");
|
|
32744
33423
|
}
|
|
32745
33424
|
function queueStopForSession2(sessionId, signals, related) {
|
|
32746
33425
|
const ctx = buildContextSafely2();
|
|
@@ -32781,7 +33460,7 @@ async function processCodexWorkerJob(job, opts = { waitForTranscript: true }) {
|
|
|
32781
33460
|
}
|
|
32782
33461
|
}
|
|
32783
33462
|
if (!knownError && job.allowSymptomOnly !== true) return;
|
|
32784
|
-
const hits = searchCaveatsFromTextSafely2(searchText);
|
|
33463
|
+
const hits = searchCaveatsFromTextSafely2(searchText, "tool_error");
|
|
32785
33464
|
if (hits.length === 0) return;
|
|
32786
33465
|
const ctx = buildContextSafely2();
|
|
32787
33466
|
if (!ctx) return;
|
|
@@ -32793,12 +33472,12 @@ async function processCodexWorkerJob(job, opts = { waitForTranscript: true }) {
|
|
|
32793
33472
|
async function runCodexWorker(workFile) {
|
|
32794
33473
|
let raw;
|
|
32795
33474
|
try {
|
|
32796
|
-
raw =
|
|
33475
|
+
raw = readFileSync6(workFile, "utf-8");
|
|
32797
33476
|
} catch {
|
|
32798
33477
|
process.exit(0);
|
|
32799
33478
|
}
|
|
32800
33479
|
try {
|
|
32801
|
-
|
|
33480
|
+
unlinkSync(workFile);
|
|
32802
33481
|
} catch {
|
|
32803
33482
|
}
|
|
32804
33483
|
let job;
|
|
@@ -32810,8 +33489,8 @@ async function runCodexWorker(workFile) {
|
|
|
32810
33489
|
await processCodexWorkerJob(job);
|
|
32811
33490
|
process.exit(0);
|
|
32812
33491
|
}
|
|
32813
|
-
function runDiagnostics(codexHome = process.env.CODEX_HOME ??
|
|
32814
|
-
const features =
|
|
33492
|
+
function runDiagnostics(codexHome = process.env.CODEX_HOME ?? join13(homedir3(), ".codex")) {
|
|
33493
|
+
const features = spawnSync5("codex", ["features", "list"], {
|
|
32815
33494
|
encoding: "utf-8",
|
|
32816
33495
|
maxBuffer: 1024 * 1024
|
|
32817
33496
|
});
|
|
@@ -32856,7 +33535,7 @@ async function runCodexHook(name, arg) {
|
|
|
32856
33535
|
if (name === "user-prompt-submit") {
|
|
32857
33536
|
const contexts = sessionId ? drainForSession2(sessionId) : [];
|
|
32858
33537
|
const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
|
|
32859
|
-
const hits = searchCaveatsFromTextSafely2(prompt);
|
|
33538
|
+
const hits = searchCaveatsFromTextSafely2(prompt, "user_prompt");
|
|
32860
33539
|
if (hits.length > 0) {
|
|
32861
33540
|
contexts.push(userPromptSubmitReminderText(hits));
|
|
32862
33541
|
}
|
|
@@ -32873,11 +33552,35 @@ async function runCodexHook(name, arg) {
|
|
|
32873
33552
|
process.exit(0);
|
|
32874
33553
|
}
|
|
32875
33554
|
if (name === "stop") {
|
|
33555
|
+
try {
|
|
33556
|
+
const ctx = buildContextSafely2();
|
|
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
|
+
}
|
|
33574
|
+
} catch (err) {
|
|
33575
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
33576
|
+
process.stderr.write(`[caveat:codex-hook] auto reindex trigger error: ${msg}
|
|
33577
|
+
`);
|
|
33578
|
+
}
|
|
32876
33579
|
if (payload.stop_hook_active === true) process.exit(0);
|
|
32877
33580
|
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
|
|
32878
33581
|
const signals = transcriptPath ? loadSignalsSafely2(transcriptPath) : null;
|
|
32879
33582
|
if (!signals || !hasAnyStruggleSignal(signals)) process.exit(0);
|
|
32880
|
-
const related = searchCaveatsFromTextSafely2(struggleSearchText(signals));
|
|
33583
|
+
const related = searchCaveatsFromTextSafely2(struggleSearchText(signals), "stop");
|
|
32881
33584
|
if (sessionId) queueStopForSession2(sessionId, signals, related);
|
|
32882
33585
|
process.exit(0);
|
|
32883
33586
|
}
|
|
@@ -32887,19 +33590,15 @@ async function runCodexHook(name, arg) {
|
|
|
32887
33590
|
}
|
|
32888
33591
|
|
|
32889
33592
|
// src/commands/pull.ts
|
|
32890
|
-
import { existsSync as
|
|
32891
|
-
import { join as join12 } from "node:path";
|
|
33593
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
32892
33594
|
async function runPull(ctx) {
|
|
32893
|
-
|
|
33595
|
+
const hasCommunityDir = existsSync12(ctx.paths.communityDir);
|
|
33596
|
+
if (!hasCommunityDir) {
|
|
32894
33597
|
ctx.logger.info(
|
|
32895
33598
|
"no community repos yet \u2014 add one with `caveat community add <github-url>`."
|
|
32896
33599
|
);
|
|
32897
|
-
return;
|
|
32898
33600
|
}
|
|
32899
|
-
const pulls = await communityPull({
|
|
32900
|
-
communityDir: ctx.paths.communityDir,
|
|
32901
|
-
logger: ctx.logger
|
|
32902
|
-
});
|
|
33601
|
+
const pulls = hasCommunityDir ? await communityPull({ communityDir: ctx.paths.communityDir, logger: ctx.logger }) : [];
|
|
32903
33602
|
for (const p of pulls) {
|
|
32904
33603
|
if (p.status === "ok") {
|
|
32905
33604
|
ctx.logger.info(`${p.handle}: pulled`);
|
|
@@ -32909,29 +33608,88 @@ async function runPull(ctx) {
|
|
|
32909
33608
|
}
|
|
32910
33609
|
const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
|
|
32911
33610
|
try {
|
|
32912
|
-
|
|
32913
|
-
|
|
32914
|
-
|
|
32915
|
-
ctx.logger.
|
|
32916
|
-
}
|
|
32917
|
-
|
|
32918
|
-
|
|
32919
|
-
const source = `community/${entry.name}`;
|
|
32920
|
-
const root = join12(ctx.paths.communityDir, entry.name, "entries");
|
|
32921
|
-
if (!existsSync9(root)) continue;
|
|
32922
|
-
const scan = scanSource({ db, source, entriesRoot: root });
|
|
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 });
|
|
33617
|
+
for (const [source, scan] of Object.entries(result.perSource)) {
|
|
32923
33618
|
ctx.logger.info(`${source}: +${scan.added}`);
|
|
32924
33619
|
}
|
|
33620
|
+
writeDigestMarker(ctx.caveatHome, computeEntriesDigest(ctx.paths));
|
|
32925
33621
|
} finally {
|
|
32926
33622
|
db.close();
|
|
32927
33623
|
}
|
|
32928
33624
|
}
|
|
33625
|
+
function errorMessage5(err) {
|
|
33626
|
+
return err instanceof Error ? err.message : String(err);
|
|
33627
|
+
}
|
|
33628
|
+
|
|
33629
|
+
// src/commands/sync.ts
|
|
33630
|
+
async function defaultPrivateRepoUrl(opts, deps) {
|
|
33631
|
+
return defaultGitHubRepoUrl({
|
|
33632
|
+
repositoryName: "Caveat-Private",
|
|
33633
|
+
visibility: "private",
|
|
33634
|
+
yes: opts.yes,
|
|
33635
|
+
retryCommand: "caveat sync --init <url>",
|
|
33636
|
+
...deps
|
|
33637
|
+
});
|
|
33638
|
+
}
|
|
33639
|
+
async function runSync(ctx, opts, dependencies = {}) {
|
|
33640
|
+
try {
|
|
33641
|
+
const ghRunner = dependencies.ghRunner ?? runGh;
|
|
33642
|
+
const isTty = dependencies.isTty ?? (() => Boolean(process.stdin.isTTY));
|
|
33643
|
+
const confirm = dependencies.confirm ?? askOnce;
|
|
33644
|
+
const initValue = opts.init;
|
|
33645
|
+
if (initValue !== void 0 && initValue !== false) {
|
|
33646
|
+
const url2 = opts.repo ?? (typeof initValue === "string" ? initValue : await defaultPrivateRepoUrl(opts, { ghRunner, isTty, confirm }));
|
|
33647
|
+
const result2 = await initOwnSync({
|
|
33648
|
+
ownDir: ctx.paths.knowledgeRepo,
|
|
33649
|
+
url: url2,
|
|
33650
|
+
caveatHome: ctx.caveatHome,
|
|
33651
|
+
paths: ctx.paths,
|
|
33652
|
+
logger: ctx.logger,
|
|
33653
|
+
trustRemotePrivate: opts.trustRemotePrivate
|
|
33654
|
+
});
|
|
33655
|
+
ctx.logger.info(
|
|
33656
|
+
result2.remoteWasEmpty ? `initialized private sync remote: ${url2}` : `checked out existing private remote: ${url2} (branch ${result2.branch})`
|
|
33657
|
+
);
|
|
33658
|
+
return;
|
|
33659
|
+
}
|
|
33660
|
+
if (opts.repo) throw new Error("--repo requires --init");
|
|
33661
|
+
const result = await syncOwn({
|
|
33662
|
+
ownDir: ctx.paths.knowledgeRepo,
|
|
33663
|
+
caveatHome: ctx.caveatHome,
|
|
33664
|
+
paths: ctx.paths,
|
|
33665
|
+
logger: ctx.logger,
|
|
33666
|
+
trustRemotePrivate: opts.trustRemotePrivate,
|
|
33667
|
+
dryRun: opts.dryRun
|
|
33668
|
+
});
|
|
33669
|
+
if (result.dryRun) {
|
|
33670
|
+
ctx.logger.info(
|
|
33671
|
+
`[dry-run] ${result.changedFiles} local change(s) pending; remote ${result.pushUrls.join(", ")} (${result.probe.kind})`
|
|
33672
|
+
);
|
|
33673
|
+
} else {
|
|
33674
|
+
const rebased = result.pulled ? ", rebased onto remote" : "";
|
|
33675
|
+
ctx.logger.info(`synced ${result.branch} \u2192 origin (${result.changedFiles} local change(s)${rebased})`);
|
|
33676
|
+
try {
|
|
33677
|
+
resetAutoSyncFailureState(ctx.caveatHome);
|
|
33678
|
+
} catch {
|
|
33679
|
+
}
|
|
33680
|
+
}
|
|
33681
|
+
} catch (err) {
|
|
33682
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
33683
|
+
ctx.logger.error(message);
|
|
33684
|
+
process.exitCode = 1;
|
|
33685
|
+
}
|
|
33686
|
+
}
|
|
32929
33687
|
|
|
32930
33688
|
// src/commands/codexSidecar.ts
|
|
32931
|
-
import { spawnSync as
|
|
32932
|
-
import { mkdirSync as mkdirSync7, mkdtempSync as
|
|
32933
|
-
import { tmpdir as
|
|
32934
|
-
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";
|
|
32935
33693
|
import { cwd, exit } from "node:process";
|
|
32936
33694
|
function runCodexSidecarDiagnostics(logger, opts) {
|
|
32937
33695
|
const plan = buildCodexSidecarDiagnosticsCommand({
|
|
@@ -32963,8 +33721,8 @@ function runCodexSidecarWithCaveats(ctx, workflow, prompt, opts) {
|
|
|
32963
33721
|
process.stdout.write(JSON.stringify({ status: "skipped", decision }, null, 2) + "\n");
|
|
32964
33722
|
exit(0);
|
|
32965
33723
|
}
|
|
32966
|
-
const contextDir =
|
|
32967
|
-
const contextFile =
|
|
33724
|
+
const contextDir = mkdtempSync3(join14(tmpdir4(), "caveat-sidecar-context-"));
|
|
33725
|
+
const contextFile = join14(contextDir, "context.json");
|
|
32968
33726
|
let status = 1;
|
|
32969
33727
|
try {
|
|
32970
33728
|
const blocks = collectCaveatContextBlocks(ctx, {
|
|
@@ -32973,7 +33731,8 @@ function runCodexSidecarWithCaveats(ctx, workflow, prompt, opts) {
|
|
|
32973
33731
|
source: opts.source,
|
|
32974
33732
|
visibility: opts.visibility
|
|
32975
33733
|
});
|
|
32976
|
-
|
|
33734
|
+
const additionalBlocks = opts.additionalContextFile ? readHookSignalAdditionalContextFile(opts.additionalContextFile) : [];
|
|
33735
|
+
writeFileSync7(contextFile, JSON.stringify({ context: [...blocks, ...additionalBlocks] }, null, 2) + "\n", "utf-8");
|
|
32977
33736
|
const plan = buildCodexSidecarRunCommand({
|
|
32978
33737
|
workflow,
|
|
32979
33738
|
projectRoot: opts.project ?? cwd(),
|
|
@@ -32984,10 +33743,94 @@ function runCodexSidecarWithCaveats(ctx, workflow, prompt, opts) {
|
|
|
32984
33743
|
});
|
|
32985
33744
|
status = executePlan(ctx.logger, plan.command, plan.args, { saveResult: opts.saveResult });
|
|
32986
33745
|
} finally {
|
|
32987
|
-
|
|
33746
|
+
rmSync3(contextDir, { recursive: true, force: true });
|
|
32988
33747
|
}
|
|
32989
33748
|
exit(status);
|
|
32990
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
|
+
}
|
|
32991
33834
|
function runCodexSidecarWorkSmoke(ctx, prompt, opts) {
|
|
32992
33835
|
const plan = buildCodexSidecarRunCommand({
|
|
32993
33836
|
workflow: "work",
|
|
@@ -33028,7 +33871,7 @@ function defaultPreset(workflow) {
|
|
|
33028
33871
|
}
|
|
33029
33872
|
function executePlan(logger, command, args, options = {}) {
|
|
33030
33873
|
logger.info(`[codex-sidecar] ${command} ${args.map(shellDisplayQuote).join(" ")}`);
|
|
33031
|
-
const result =
|
|
33874
|
+
const result = spawnSync6(command, args, { encoding: "utf-8", stdio: "pipe" });
|
|
33032
33875
|
if (result.stdout) process.stdout.write(result.stdout);
|
|
33033
33876
|
if (result.stderr) process.stderr.write(result.stderr);
|
|
33034
33877
|
if (options.saveResult && result.stdout) {
|
|
@@ -33042,8 +33885,8 @@ function executePlan(logger, command, args, options = {}) {
|
|
|
33042
33885
|
}
|
|
33043
33886
|
function saveStructuredResult(path, stdout) {
|
|
33044
33887
|
const parsed = JSON.parse(stdout);
|
|
33045
|
-
mkdirSync7(
|
|
33046
|
-
|
|
33888
|
+
mkdirSync7(dirname7(path), { recursive: true });
|
|
33889
|
+
writeFileSync7(path, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
33047
33890
|
}
|
|
33048
33891
|
function shellDisplayQuote(value) {
|
|
33049
33892
|
return /[\s"'$`]/.test(value) ? JSON.stringify(value) : value;
|
|
@@ -33131,10 +33974,13 @@ function runCommunityRemove(ctx, handle, opts) {
|
|
|
33131
33974
|
|
|
33132
33975
|
// src/index.ts
|
|
33133
33976
|
var program = new Command();
|
|
33977
|
+
function collectRepeatable(value, previous) {
|
|
33978
|
+
return [...previous, value];
|
|
33979
|
+
}
|
|
33134
33980
|
program.name("caveat").description("External spec gotcha knowledge base CLI").version(CAVEAT_VERSION);
|
|
33135
33981
|
program.command("init").description(
|
|
33136
33982
|
"Initialize ~/.caveatrc.json, ~/.caveat/, and register Claude Code integration. Add knowledge sources later with `caveat community add <github-url>`."
|
|
33137
|
-
).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(
|
|
33138
33984
|
"--pending-stale-days <days>",
|
|
33139
33985
|
"sweep `<caveatHome>/pending/<sessionId>/` whose newest entry is older than this (default 7)",
|
|
33140
33986
|
(raw) => {
|
|
@@ -33150,7 +33996,11 @@ program.command("init").description(
|
|
|
33150
33996
|
await runInit(ctx, {
|
|
33151
33997
|
skipClaude: opts.skipClaude,
|
|
33152
33998
|
dryRun: opts.dryRun,
|
|
33153
|
-
pendingStaleDays: opts.pendingStaleDays
|
|
33999
|
+
pendingStaleDays: opts.pendingStaleDays,
|
|
34000
|
+
sync: opts.sync,
|
|
34001
|
+
publishTarget: opts.publishTarget,
|
|
34002
|
+
yes: opts.yes,
|
|
34003
|
+
skipCodexHook: opts.skipCodexHook
|
|
33154
34004
|
});
|
|
33155
34005
|
}
|
|
33156
34006
|
);
|
|
@@ -33158,9 +34008,9 @@ program.command("uninstall").description("Remove Claude Code MCP server and hook
|
|
|
33158
34008
|
const ctx = buildContext(stdoutLogger);
|
|
33159
34009
|
runUninstall(ctx, { dryRun: opts.dryRun });
|
|
33160
34010
|
});
|
|
33161
|
-
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) => {
|
|
33162
34012
|
const ctx = buildContext(stdoutLogger);
|
|
33163
|
-
runIndex(ctx, { full: opts.full });
|
|
34013
|
+
await runIndex(ctx, { full: opts.full });
|
|
33164
34014
|
});
|
|
33165
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(
|
|
33166
34016
|
(query, opts) => {
|
|
@@ -33198,6 +34048,14 @@ program.command("pull").description(
|
|
|
33198
34048
|
const ctx = buildContext(stdoutLogger);
|
|
33199
34049
|
await runPull(ctx);
|
|
33200
34050
|
});
|
|
34051
|
+
program.command("sync").description("Sync own Caveat entries to a private git remote").option("--init [url]", "initialize private remote sync (optionally with its URL)").option("--repo <url>", "private remote URL (requires --init)").option("--dry-run", "show pending own-repo changes without writing", false).option("--trust-remote-private", "continue when anonymous remote visibility cannot be determined", false).option("--yes", "approve creating the default GitHub private repository", false).action(async (opts) => {
|
|
34052
|
+
const ctx = buildContext(stdoutLogger);
|
|
34053
|
+
await runSync(ctx, opts);
|
|
34054
|
+
});
|
|
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) => {
|
|
34056
|
+
const ctx = buildContext(stdoutLogger);
|
|
34057
|
+
await runPublish(ctx, opts);
|
|
34058
|
+
});
|
|
33201
34059
|
program.command("serve").description("Start the read-only web share portal").option("--port <n>", "port number", (v) => Number(v), 4242).action(async (opts) => {
|
|
33202
34060
|
await runServe({ port: opts.port });
|
|
33203
34061
|
});
|
|
@@ -33205,7 +34063,7 @@ program.command("mcp-server").description("Run the MCP stdio server (registered
|
|
|
33205
34063
|
await runMcpServer();
|
|
33206
34064
|
});
|
|
33207
34065
|
program.command("hook <name> [arg]").description(
|
|
33208
|
-
"Run a Claude Code hook. name: user-prompt-submit | post-tool-use | stop | worker"
|
|
34066
|
+
"Run a Claude Code hook. name: user-prompt-submit | post-tool-use | stop | worker | reindex | autosync"
|
|
33209
34067
|
).action(async (name, arg) => {
|
|
33210
34068
|
await runHook(name, arg);
|
|
33211
34069
|
});
|
|
@@ -33227,6 +34085,11 @@ codexHook.command("install").description("Install Caveat hooks into ~/.codex/hoo
|
|
|
33227
34085
|
stdoutLogger.info(`PostToolUse hook: ${result.hooks.postToolUse}`);
|
|
33228
34086
|
stdoutLogger.info(`Stop hook: ${result.hooks.stop}`);
|
|
33229
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
|
+
}
|
|
33230
34093
|
if (result.backupPath) stdoutLogger.info(`hooks.json backed up: ${result.backupPath}`);
|
|
33231
34094
|
if (result.configBackupPath) {
|
|
33232
34095
|
stdoutLogger.info(`config.toml backed up: ${result.configBackupPath}`);
|
|
@@ -33272,7 +34135,7 @@ codexSidecar.command("diagnostics").description("Run codex-sidecar diagnostics f
|
|
|
33272
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) => {
|
|
33273
34136
|
runCodexSidecarSmoke(stdoutLogger, opts);
|
|
33274
34137
|
});
|
|
33275
|
-
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(
|
|
33276
34139
|
(workflow, prompt, opts) => {
|
|
33277
34140
|
const normalized = workflow === "risk" ? "risk-check" : workflow;
|
|
33278
34141
|
if (normalized !== "review" && normalized !== "explore" && normalized !== "opinion" && normalized !== "risk-check") {
|
|
@@ -33289,7 +34152,7 @@ codexSidecar.command("work-smoke [prompt]").description("Run codex_work in an is
|
|
|
33289
34152
|
runCodexSidecarWorkSmoke(ctx, prompt, opts);
|
|
33290
34153
|
}
|
|
33291
34154
|
);
|
|
33292
|
-
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/)");
|
|
33293
34156
|
community.command("add <url>").description("Shallow-clone a GitHub caveat repo into community/<handle>/").action(async (url2) => {
|
|
33294
34157
|
const ctx = buildContext(stdoutLogger);
|
|
33295
34158
|
await runCommunityAdd(ctx, url2);
|