caveat-cli 0.7.0 → 0.10.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/caveat.js +0 -0
- package/dist/index.js +566 -105
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
package/dist/index.js
CHANGED
|
@@ -12976,7 +12976,7 @@ import { Command } from "commander";
|
|
|
12976
12976
|
|
|
12977
12977
|
// src/context.ts
|
|
12978
12978
|
import { homedir } from "node:os";
|
|
12979
|
-
import { join as
|
|
12979
|
+
import { join as join8 } from "node:path";
|
|
12980
12980
|
|
|
12981
12981
|
// ../../packages/core/dist/db.js
|
|
12982
12982
|
import { DatabaseSync } from "node:sqlite";
|
|
@@ -21203,56 +21203,334 @@ function communityRemove(opts) {
|
|
|
21203
21203
|
}
|
|
21204
21204
|
|
|
21205
21205
|
// ../../packages/core/dist/claudeHooks.js
|
|
21206
|
-
var
|
|
21207
|
-
|
|
21208
|
-
|
|
21209
|
-
|
|
21210
|
-
|
|
21211
|
-
|
|
21212
|
-
|
|
21213
|
-
|
|
21214
|
-
|
|
21215
|
-
|
|
21216
|
-
|
|
21217
|
-
|
|
21218
|
-
|
|
21219
|
-
|
|
21220
|
-
|
|
21221
|
-
|
|
21206
|
+
var PROMPT_TOKEN_MIN_LENGTH = 3;
|
|
21207
|
+
var PROMPT_MAX_CANDIDATE_TOKENS = 50;
|
|
21208
|
+
var DEFAULT_REMINDER_HIT_LIMIT = 5;
|
|
21209
|
+
var SYMPTOM_EXCERPT_LENGTH2 = 200;
|
|
21210
|
+
var SYMPTOM_LINE_MAX = 120;
|
|
21211
|
+
var MIN_DISTINCT_TOKEN_MATCHES_CEILING = 2;
|
|
21212
|
+
var CJK_CHAR = /[-ゟ゠-ヿ一-鿿ヲ-゚]/;
|
|
21213
|
+
function isCjkDominated(token) {
|
|
21214
|
+
return CJK_CHAR.test(token);
|
|
21215
|
+
}
|
|
21216
|
+
function expandToken(token, out) {
|
|
21217
|
+
if (isCjkDominated(token)) {
|
|
21218
|
+
if (token.length < PROMPT_TOKEN_MIN_LENGTH) return;
|
|
21219
|
+
for (let i2 = 0; i2 <= token.length - PROMPT_TOKEN_MIN_LENGTH; i2++) {
|
|
21220
|
+
out.push(token.slice(i2, i2 + PROMPT_TOKEN_MIN_LENGTH));
|
|
21221
|
+
}
|
|
21222
|
+
} else if (token.length >= PROMPT_TOKEN_MIN_LENGTH) {
|
|
21223
|
+
out.push(token);
|
|
21224
|
+
}
|
|
21225
|
+
}
|
|
21226
|
+
function extractPromptCandidates(prompt) {
|
|
21227
|
+
if (typeof prompt !== "string" || prompt.length === 0) return [];
|
|
21228
|
+
const cleaned = prompt.replace(/[^\p{L}\p{N}\s]/gu, " ");
|
|
21229
|
+
const rawTokens = cleaned.split(/\s+/).filter((t2) => t2.length > 0);
|
|
21230
|
+
const expanded = [];
|
|
21231
|
+
for (const t2 of rawTokens) expandToken(t2, expanded);
|
|
21232
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21233
|
+
const unique = [];
|
|
21234
|
+
for (const t2 of expanded) {
|
|
21235
|
+
const key = t2.toLowerCase();
|
|
21236
|
+
if (seen.has(key)) continue;
|
|
21237
|
+
seen.add(key);
|
|
21238
|
+
unique.push(t2);
|
|
21239
|
+
}
|
|
21240
|
+
return unique.slice(0, PROMPT_MAX_CANDIDATE_TOKENS);
|
|
21241
|
+
}
|
|
21242
|
+
function toSearchResult2(row) {
|
|
21243
|
+
const fm = JSON.parse(row.frontmatter_json);
|
|
21244
|
+
const symptomMatch = /##\s+Symptom\s*\n([\s\S]*?)(?=\n##|\n*$)/.exec(row.body);
|
|
21245
|
+
const symptom = symptomMatch?.[1]?.trim() ?? row.body;
|
|
21246
|
+
return {
|
|
21247
|
+
id: row.id,
|
|
21248
|
+
source: row.source,
|
|
21249
|
+
title: row.title,
|
|
21250
|
+
symptomExcerpt: symptom.slice(0, SYMPTOM_EXCERPT_LENGTH2),
|
|
21251
|
+
confidence: row.confidence,
|
|
21252
|
+
visibility: row.visibility ?? "public",
|
|
21253
|
+
environment: fm.environment ?? {}
|
|
21254
|
+
};
|
|
21255
|
+
}
|
|
21256
|
+
function findCaveatsForPrompt(db, prompt, opts = {}) {
|
|
21257
|
+
const tokens = extractPromptCandidates(prompt);
|
|
21258
|
+
if (tokens.length === 0) return [];
|
|
21259
|
+
const minMatches = Math.min(MIN_DISTINCT_TOKEN_MATCHES_CEILING, tokens.length);
|
|
21260
|
+
const perEntry = /* @__PURE__ */ new Map();
|
|
21261
|
+
const stmt = db.prepare(
|
|
21262
|
+
"SELECT e.* FROM entries_fts f JOIN entries e ON e.rowid = f.rowid WHERE entries_fts MATCH ?"
|
|
21263
|
+
);
|
|
21264
|
+
for (const tok of tokens) {
|
|
21265
|
+
let rows = [];
|
|
21266
|
+
try {
|
|
21267
|
+
rows = stmt.all(`"${tok}"`);
|
|
21268
|
+
} catch {
|
|
21269
|
+
continue;
|
|
21270
|
+
}
|
|
21271
|
+
for (const row of rows) {
|
|
21272
|
+
const existing = perEntry.get(row.rowid);
|
|
21273
|
+
if (existing) existing.count += 1;
|
|
21274
|
+
else perEntry.set(row.rowid, { count: 1, row });
|
|
21275
|
+
}
|
|
21276
|
+
}
|
|
21277
|
+
const limit = opts.limit ?? DEFAULT_REMINDER_HIT_LIMIT;
|
|
21278
|
+
return [...perEntry.values()].filter(({ count }) => count >= minMatches).sort((a, b2) => b2.count - a.count).slice(0, limit).map(({ row }) => toSearchResult2(row));
|
|
21279
|
+
}
|
|
21280
|
+
function toolErrorReminderText(hits) {
|
|
21281
|
+
const lines = [];
|
|
21282
|
+
lines.push(
|
|
21283
|
+
`[caveat] \u76F4\u524D\u306E\u30A8\u30E9\u30FC\u306B\u4E00\u81F4\u3059\u308B\u53EF\u80FD\u6027\u306E\u3042\u308B\u65E2\u77E5\u306E\u7F60\u304C ${hits.length} \u4EF6\u3042\u308A\u307E\u3059:`
|
|
21284
|
+
);
|
|
21285
|
+
lines.push("");
|
|
21286
|
+
hits.forEach((h2, i2) => {
|
|
21287
|
+
lines.push(`${i2 + 1}. ${h2.id} (${h2.source}) \u2014 ${h2.title}`);
|
|
21288
|
+
const excerpt = h2.symptomExcerpt.replace(/\s+/g, " ").trim().slice(0, SYMPTOM_LINE_MAX);
|
|
21289
|
+
if (excerpt) lines.push(` \u75C7\u72B6: ${excerpt}`);
|
|
21290
|
+
});
|
|
21291
|
+
lines.push("");
|
|
21292
|
+
lines.push(
|
|
21293
|
+
"mcp__caveat__caveat_get \u3067\u8A73\u7D30\u3092\u78BA\u8A8D\u3057\u3001documented \u306A\u5BFE\u51E6\u304C\u3042\u308C\u3070\u9069\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u7121\u95A2\u4FC2\u3068\u5224\u65AD\u3057\u305F\u3089\u7121\u8996\u3057\u3066\u7D9A\u884C\u3067 OK\u3002"
|
|
21294
|
+
);
|
|
21295
|
+
return lines.join("\n");
|
|
21296
|
+
}
|
|
21297
|
+
function userPromptSubmitReminderText(hits) {
|
|
21298
|
+
const lines = [];
|
|
21299
|
+
lines.push(
|
|
21300
|
+
`[caveat] \u3053\u306E\u30D7\u30ED\u30F3\u30D7\u30C8\u306B\u95A2\u9023\u3059\u308B\u53EF\u80FD\u6027\u306E\u3042\u308B\u65E2\u77E5\u306E\u7F60\u304C ${hits.length} \u4EF6\u3042\u308A\u307E\u3059:`
|
|
21301
|
+
);
|
|
21302
|
+
lines.push("");
|
|
21303
|
+
hits.forEach((h2, i2) => {
|
|
21304
|
+
lines.push(`${i2 + 1}. ${h2.id} (${h2.source}) \u2014 ${h2.title}`);
|
|
21305
|
+
const excerpt = h2.symptomExcerpt.replace(/\s+/g, " ").trim().slice(0, SYMPTOM_LINE_MAX);
|
|
21306
|
+
if (excerpt) lines.push(` \u75C7\u72B6: ${excerpt}`);
|
|
21307
|
+
});
|
|
21308
|
+
lines.push("");
|
|
21309
|
+
lines.push(
|
|
21310
|
+
"\u8A73\u7D30\u306F mcp__caveat__caveat_get \u306B id + source \u3092\u6E21\u3057\u3066\u53D6\u5F97\u3002environment \u304C\u4E00\u81F4\u3059\u308B\u304B\u78BA\u8A8D\u3057\u3066\u304B\u3089\u9069\u7528\u5224\u65AD\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u7121\u95A2\u4FC2\u3068\u5224\u65AD\u3057\u305F\u3089\u7121\u8996\u3057\u3066\u7D9A\u884C\u3067 OK\u3002"
|
|
21311
|
+
);
|
|
21312
|
+
return lines.join("\n");
|
|
21313
|
+
}
|
|
21314
|
+
function shortPath(p2) {
|
|
21315
|
+
const parts = p2.split(/[\\/]/);
|
|
21316
|
+
return parts[parts.length - 1] ?? p2;
|
|
21317
|
+
}
|
|
21318
|
+
function stopReminderText(signals, related) {
|
|
21319
|
+
const lines = [];
|
|
21320
|
+
lines.push("[caveat] \u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u3067\u5916\u90E8\u4ED5\u69D8\u306E\u7F60\u306B\u5F53\u305F\u3063\u305F\u53EF\u80FD\u6027\u3092\u793A\u3059\u30B7\u30B0\u30CA\u30EB:");
|
|
21321
|
+
if (signals.toolFailureCount > 0) {
|
|
21322
|
+
lines.push(`- tool failure: ${signals.toolFailureCount} \u4EF6`);
|
|
21323
|
+
}
|
|
21324
|
+
if (signals.fileEditCounts.length > 0) {
|
|
21325
|
+
const top = signals.fileEditCounts.slice(0, 3).map((e) => `${shortPath(e.path)} \xD7 ${e.count}`).join(", ");
|
|
21326
|
+
lines.push(`- \u540C\u4E00\u30D5\u30A1\u30A4\u30EB\u8907\u6570\u7DE8\u96C6: ${top}`);
|
|
21327
|
+
}
|
|
21328
|
+
if (signals.webSearchCount > 0) {
|
|
21329
|
+
const sample = signals.searchQueries[0];
|
|
21330
|
+
const note = sample ? ` (\u4F8B: "${sample.slice(0, 60)}")` : "";
|
|
21331
|
+
lines.push(`- WebSearch: ${signals.webSearchCount} \u56DE${note}`);
|
|
21332
|
+
}
|
|
21333
|
+
if (signals.webFetchCount > 0) {
|
|
21334
|
+
lines.push(`- WebFetch: ${signals.webFetchCount} \u56DE`);
|
|
21335
|
+
}
|
|
21336
|
+
if (signals.bashRetryCount > 0) {
|
|
21337
|
+
lines.push(`- \u540C\u4E00 Bash \u30B3\u30DE\u30F3\u30C9\u306E\u518D\u5B9F\u884C: ${signals.bashRetryCount} \u7A2E`);
|
|
21338
|
+
}
|
|
21339
|
+
if (signals.durationMinutes > 0) {
|
|
21340
|
+
lines.push(`- \u7D4C\u904E\u6642\u9593: ${signals.durationMinutes} \u5206`);
|
|
21341
|
+
}
|
|
21342
|
+
lines.push("");
|
|
21343
|
+
if (related.length > 0) {
|
|
21344
|
+
lines.push(
|
|
21345
|
+
`\u30BB\u30C3\u30B7\u30E7\u30F3\u5185\u5BB9\u3068\u5171\u8D77\u3059\u308B\u65E2\u5B58\u7F60 ${related.length} \u4EF6\uFF08\u95A2\u9023\u304C\u3042\u308C\u3070 mcp__caveat__caveat_update \u3067 last_verified \u3092\u66F4\u65B0 or \u8FFD\u8A18\uFF09:`
|
|
21346
|
+
);
|
|
21347
|
+
related.forEach((h2, i2) => {
|
|
21348
|
+
lines.push(`${i2 + 1}. ${h2.id} (${h2.source}) \u2014 ${h2.title}`);
|
|
21349
|
+
});
|
|
21350
|
+
lines.push("");
|
|
21351
|
+
lines.push(
|
|
21352
|
+
"\u4E0A\u8A18\u3068\u7570\u306A\u308B\u65B0\u898F\u306E\u7F60\u3092\u8E0F\u3093\u3067\u3044\u305F\u3089 mcp__caveat__caveat_record \u3067\u767B\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002outcome: impossible\uFF08\u73FE\u72B6\u306E\u5236\u7D04\u3067\u306F\u4E0D\u53EF\u80FD\u3068\u5224\u5B9A\u3057\u305F\u7D50\u8AD6\uFF09\u3082\u8A18\u9332\u5BFE\u8C61\u3002"
|
|
21353
|
+
);
|
|
21354
|
+
} else {
|
|
21355
|
+
lines.push(
|
|
21356
|
+
"\u65E2\u5B58\u7F60\u306B\u8A72\u5F53\u306A\u3057\u3002\u5916\u90E8\u4ED5\u69D8\u306E\u7F60\u306B\u82E6\u6226\u3057\u3066\u3044\u305F\u306A\u3089 mcp__caveat__caveat_record \u3067\u767B\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002outcome: impossible \u3082\u8A18\u9332\u5BFE\u8C61\u3002"
|
|
21357
|
+
);
|
|
21358
|
+
}
|
|
21359
|
+
return lines.join("\n");
|
|
21222
21360
|
}
|
|
21223
|
-
|
|
21224
|
-
|
|
21225
|
-
|
|
21226
|
-
|
|
21227
|
-
|
|
21228
|
-
|
|
21361
|
+
|
|
21362
|
+
// ../../packages/core/dist/transcriptSignals.js
|
|
21363
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
21364
|
+
var MAX_ERROR_SNIPPETS = 10;
|
|
21365
|
+
var MAX_ERROR_SNIPPET_LENGTH = 300;
|
|
21366
|
+
var MAX_SEARCH_QUERIES = 10;
|
|
21367
|
+
var MAX_SEARCH_QUERY_LENGTH = 200;
|
|
21368
|
+
var MAX_FILE_EDIT_ENTRIES = 20;
|
|
21369
|
+
function isRecord(v) {
|
|
21370
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
21371
|
+
}
|
|
21372
|
+
function extractResultText(content) {
|
|
21373
|
+
if (typeof content === "string") return content;
|
|
21374
|
+
if (Array.isArray(content)) {
|
|
21375
|
+
const parts = [];
|
|
21376
|
+
for (const c3 of content) {
|
|
21377
|
+
if (isRecord(c3) && typeof c3.text === "string") parts.push(c3.text);
|
|
21378
|
+
}
|
|
21379
|
+
return parts.join(" ");
|
|
21380
|
+
}
|
|
21381
|
+
return "";
|
|
21382
|
+
}
|
|
21383
|
+
function parseTimestamp(raw2) {
|
|
21384
|
+
if (typeof raw2 !== "string") return void 0;
|
|
21385
|
+
const ms = Date.parse(raw2);
|
|
21386
|
+
return Number.isNaN(ms) ? void 0 : ms;
|
|
21387
|
+
}
|
|
21388
|
+
function readSessionSignals(transcriptPath) {
|
|
21389
|
+
if (!transcriptPath || !existsSync7(transcriptPath)) return null;
|
|
21390
|
+
let raw2;
|
|
21391
|
+
try {
|
|
21392
|
+
raw2 = readFileSync5(transcriptPath, "utf-8");
|
|
21393
|
+
} catch {
|
|
21394
|
+
return null;
|
|
21395
|
+
}
|
|
21396
|
+
const editCounts = /* @__PURE__ */ new Map();
|
|
21397
|
+
const bashCounts = /* @__PURE__ */ new Map();
|
|
21398
|
+
const errorSnippets = [];
|
|
21399
|
+
const searchQueries = [];
|
|
21400
|
+
let toolFailureCount = 0;
|
|
21401
|
+
let webSearchCount = 0;
|
|
21402
|
+
let webFetchCount = 0;
|
|
21403
|
+
let firstTs;
|
|
21404
|
+
let lastTs;
|
|
21405
|
+
for (const line of raw2.split("\n")) {
|
|
21406
|
+
if (line.length === 0) continue;
|
|
21407
|
+
let parsed;
|
|
21408
|
+
try {
|
|
21409
|
+
parsed = JSON.parse(line);
|
|
21410
|
+
} catch {
|
|
21411
|
+
continue;
|
|
21412
|
+
}
|
|
21413
|
+
const ts = parseTimestamp(parsed.timestamp);
|
|
21414
|
+
if (ts !== void 0) {
|
|
21415
|
+
if (firstTs === void 0 || ts < firstTs) firstTs = ts;
|
|
21416
|
+
if (lastTs === void 0 || ts > lastTs) lastTs = ts;
|
|
21417
|
+
}
|
|
21418
|
+
if (parsed.type !== "assistant" && parsed.type !== "user") continue;
|
|
21419
|
+
const content = parsed.message?.content;
|
|
21420
|
+
if (!Array.isArray(content)) continue;
|
|
21421
|
+
for (const item of content) {
|
|
21422
|
+
if (!isRecord(item)) continue;
|
|
21423
|
+
if (item.type === "tool_use") {
|
|
21424
|
+
const name = item.name;
|
|
21425
|
+
const input = isRecord(item.input) ? item.input : {};
|
|
21426
|
+
if (name === "Edit" || name === "Write" || name === "NotebookEdit") {
|
|
21427
|
+
const p2 = typeof input.file_path === "string" ? input.file_path : "";
|
|
21428
|
+
if (p2) editCounts.set(p2, (editCounts.get(p2) ?? 0) + 1);
|
|
21429
|
+
} else if (name === "WebSearch") {
|
|
21430
|
+
webSearchCount += 1;
|
|
21431
|
+
if (typeof input.query === "string" && searchQueries.length < MAX_SEARCH_QUERIES) {
|
|
21432
|
+
searchQueries.push(input.query.slice(0, MAX_SEARCH_QUERY_LENGTH));
|
|
21433
|
+
}
|
|
21434
|
+
} else if (name === "WebFetch") {
|
|
21435
|
+
webFetchCount += 1;
|
|
21436
|
+
} else if (name === "Bash") {
|
|
21437
|
+
const cmd = typeof input.command === "string" ? input.command : "";
|
|
21438
|
+
if (cmd) bashCounts.set(cmd, (bashCounts.get(cmd) ?? 0) + 1);
|
|
21439
|
+
}
|
|
21440
|
+
} else if (item.type === "tool_result") {
|
|
21441
|
+
if (item.is_error === true) {
|
|
21442
|
+
toolFailureCount += 1;
|
|
21443
|
+
const text2 = extractResultText(item.content).replace(/\s+/g, " ").trim();
|
|
21444
|
+
if (text2 && errorSnippets.length < MAX_ERROR_SNIPPETS) {
|
|
21445
|
+
errorSnippets.push(text2.slice(0, MAX_ERROR_SNIPPET_LENGTH));
|
|
21446
|
+
}
|
|
21447
|
+
}
|
|
21448
|
+
}
|
|
21449
|
+
}
|
|
21450
|
+
}
|
|
21451
|
+
const fileEditCounts = [...editCounts.entries()].filter(([, c3]) => c3 > 1).map(([path, count]) => ({ path, count })).sort((a, b2) => b2.count - a.count).slice(0, MAX_FILE_EDIT_ENTRIES);
|
|
21452
|
+
const bashRetryCount = [...bashCounts.values()].filter((c3) => c3 > 1).length;
|
|
21453
|
+
const durationMinutes = firstTs !== void 0 && lastTs !== void 0 ? Math.max(0, Math.round((lastTs - firstTs) / 6e4)) : 0;
|
|
21454
|
+
return {
|
|
21455
|
+
toolFailureCount,
|
|
21456
|
+
fileEditCounts,
|
|
21457
|
+
webSearchCount,
|
|
21458
|
+
webFetchCount,
|
|
21459
|
+
bashRetryCount,
|
|
21460
|
+
durationMinutes,
|
|
21461
|
+
errorSnippets,
|
|
21462
|
+
searchQueries
|
|
21463
|
+
};
|
|
21229
21464
|
}
|
|
21230
|
-
function
|
|
21231
|
-
return
|
|
21232
|
-
|
|
21233
|
-
|
|
21234
|
-
|
|
21235
|
-
|
|
21465
|
+
function hasAnyStruggleSignal(s) {
|
|
21466
|
+
return s.toolFailureCount > 0 || s.fileEditCounts.length > 0 || s.webSearchCount > 0 || s.webFetchCount > 0 || s.bashRetryCount > 0;
|
|
21467
|
+
}
|
|
21468
|
+
function struggleSearchText(s) {
|
|
21469
|
+
return [...s.errorSnippets, ...s.searchQueries].join(" ");
|
|
21470
|
+
}
|
|
21471
|
+
|
|
21472
|
+
// ../../packages/core/dist/pendingReminders.js
|
|
21473
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync6, unlinkSync, writeFileSync as writeFileSync4 } from "node:fs";
|
|
21474
|
+
import { join as join7 } from "node:path";
|
|
21475
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
21476
|
+
function sanitizeSessionId(raw2) {
|
|
21477
|
+
const clean = raw2.replace(/[^A-Za-z0-9_-]/g, "");
|
|
21478
|
+
return clean.length > 0 ? clean : "_unknown";
|
|
21479
|
+
}
|
|
21480
|
+
function pendingDirFor(caveatHome, sessionId) {
|
|
21481
|
+
return join7(caveatHome, "pending", sanitizeSessionId(sessionId));
|
|
21482
|
+
}
|
|
21483
|
+
function appendPendingReminder(caveatHome, sessionId, text2) {
|
|
21484
|
+
const dir = pendingDirFor(caveatHome, sessionId);
|
|
21485
|
+
mkdirSync3(dir, { recursive: true });
|
|
21486
|
+
const name = `${Date.now()}-${randomBytes2(4).toString("hex")}.txt`;
|
|
21487
|
+
const path = join7(dir, name);
|
|
21488
|
+
writeFileSync4(path, text2, "utf-8");
|
|
21489
|
+
return path;
|
|
21490
|
+
}
|
|
21491
|
+
function drainPendingReminders(caveatHome, sessionId) {
|
|
21492
|
+
const dir = pendingDirFor(caveatHome, sessionId);
|
|
21493
|
+
if (!existsSync8(dir)) return [];
|
|
21494
|
+
let entries;
|
|
21495
|
+
try {
|
|
21496
|
+
entries = readdirSync4(dir).filter((f) => f.endsWith(".txt")).sort();
|
|
21497
|
+
} catch {
|
|
21498
|
+
return [];
|
|
21499
|
+
}
|
|
21500
|
+
const out = [];
|
|
21501
|
+
for (const entry of entries) {
|
|
21502
|
+
const path = join7(dir, entry);
|
|
21503
|
+
try {
|
|
21504
|
+
out.push(readFileSync6(path, "utf-8"));
|
|
21505
|
+
} catch {
|
|
21506
|
+
continue;
|
|
21507
|
+
}
|
|
21508
|
+
try {
|
|
21509
|
+
unlinkSync(path);
|
|
21510
|
+
} catch {
|
|
21511
|
+
}
|
|
21512
|
+
}
|
|
21513
|
+
return out;
|
|
21236
21514
|
}
|
|
21237
21515
|
|
|
21238
21516
|
// src/context.ts
|
|
21239
21517
|
function buildContext(logger, overrides = {}) {
|
|
21240
21518
|
const userHome = overrides.userHome ?? homedir();
|
|
21241
21519
|
const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
|
|
21242
|
-
const userConfigPath =
|
|
21520
|
+
const userConfigPath = join8(userHome, ".caveatrc.json");
|
|
21243
21521
|
const config3 = loadConfig(userConfigPath);
|
|
21244
21522
|
const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
|
|
21245
21523
|
return { caveatHome, userHome, userConfigPath, config: config3, paths, logger };
|
|
21246
21524
|
}
|
|
21247
21525
|
|
|
21248
21526
|
// src/version.ts
|
|
21249
|
-
import { readFileSync as
|
|
21250
|
-
import { dirname as dirname4, join as
|
|
21527
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
21528
|
+
import { dirname as dirname4, join as join9 } from "node:path";
|
|
21251
21529
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
21252
21530
|
function resolveVersion() {
|
|
21253
21531
|
try {
|
|
21254
21532
|
const here2 = dirname4(fileURLToPath3(import.meta.url));
|
|
21255
|
-
const pkg = JSON.parse(
|
|
21533
|
+
const pkg = JSON.parse(readFileSync7(join9(here2, "..", "package.json"), "utf-8"));
|
|
21256
21534
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
21257
21535
|
} catch {
|
|
21258
21536
|
return "0.0.0";
|
|
@@ -21271,14 +21549,15 @@ var stdoutLogger = {
|
|
|
21271
21549
|
};
|
|
21272
21550
|
|
|
21273
21551
|
// src/commands/init.ts
|
|
21274
|
-
import { existsSync as
|
|
21275
|
-
import { dirname as dirname6, join as
|
|
21552
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync5, renameSync, rmdirSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
21553
|
+
import { dirname as dirname6, join as join11 } from "node:path";
|
|
21276
21554
|
|
|
21277
21555
|
// src/claudeInstall.ts
|
|
21278
21556
|
import { spawnSync } from "node:child_process";
|
|
21279
|
-
import { copyFileSync, existsSync as
|
|
21280
|
-
import { dirname as dirname5, join as
|
|
21557
|
+
import { copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
|
|
21558
|
+
import { dirname as dirname5, join as join10 } from "node:path";
|
|
21281
21559
|
var EVENT_USER_PROMPT_SUBMIT = "UserPromptSubmit";
|
|
21560
|
+
var EVENT_POST_TOOL_USE = "PostToolUse";
|
|
21282
21561
|
var EVENT_STOP = "Stop";
|
|
21283
21562
|
function quote(p2) {
|
|
21284
21563
|
return p2.includes(" ") ? `"${p2}"` : p2;
|
|
@@ -21311,18 +21590,18 @@ function removeHook(settings, event, command) {
|
|
|
21311
21590
|
return true;
|
|
21312
21591
|
}
|
|
21313
21592
|
function readSettings(path) {
|
|
21314
|
-
if (!
|
|
21315
|
-
return JSON.parse(
|
|
21593
|
+
if (!existsSync9(path)) return {};
|
|
21594
|
+
return JSON.parse(readFileSync8(path, "utf-8"));
|
|
21316
21595
|
}
|
|
21317
21596
|
function writeSettings(path, settings) {
|
|
21318
21597
|
const dir = dirname5(path);
|
|
21319
|
-
if (!
|
|
21598
|
+
if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
|
|
21320
21599
|
let backupPath = "";
|
|
21321
|
-
if (
|
|
21600
|
+
if (existsSync9(path)) {
|
|
21322
21601
|
backupPath = `${path}.caveat-backup-${Date.now()}`;
|
|
21323
21602
|
copyFileSync(path, backupPath);
|
|
21324
21603
|
}
|
|
21325
|
-
|
|
21604
|
+
writeFileSync5(path, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
21326
21605
|
return backupPath;
|
|
21327
21606
|
}
|
|
21328
21607
|
var CLAUDE_BIN = "claude";
|
|
@@ -21381,33 +21660,38 @@ function unregisterMcp(dryRun, logger) {
|
|
|
21381
21660
|
return { action: "skipped", detail: "not registered or removal failed" };
|
|
21382
21661
|
}
|
|
21383
21662
|
function installClaudeIntegration(opts) {
|
|
21384
|
-
const settingsPath =
|
|
21663
|
+
const settingsPath = join10(opts.claudeDir, "settings.json");
|
|
21385
21664
|
const settings = readSettings(settingsPath);
|
|
21386
21665
|
const usCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "user-prompt-submit");
|
|
21666
|
+
const ptCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "post-tool-use");
|
|
21387
21667
|
const stopCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "stop");
|
|
21388
21668
|
const userPromptSubmit = upsertHook(settings, EVENT_USER_PROMPT_SUBMIT, usCmd);
|
|
21669
|
+
const postToolUse = upsertHook(settings, EVENT_POST_TOOL_USE, ptCmd);
|
|
21389
21670
|
const stop = upsertHook(settings, EVENT_STOP, stopCmd);
|
|
21390
21671
|
let backupPath;
|
|
21391
|
-
|
|
21672
|
+
const anyAdded = userPromptSubmit === "added" || postToolUse === "added" || stop === "added";
|
|
21673
|
+
if (!opts.dryRun && anyAdded) {
|
|
21392
21674
|
const backup = writeSettings(settingsPath, settings);
|
|
21393
21675
|
if (backup) backupPath = backup;
|
|
21394
21676
|
} else if (opts.dryRun) {
|
|
21395
21677
|
opts.logger.info(
|
|
21396
|
-
`[dry-run] would ${userPromptSubmit === "added" ? "add" : "keep"} UserPromptSubmit
|
|
21678
|
+
`[dry-run] would ${userPromptSubmit === "added" ? "add" : "keep"} UserPromptSubmit, ${postToolUse === "added" ? "add" : "keep"} PostToolUse, ${stop === "added" ? "add" : "keep"} Stop hook in ${settingsPath}`
|
|
21397
21679
|
);
|
|
21398
21680
|
}
|
|
21399
21681
|
const mcp = opts.skipMcpRegistration ? { action: "skipped", detail: "skipped by caller" } : registerMcp(opts.nodePath, opts.cliScriptPath, opts.dryRun, opts.logger);
|
|
21400
|
-
return { mcp, hooks: { userPromptSubmit, stop }, backupPath };
|
|
21682
|
+
return { mcp, hooks: { userPromptSubmit, postToolUse, stop }, backupPath };
|
|
21401
21683
|
}
|
|
21402
21684
|
function uninstallClaudeIntegration(opts) {
|
|
21403
|
-
const settingsPath =
|
|
21685
|
+
const settingsPath = join10(opts.claudeDir, "settings.json");
|
|
21404
21686
|
const settings = readSettings(settingsPath);
|
|
21405
21687
|
const usCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "user-prompt-submit");
|
|
21688
|
+
const ptCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "post-tool-use");
|
|
21406
21689
|
const stopCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "stop");
|
|
21407
21690
|
const removedUs = removeHook(settings, EVENT_USER_PROMPT_SUBMIT, usCmd);
|
|
21691
|
+
const removedPt = removeHook(settings, EVENT_POST_TOOL_USE, ptCmd);
|
|
21408
21692
|
const removedStop = removeHook(settings, EVENT_STOP, stopCmd);
|
|
21409
21693
|
let backupPath;
|
|
21410
|
-
if (!opts.dryRun && (removedUs || removedStop)) {
|
|
21694
|
+
if (!opts.dryRun && (removedUs || removedPt || removedStop)) {
|
|
21411
21695
|
const backup = writeSettings(settingsPath, settings);
|
|
21412
21696
|
if (backup) backupPath = backup;
|
|
21413
21697
|
}
|
|
@@ -21416,6 +21700,7 @@ function uninstallClaudeIntegration(opts) {
|
|
|
21416
21700
|
mcp,
|
|
21417
21701
|
hooks: {
|
|
21418
21702
|
userPromptSubmit: removedUs ? "added" : "unchanged",
|
|
21703
|
+
postToolUse: removedPt ? "added" : "unchanged",
|
|
21419
21704
|
stop: removedStop ? "added" : "unchanged"
|
|
21420
21705
|
},
|
|
21421
21706
|
backupPath
|
|
@@ -21435,22 +21720,22 @@ var KNOWLEDGE_GITIGNORE = [
|
|
|
21435
21720
|
async function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
|
|
21436
21721
|
ensureUserConfig(ctx.userConfigPath);
|
|
21437
21722
|
ctx.logger.info(`user config: ${ctx.userConfigPath}`);
|
|
21438
|
-
if (!
|
|
21439
|
-
|
|
21440
|
-
|
|
21723
|
+
if (!existsSync10(ctx.paths.knowledgeRepo)) {
|
|
21724
|
+
mkdirSync5(ctx.paths.knowledgeRepo, { recursive: true });
|
|
21725
|
+
mkdirSync5(ctx.paths.entriesDir, { recursive: true });
|
|
21441
21726
|
ctx.logger.info(`knowledge repo scaffolded: ${ctx.paths.knowledgeRepo}`);
|
|
21442
21727
|
} else {
|
|
21443
21728
|
ctx.logger.info(`knowledge repo: ${ctx.paths.knowledgeRepo}`);
|
|
21444
21729
|
}
|
|
21445
21730
|
migrateLegacyCommunityDir(ctx);
|
|
21446
|
-
const gitignorePath =
|
|
21447
|
-
if (!
|
|
21448
|
-
|
|
21731
|
+
const gitignorePath = join11(ctx.paths.knowledgeRepo, ".gitignore");
|
|
21732
|
+
if (!existsSync10(gitignorePath)) {
|
|
21733
|
+
writeFileSync6(gitignorePath, KNOWLEDGE_GITIGNORE, "utf-8");
|
|
21449
21734
|
ctx.logger.info(`.gitignore created: ${gitignorePath}`);
|
|
21450
21735
|
}
|
|
21451
21736
|
if (!opts.dryRun) {
|
|
21452
21737
|
const dbDir = dirname6(ctx.paths.dbPath);
|
|
21453
|
-
if (!
|
|
21738
|
+
if (!existsSync10(dbDir)) mkdirSync5(dbDir, { recursive: true });
|
|
21454
21739
|
const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
|
|
21455
21740
|
db.close();
|
|
21456
21741
|
ctx.logger.info(`db initialized: ${ctx.paths.dbPath}`);
|
|
@@ -21470,7 +21755,7 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
|
|
|
21470
21755
|
return;
|
|
21471
21756
|
}
|
|
21472
21757
|
const result = installClaudeIntegration({
|
|
21473
|
-
claudeDir:
|
|
21758
|
+
claudeDir: join11(ctx.userHome, ".claude"),
|
|
21474
21759
|
cliScriptPath,
|
|
21475
21760
|
nodePath: process.execPath,
|
|
21476
21761
|
dryRun: opts.dryRun,
|
|
@@ -21479,20 +21764,20 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
|
|
|
21479
21764
|
reportInstallResult(ctx, result, opts.dryRun);
|
|
21480
21765
|
}
|
|
21481
21766
|
function migrateLegacyCommunityDir(ctx) {
|
|
21482
|
-
const legacy =
|
|
21767
|
+
const legacy = join11(ctx.paths.knowledgeRepo, "community");
|
|
21483
21768
|
const current = ctx.paths.communityDir;
|
|
21484
21769
|
if (legacy === current) return;
|
|
21485
|
-
if (!
|
|
21486
|
-
if (
|
|
21770
|
+
if (!existsSync10(legacy)) return;
|
|
21771
|
+
if (existsSync10(current)) {
|
|
21487
21772
|
ctx.logger.warn(
|
|
21488
21773
|
`legacy community dir still exists at ${legacy} \u2014 remove manually (new location in use)`
|
|
21489
21774
|
);
|
|
21490
21775
|
return;
|
|
21491
21776
|
}
|
|
21492
|
-
|
|
21493
|
-
for (const entry of
|
|
21777
|
+
mkdirSync5(current, { recursive: true });
|
|
21778
|
+
for (const entry of readdirSync5(legacy, { withFileTypes: true })) {
|
|
21494
21779
|
if (!entry.isDirectory()) continue;
|
|
21495
|
-
renameSync(
|
|
21780
|
+
renameSync(join11(legacy, entry.name), join11(current, entry.name));
|
|
21496
21781
|
}
|
|
21497
21782
|
try {
|
|
21498
21783
|
rmdirSync(legacy);
|
|
@@ -21507,7 +21792,7 @@ function runUninstall(ctx, opts) {
|
|
|
21507
21792
|
process.exit(1);
|
|
21508
21793
|
}
|
|
21509
21794
|
const result = uninstallClaudeIntegration({
|
|
21510
|
-
claudeDir:
|
|
21795
|
+
claudeDir: join11(ctx.userHome, ".claude"),
|
|
21511
21796
|
cliScriptPath,
|
|
21512
21797
|
nodePath: process.execPath,
|
|
21513
21798
|
dryRun: opts.dryRun,
|
|
@@ -21544,29 +21829,29 @@ function reportInstallResult(ctx, result, dryRun) {
|
|
|
21544
21829
|
}
|
|
21545
21830
|
|
|
21546
21831
|
// src/commands/indexCmd.ts
|
|
21547
|
-
import { existsSync as
|
|
21548
|
-
import { dirname as dirname7, join as
|
|
21832
|
+
import { existsSync as existsSync11, readdirSync as readdirSync6, mkdirSync as mkdirSync6 } from "node:fs";
|
|
21833
|
+
import { dirname as dirname7, join as join12 } from "node:path";
|
|
21549
21834
|
function runIndex(ctx, opts) {
|
|
21550
21835
|
const dbDir = dirname7(ctx.paths.dbPath);
|
|
21551
|
-
if (!
|
|
21836
|
+
if (!existsSync11(dbDir)) mkdirSync6(dbDir, { recursive: true });
|
|
21552
21837
|
const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
|
|
21553
21838
|
try {
|
|
21554
21839
|
if (opts.full) {
|
|
21555
21840
|
ctx.logger.info("full rebuild: DELETE FROM entries");
|
|
21556
21841
|
rebuildAll(db);
|
|
21557
21842
|
}
|
|
21558
|
-
if (
|
|
21843
|
+
if (existsSync11(ctx.paths.entriesDir)) {
|
|
21559
21844
|
const result = scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
|
|
21560
21845
|
ctx.logger.info(`own: +${result.added} ~${result.updated} -${result.deleted}`);
|
|
21561
21846
|
} else {
|
|
21562
21847
|
ctx.logger.warn(`entries dir not found: ${ctx.paths.entriesDir}`);
|
|
21563
21848
|
}
|
|
21564
|
-
if (
|
|
21565
|
-
for (const entry of
|
|
21849
|
+
if (existsSync11(ctx.paths.communityDir)) {
|
|
21850
|
+
for (const entry of readdirSync6(ctx.paths.communityDir, { withFileTypes: true })) {
|
|
21566
21851
|
if (!entry.isDirectory()) continue;
|
|
21567
21852
|
const source = `community/${entry.name}`;
|
|
21568
|
-
const root =
|
|
21569
|
-
if (!
|
|
21853
|
+
const root = join12(ctx.paths.communityDir, entry.name, "entries");
|
|
21854
|
+
if (!existsSync11(root)) continue;
|
|
21570
21855
|
const result = scanSource({ db, source, entriesRoot: root });
|
|
21571
21856
|
ctx.logger.info(`${source}: +${result.added} ~${result.updated} -${result.deleted}`);
|
|
21572
21857
|
}
|
|
@@ -22340,11 +22625,11 @@ var serve = (options2, listeningListener) => {
|
|
|
22340
22625
|
|
|
22341
22626
|
// ../web/dist/context.js
|
|
22342
22627
|
import { homedir as homedir2 } from "node:os";
|
|
22343
|
-
import { join as
|
|
22628
|
+
import { join as join13 } from "node:path";
|
|
22344
22629
|
function buildWebContext(overrides = {}) {
|
|
22345
22630
|
const userHome = overrides.userHome ?? homedir2();
|
|
22346
22631
|
const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
|
|
22347
|
-
const userConfigPath =
|
|
22632
|
+
const userConfigPath = join13(userHome, ".caveatrc.json");
|
|
22348
22633
|
const logger = overrides.logger ?? stderrLogger;
|
|
22349
22634
|
const config3 = loadConfig(userConfigPath);
|
|
22350
22635
|
const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
|
|
@@ -30089,14 +30374,14 @@ function createDetailRoute(ctx) {
|
|
|
30089
30374
|
}
|
|
30090
30375
|
|
|
30091
30376
|
// ../web/dist/routes/community.js
|
|
30092
|
-
import { existsSync as
|
|
30093
|
-
import { join as
|
|
30377
|
+
import { existsSync as existsSync12, readdirSync as readdirSync7, statSync as statSync5 } from "node:fs";
|
|
30378
|
+
import { join as join14 } from "node:path";
|
|
30094
30379
|
function listCommunity(communityDir, db) {
|
|
30095
|
-
if (!
|
|
30380
|
+
if (!existsSync12(communityDir)) return [];
|
|
30096
30381
|
const handles = [];
|
|
30097
|
-
for (const entry of
|
|
30382
|
+
for (const entry of readdirSync7(communityDir, { withFileTypes: true })) {
|
|
30098
30383
|
if (!entry.isDirectory()) continue;
|
|
30099
|
-
const handlePath =
|
|
30384
|
+
const handlePath = join14(communityDir, entry.name);
|
|
30100
30385
|
const countRow = db.prepare("SELECT COUNT(*) AS n FROM entries WHERE source = ?").get(`community/${entry.name}`);
|
|
30101
30386
|
handles.push({
|
|
30102
30387
|
handle: entry.name,
|
|
@@ -44394,11 +44679,11 @@ var StdioServerTransport = class {
|
|
|
44394
44679
|
|
|
44395
44680
|
// ../mcp/dist/context.js
|
|
44396
44681
|
import { homedir as homedir3 } from "node:os";
|
|
44397
|
-
import { join as
|
|
44682
|
+
import { join as join15 } from "node:path";
|
|
44398
44683
|
function buildMcpContext(overrides = {}) {
|
|
44399
44684
|
const userHome = overrides.userHome ?? homedir3();
|
|
44400
44685
|
const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
|
|
44401
|
-
const userConfigPath =
|
|
44686
|
+
const userConfigPath = join15(userHome, ".caveatrc.json");
|
|
44402
44687
|
const logger = overrides.logger ?? stderrLogger;
|
|
44403
44688
|
const config3 = loadConfig(userConfigPath);
|
|
44404
44689
|
const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
|
|
@@ -44507,13 +44792,13 @@ function handleListRecent(ctx, args) {
|
|
|
44507
44792
|
}
|
|
44508
44793
|
|
|
44509
44794
|
// ../mcp/dist/tools/pull.js
|
|
44510
|
-
import { existsSync as
|
|
44511
|
-
import { join as
|
|
44795
|
+
import { existsSync as existsSync13, readdirSync as readdirSync8 } from "node:fs";
|
|
44796
|
+
import { join as join16 } from "node:path";
|
|
44512
44797
|
var pullInputShape = {};
|
|
44513
44798
|
async function handlePull(ctx, _args = {}) {
|
|
44514
44799
|
const pulled = [];
|
|
44515
44800
|
const indexed = [];
|
|
44516
|
-
if (
|
|
44801
|
+
if (existsSync13(ctx.paths.communityDir)) {
|
|
44517
44802
|
const results = await communityPull({
|
|
44518
44803
|
communityDir: ctx.paths.communityDir,
|
|
44519
44804
|
logger: ctx.logger
|
|
@@ -44523,16 +44808,16 @@ async function handlePull(ctx, _args = {}) {
|
|
|
44523
44808
|
}
|
|
44524
44809
|
}
|
|
44525
44810
|
rebuildAll(ctx.db);
|
|
44526
|
-
if (
|
|
44811
|
+
if (existsSync13(ctx.paths.entriesDir)) {
|
|
44527
44812
|
const own = scanSource({ db: ctx.db, source: "own", entriesRoot: ctx.paths.entriesDir });
|
|
44528
44813
|
indexed.push({ source: "own", ...own });
|
|
44529
44814
|
}
|
|
44530
|
-
if (
|
|
44531
|
-
for (const entry of
|
|
44815
|
+
if (existsSync13(ctx.paths.communityDir)) {
|
|
44816
|
+
for (const entry of readdirSync8(ctx.paths.communityDir, { withFileTypes: true })) {
|
|
44532
44817
|
if (!entry.isDirectory()) continue;
|
|
44533
44818
|
const source = `community/${entry.name}`;
|
|
44534
|
-
const root =
|
|
44535
|
-
if (!
|
|
44819
|
+
const root = join16(ctx.paths.communityDir, entry.name, "entries");
|
|
44820
|
+
if (!existsSync13(root)) continue;
|
|
44536
44821
|
const scan = scanSource({ db: ctx.db, source, entriesRoot: root });
|
|
44537
44822
|
indexed.push({ source, ...scan });
|
|
44538
44823
|
}
|
|
@@ -44645,6 +44930,19 @@ async function runMcpServer() {
|
|
|
44645
44930
|
}
|
|
44646
44931
|
|
|
44647
44932
|
// src/commands/hookCmd.ts
|
|
44933
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
44934
|
+
import { existsSync as existsSync14, readFileSync as readFileSync9, unlinkSync as unlinkSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
44935
|
+
import { tmpdir } from "node:os";
|
|
44936
|
+
import { join as join17 } from "node:path";
|
|
44937
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
44938
|
+
var silentLogger = {
|
|
44939
|
+
info: () => {
|
|
44940
|
+
},
|
|
44941
|
+
warn: () => {
|
|
44942
|
+
},
|
|
44943
|
+
error: (m) => process.stderr.write(`[caveat:hook] ${m}
|
|
44944
|
+
`)
|
|
44945
|
+
};
|
|
44648
44946
|
async function readStdin() {
|
|
44649
44947
|
const chunks = [];
|
|
44650
44948
|
for await (const chunk of process.stdin) {
|
|
@@ -44663,7 +44961,153 @@ function parsePayload(raw2) {
|
|
|
44663
44961
|
return {};
|
|
44664
44962
|
}
|
|
44665
44963
|
}
|
|
44666
|
-
|
|
44964
|
+
function getSessionId(payload) {
|
|
44965
|
+
const v = payload.session_id ?? payload.sessionId;
|
|
44966
|
+
return typeof v === "string" && v.length > 0 ? v : "_unknown";
|
|
44967
|
+
}
|
|
44968
|
+
function buildContextSafely() {
|
|
44969
|
+
try {
|
|
44970
|
+
return buildContext(silentLogger);
|
|
44971
|
+
} catch (err) {
|
|
44972
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
44973
|
+
process.stderr.write(`[caveat:hook] context error: ${msg}
|
|
44974
|
+
`);
|
|
44975
|
+
return null;
|
|
44976
|
+
}
|
|
44977
|
+
}
|
|
44978
|
+
function searchCaveatsFromTextSafely(text2) {
|
|
44979
|
+
if (!text2) return [];
|
|
44980
|
+
let db;
|
|
44981
|
+
try {
|
|
44982
|
+
const ctx = buildContextSafely();
|
|
44983
|
+
if (!ctx || !existsSync14(ctx.paths.dbPath)) return [];
|
|
44984
|
+
db = openDb({ path: ctx.paths.dbPath });
|
|
44985
|
+
return findCaveatsForPrompt(db, text2);
|
|
44986
|
+
} catch (err) {
|
|
44987
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
44988
|
+
process.stderr.write(`[caveat:hook] search error: ${msg}
|
|
44989
|
+
`);
|
|
44990
|
+
return [];
|
|
44991
|
+
} finally {
|
|
44992
|
+
db?.close();
|
|
44993
|
+
}
|
|
44994
|
+
}
|
|
44995
|
+
function loadSignalsSafely(path) {
|
|
44996
|
+
try {
|
|
44997
|
+
return readSessionSignals(path);
|
|
44998
|
+
} catch (err) {
|
|
44999
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
45000
|
+
process.stderr.write(`[caveat:hook] transcript read error: ${msg}
|
|
45001
|
+
`);
|
|
45002
|
+
return null;
|
|
45003
|
+
}
|
|
45004
|
+
}
|
|
45005
|
+
function drainForSession(sessionId) {
|
|
45006
|
+
const ctx = buildContextSafely();
|
|
45007
|
+
if (!ctx) return;
|
|
45008
|
+
const reminders = drainPendingReminders(ctx.caveatHome, sessionId);
|
|
45009
|
+
for (const text2 of reminders) {
|
|
45010
|
+
process.stdout.write(`<system-reminder>${text2}</system-reminder>
|
|
45011
|
+
`);
|
|
45012
|
+
}
|
|
45013
|
+
}
|
|
45014
|
+
function extractToolResponseText(response) {
|
|
45015
|
+
if (typeof response === "string") return response;
|
|
45016
|
+
if (Array.isArray(response)) {
|
|
45017
|
+
const parts = [];
|
|
45018
|
+
for (const item of response) {
|
|
45019
|
+
if (typeof item === "string") parts.push(item);
|
|
45020
|
+
else if (item !== null && typeof item === "object" && typeof item.text === "string") {
|
|
45021
|
+
parts.push(item.text);
|
|
45022
|
+
}
|
|
45023
|
+
}
|
|
45024
|
+
return parts.join(" ");
|
|
45025
|
+
}
|
|
45026
|
+
if (response !== null && typeof response === "object") {
|
|
45027
|
+
const r2 = response;
|
|
45028
|
+
if (typeof r2.content === "string") return r2.content;
|
|
45029
|
+
if (Array.isArray(r2.content)) return extractToolResponseText(r2.content);
|
|
45030
|
+
if (typeof r2.output === "string") return r2.output;
|
|
45031
|
+
if (typeof r2.stdout === "string" || typeof r2.stderr === "string") {
|
|
45032
|
+
return [r2.stdout, r2.stderr].filter((x2) => typeof x2 === "string").join(" ");
|
|
45033
|
+
}
|
|
45034
|
+
}
|
|
45035
|
+
return "";
|
|
45036
|
+
}
|
|
45037
|
+
function isToolError(payload) {
|
|
45038
|
+
const resp = payload.tool_response ?? payload.toolResponse;
|
|
45039
|
+
if (resp !== null && typeof resp === "object" && !Array.isArray(resp)) {
|
|
45040
|
+
if (resp.is_error === true) return true;
|
|
45041
|
+
}
|
|
45042
|
+
if (payload.is_error === true) return true;
|
|
45043
|
+
return false;
|
|
45044
|
+
}
|
|
45045
|
+
function spawnWorker(job) {
|
|
45046
|
+
const workFile = join17(
|
|
45047
|
+
tmpdir(),
|
|
45048
|
+
`caveat-worker-${Date.now()}-${randomBytes3(4).toString("hex")}.json`
|
|
45049
|
+
);
|
|
45050
|
+
try {
|
|
45051
|
+
writeFileSync7(workFile, JSON.stringify(job), "utf-8");
|
|
45052
|
+
} catch (err) {
|
|
45053
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
45054
|
+
process.stderr.write(`[caveat:hook] worker writefile error: ${msg}
|
|
45055
|
+
`);
|
|
45056
|
+
return;
|
|
45057
|
+
}
|
|
45058
|
+
const cliScript = process.argv[1];
|
|
45059
|
+
if (!cliScript) return;
|
|
45060
|
+
try {
|
|
45061
|
+
const child = spawn2(
|
|
45062
|
+
process.execPath,
|
|
45063
|
+
["--disable-warning=ExperimentalWarning", cliScript, "hook", "worker", workFile],
|
|
45064
|
+
{ detached: true, stdio: "ignore", windowsHide: true }
|
|
45065
|
+
);
|
|
45066
|
+
child.unref();
|
|
45067
|
+
} catch (err) {
|
|
45068
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
45069
|
+
process.stderr.write(`[caveat:hook] worker spawn error: ${msg}
|
|
45070
|
+
`);
|
|
45071
|
+
try {
|
|
45072
|
+
unlinkSync2(workFile);
|
|
45073
|
+
} catch {
|
|
45074
|
+
}
|
|
45075
|
+
}
|
|
45076
|
+
}
|
|
45077
|
+
async function runWorker(workFile) {
|
|
45078
|
+
let raw2;
|
|
45079
|
+
try {
|
|
45080
|
+
raw2 = readFileSync9(workFile, "utf-8");
|
|
45081
|
+
} catch {
|
|
45082
|
+
process.exit(0);
|
|
45083
|
+
}
|
|
45084
|
+
try {
|
|
45085
|
+
unlinkSync2(workFile);
|
|
45086
|
+
} catch {
|
|
45087
|
+
}
|
|
45088
|
+
let job;
|
|
45089
|
+
try {
|
|
45090
|
+
job = JSON.parse(raw2);
|
|
45091
|
+
} catch {
|
|
45092
|
+
process.exit(0);
|
|
45093
|
+
}
|
|
45094
|
+
if (!job.searchText || !job.sessionId) process.exit(0);
|
|
45095
|
+
const hits = searchCaveatsFromTextSafely(job.searchText);
|
|
45096
|
+
if (hits.length === 0) process.exit(0);
|
|
45097
|
+
const ctx = buildContextSafely();
|
|
45098
|
+
if (!ctx) process.exit(0);
|
|
45099
|
+
try {
|
|
45100
|
+
appendPendingReminder(ctx.caveatHome, job.sessionId, toolErrorReminderText(hits));
|
|
45101
|
+
} catch {
|
|
45102
|
+
}
|
|
45103
|
+
process.exit(0);
|
|
45104
|
+
}
|
|
45105
|
+
async function runHook(name, arg) {
|
|
45106
|
+
if (name === "worker") {
|
|
45107
|
+
if (!arg) process.exit(0);
|
|
45108
|
+
await runWorker(arg);
|
|
45109
|
+
return;
|
|
45110
|
+
}
|
|
44667
45111
|
let raw2 = "";
|
|
44668
45112
|
try {
|
|
44669
45113
|
raw2 = await readStdin();
|
|
@@ -44674,22 +45118,37 @@ async function runHook(name) {
|
|
|
44674
45118
|
process.exit(0);
|
|
44675
45119
|
}
|
|
44676
45120
|
const payload = parsePayload(raw2);
|
|
45121
|
+
const sessionId = getSessionId(payload);
|
|
45122
|
+
drainForSession(sessionId);
|
|
44677
45123
|
if (name === "user-prompt-submit") {
|
|
44678
45124
|
const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
|
|
44679
|
-
|
|
45125
|
+
const hits = searchCaveatsFromTextSafely(prompt);
|
|
45126
|
+
if (hits.length > 0) {
|
|
44680
45127
|
process.stdout.write(
|
|
44681
|
-
`<system-reminder>${userPromptSubmitReminderText()}</system-reminder>
|
|
45128
|
+
`<system-reminder>${userPromptSubmitReminderText(hits)}</system-reminder>
|
|
44682
45129
|
`
|
|
44683
45130
|
);
|
|
44684
45131
|
}
|
|
44685
45132
|
process.exit(0);
|
|
44686
45133
|
}
|
|
44687
|
-
if (name === "
|
|
44688
|
-
if (payload.
|
|
44689
|
-
|
|
45134
|
+
if (name === "post-tool-use") {
|
|
45135
|
+
if (!isToolError(payload)) process.exit(0);
|
|
45136
|
+
const errText = extractToolResponseText(payload.tool_response ?? payload);
|
|
45137
|
+
if (errText) {
|
|
45138
|
+
spawnWorker({ sessionId, searchText: errText });
|
|
44690
45139
|
}
|
|
44691
|
-
process.
|
|
44692
|
-
|
|
45140
|
+
process.exit(0);
|
|
45141
|
+
}
|
|
45142
|
+
if (name === "stop") {
|
|
45143
|
+
if (payload.stop_hook_active === true) process.exit(0);
|
|
45144
|
+
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
|
|
45145
|
+
const signals = transcriptPath ? loadSignalsSafely(transcriptPath) : null;
|
|
45146
|
+
if (!signals || !hasAnyStruggleSignal(signals)) process.exit(0);
|
|
45147
|
+
const related = searchCaveatsFromTextSafely(struggleSearchText(signals));
|
|
45148
|
+
process.stdout.write(
|
|
45149
|
+
`<system-reminder>${stopReminderText(signals, related)}</system-reminder>
|
|
45150
|
+
`
|
|
45151
|
+
);
|
|
44693
45152
|
process.exit(0);
|
|
44694
45153
|
}
|
|
44695
45154
|
process.stderr.write(`[caveat:hook] unknown hook name: ${name}
|
|
@@ -44698,10 +45157,10 @@ async function runHook(name) {
|
|
|
44698
45157
|
}
|
|
44699
45158
|
|
|
44700
45159
|
// src/commands/pull.ts
|
|
44701
|
-
import { existsSync as
|
|
44702
|
-
import { join as
|
|
45160
|
+
import { existsSync as existsSync15, readdirSync as readdirSync9 } from "node:fs";
|
|
45161
|
+
import { join as join18 } from "node:path";
|
|
44703
45162
|
async function runPull(ctx) {
|
|
44704
|
-
if (!
|
|
45163
|
+
if (!existsSync15(ctx.paths.communityDir)) {
|
|
44705
45164
|
ctx.logger.info(
|
|
44706
45165
|
"no community repos yet \u2014 add one with `caveat community add <github-url>`."
|
|
44707
45166
|
);
|
|
@@ -44721,15 +45180,15 @@ async function runPull(ctx) {
|
|
|
44721
45180
|
const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
|
|
44722
45181
|
try {
|
|
44723
45182
|
rebuildAll(db);
|
|
44724
|
-
if (
|
|
45183
|
+
if (existsSync15(ctx.paths.entriesDir)) {
|
|
44725
45184
|
const own = scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
|
|
44726
45185
|
ctx.logger.info(`own: +${own.added}`);
|
|
44727
45186
|
}
|
|
44728
|
-
for (const entry of
|
|
45187
|
+
for (const entry of readdirSync9(ctx.paths.communityDir, { withFileTypes: true })) {
|
|
44729
45188
|
if (!entry.isDirectory()) continue;
|
|
44730
45189
|
const source = `community/${entry.name}`;
|
|
44731
|
-
const root =
|
|
44732
|
-
if (!
|
|
45190
|
+
const root = join18(ctx.paths.communityDir, entry.name, "entries");
|
|
45191
|
+
if (!existsSync15(root)) continue;
|
|
44733
45192
|
const scan = scanSource({ db, source, entriesRoot: root });
|
|
44734
45193
|
ctx.logger.info(`${source}: +${scan.added}`);
|
|
44735
45194
|
}
|
|
@@ -44873,8 +45332,10 @@ program.command("serve").description("Start the read-only web share portal").opt
|
|
|
44873
45332
|
program.command("mcp-server").description("Run the MCP stdio server (registered by `caveat init`)").action(async () => {
|
|
44874
45333
|
await runMcpServer();
|
|
44875
45334
|
});
|
|
44876
|
-
program.command("hook <name>").description(
|
|
44877
|
-
|
|
45335
|
+
program.command("hook <name> [arg]").description(
|
|
45336
|
+
"Run a Claude Code hook. name: user-prompt-submit | post-tool-use | stop | worker"
|
|
45337
|
+
).action(async (name, arg) => {
|
|
45338
|
+
await runHook(name, arg);
|
|
44878
45339
|
});
|
|
44879
45340
|
var community = program.command("community").description("Manage community caveat repos (shallow clones under <knowledgeRepo>/community/)");
|
|
44880
45341
|
community.command("add <url>").description("Shallow-clone a GitHub caveat repo into community/<handle>/").action(async (url) => {
|