caveat-cli 0.16.0 → 0.16.2
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-ZNAFNCPW.js → chunk-3MLJQLWY.js} +330 -96
- package/dist/chunk-3MLJQLWY.js.map +1 -0
- package/dist/index.js +317 -91
- package/dist/index.js.map +1 -1
- package/dist/{server-SZAVLE56.js → server-LAJCQXS3.js} +2 -2
- package/package.json +5 -4
- package/dist/chunk-ZNAFNCPW.js.map +0 -1
- /package/dist/{server-SZAVLE56.js.map → server-LAJCQXS3.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -11,11 +11,12 @@ import {
|
|
|
11
11
|
__export,
|
|
12
12
|
__toESM,
|
|
13
13
|
acquireReindexLock,
|
|
14
|
-
|
|
14
|
+
buildAndPublishPendingReminder,
|
|
15
15
|
buildCodexSidecarDiagnosticsCommand,
|
|
16
16
|
buildCodexSidecarReadOnlySmokeCommand,
|
|
17
17
|
buildCodexSidecarRunCommand,
|
|
18
18
|
buildHookSignalSidecarContextBlock,
|
|
19
|
+
buildPendingSemanticKey,
|
|
19
20
|
caveatEntriesToSidecarContextBlocks,
|
|
20
21
|
cleanupStalePendingDirs,
|
|
21
22
|
communityAdd,
|
|
@@ -26,11 +27,11 @@ import {
|
|
|
26
27
|
createKeyserverKeyProvider,
|
|
27
28
|
decideCodexSidecarExecution,
|
|
28
29
|
defaultSelfIdentityTokens,
|
|
29
|
-
|
|
30
|
-
drainPendingReminders,
|
|
30
|
+
drainPendingRemindersDetailed,
|
|
31
31
|
ensureUserConfig,
|
|
32
32
|
findCaveatHome,
|
|
33
|
-
|
|
33
|
+
findCaveatsForHook,
|
|
34
|
+
findCaveatsForHookSegments,
|
|
34
35
|
get,
|
|
35
36
|
hasAnyStruggleSignal,
|
|
36
37
|
initOwnSync,
|
|
@@ -63,7 +64,7 @@ import {
|
|
|
63
64
|
userPromptSubmitReminderText,
|
|
64
65
|
writeDigestMarker,
|
|
65
66
|
writeUserConfigPatch
|
|
66
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-3MLJQLWY.js";
|
|
67
68
|
|
|
68
69
|
// ../../node_modules/.pnpm/ajv@8.18.0/node_modules/ajv/dist/compile/codegen/code.js
|
|
69
70
|
var require_code = __commonJS({
|
|
@@ -7075,6 +7076,7 @@ function uninstallClaudeIntegration(opts) {
|
|
|
7075
7076
|
// src/codexHookInstall.ts
|
|
7076
7077
|
import { copyFileSync as copyFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
7077
7078
|
import { dirname as dirname3, join as join4 } from "node:path";
|
|
7079
|
+
import { parse as parseToml } from "smol-toml";
|
|
7078
7080
|
function quote2(p) {
|
|
7079
7081
|
return p.includes(" ") ? `"${p}"` : p;
|
|
7080
7082
|
}
|
|
@@ -7154,74 +7156,183 @@ function removeHook2(hooksJson, event, command, subcommand) {
|
|
|
7154
7156
|
return true;
|
|
7155
7157
|
}
|
|
7156
7158
|
function enableCodexHooksFeature(raw) {
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
if (unsupportedFeatureShape) {
|
|
7159
|
+
let parsed;
|
|
7160
|
+
try {
|
|
7161
|
+
parsed = parseToml(raw);
|
|
7162
|
+
} catch {
|
|
7162
7163
|
return {
|
|
7163
7164
|
status: "blocked",
|
|
7164
7165
|
text: raw,
|
|
7165
7166
|
changed: false,
|
|
7166
|
-
reason: "config.toml
|
|
7167
|
+
reason: "config.toml is invalid TOML; fix it before installing Caveat hooks"
|
|
7167
7168
|
};
|
|
7168
7169
|
}
|
|
7169
|
-
const
|
|
7170
|
-
if (
|
|
7170
|
+
const featureValue = parsed.features;
|
|
7171
|
+
if (featureValue !== void 0 && !isPlainRecord(featureValue)) {
|
|
7172
|
+
return {
|
|
7173
|
+
status: "blocked",
|
|
7174
|
+
text: raw,
|
|
7175
|
+
changed: false,
|
|
7176
|
+
reason: "config.toml features value is not a table"
|
|
7177
|
+
};
|
|
7178
|
+
}
|
|
7179
|
+
const features = featureValue;
|
|
7180
|
+
const canonicalValue = features?.hooks;
|
|
7181
|
+
const legacyValue = features?.codex_hooks;
|
|
7182
|
+
if (canonicalValue !== void 0 && typeof canonicalValue !== "boolean") {
|
|
7183
|
+
return { status: "blocked", text: raw, changed: false, reason: "[features].hooks must be boolean" };
|
|
7184
|
+
}
|
|
7185
|
+
if (legacyValue !== void 0 && typeof legacyValue !== "boolean") {
|
|
7186
|
+
return { status: "blocked", text: raw, changed: false, reason: "[features].codex_hooks must be boolean" };
|
|
7187
|
+
}
|
|
7188
|
+
if (canonicalValue === false) {
|
|
7189
|
+
return { status: "blocked", text: raw, changed: false, reason: "[features].hooks is explicitly false" };
|
|
7190
|
+
}
|
|
7191
|
+
if (legacyValue === false) {
|
|
7171
7192
|
return {
|
|
7172
7193
|
status: "blocked",
|
|
7173
7194
|
text: raw,
|
|
7174
7195
|
changed: false,
|
|
7175
|
-
reason: "
|
|
7196
|
+
reason: canonicalValue === true ? "[features].hooks conflicts with the deprecated codex_hooks alias" : "[features].codex_hooks is explicitly false"
|
|
7176
7197
|
};
|
|
7177
7198
|
}
|
|
7178
|
-
|
|
7179
|
-
|
|
7199
|
+
if (canonicalValue === true && legacyValue === void 0) {
|
|
7200
|
+
return { status: "enabled", text: raw, changed: false };
|
|
7201
|
+
}
|
|
7202
|
+
const lines = raw.split(/\r?\n/);
|
|
7203
|
+
const codeLines = maskTomlStringsAndComments(raw).split(/\r?\n/);
|
|
7204
|
+
const featureHeaders = codeLines.map((line, index) => /^\s*\[\s*features\s*]\s*$/.test(line) ? index : -1).filter((index) => index >= 0);
|
|
7205
|
+
if (featureValue === void 0) {
|
|
7180
7206
|
const prefix = raw.trimEnd();
|
|
7181
7207
|
const text = `${prefix}${prefix ? "\n\n" : ""}[features]
|
|
7182
|
-
|
|
7208
|
+
hooks = true
|
|
7183
7209
|
`;
|
|
7184
7210
|
return { status: "enabled", text, changed: true };
|
|
7185
7211
|
}
|
|
7212
|
+
if (featureHeaders.length !== 1) {
|
|
7213
|
+
return {
|
|
7214
|
+
status: "blocked",
|
|
7215
|
+
text: raw,
|
|
7216
|
+
changed: false,
|
|
7217
|
+
reason: "config.toml uses a features table shape that Caveat will not rewrite safely"
|
|
7218
|
+
};
|
|
7219
|
+
}
|
|
7220
|
+
const featuresStart = featureHeaders[0];
|
|
7186
7221
|
const assignments = [];
|
|
7187
7222
|
for (let i = featuresStart + 1; i < lines.length; i += 1) {
|
|
7188
|
-
if (/^\s*\[\[
|
|
7223
|
+
if (/^\s*\[\[?/.test(codeLines[i])) {
|
|
7189
7224
|
break;
|
|
7190
7225
|
}
|
|
7191
|
-
const assignment = /^\s*codex_hooks
|
|
7226
|
+
const assignment = /^\s*(hooks|codex_hooks)\s*=/.exec(codeLines[i]);
|
|
7192
7227
|
if (assignment) {
|
|
7193
|
-
assignments.push({ index: i,
|
|
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
|
-
};
|
|
7228
|
+
assignments.push({ index: i, key: assignment[1] });
|
|
7201
7229
|
}
|
|
7202
7230
|
}
|
|
7203
|
-
|
|
7231
|
+
const canonical = assignments.filter((assignment) => assignment.key === "hooks");
|
|
7232
|
+
const legacy = assignments.filter((assignment) => assignment.key === "codex_hooks");
|
|
7233
|
+
if (canonicalValue !== void 0 && canonical.length !== 1 || legacyValue !== void 0 && legacy.length !== 1) {
|
|
7204
7234
|
return {
|
|
7205
7235
|
status: "blocked",
|
|
7206
7236
|
text: raw,
|
|
7207
7237
|
changed: false,
|
|
7208
|
-
reason: "[features].
|
|
7238
|
+
reason: "[features].hooks uses a key shape that Caveat will not rewrite safely"
|
|
7209
7239
|
};
|
|
7210
7240
|
}
|
|
7211
|
-
const
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
}
|
|
7241
|
+
const current = canonical[0];
|
|
7242
|
+
const deprecated = legacy[0];
|
|
7243
|
+
if (canonicalValue === true && legacyValue === true && current && deprecated) {
|
|
7244
|
+
const inlineComment = /(#.*)$/.exec(lines[deprecated.index])?.[1];
|
|
7245
|
+
lines.splice(deprecated.index, 1);
|
|
7246
|
+
if (inlineComment) {
|
|
7247
|
+
const canonicalIndex = current.index > deprecated.index ? current.index - 1 : current.index;
|
|
7248
|
+
lines.splice(canonicalIndex + 1, 0, inlineComment);
|
|
7249
|
+
}
|
|
7250
|
+
return { status: "enabled", text: `${lines.join("\n").trimEnd()}
|
|
7251
|
+
`, changed: true };
|
|
7220
7252
|
}
|
|
7221
|
-
|
|
7253
|
+
if (legacyValue === true && canonicalValue === void 0 && deprecated) {
|
|
7254
|
+
lines[deprecated.index] = lines[deprecated.index].replace(/\bcodex_hooks\b/, "hooks");
|
|
7255
|
+
return { status: "enabled", text: `${lines.join("\n").trimEnd()}
|
|
7256
|
+
`, changed: true };
|
|
7257
|
+
}
|
|
7258
|
+
lines.splice(featuresStart + 1, 0, "hooks = true");
|
|
7222
7259
|
return { status: "enabled", text: `${lines.join("\n").trimEnd()}
|
|
7223
7260
|
`, changed: true };
|
|
7224
7261
|
}
|
|
7262
|
+
function isPlainRecord(value) {
|
|
7263
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
7264
|
+
}
|
|
7265
|
+
function maskTomlStringsAndComments(raw) {
|
|
7266
|
+
let state = "code";
|
|
7267
|
+
let output = "";
|
|
7268
|
+
for (let i = 0; i < raw.length; i += 1) {
|
|
7269
|
+
const char = raw[i];
|
|
7270
|
+
const triple = raw.slice(i, i + 3);
|
|
7271
|
+
if (char === "\n") {
|
|
7272
|
+
output += "\n";
|
|
7273
|
+
if (state === "comment") state = "code";
|
|
7274
|
+
continue;
|
|
7275
|
+
}
|
|
7276
|
+
if (state === "comment") {
|
|
7277
|
+
output += " ";
|
|
7278
|
+
continue;
|
|
7279
|
+
}
|
|
7280
|
+
if (state === "basic") {
|
|
7281
|
+
output += " ";
|
|
7282
|
+
if (char === "\\" && i + 1 < raw.length) {
|
|
7283
|
+
output += raw[i + 1] === "\n" ? "\n" : " ";
|
|
7284
|
+
i += 1;
|
|
7285
|
+
} else if (char === '"') state = "code";
|
|
7286
|
+
continue;
|
|
7287
|
+
}
|
|
7288
|
+
if (state === "literal") {
|
|
7289
|
+
output += " ";
|
|
7290
|
+
if (char === "'") state = "code";
|
|
7291
|
+
continue;
|
|
7292
|
+
}
|
|
7293
|
+
if (state === "multi-basic") {
|
|
7294
|
+
if (triple === '"""') {
|
|
7295
|
+
output += " ";
|
|
7296
|
+
i += 2;
|
|
7297
|
+
state = "code";
|
|
7298
|
+
} else {
|
|
7299
|
+
output += " ";
|
|
7300
|
+
if (char === "\\" && i + 1 < raw.length) {
|
|
7301
|
+
output += raw[i + 1] === "\n" ? "\n" : " ";
|
|
7302
|
+
i += 1;
|
|
7303
|
+
}
|
|
7304
|
+
}
|
|
7305
|
+
continue;
|
|
7306
|
+
}
|
|
7307
|
+
if (state === "multi-literal") {
|
|
7308
|
+
if (triple === "'''") {
|
|
7309
|
+
output += " ";
|
|
7310
|
+
i += 2;
|
|
7311
|
+
state = "code";
|
|
7312
|
+
} else output += " ";
|
|
7313
|
+
continue;
|
|
7314
|
+
}
|
|
7315
|
+
if (char === "#") {
|
|
7316
|
+
output += " ";
|
|
7317
|
+
state = "comment";
|
|
7318
|
+
} else if (triple === '"""') {
|
|
7319
|
+
output += " ";
|
|
7320
|
+
i += 2;
|
|
7321
|
+
state = "multi-basic";
|
|
7322
|
+
} else if (triple === "'''") {
|
|
7323
|
+
output += " ";
|
|
7324
|
+
i += 2;
|
|
7325
|
+
state = "multi-literal";
|
|
7326
|
+
} else if (char === '"') {
|
|
7327
|
+
output += " ";
|
|
7328
|
+
state = "basic";
|
|
7329
|
+
} else if (char === "'") {
|
|
7330
|
+
output += " ";
|
|
7331
|
+
state = "literal";
|
|
7332
|
+
} else output += char;
|
|
7333
|
+
}
|
|
7334
|
+
return output;
|
|
7335
|
+
}
|
|
7225
7336
|
function writeConfigWithBackup(path, text) {
|
|
7226
7337
|
const dir = dirname3(path);
|
|
7227
7338
|
if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
|
|
@@ -7269,7 +7380,7 @@ function installCodexHooks(opts) {
|
|
|
7269
7380
|
let configBackupPath;
|
|
7270
7381
|
if (opts.dryRun) {
|
|
7271
7382
|
opts.logger.info(`[dry-run] would update ${hooksPath}`);
|
|
7272
|
-
opts.logger.info(`[dry-run] would ensure [features].
|
|
7383
|
+
opts.logger.info(`[dry-run] would ensure [features].hooks = true in ${configPath}`);
|
|
7273
7384
|
} else {
|
|
7274
7385
|
if (anyHookAdded) {
|
|
7275
7386
|
const backup = writeJsonWithBackup(hooksPath, hooksJson);
|
|
@@ -7434,6 +7545,16 @@ import { spawnSync as spawnSync3 } from "node:child_process";
|
|
|
7434
7545
|
import { mkdtempSync, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
7435
7546
|
import { tmpdir } from "node:os";
|
|
7436
7547
|
import { join as join6 } from "node:path";
|
|
7548
|
+
var DEFAULT_HOOK_SIDECAR_TIMEOUT_MS = 12e4;
|
|
7549
|
+
var MAX_HOOK_SIDECAR_TIMEOUT_MS = 24e4;
|
|
7550
|
+
function resolveHookSidecarTimeoutMs(raw) {
|
|
7551
|
+
if (raw === void 0) return DEFAULT_HOOK_SIDECAR_TIMEOUT_MS;
|
|
7552
|
+
const value = Number(raw);
|
|
7553
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > MAX_HOOK_SIDECAR_TIMEOUT_MS) {
|
|
7554
|
+
throw new Error(`CAVEAT_HOOK_CODEX_SIDECAR_TIMEOUT_MS must be an integer from 1 to ${MAX_HOOK_SIDECAR_TIMEOUT_MS}`);
|
|
7555
|
+
}
|
|
7556
|
+
return value;
|
|
7557
|
+
}
|
|
7437
7558
|
function compactFailureMessage(message) {
|
|
7438
7559
|
const singleLine = message.replace(/\s+/g, " ").trim();
|
|
7439
7560
|
if (!singleLine) return "unknown error";
|
|
@@ -7497,7 +7618,9 @@ function runCodexSidecarAdvisory(input) {
|
|
|
7497
7618
|
const result = spawnSync3(process.execPath, args, {
|
|
7498
7619
|
cwd: input.projectRoot,
|
|
7499
7620
|
encoding: "utf-8",
|
|
7500
|
-
|
|
7621
|
+
// Keep at least one minute between the longest supported model call and
|
|
7622
|
+
// the pending single-flight claim TTL (five minutes).
|
|
7623
|
+
timeout: resolveHookSidecarTimeoutMs(process.env.CAVEAT_HOOK_CODEX_SIDECAR_TIMEOUT_MS),
|
|
7501
7624
|
maxBuffer: 10 * 1024 * 1024
|
|
7502
7625
|
});
|
|
7503
7626
|
if (result.error) {
|
|
@@ -7794,7 +7917,7 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }, depende
|
|
|
7794
7917
|
if (result.feature === "blocked") {
|
|
7795
7918
|
codexHookState = "skipped";
|
|
7796
7919
|
ctx.logger.warn(
|
|
7797
|
-
`${result.blockedReason}; preserving explicit consent. Set \`
|
|
7920
|
+
`${result.blockedReason}; preserving explicit consent. Set \`hooks = true\` in ${join8(codexHome, "config.toml")}, then rerun \`caveat init\`.`
|
|
7798
7921
|
);
|
|
7799
7922
|
} else if (opts.dryRun) {
|
|
7800
7923
|
codexHookState = "skipped";
|
|
@@ -8095,7 +8218,7 @@ function runStats(ctx) {
|
|
|
8095
8218
|
|
|
8096
8219
|
// src/commands/serve.ts
|
|
8097
8220
|
async function runServe(opts) {
|
|
8098
|
-
const { startServer } = await import("./server-
|
|
8221
|
+
const { startServer } = await import("./server-LAJCQXS3.js");
|
|
8099
8222
|
const { port, host } = startServer({ port: opts.port });
|
|
8100
8223
|
process.stdout.write(`[caveat] web portal: http://${host}:${port}/
|
|
8101
8224
|
`);
|
|
@@ -32560,8 +32683,10 @@ function buildContextSafely() {
|
|
|
32560
32683
|
return null;
|
|
32561
32684
|
}
|
|
32562
32685
|
}
|
|
32563
|
-
function
|
|
32564
|
-
|
|
32686
|
+
function searchCaveatsSafely(input) {
|
|
32687
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
32688
|
+
const queryForLog = inputs.map((item) => item.surface === "user_prompt" ? item.topicText || item.failureText : item.failureText).filter(Boolean).join("\n");
|
|
32689
|
+
if (inputs.length === 0 || inputs.every((item) => !item.topicText && !item.failureText)) return [];
|
|
32565
32690
|
let db;
|
|
32566
32691
|
let caveatHome;
|
|
32567
32692
|
let hits;
|
|
@@ -32570,9 +32695,10 @@ function searchCaveatsFromTextSafely(text, surface) {
|
|
|
32570
32695
|
if (!ctx || !existsSync10(ctx.paths.dbPath)) return [];
|
|
32571
32696
|
caveatHome = ctx.caveatHome;
|
|
32572
32697
|
db = openDb({ path: ctx.paths.dbPath });
|
|
32573
|
-
|
|
32698
|
+
const searchOptions = {
|
|
32574
32699
|
selfIdentity: defaultSelfIdentityTokens()
|
|
32575
|
-
}
|
|
32700
|
+
};
|
|
32701
|
+
hits = inputs.length === 1 ? findCaveatsForHook(db, inputs[0], searchOptions) : findCaveatsForHookSegments(db, inputs, searchOptions);
|
|
32576
32702
|
} catch (err) {
|
|
32577
32703
|
const msg = err instanceof Error ? err.message : String(err);
|
|
32578
32704
|
process.stderr.write(`[caveat:hook] search error: ${msg}
|
|
@@ -32589,7 +32715,7 @@ function searchCaveatsFromTextSafely(text, surface) {
|
|
|
32589
32715
|
}
|
|
32590
32716
|
} else {
|
|
32591
32717
|
try {
|
|
32592
|
-
logHookQueryMiss({ caveatHome, agent: "claude", surface, query:
|
|
32718
|
+
logHookQueryMiss({ caveatHome, agent: "claude", surface: inputs[0].surface, query: queryForLog });
|
|
32593
32719
|
} catch (err) {
|
|
32594
32720
|
const msg = err instanceof Error ? err.message : String(err);
|
|
32595
32721
|
process.stderr.write(`[caveat:hook] query log error: ${msg}
|
|
@@ -32615,13 +32741,19 @@ function loadSignalsSafely(path) {
|
|
|
32615
32741
|
function systemReminderOutput(text) {
|
|
32616
32742
|
return `<system-reminder>${text.replace(/</g, "\u2039").replace(/>/g, "\u203A")}</system-reminder>`;
|
|
32617
32743
|
}
|
|
32744
|
+
function claudePendingCleanupFailureText() {
|
|
32745
|
+
return "[caveat:hook] pending reminder cleanup failed";
|
|
32746
|
+
}
|
|
32618
32747
|
function drainForSession(sessionId) {
|
|
32619
32748
|
const ctx = buildContextSafely();
|
|
32620
32749
|
if (!ctx) return [];
|
|
32621
|
-
|
|
32622
|
-
|
|
32623
|
-
|
|
32624
|
-
|
|
32750
|
+
const local = drainPendingRemindersDetailed(ctx.caveatHome, sessionId);
|
|
32751
|
+
const global = drainPendingRemindersDetailed(ctx.caveatHome, "_global");
|
|
32752
|
+
for (const _failure of [...local.cleanupFailures, ...global.cleanupFailures]) {
|
|
32753
|
+
process.stderr.write(`${claudePendingCleanupFailureText()}
|
|
32754
|
+
`);
|
|
32755
|
+
}
|
|
32756
|
+
return [...local.reminders, ...global.reminders];
|
|
32625
32757
|
}
|
|
32626
32758
|
function claudeContextDedupeKey(text) {
|
|
32627
32759
|
if (text.startsWith(CLAUDE_STOP_REMINDER_PREFIX)) return "claude-stop-reminder";
|
|
@@ -32640,7 +32772,7 @@ function compactClaudeContexts(contexts) {
|
|
|
32640
32772
|
}
|
|
32641
32773
|
selected.reverse();
|
|
32642
32774
|
const limited = selected.slice(-CLAUDE_MAX_CONTEXT_BLOCKS);
|
|
32643
|
-
const omitted =
|
|
32775
|
+
const omitted = selected.length - limited.length;
|
|
32644
32776
|
if (omitted > 0) {
|
|
32645
32777
|
limited.push(
|
|
32646
32778
|
`[caveat] pending reminder ${omitted} \u4EF6\u3092\u91CD\u8907\u307E\u305F\u306F\u4E0A\u9650\u306B\u3088\u308A\u7701\u7565\u3057\u307E\u3057\u305F\u3002`
|
|
@@ -32685,8 +32817,20 @@ function queueStopForSession(sessionId, signals, related) {
|
|
|
32685
32817
|
if (!ctx) return;
|
|
32686
32818
|
const key = stopSignalKey(signals, related);
|
|
32687
32819
|
if (wasStopReminderQueued(ctx.caveatHome, sessionId, key)) return;
|
|
32820
|
+
let result;
|
|
32821
|
+
try {
|
|
32822
|
+
result = buildAndPublishPendingReminder(ctx.caveatHome, sessionId, buildPendingSemanticKey({
|
|
32823
|
+
agent: "claude",
|
|
32824
|
+
surface: "stop",
|
|
32825
|
+
refs: related,
|
|
32826
|
+
stopSignalDigest: key
|
|
32827
|
+
}), () => buildStopReminder(signals, related));
|
|
32828
|
+
} catch {
|
|
32829
|
+
process.stderr.write("[caveat:hook] pending reminder build or publish failed\n");
|
|
32830
|
+
return;
|
|
32831
|
+
}
|
|
32832
|
+
if (!result.ran) return;
|
|
32688
32833
|
try {
|
|
32689
|
-
appendPendingReminder(ctx.caveatHome, sessionId, buildStopReminder(signals, related));
|
|
32690
32834
|
markStopReminderQueued(ctx.caveatHome, sessionId, key);
|
|
32691
32835
|
} catch (err) {
|
|
32692
32836
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -32717,6 +32861,20 @@ function extractToolResponseText(response) {
|
|
|
32717
32861
|
}
|
|
32718
32862
|
return "";
|
|
32719
32863
|
}
|
|
32864
|
+
function toolTopicText(payload) {
|
|
32865
|
+
const parts = [];
|
|
32866
|
+
const toolName = payload.tool_name ?? payload.toolName;
|
|
32867
|
+
if (typeof toolName === "string") parts.push(toolName);
|
|
32868
|
+
const input = payload.tool_input ?? payload.toolInput;
|
|
32869
|
+
if (typeof input === "string") parts.push(input);
|
|
32870
|
+
if (input !== null && typeof input === "object" && !Array.isArray(input)) {
|
|
32871
|
+
const record2 = input;
|
|
32872
|
+
for (const key of ["command", "cmd", "query", "url"]) {
|
|
32873
|
+
if (typeof record2[key] === "string") parts.push(record2[key]);
|
|
32874
|
+
}
|
|
32875
|
+
}
|
|
32876
|
+
return parts.join("\n");
|
|
32877
|
+
}
|
|
32720
32878
|
function isToolError(payload) {
|
|
32721
32879
|
if (payload.hook_event_name === "PostToolUseFailure") return true;
|
|
32722
32880
|
const resp = payload.tool_response ?? payload.toolResponse;
|
|
@@ -32740,7 +32898,7 @@ function spawnWorker(job) {
|
|
|
32740
32898
|
workDir = mkdtempSync2(join12(root, "job-"));
|
|
32741
32899
|
chmodSync(workDir, 448);
|
|
32742
32900
|
workFile = join12(workDir, `${randomBytes(4).toString("hex")}.json`);
|
|
32743
|
-
writeFileSync5(workFile, JSON.stringify({ ...job, schemaVersion: "caveat-worker-job/
|
|
32901
|
+
writeFileSync5(workFile, JSON.stringify({ ...job, schemaVersion: "caveat-worker-job/v2" }), { encoding: "utf-8", mode: 384, flag: "wx" });
|
|
32744
32902
|
} catch (err) {
|
|
32745
32903
|
const msg = err instanceof Error ? err.message : String(err);
|
|
32746
32904
|
process.stderr.write(`[caveat:hook] worker writefile error: ${msg}
|
|
@@ -32784,15 +32942,27 @@ async function runWorker(workFile) {
|
|
|
32784
32942
|
} catch {
|
|
32785
32943
|
process.exit(0);
|
|
32786
32944
|
}
|
|
32787
|
-
if (!job.
|
|
32788
|
-
const hits =
|
|
32945
|
+
if (!job.failureText || !job.sessionId) process.exit(0);
|
|
32946
|
+
const hits = searchCaveatsSafely({
|
|
32947
|
+
topicText: job.topicText,
|
|
32948
|
+
failureText: job.failureText,
|
|
32949
|
+
surface: "tool_error"
|
|
32950
|
+
});
|
|
32789
32951
|
if (hits.length === 0) process.exit(0);
|
|
32790
32952
|
const ctx = buildContextSafely();
|
|
32791
32953
|
if (!ctx) process.exit(0);
|
|
32954
|
+
let result;
|
|
32792
32955
|
try {
|
|
32793
|
-
|
|
32956
|
+
result = buildAndPublishPendingReminder(ctx.caveatHome, job.sessionId, buildPendingSemanticKey({
|
|
32957
|
+
agent: "claude",
|
|
32958
|
+
surface: "tool_error",
|
|
32959
|
+
refs: hits
|
|
32960
|
+
}), () => buildToolErrorReminder(job, hits));
|
|
32794
32961
|
} catch {
|
|
32962
|
+
process.stderr.write("[caveat:hook] pending reminder build or publish failed\n");
|
|
32963
|
+
process.exit(0);
|
|
32795
32964
|
}
|
|
32965
|
+
if (!result.ran) process.exit(0);
|
|
32796
32966
|
process.exit(0);
|
|
32797
32967
|
}
|
|
32798
32968
|
function cleanupWorkerDir(workDir, root) {
|
|
@@ -32859,12 +33029,20 @@ function isStaleWorkerJobDir(path) {
|
|
|
32859
33029
|
const stat = lstatSync(file2);
|
|
32860
33030
|
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
32861
33031
|
if (!stat.isFile() || stat.isSymbolicLink() || !hasPrivateOwnership(stat, uid)) return false;
|
|
32862
|
-
return
|
|
33032
|
+
return isKnownStaleWorkerJob(JSON.parse(readFileSync5(file2, "utf-8")));
|
|
32863
33033
|
}
|
|
32864
33034
|
function hasPrivateOwnership(stat, uid) {
|
|
32865
33035
|
return process.platform === "win32" || (stat.mode & 63) === 0 && (uid === void 0 || stat.uid === uid);
|
|
32866
33036
|
}
|
|
32867
33037
|
function isWorkerJob(value) {
|
|
33038
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
33039
|
+
const job = value;
|
|
33040
|
+
const keys = Object.keys(job).sort();
|
|
33041
|
+
if (!(keys.length === 4 && keys.join(",") === "failureText,schemaVersion,sessionId,topicText") && !(keys.length === 5 && keys.join(",") === "additionalContext,failureText,schemaVersion,sessionId,topicText")) return false;
|
|
33042
|
+
return job.schemaVersion === "caveat-worker-job/v2" && typeof job.sessionId === "string" && typeof job.topicText === "string" && typeof job.failureText === "string" && (job.additionalContext === void 0 || job.additionalContext !== null && typeof job.additionalContext === "object" && !Array.isArray(job.additionalContext));
|
|
33043
|
+
}
|
|
33044
|
+
function isKnownStaleWorkerJob(value) {
|
|
33045
|
+
if (isWorkerJob(value)) return true;
|
|
32868
33046
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
32869
33047
|
const job = value;
|
|
32870
33048
|
const keys = Object.keys(job).sort();
|
|
@@ -32960,7 +33138,7 @@ function buildToolErrorReminder(job, hits) {
|
|
|
32960
33138
|
const hasSidecarConfig = existsSync10(join12(projectRoot, ".codex-sidecar.yml"));
|
|
32961
33139
|
if (mode === "auto" && !hasSidecarConfig) return base;
|
|
32962
33140
|
const advisory = runCodexSidecarAdvisory({
|
|
32963
|
-
searchText: job.
|
|
33141
|
+
searchText: job.failureText,
|
|
32964
33142
|
limit: hits.length,
|
|
32965
33143
|
projectRoot,
|
|
32966
33144
|
prompt: [
|
|
@@ -33060,7 +33238,7 @@ async function runHook(name, arg) {
|
|
|
33060
33238
|
const contexts = name === "stop" ? [] : drainForSession(sessionId);
|
|
33061
33239
|
if (name === "user-prompt-submit") {
|
|
33062
33240
|
const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
|
|
33063
|
-
const hits =
|
|
33241
|
+
const hits = searchCaveatsSafely({ topicText: prompt, failureText: prompt, surface: "user_prompt" });
|
|
33064
33242
|
if (hits.length > 0) {
|
|
33065
33243
|
contexts.push(userPromptSubmitReminderText(hits));
|
|
33066
33244
|
}
|
|
@@ -33088,7 +33266,8 @@ async function runHook(name, arg) {
|
|
|
33088
33266
|
toolName: payload.tool_name ?? payload.toolName,
|
|
33089
33267
|
failureKind
|
|
33090
33268
|
});
|
|
33091
|
-
|
|
33269
|
+
const topicText = toolTopicText(payload);
|
|
33270
|
+
spawnWorker({ sessionId, topicText, failureText: errText, ...additionalContext ? { additionalContext } : {} });
|
|
33092
33271
|
}
|
|
33093
33272
|
process.exit(0);
|
|
33094
33273
|
}
|
|
@@ -33121,7 +33300,11 @@ async function runHook(name, arg) {
|
|
|
33121
33300
|
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
|
|
33122
33301
|
const signals = transcriptPath ? loadSignalsSafely(transcriptPath) : null;
|
|
33123
33302
|
if (!signals || !hasAnyStruggleSignal(signals)) process.exit(0);
|
|
33124
|
-
const related =
|
|
33303
|
+
const related = searchCaveatsSafely(signals.errorSnippets.map((failureText) => ({
|
|
33304
|
+
topicText: "",
|
|
33305
|
+
failureText,
|
|
33306
|
+
surface: "stop"
|
|
33307
|
+
})));
|
|
33125
33308
|
queueStopForSession(sessionId, signals, related);
|
|
33126
33309
|
process.exit(0);
|
|
33127
33310
|
}
|
|
@@ -33175,8 +33358,10 @@ function buildContextSafely2() {
|
|
|
33175
33358
|
return null;
|
|
33176
33359
|
}
|
|
33177
33360
|
}
|
|
33178
|
-
function
|
|
33179
|
-
|
|
33361
|
+
function searchCaveatsSafely2(input) {
|
|
33362
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
33363
|
+
const queryForLog = inputs.map((item) => item.surface === "user_prompt" ? item.topicText || item.failureText : item.failureText).filter(Boolean).join("\n");
|
|
33364
|
+
if (inputs.length === 0 || inputs.every((item) => !item.topicText && !item.failureText)) return [];
|
|
33180
33365
|
let db;
|
|
33181
33366
|
let caveatHome;
|
|
33182
33367
|
let hits;
|
|
@@ -33185,9 +33370,10 @@ function searchCaveatsFromTextSafely2(text, surface) {
|
|
|
33185
33370
|
if (!ctx || !existsSync11(ctx.paths.dbPath)) return [];
|
|
33186
33371
|
caveatHome = ctx.caveatHome;
|
|
33187
33372
|
db = openDb({ path: ctx.paths.dbPath });
|
|
33188
|
-
|
|
33373
|
+
const searchOptions = {
|
|
33189
33374
|
selfIdentity: defaultSelfIdentityTokens()
|
|
33190
|
-
}
|
|
33375
|
+
};
|
|
33376
|
+
hits = inputs.length === 1 ? findCaveatsForHook(db, inputs[0], searchOptions) : findCaveatsForHookSegments(db, inputs, searchOptions);
|
|
33191
33377
|
} catch (err) {
|
|
33192
33378
|
const msg = err instanceof Error ? err.message : String(err);
|
|
33193
33379
|
process.stderr.write(`[caveat:codex-hook] search error: ${msg}
|
|
@@ -33204,7 +33390,7 @@ function searchCaveatsFromTextSafely2(text, surface) {
|
|
|
33204
33390
|
}
|
|
33205
33391
|
} else {
|
|
33206
33392
|
try {
|
|
33207
|
-
logHookQueryMiss({ caveatHome, agent: "codex", surface, query:
|
|
33393
|
+
logHookQueryMiss({ caveatHome, agent: "codex", surface: inputs[0].surface, query: queryForLog });
|
|
33208
33394
|
} catch (err) {
|
|
33209
33395
|
const msg = err instanceof Error ? err.message : String(err);
|
|
33210
33396
|
process.stderr.write(`[caveat:codex-hook] query log error: ${msg}
|
|
@@ -33333,15 +33519,17 @@ function buildCodexPostToolUseWorkerJob(payload) {
|
|
|
33333
33519
|
const responseText = extractToolResponseText2(payload.tool_response ?? payload.toolResponse);
|
|
33334
33520
|
const inputText = toolInputText(payload.tool_input);
|
|
33335
33521
|
const transcriptOutput = transcriptPath && toolUseId ? transcriptToolOutput(transcriptPath, toolUseId) : null;
|
|
33336
|
-
const
|
|
33522
|
+
const topicText = inputText.trim();
|
|
33523
|
+
const failureText = [responseText, transcriptOutput ?? ""].map((s) => s.trim()).filter(Boolean).join("\n");
|
|
33337
33524
|
const knownError = isCodexToolError(payload);
|
|
33338
33525
|
if (knownError) {
|
|
33339
|
-
return { sessionId,
|
|
33526
|
+
return { sessionId, topicText, failureText, knownError: true, transcriptPath, toolUseId };
|
|
33340
33527
|
}
|
|
33341
|
-
if (transcriptPath && toolUseId && isShellLikeTool(payload) &&
|
|
33528
|
+
if (transcriptPath && toolUseId && isShellLikeTool(payload) && (topicText || failureText)) {
|
|
33342
33529
|
return {
|
|
33343
33530
|
sessionId,
|
|
33344
|
-
|
|
33531
|
+
topicText,
|
|
33532
|
+
failureText,
|
|
33345
33533
|
knownError: false,
|
|
33346
33534
|
allowSymptomOnly: true,
|
|
33347
33535
|
transcriptPath,
|
|
@@ -33358,13 +33546,19 @@ function codexContextOutput(text, eventName = "UserPromptSubmit") {
|
|
|
33358
33546
|
}
|
|
33359
33547
|
});
|
|
33360
33548
|
}
|
|
33549
|
+
function codexPendingCleanupFailureText() {
|
|
33550
|
+
return "[caveat:codex-hook] pending reminder cleanup failed";
|
|
33551
|
+
}
|
|
33361
33552
|
function drainForSession2(sessionId) {
|
|
33362
33553
|
const ctx = buildContextSafely2();
|
|
33363
33554
|
if (!ctx) return [];
|
|
33364
|
-
|
|
33365
|
-
|
|
33366
|
-
|
|
33367
|
-
|
|
33555
|
+
const local = drainPendingRemindersDetailed(ctx.caveatHome, sessionId);
|
|
33556
|
+
const global = drainPendingRemindersDetailed(ctx.caveatHome, "_global");
|
|
33557
|
+
for (const _failure of [...local.cleanupFailures, ...global.cleanupFailures]) {
|
|
33558
|
+
process.stderr.write(`${codexPendingCleanupFailureText()}
|
|
33559
|
+
`);
|
|
33560
|
+
}
|
|
33561
|
+
return [...local.reminders, ...global.reminders];
|
|
33368
33562
|
}
|
|
33369
33563
|
function codexContextDedupeKey(text) {
|
|
33370
33564
|
if (text.startsWith(CODEX_STOP_REMINDER_PREFIX)) return "codex-stop-reminder";
|
|
@@ -33383,7 +33577,7 @@ function compactCodexContexts(contexts) {
|
|
|
33383
33577
|
}
|
|
33384
33578
|
selected.reverse();
|
|
33385
33579
|
const limited = selected.slice(-CODEX_MAX_CONTEXT_BLOCKS);
|
|
33386
|
-
const omitted =
|
|
33580
|
+
const omitted = selected.length - limited.length;
|
|
33387
33581
|
if (omitted > 0) {
|
|
33388
33582
|
limited.push(`[caveat] pending reminder ${omitted} \u4EF6\u3092\u91CD\u8907\u307E\u305F\u306F\u4E0A\u9650\u306B\u3088\u308A\u7701\u7565\u3057\u307E\u3057\u305F\u3002`);
|
|
33389
33583
|
}
|
|
@@ -33426,8 +33620,20 @@ function queueStopForSession2(sessionId, signals, related) {
|
|
|
33426
33620
|
if (!ctx) return;
|
|
33427
33621
|
const key = stopSignalKey2(signals, related);
|
|
33428
33622
|
if (wasStopReminderQueued2(ctx.caveatHome, sessionId, key)) return;
|
|
33623
|
+
let result;
|
|
33624
|
+
try {
|
|
33625
|
+
result = buildAndPublishPendingReminder(ctx.caveatHome, sessionId, buildPendingSemanticKey({
|
|
33626
|
+
agent: "codex",
|
|
33627
|
+
surface: "stop",
|
|
33628
|
+
refs: related,
|
|
33629
|
+
stopSignalDigest: key
|
|
33630
|
+
}), () => stopReminderText(signals, related));
|
|
33631
|
+
} catch {
|
|
33632
|
+
process.stderr.write("[caveat:codex-hook] pending reminder build or publish failed\n");
|
|
33633
|
+
return;
|
|
33634
|
+
}
|
|
33635
|
+
if (!result.ran) return;
|
|
33429
33636
|
try {
|
|
33430
|
-
appendPendingReminder(ctx.caveatHome, sessionId, stopReminderText(signals, related));
|
|
33431
33637
|
markStopReminderQueued2(ctx.caveatHome, sessionId, key);
|
|
33432
33638
|
} catch (err) {
|
|
33433
33639
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -33445,8 +33651,8 @@ async function waitForTranscriptOutput(transcriptPath, toolUseId) {
|
|
|
33445
33651
|
return null;
|
|
33446
33652
|
}
|
|
33447
33653
|
async function processCodexWorkerJob(job, opts = { waitForTranscript: true }) {
|
|
33448
|
-
if (!job.
|
|
33449
|
-
let
|
|
33654
|
+
if (!job.topicText && !job.failureText || !job.sessionId) return;
|
|
33655
|
+
let failureText = job.failureText;
|
|
33450
33656
|
let knownError = job.knownError === true;
|
|
33451
33657
|
if (opts.waitForTranscript && job.transcriptPath && job.toolUseId) {
|
|
33452
33658
|
const transcriptOutput = await waitForTranscriptOutput(job.transcriptPath, job.toolUseId);
|
|
@@ -33456,18 +33662,30 @@ async function processCodexWorkerJob(job, opts = { waitForTranscript: true }) {
|
|
|
33456
33662
|
if (exit2 === 0) return;
|
|
33457
33663
|
knownError = true;
|
|
33458
33664
|
}
|
|
33459
|
-
|
|
33665
|
+
failureText = [failureText, transcriptOutput].filter(Boolean).join("\n");
|
|
33460
33666
|
}
|
|
33461
33667
|
}
|
|
33462
33668
|
if (!knownError && job.allowSymptomOnly !== true) return;
|
|
33463
|
-
const hits =
|
|
33669
|
+
const hits = searchCaveatsSafely2({
|
|
33670
|
+
topicText: job.topicText,
|
|
33671
|
+
failureText,
|
|
33672
|
+
surface: "tool_error"
|
|
33673
|
+
});
|
|
33464
33674
|
if (hits.length === 0) return;
|
|
33465
33675
|
const ctx = buildContextSafely2();
|
|
33466
33676
|
if (!ctx) return;
|
|
33677
|
+
let result;
|
|
33467
33678
|
try {
|
|
33468
|
-
|
|
33679
|
+
result = buildAndPublishPendingReminder(ctx.caveatHome, job.sessionId, buildPendingSemanticKey({
|
|
33680
|
+
agent: "codex",
|
|
33681
|
+
surface: "tool_error",
|
|
33682
|
+
refs: hits
|
|
33683
|
+
}), () => toolErrorReminderText(hits));
|
|
33469
33684
|
} catch {
|
|
33685
|
+
process.stderr.write("[caveat:codex-hook] pending reminder build or publish failed\n");
|
|
33686
|
+
return;
|
|
33470
33687
|
}
|
|
33688
|
+
if (!result.ran) return;
|
|
33471
33689
|
}
|
|
33472
33690
|
async function runCodexWorker(workFile) {
|
|
33473
33691
|
let raw;
|
|
@@ -33492,10 +33710,11 @@ async function runCodexWorker(workFile) {
|
|
|
33492
33710
|
function runDiagnostics(codexHome = process.env.CODEX_HOME ?? join13(homedir3(), ".codex")) {
|
|
33493
33711
|
const features = spawnSync5("codex", ["features", "list"], {
|
|
33494
33712
|
encoding: "utf-8",
|
|
33495
|
-
maxBuffer: 1024 * 1024
|
|
33713
|
+
maxBuffer: 1024 * 1024,
|
|
33714
|
+
env: codexFeatureListEnv(codexHome)
|
|
33496
33715
|
});
|
|
33497
33716
|
const featureOutput = [features.stdout, features.stderr].filter(Boolean).join("\n");
|
|
33498
|
-
const hasHooks = /^
|
|
33717
|
+
const hasHooks = /^hooks\s+\S+\s+true\b/m.test(featureOutput);
|
|
33499
33718
|
const installation = detectCodexHookInstallation(codexHome);
|
|
33500
33719
|
const result = {
|
|
33501
33720
|
availability: features.error || features.status !== 0 || !hasHooks ? "unavailable" : "available",
|
|
@@ -33505,11 +33724,14 @@ function runDiagnostics(codexHome = process.env.CODEX_HOME ?? join13(homedir3(),
|
|
|
33505
33724
|
codexHome,
|
|
33506
33725
|
hooksPath: installation.hooksPath,
|
|
33507
33726
|
installedHooks: installation.hooks,
|
|
33508
|
-
evidence: featureOutput.split("\n").find((line) => line.trim().startsWith("
|
|
33727
|
+
evidence: featureOutput.split("\n").find((line) => line.trim().startsWith("hooks")) ?? null
|
|
33509
33728
|
};
|
|
33510
33729
|
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
33511
33730
|
`);
|
|
33512
33731
|
}
|
|
33732
|
+
function codexFeatureListEnv(codexHome, inherited = process.env) {
|
|
33733
|
+
return { ...inherited, CODEX_HOME: codexHome };
|
|
33734
|
+
}
|
|
33513
33735
|
async function runCodexHook(name, arg) {
|
|
33514
33736
|
if (name === "diagnostics") {
|
|
33515
33737
|
runDiagnostics(arg);
|
|
@@ -33535,7 +33757,7 @@ async function runCodexHook(name, arg) {
|
|
|
33535
33757
|
if (name === "user-prompt-submit") {
|
|
33536
33758
|
const contexts = sessionId ? drainForSession2(sessionId) : [];
|
|
33537
33759
|
const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
|
|
33538
|
-
const hits =
|
|
33760
|
+
const hits = searchCaveatsSafely2({ topicText: prompt, failureText: prompt, surface: "user_prompt" });
|
|
33539
33761
|
if (hits.length > 0) {
|
|
33540
33762
|
contexts.push(userPromptSubmitReminderText(hits));
|
|
33541
33763
|
}
|
|
@@ -33580,7 +33802,11 @@ async function runCodexHook(name, arg) {
|
|
|
33580
33802
|
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
|
|
33581
33803
|
const signals = transcriptPath ? loadSignalsSafely2(transcriptPath) : null;
|
|
33582
33804
|
if (!signals || !hasAnyStruggleSignal(signals)) process.exit(0);
|
|
33583
|
-
const related =
|
|
33805
|
+
const related = searchCaveatsSafely2(signals.errorSnippets.map((failureText) => ({
|
|
33806
|
+
topicText: "",
|
|
33807
|
+
failureText,
|
|
33808
|
+
surface: "stop"
|
|
33809
|
+
})));
|
|
33584
33810
|
if (sessionId) queueStopForSession2(sessionId, signals, related);
|
|
33585
33811
|
process.exit(0);
|
|
33586
33812
|
}
|
|
@@ -34068,7 +34294,7 @@ program.command("hook <name> [arg]").description(
|
|
|
34068
34294
|
await runHook(name, arg);
|
|
34069
34295
|
});
|
|
34070
34296
|
var codexHook = program.command("codex-hook").description("Install or run Codex hooks for Caveat");
|
|
34071
|
-
codexHook.command("install").description("Install Caveat hooks into ~/.codex/hooks.json and enable
|
|
34297
|
+
codexHook.command("install").description("Install Caveat hooks into ~/.codex/hooks.json and enable hooks").option("--dry-run", "show planned changes without writing", false).option("--codex-home <path>", "Codex home directory", process.env.CODEX_HOME ?? `${process.env.HOME}/.codex`).action((opts) => {
|
|
34072
34298
|
const cliScriptPath = process.argv[1];
|
|
34073
34299
|
if (!cliScriptPath) {
|
|
34074
34300
|
process.stderr.write("[caveat:error] cannot determine CLI script path\n");
|
|
@@ -34084,10 +34310,10 @@ codexHook.command("install").description("Install Caveat hooks into ~/.codex/hoo
|
|
|
34084
34310
|
stdoutLogger.info(`UserPromptSubmit hook: ${result.hooks.userPromptSubmit}`);
|
|
34085
34311
|
stdoutLogger.info(`PostToolUse hook: ${result.hooks.postToolUse}`);
|
|
34086
34312
|
stdoutLogger.info(`Stop hook: ${result.hooks.stop}`);
|
|
34087
|
-
stdoutLogger.info(`
|
|
34313
|
+
stdoutLogger.info(`hooks feature: ${result.feature}`);
|
|
34088
34314
|
if (result.feature === "blocked") {
|
|
34089
34315
|
stdoutLogger.warn(
|
|
34090
|
-
`${result.blockedReason}; preserving explicit consent. Set \`
|
|
34316
|
+
`${result.blockedReason}; preserving explicit consent. Set \`hooks = true\` in ${opts.codexHome}/config.toml, then rerun \`caveat codex-hook install\`.`
|
|
34091
34317
|
);
|
|
34092
34318
|
}
|
|
34093
34319
|
if (result.backupPath) stdoutLogger.info(`hooks.json backed up: ${result.backupPath}`);
|