caveat-cli 0.7.0 → 0.11.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 +684 -108
- package/dist/index.js.map +1 -1
- package/dist/migrations/002_last_hit_at.sql +3 -0
- package/dist/schema.sql +2 -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,383 @@ 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
|
+
const externalLookup = signals.webSearchCount + signals.webFetchCount > 0;
|
|
21343
|
+
lines.push(
|
|
21344
|
+
`- \u5206\u985E\u30D2\u30F3\u30C8: ${externalLookup ? "\u5916\u90E8\u4ED5\u69D8\u8ABF\u67FB\u3042\u308A \u2192 public \u5BC4\u308A" : "\u5916\u90E8\u8ABF\u67FB\u306A\u3057 \u2192 private \u5BC4\u308A"}`
|
|
21345
|
+
);
|
|
21346
|
+
lines.push("");
|
|
21347
|
+
if (related.length > 0) {
|
|
21348
|
+
lines.push(
|
|
21349
|
+
`\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:`
|
|
21350
|
+
);
|
|
21351
|
+
related.forEach((h2, i2) => {
|
|
21352
|
+
lines.push(`${i2 + 1}. ${h2.id} (${h2.source}) \u2014 ${h2.title}`);
|
|
21353
|
+
});
|
|
21354
|
+
lines.push("");
|
|
21355
|
+
lines.push(
|
|
21356
|
+
"\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"
|
|
21357
|
+
);
|
|
21358
|
+
} else {
|
|
21359
|
+
lines.push(
|
|
21360
|
+
"\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"
|
|
21361
|
+
);
|
|
21362
|
+
}
|
|
21363
|
+
lines.push(
|
|
21364
|
+
"\u8A18\u9332\u6642\u306F tool \u8AAC\u660E\u306E\u4E8C\u9805\u57FA\u6E96\u3067 visibility \u3092\u9078\u3076\uFF08public = \u7B2C\u4E09\u8005\u518D\u73FE\u53EF\u80FD / private = repo \u56FA\u6709\uFF09\u3002\u8FF7\u3063\u305F\u3089 private\u3002"
|
|
21365
|
+
);
|
|
21366
|
+
return lines.join("\n");
|
|
21367
|
+
}
|
|
21368
|
+
|
|
21369
|
+
// ../../packages/core/dist/transcriptSignals.js
|
|
21370
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
21371
|
+
var MAX_ERROR_SNIPPETS = 10;
|
|
21372
|
+
var MAX_ERROR_SNIPPET_LENGTH = 300;
|
|
21373
|
+
var MAX_SEARCH_QUERIES = 10;
|
|
21374
|
+
var MAX_SEARCH_QUERY_LENGTH = 200;
|
|
21375
|
+
var MAX_FILE_EDIT_ENTRIES = 20;
|
|
21376
|
+
function isRecord(v) {
|
|
21377
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
21378
|
+
}
|
|
21379
|
+
function extractResultText(content) {
|
|
21380
|
+
if (typeof content === "string") return content;
|
|
21381
|
+
if (Array.isArray(content)) {
|
|
21382
|
+
const parts = [];
|
|
21383
|
+
for (const c3 of content) {
|
|
21384
|
+
if (isRecord(c3) && typeof c3.text === "string") parts.push(c3.text);
|
|
21385
|
+
}
|
|
21386
|
+
return parts.join(" ");
|
|
21387
|
+
}
|
|
21388
|
+
return "";
|
|
21222
21389
|
}
|
|
21223
|
-
function
|
|
21224
|
-
return
|
|
21225
|
-
|
|
21226
|
-
|
|
21227
|
-
"3 \u6587\u5B57\u4EE5\u4E0A\u306E\u30AF\u30A8\u30EA\u3067\u691C\u7D22\u3001\u65E5\u672C\u8A9E\u306F trigram \u4E00\u81F4\u3002\u8A72\u5F53\u306A\u3057\u306A\u3089\u7D9A\u884C\u3001\u8A72\u5F53\u3042\u308C\u3070 environment \u4E00\u81F4\u3092\u898B\u3066\u9069\u7528\u5224\u65AD\u3092\u3002"
|
|
21228
|
-
].join("\n");
|
|
21390
|
+
function parseTimestamp(raw2) {
|
|
21391
|
+
if (typeof raw2 !== "string") return void 0;
|
|
21392
|
+
const ms = Date.parse(raw2);
|
|
21393
|
+
return Number.isNaN(ms) ? void 0 : ms;
|
|
21229
21394
|
}
|
|
21230
|
-
function
|
|
21231
|
-
return
|
|
21232
|
-
|
|
21233
|
-
|
|
21234
|
-
|
|
21235
|
-
|
|
21395
|
+
function readSessionSignals(transcriptPath) {
|
|
21396
|
+
if (!transcriptPath || !existsSync7(transcriptPath)) return null;
|
|
21397
|
+
let raw2;
|
|
21398
|
+
try {
|
|
21399
|
+
raw2 = readFileSync5(transcriptPath, "utf-8");
|
|
21400
|
+
} catch {
|
|
21401
|
+
return null;
|
|
21402
|
+
}
|
|
21403
|
+
const editCounts = /* @__PURE__ */ new Map();
|
|
21404
|
+
const bashCounts = /* @__PURE__ */ new Map();
|
|
21405
|
+
const errorSnippets = [];
|
|
21406
|
+
const searchQueries = [];
|
|
21407
|
+
let toolFailureCount = 0;
|
|
21408
|
+
let webSearchCount = 0;
|
|
21409
|
+
let webFetchCount = 0;
|
|
21410
|
+
let firstTs;
|
|
21411
|
+
let lastTs;
|
|
21412
|
+
for (const line of raw2.split("\n")) {
|
|
21413
|
+
if (line.length === 0) continue;
|
|
21414
|
+
let parsed;
|
|
21415
|
+
try {
|
|
21416
|
+
parsed = JSON.parse(line);
|
|
21417
|
+
} catch {
|
|
21418
|
+
continue;
|
|
21419
|
+
}
|
|
21420
|
+
const ts = parseTimestamp(parsed.timestamp);
|
|
21421
|
+
if (ts !== void 0) {
|
|
21422
|
+
if (firstTs === void 0 || ts < firstTs) firstTs = ts;
|
|
21423
|
+
if (lastTs === void 0 || ts > lastTs) lastTs = ts;
|
|
21424
|
+
}
|
|
21425
|
+
if (parsed.type !== "assistant" && parsed.type !== "user") continue;
|
|
21426
|
+
const content = parsed.message?.content;
|
|
21427
|
+
if (!Array.isArray(content)) continue;
|
|
21428
|
+
for (const item of content) {
|
|
21429
|
+
if (!isRecord(item)) continue;
|
|
21430
|
+
if (item.type === "tool_use") {
|
|
21431
|
+
const name = item.name;
|
|
21432
|
+
const input = isRecord(item.input) ? item.input : {};
|
|
21433
|
+
if (name === "Edit" || name === "Write" || name === "NotebookEdit") {
|
|
21434
|
+
const p2 = typeof input.file_path === "string" ? input.file_path : "";
|
|
21435
|
+
if (p2) editCounts.set(p2, (editCounts.get(p2) ?? 0) + 1);
|
|
21436
|
+
} else if (name === "WebSearch") {
|
|
21437
|
+
webSearchCount += 1;
|
|
21438
|
+
if (typeof input.query === "string" && searchQueries.length < MAX_SEARCH_QUERIES) {
|
|
21439
|
+
searchQueries.push(input.query.slice(0, MAX_SEARCH_QUERY_LENGTH));
|
|
21440
|
+
}
|
|
21441
|
+
} else if (name === "WebFetch") {
|
|
21442
|
+
webFetchCount += 1;
|
|
21443
|
+
} else if (name === "Bash") {
|
|
21444
|
+
const cmd = typeof input.command === "string" ? input.command : "";
|
|
21445
|
+
if (cmd) bashCounts.set(cmd, (bashCounts.get(cmd) ?? 0) + 1);
|
|
21446
|
+
}
|
|
21447
|
+
} else if (item.type === "tool_result") {
|
|
21448
|
+
if (item.is_error === true) {
|
|
21449
|
+
toolFailureCount += 1;
|
|
21450
|
+
const text2 = extractResultText(item.content).replace(/\s+/g, " ").trim();
|
|
21451
|
+
if (text2 && errorSnippets.length < MAX_ERROR_SNIPPETS) {
|
|
21452
|
+
errorSnippets.push(text2.slice(0, MAX_ERROR_SNIPPET_LENGTH));
|
|
21453
|
+
}
|
|
21454
|
+
}
|
|
21455
|
+
}
|
|
21456
|
+
}
|
|
21457
|
+
}
|
|
21458
|
+
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);
|
|
21459
|
+
const bashRetryCount = [...bashCounts.values()].filter((c3) => c3 > 1).length;
|
|
21460
|
+
const durationMinutes = firstTs !== void 0 && lastTs !== void 0 ? Math.max(0, Math.round((lastTs - firstTs) / 6e4)) : 0;
|
|
21461
|
+
return {
|
|
21462
|
+
toolFailureCount,
|
|
21463
|
+
fileEditCounts,
|
|
21464
|
+
webSearchCount,
|
|
21465
|
+
webFetchCount,
|
|
21466
|
+
bashRetryCount,
|
|
21467
|
+
durationMinutes,
|
|
21468
|
+
errorSnippets,
|
|
21469
|
+
searchQueries
|
|
21470
|
+
};
|
|
21471
|
+
}
|
|
21472
|
+
function hasAnyStruggleSignal(s) {
|
|
21473
|
+
return s.toolFailureCount > 0 || s.fileEditCounts.length > 0 || s.webSearchCount > 0 || s.webFetchCount > 0 || s.bashRetryCount > 0;
|
|
21474
|
+
}
|
|
21475
|
+
function struggleSearchText(s) {
|
|
21476
|
+
return [...s.errorSnippets, ...s.searchQueries].join(" ");
|
|
21477
|
+
}
|
|
21478
|
+
|
|
21479
|
+
// ../../packages/core/dist/pendingReminders.js
|
|
21480
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync6, unlinkSync, writeFileSync as writeFileSync4 } from "node:fs";
|
|
21481
|
+
import { join as join7 } from "node:path";
|
|
21482
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
21483
|
+
function sanitizeSessionId(raw2) {
|
|
21484
|
+
const clean = raw2.replace(/[^A-Za-z0-9_-]/g, "");
|
|
21485
|
+
return clean.length > 0 ? clean : "_unknown";
|
|
21486
|
+
}
|
|
21487
|
+
function pendingDirFor(caveatHome, sessionId) {
|
|
21488
|
+
return join7(caveatHome, "pending", sanitizeSessionId(sessionId));
|
|
21489
|
+
}
|
|
21490
|
+
function appendPendingReminder(caveatHome, sessionId, text2) {
|
|
21491
|
+
const dir = pendingDirFor(caveatHome, sessionId);
|
|
21492
|
+
mkdirSync3(dir, { recursive: true });
|
|
21493
|
+
const name = `${Date.now()}-${randomBytes2(4).toString("hex")}.txt`;
|
|
21494
|
+
const path = join7(dir, name);
|
|
21495
|
+
writeFileSync4(path, text2, "utf-8");
|
|
21496
|
+
return path;
|
|
21497
|
+
}
|
|
21498
|
+
function drainPendingReminders(caveatHome, sessionId) {
|
|
21499
|
+
const dir = pendingDirFor(caveatHome, sessionId);
|
|
21500
|
+
if (!existsSync8(dir)) return [];
|
|
21501
|
+
let entries;
|
|
21502
|
+
try {
|
|
21503
|
+
entries = readdirSync4(dir).filter((f) => f.endsWith(".txt")).sort();
|
|
21504
|
+
} catch {
|
|
21505
|
+
return [];
|
|
21506
|
+
}
|
|
21507
|
+
const out = [];
|
|
21508
|
+
for (const entry of entries) {
|
|
21509
|
+
const path = join7(dir, entry);
|
|
21510
|
+
try {
|
|
21511
|
+
out.push(readFileSync6(path, "utf-8"));
|
|
21512
|
+
} catch {
|
|
21513
|
+
continue;
|
|
21514
|
+
}
|
|
21515
|
+
try {
|
|
21516
|
+
unlinkSync(path);
|
|
21517
|
+
} catch {
|
|
21518
|
+
}
|
|
21519
|
+
}
|
|
21520
|
+
return out;
|
|
21521
|
+
}
|
|
21522
|
+
|
|
21523
|
+
// ../../packages/core/dist/markHit.js
|
|
21524
|
+
function markHit(db, keys, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
|
|
21525
|
+
if (keys.length === 0) return;
|
|
21526
|
+
const ts = now();
|
|
21527
|
+
const stmt = db.prepare(
|
|
21528
|
+
"UPDATE entries SET last_hit_at = ? WHERE source = ? AND id = ?"
|
|
21529
|
+
);
|
|
21530
|
+
for (const k2 of keys) {
|
|
21531
|
+
stmt.run(ts, k2.source, k2.id);
|
|
21532
|
+
}
|
|
21533
|
+
}
|
|
21534
|
+
|
|
21535
|
+
// ../../packages/core/dist/stale.js
|
|
21536
|
+
function listStale(db, opts = {}) {
|
|
21537
|
+
const days = opts.days ?? 90;
|
|
21538
|
+
const limit = opts.limit ?? 50;
|
|
21539
|
+
const now = (opts.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
21540
|
+
const cutoff = new Date(now.getTime() - days * 24 * 60 * 60 * 1e3).toISOString();
|
|
21541
|
+
const conditions = ["(last_hit_at IS NULL OR last_hit_at < ?)"];
|
|
21542
|
+
const params = [cutoff];
|
|
21543
|
+
if (opts.visibility === "public" || opts.visibility === "private") {
|
|
21544
|
+
conditions.push("visibility = ?");
|
|
21545
|
+
params.push(opts.visibility);
|
|
21546
|
+
}
|
|
21547
|
+
const sql = `
|
|
21548
|
+
SELECT id, source, title, visibility, last_hit_at
|
|
21549
|
+
FROM entries
|
|
21550
|
+
WHERE ${conditions.join(" AND ")}
|
|
21551
|
+
ORDER BY last_hit_at IS NULL DESC, last_hit_at ASC
|
|
21552
|
+
LIMIT ?
|
|
21553
|
+
`;
|
|
21554
|
+
params.push(limit);
|
|
21555
|
+
const rows = db.prepare(sql).all(...params);
|
|
21556
|
+
return rows.map((r2) => ({
|
|
21557
|
+
id: r2.id,
|
|
21558
|
+
source: r2.source,
|
|
21559
|
+
title: r2.title,
|
|
21560
|
+
visibility: r2.visibility ?? "public",
|
|
21561
|
+
last_hit_at: r2.last_hit_at
|
|
21562
|
+
}));
|
|
21236
21563
|
}
|
|
21237
21564
|
|
|
21238
21565
|
// src/context.ts
|
|
21239
21566
|
function buildContext(logger, overrides = {}) {
|
|
21240
21567
|
const userHome = overrides.userHome ?? homedir();
|
|
21241
21568
|
const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
|
|
21242
|
-
const userConfigPath =
|
|
21569
|
+
const userConfigPath = join8(userHome, ".caveatrc.json");
|
|
21243
21570
|
const config3 = loadConfig(userConfigPath);
|
|
21244
21571
|
const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
|
|
21245
21572
|
return { caveatHome, userHome, userConfigPath, config: config3, paths, logger };
|
|
21246
21573
|
}
|
|
21247
21574
|
|
|
21248
21575
|
// src/version.ts
|
|
21249
|
-
import { readFileSync as
|
|
21250
|
-
import { dirname as dirname4, join as
|
|
21576
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
21577
|
+
import { dirname as dirname4, join as join9 } from "node:path";
|
|
21251
21578
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
21252
21579
|
function resolveVersion() {
|
|
21253
21580
|
try {
|
|
21254
21581
|
const here2 = dirname4(fileURLToPath3(import.meta.url));
|
|
21255
|
-
const pkg = JSON.parse(
|
|
21582
|
+
const pkg = JSON.parse(readFileSync7(join9(here2, "..", "package.json"), "utf-8"));
|
|
21256
21583
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
21257
21584
|
} catch {
|
|
21258
21585
|
return "0.0.0";
|
|
@@ -21271,14 +21598,15 @@ var stdoutLogger = {
|
|
|
21271
21598
|
};
|
|
21272
21599
|
|
|
21273
21600
|
// src/commands/init.ts
|
|
21274
|
-
import { existsSync as
|
|
21275
|
-
import { dirname as dirname6, join as
|
|
21601
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync5, renameSync, rmdirSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
21602
|
+
import { dirname as dirname6, join as join11 } from "node:path";
|
|
21276
21603
|
|
|
21277
21604
|
// src/claudeInstall.ts
|
|
21278
21605
|
import { spawnSync } from "node:child_process";
|
|
21279
|
-
import { copyFileSync, existsSync as
|
|
21280
|
-
import { dirname as dirname5, join as
|
|
21606
|
+
import { copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
|
|
21607
|
+
import { dirname as dirname5, join as join10 } from "node:path";
|
|
21281
21608
|
var EVENT_USER_PROMPT_SUBMIT = "UserPromptSubmit";
|
|
21609
|
+
var EVENT_POST_TOOL_USE = "PostToolUse";
|
|
21282
21610
|
var EVENT_STOP = "Stop";
|
|
21283
21611
|
function quote(p2) {
|
|
21284
21612
|
return p2.includes(" ") ? `"${p2}"` : p2;
|
|
@@ -21311,18 +21639,18 @@ function removeHook(settings, event, command) {
|
|
|
21311
21639
|
return true;
|
|
21312
21640
|
}
|
|
21313
21641
|
function readSettings(path) {
|
|
21314
|
-
if (!
|
|
21315
|
-
return JSON.parse(
|
|
21642
|
+
if (!existsSync9(path)) return {};
|
|
21643
|
+
return JSON.parse(readFileSync8(path, "utf-8"));
|
|
21316
21644
|
}
|
|
21317
21645
|
function writeSettings(path, settings) {
|
|
21318
21646
|
const dir = dirname5(path);
|
|
21319
|
-
if (!
|
|
21647
|
+
if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
|
|
21320
21648
|
let backupPath = "";
|
|
21321
|
-
if (
|
|
21649
|
+
if (existsSync9(path)) {
|
|
21322
21650
|
backupPath = `${path}.caveat-backup-${Date.now()}`;
|
|
21323
21651
|
copyFileSync(path, backupPath);
|
|
21324
21652
|
}
|
|
21325
|
-
|
|
21653
|
+
writeFileSync5(path, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
21326
21654
|
return backupPath;
|
|
21327
21655
|
}
|
|
21328
21656
|
var CLAUDE_BIN = "claude";
|
|
@@ -21381,33 +21709,38 @@ function unregisterMcp(dryRun, logger) {
|
|
|
21381
21709
|
return { action: "skipped", detail: "not registered or removal failed" };
|
|
21382
21710
|
}
|
|
21383
21711
|
function installClaudeIntegration(opts) {
|
|
21384
|
-
const settingsPath =
|
|
21712
|
+
const settingsPath = join10(opts.claudeDir, "settings.json");
|
|
21385
21713
|
const settings = readSettings(settingsPath);
|
|
21386
21714
|
const usCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "user-prompt-submit");
|
|
21715
|
+
const ptCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "post-tool-use");
|
|
21387
21716
|
const stopCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "stop");
|
|
21388
21717
|
const userPromptSubmit = upsertHook(settings, EVENT_USER_PROMPT_SUBMIT, usCmd);
|
|
21718
|
+
const postToolUse = upsertHook(settings, EVENT_POST_TOOL_USE, ptCmd);
|
|
21389
21719
|
const stop = upsertHook(settings, EVENT_STOP, stopCmd);
|
|
21390
21720
|
let backupPath;
|
|
21391
|
-
|
|
21721
|
+
const anyAdded = userPromptSubmit === "added" || postToolUse === "added" || stop === "added";
|
|
21722
|
+
if (!opts.dryRun && anyAdded) {
|
|
21392
21723
|
const backup = writeSettings(settingsPath, settings);
|
|
21393
21724
|
if (backup) backupPath = backup;
|
|
21394
21725
|
} else if (opts.dryRun) {
|
|
21395
21726
|
opts.logger.info(
|
|
21396
|
-
`[dry-run] would ${userPromptSubmit === "added" ? "add" : "keep"} UserPromptSubmit
|
|
21727
|
+
`[dry-run] would ${userPromptSubmit === "added" ? "add" : "keep"} UserPromptSubmit, ${postToolUse === "added" ? "add" : "keep"} PostToolUse, ${stop === "added" ? "add" : "keep"} Stop hook in ${settingsPath}`
|
|
21397
21728
|
);
|
|
21398
21729
|
}
|
|
21399
21730
|
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 };
|
|
21731
|
+
return { mcp, hooks: { userPromptSubmit, postToolUse, stop }, backupPath };
|
|
21401
21732
|
}
|
|
21402
21733
|
function uninstallClaudeIntegration(opts) {
|
|
21403
|
-
const settingsPath =
|
|
21734
|
+
const settingsPath = join10(opts.claudeDir, "settings.json");
|
|
21404
21735
|
const settings = readSettings(settingsPath);
|
|
21405
21736
|
const usCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "user-prompt-submit");
|
|
21737
|
+
const ptCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "post-tool-use");
|
|
21406
21738
|
const stopCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "stop");
|
|
21407
21739
|
const removedUs = removeHook(settings, EVENT_USER_PROMPT_SUBMIT, usCmd);
|
|
21740
|
+
const removedPt = removeHook(settings, EVENT_POST_TOOL_USE, ptCmd);
|
|
21408
21741
|
const removedStop = removeHook(settings, EVENT_STOP, stopCmd);
|
|
21409
21742
|
let backupPath;
|
|
21410
|
-
if (!opts.dryRun && (removedUs || removedStop)) {
|
|
21743
|
+
if (!opts.dryRun && (removedUs || removedPt || removedStop)) {
|
|
21411
21744
|
const backup = writeSettings(settingsPath, settings);
|
|
21412
21745
|
if (backup) backupPath = backup;
|
|
21413
21746
|
}
|
|
@@ -21416,6 +21749,7 @@ function uninstallClaudeIntegration(opts) {
|
|
|
21416
21749
|
mcp,
|
|
21417
21750
|
hooks: {
|
|
21418
21751
|
userPromptSubmit: removedUs ? "added" : "unchanged",
|
|
21752
|
+
postToolUse: removedPt ? "added" : "unchanged",
|
|
21419
21753
|
stop: removedStop ? "added" : "unchanged"
|
|
21420
21754
|
},
|
|
21421
21755
|
backupPath
|
|
@@ -21435,22 +21769,22 @@ var KNOWLEDGE_GITIGNORE = [
|
|
|
21435
21769
|
async function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
|
|
21436
21770
|
ensureUserConfig(ctx.userConfigPath);
|
|
21437
21771
|
ctx.logger.info(`user config: ${ctx.userConfigPath}`);
|
|
21438
|
-
if (!
|
|
21439
|
-
|
|
21440
|
-
|
|
21772
|
+
if (!existsSync10(ctx.paths.knowledgeRepo)) {
|
|
21773
|
+
mkdirSync5(ctx.paths.knowledgeRepo, { recursive: true });
|
|
21774
|
+
mkdirSync5(ctx.paths.entriesDir, { recursive: true });
|
|
21441
21775
|
ctx.logger.info(`knowledge repo scaffolded: ${ctx.paths.knowledgeRepo}`);
|
|
21442
21776
|
} else {
|
|
21443
21777
|
ctx.logger.info(`knowledge repo: ${ctx.paths.knowledgeRepo}`);
|
|
21444
21778
|
}
|
|
21445
21779
|
migrateLegacyCommunityDir(ctx);
|
|
21446
|
-
const gitignorePath =
|
|
21447
|
-
if (!
|
|
21448
|
-
|
|
21780
|
+
const gitignorePath = join11(ctx.paths.knowledgeRepo, ".gitignore");
|
|
21781
|
+
if (!existsSync10(gitignorePath)) {
|
|
21782
|
+
writeFileSync6(gitignorePath, KNOWLEDGE_GITIGNORE, "utf-8");
|
|
21449
21783
|
ctx.logger.info(`.gitignore created: ${gitignorePath}`);
|
|
21450
21784
|
}
|
|
21451
21785
|
if (!opts.dryRun) {
|
|
21452
21786
|
const dbDir = dirname6(ctx.paths.dbPath);
|
|
21453
|
-
if (!
|
|
21787
|
+
if (!existsSync10(dbDir)) mkdirSync5(dbDir, { recursive: true });
|
|
21454
21788
|
const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
|
|
21455
21789
|
db.close();
|
|
21456
21790
|
ctx.logger.info(`db initialized: ${ctx.paths.dbPath}`);
|
|
@@ -21470,7 +21804,7 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
|
|
|
21470
21804
|
return;
|
|
21471
21805
|
}
|
|
21472
21806
|
const result = installClaudeIntegration({
|
|
21473
|
-
claudeDir:
|
|
21807
|
+
claudeDir: join11(ctx.userHome, ".claude"),
|
|
21474
21808
|
cliScriptPath,
|
|
21475
21809
|
nodePath: process.execPath,
|
|
21476
21810
|
dryRun: opts.dryRun,
|
|
@@ -21479,20 +21813,20 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
|
|
|
21479
21813
|
reportInstallResult(ctx, result, opts.dryRun);
|
|
21480
21814
|
}
|
|
21481
21815
|
function migrateLegacyCommunityDir(ctx) {
|
|
21482
|
-
const legacy =
|
|
21816
|
+
const legacy = join11(ctx.paths.knowledgeRepo, "community");
|
|
21483
21817
|
const current = ctx.paths.communityDir;
|
|
21484
21818
|
if (legacy === current) return;
|
|
21485
|
-
if (!
|
|
21486
|
-
if (
|
|
21819
|
+
if (!existsSync10(legacy)) return;
|
|
21820
|
+
if (existsSync10(current)) {
|
|
21487
21821
|
ctx.logger.warn(
|
|
21488
21822
|
`legacy community dir still exists at ${legacy} \u2014 remove manually (new location in use)`
|
|
21489
21823
|
);
|
|
21490
21824
|
return;
|
|
21491
21825
|
}
|
|
21492
|
-
|
|
21493
|
-
for (const entry of
|
|
21826
|
+
mkdirSync5(current, { recursive: true });
|
|
21827
|
+
for (const entry of readdirSync5(legacy, { withFileTypes: true })) {
|
|
21494
21828
|
if (!entry.isDirectory()) continue;
|
|
21495
|
-
renameSync(
|
|
21829
|
+
renameSync(join11(legacy, entry.name), join11(current, entry.name));
|
|
21496
21830
|
}
|
|
21497
21831
|
try {
|
|
21498
21832
|
rmdirSync(legacy);
|
|
@@ -21507,7 +21841,7 @@ function runUninstall(ctx, opts) {
|
|
|
21507
21841
|
process.exit(1);
|
|
21508
21842
|
}
|
|
21509
21843
|
const result = uninstallClaudeIntegration({
|
|
21510
|
-
claudeDir:
|
|
21844
|
+
claudeDir: join11(ctx.userHome, ".claude"),
|
|
21511
21845
|
cliScriptPath,
|
|
21512
21846
|
nodePath: process.execPath,
|
|
21513
21847
|
dryRun: opts.dryRun,
|
|
@@ -21544,29 +21878,29 @@ function reportInstallResult(ctx, result, dryRun) {
|
|
|
21544
21878
|
}
|
|
21545
21879
|
|
|
21546
21880
|
// src/commands/indexCmd.ts
|
|
21547
|
-
import { existsSync as
|
|
21548
|
-
import { dirname as dirname7, join as
|
|
21881
|
+
import { existsSync as existsSync11, readdirSync as readdirSync6, mkdirSync as mkdirSync6 } from "node:fs";
|
|
21882
|
+
import { dirname as dirname7, join as join12 } from "node:path";
|
|
21549
21883
|
function runIndex(ctx, opts) {
|
|
21550
21884
|
const dbDir = dirname7(ctx.paths.dbPath);
|
|
21551
|
-
if (!
|
|
21885
|
+
if (!existsSync11(dbDir)) mkdirSync6(dbDir, { recursive: true });
|
|
21552
21886
|
const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
|
|
21553
21887
|
try {
|
|
21554
21888
|
if (opts.full) {
|
|
21555
21889
|
ctx.logger.info("full rebuild: DELETE FROM entries");
|
|
21556
21890
|
rebuildAll(db);
|
|
21557
21891
|
}
|
|
21558
|
-
if (
|
|
21892
|
+
if (existsSync11(ctx.paths.entriesDir)) {
|
|
21559
21893
|
const result = scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
|
|
21560
21894
|
ctx.logger.info(`own: +${result.added} ~${result.updated} -${result.deleted}`);
|
|
21561
21895
|
} else {
|
|
21562
21896
|
ctx.logger.warn(`entries dir not found: ${ctx.paths.entriesDir}`);
|
|
21563
21897
|
}
|
|
21564
|
-
if (
|
|
21565
|
-
for (const entry of
|
|
21898
|
+
if (existsSync11(ctx.paths.communityDir)) {
|
|
21899
|
+
for (const entry of readdirSync6(ctx.paths.communityDir, { withFileTypes: true })) {
|
|
21566
21900
|
if (!entry.isDirectory()) continue;
|
|
21567
21901
|
const source = `community/${entry.name}`;
|
|
21568
|
-
const root =
|
|
21569
|
-
if (!
|
|
21902
|
+
const root = join12(ctx.paths.communityDir, entry.name, "entries");
|
|
21903
|
+
if (!existsSync11(root)) continue;
|
|
21570
21904
|
const result = scanSource({ db, source, entriesRoot: root });
|
|
21571
21905
|
ctx.logger.info(`${source}: +${result.added} ~${result.updated} -${result.deleted}`);
|
|
21572
21906
|
}
|
|
@@ -21621,6 +21955,29 @@ function runList(ctx, opts) {
|
|
|
21621
21955
|
}
|
|
21622
21956
|
}
|
|
21623
21957
|
|
|
21958
|
+
// src/commands/stale.ts
|
|
21959
|
+
function runStale(ctx, opts) {
|
|
21960
|
+
const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
|
|
21961
|
+
try {
|
|
21962
|
+
const rows = listStale(db, {
|
|
21963
|
+
days: opts.days,
|
|
21964
|
+
visibility: opts.visibility,
|
|
21965
|
+
limit: opts.limit
|
|
21966
|
+
});
|
|
21967
|
+
if (rows.length === 0) {
|
|
21968
|
+
process.stdout.write("(no stale entries)\n");
|
|
21969
|
+
return;
|
|
21970
|
+
}
|
|
21971
|
+
for (const r2 of rows) {
|
|
21972
|
+
const age = r2.last_hit_at ?? "never";
|
|
21973
|
+
process.stdout.write(`${r2.id} [${r2.source}] (${r2.visibility}) ${age} \u2014 ${r2.title}
|
|
21974
|
+
`);
|
|
21975
|
+
}
|
|
21976
|
+
} finally {
|
|
21977
|
+
db.close();
|
|
21978
|
+
}
|
|
21979
|
+
}
|
|
21980
|
+
|
|
21624
21981
|
// src/commands/show.ts
|
|
21625
21982
|
function runShow(ctx, opts) {
|
|
21626
21983
|
const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
|
|
@@ -22340,11 +22697,11 @@ var serve = (options2, listeningListener) => {
|
|
|
22340
22697
|
|
|
22341
22698
|
// ../web/dist/context.js
|
|
22342
22699
|
import { homedir as homedir2 } from "node:os";
|
|
22343
|
-
import { join as
|
|
22700
|
+
import { join as join13 } from "node:path";
|
|
22344
22701
|
function buildWebContext(overrides = {}) {
|
|
22345
22702
|
const userHome = overrides.userHome ?? homedir2();
|
|
22346
22703
|
const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
|
|
22347
|
-
const userConfigPath =
|
|
22704
|
+
const userConfigPath = join13(userHome, ".caveatrc.json");
|
|
22348
22705
|
const logger = overrides.logger ?? stderrLogger;
|
|
22349
22706
|
const config3 = loadConfig(userConfigPath);
|
|
22350
22707
|
const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
|
|
@@ -30089,14 +30446,14 @@ function createDetailRoute(ctx) {
|
|
|
30089
30446
|
}
|
|
30090
30447
|
|
|
30091
30448
|
// ../web/dist/routes/community.js
|
|
30092
|
-
import { existsSync as
|
|
30093
|
-
import { join as
|
|
30449
|
+
import { existsSync as existsSync12, readdirSync as readdirSync7, statSync as statSync5 } from "node:fs";
|
|
30450
|
+
import { join as join14 } from "node:path";
|
|
30094
30451
|
function listCommunity(communityDir, db) {
|
|
30095
|
-
if (!
|
|
30452
|
+
if (!existsSync12(communityDir)) return [];
|
|
30096
30453
|
const handles = [];
|
|
30097
|
-
for (const entry of
|
|
30454
|
+
for (const entry of readdirSync7(communityDir, { withFileTypes: true })) {
|
|
30098
30455
|
if (!entry.isDirectory()) continue;
|
|
30099
|
-
const handlePath =
|
|
30456
|
+
const handlePath = join14(communityDir, entry.name);
|
|
30100
30457
|
const countRow = db.prepare("SELECT COUNT(*) AS n FROM entries WHERE source = ?").get(`community/${entry.name}`);
|
|
30101
30458
|
handles.push({
|
|
30102
30459
|
handle: entry.name,
|
|
@@ -44394,11 +44751,11 @@ var StdioServerTransport = class {
|
|
|
44394
44751
|
|
|
44395
44752
|
// ../mcp/dist/context.js
|
|
44396
44753
|
import { homedir as homedir3 } from "node:os";
|
|
44397
|
-
import { join as
|
|
44754
|
+
import { join as join15 } from "node:path";
|
|
44398
44755
|
function buildMcpContext(overrides = {}) {
|
|
44399
44756
|
const userHome = overrides.userHome ?? homedir3();
|
|
44400
44757
|
const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
|
|
44401
|
-
const userConfigPath =
|
|
44758
|
+
const userConfigPath = join15(userHome, ".caveatrc.json");
|
|
44402
44759
|
const logger = overrides.logger ?? stderrLogger;
|
|
44403
44760
|
const config3 = loadConfig(userConfigPath);
|
|
44404
44761
|
const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
|
|
@@ -44409,12 +44766,24 @@ function buildMcpContext(overrides = {}) {
|
|
|
44409
44766
|
// ../mcp/dist/tools/search.js
|
|
44410
44767
|
var sourceFilter = external_exports.enum(["own", "community", "all"]);
|
|
44411
44768
|
var confidenceSchema = external_exports.enum(["confirmed", "reproduced", "tentative"]);
|
|
44769
|
+
var visibilityFilter = external_exports.enum(["public", "private", "all"]);
|
|
44412
44770
|
var searchInputShape = {
|
|
44413
44771
|
query: external_exports.string().describe("FTS query (3+ chars for trigram). Empty string lists without text filter."),
|
|
44414
44772
|
filters: external_exports.object({
|
|
44415
44773
|
tags: external_exports.array(external_exports.string()).optional(),
|
|
44416
44774
|
confidence: external_exports.array(confidenceSchema).optional(),
|
|
44417
|
-
source: sourceFilter.optional()
|
|
44775
|
+
source: sourceFilter.optional(),
|
|
44776
|
+
visibility: visibilityFilter.optional().describe(
|
|
44777
|
+
[
|
|
44778
|
+
"Narrow by publish tier.",
|
|
44779
|
+
"'public' = external-spec gotchas reproducible by any third party (PyInstaller, Stripe, Claude Code hook behavior, etc).",
|
|
44780
|
+
"'private' = your own cross-project notes (repo-specific, your workflow, intentional non-standard design).",
|
|
44781
|
+
"'all' (or omit) = both tiers.",
|
|
44782
|
+
"Use 'public' when drafting externally-visible output (PR descriptions, public docs, answers to third parties) so private notes do not bleed into external content.",
|
|
44783
|
+
"Use 'private' when specifically recalling your own past decisions.",
|
|
44784
|
+
"Default to omitting this filter \u2014 narrowing too aggressively hides relevant entries."
|
|
44785
|
+
].join(" ")
|
|
44786
|
+
)
|
|
44418
44787
|
}).optional(),
|
|
44419
44788
|
limit: external_exports.number().int().min(1).max(200).optional()
|
|
44420
44789
|
};
|
|
@@ -44424,6 +44793,7 @@ function handleSearch(ctx, args) {
|
|
|
44424
44793
|
filters: args.filters,
|
|
44425
44794
|
limit: args.limit
|
|
44426
44795
|
});
|
|
44796
|
+
if (results.length > 0) markHit(ctx.db, results);
|
|
44427
44797
|
return results;
|
|
44428
44798
|
}
|
|
44429
44799
|
|
|
@@ -44455,7 +44825,14 @@ var recordInputShape = {
|
|
|
44455
44825
|
confidence: confidenceSchema2.optional(),
|
|
44456
44826
|
outcome: outcomeSchema.optional(),
|
|
44457
44827
|
visibility: visibilitySchema.describe(
|
|
44458
|
-
|
|
44828
|
+
[
|
|
44829
|
+
"REQUIRED. Classify using this binary criterion:",
|
|
44830
|
+
"- 'public' if a third party running the same external tool/spec could reproduce this gotcha (external-spec trap, e.g. PyInstaller/Stripe/Podman/Claude Code hook behavior).",
|
|
44831
|
+
"- 'private' if it is specific to your repo, your workflow, an intentional non-standard design, or context that only exists in this project.",
|
|
44832
|
+
"- When unclear, prefer 'private' (leak-safety).",
|
|
44833
|
+
`Exception: if the user explicitly asks to record it as private/public (e.g. "save this as private", "\u3053\u308C\u306F\u81EA\u5206\u7528\u306B\u30E1\u30E2\u3057\u3066"), follow the user's instruction regardless of the criterion \u2014 explicit user intent overrides auto-classification.`,
|
|
44834
|
+
"When recording with visibility: 'private', always include repo-specific identifiers (function names, file paths, class names, custom terminology) in the body so the entry can be retrieved later by co-occurrence FTS when you touch that area again."
|
|
44835
|
+
].join(" ")
|
|
44459
44836
|
),
|
|
44460
44837
|
tags: external_exports.array(external_exports.string()).optional(),
|
|
44461
44838
|
environment: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
@@ -44476,7 +44853,13 @@ var patchFrontmatterSchema = external_exports.object({
|
|
|
44476
44853
|
title: external_exports.string().optional(),
|
|
44477
44854
|
confidence: confidenceSchema3.optional(),
|
|
44478
44855
|
outcome: outcomeSchema2.optional(),
|
|
44479
|
-
visibility: visibilitySchema2.optional()
|
|
44856
|
+
visibility: visibilitySchema2.optional().describe(
|
|
44857
|
+
[
|
|
44858
|
+
"Change the publish tier. Use the same binary criterion as caveat_record:",
|
|
44859
|
+
"'public' if third-party reproducible, 'private' if repo-specific/your-workflow-specific.",
|
|
44860
|
+
"When unclear, prefer 'private'. Explicit user instruction overrides auto-classification."
|
|
44861
|
+
].join(" ")
|
|
44862
|
+
),
|
|
44480
44863
|
tags: external_exports.array(external_exports.string()).optional(),
|
|
44481
44864
|
environment: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
44482
44865
|
last_verified: external_exports.string().optional()
|
|
@@ -44507,13 +44890,13 @@ function handleListRecent(ctx, args) {
|
|
|
44507
44890
|
}
|
|
44508
44891
|
|
|
44509
44892
|
// ../mcp/dist/tools/pull.js
|
|
44510
|
-
import { existsSync as
|
|
44511
|
-
import { join as
|
|
44893
|
+
import { existsSync as existsSync13, readdirSync as readdirSync8 } from "node:fs";
|
|
44894
|
+
import { join as join16 } from "node:path";
|
|
44512
44895
|
var pullInputShape = {};
|
|
44513
44896
|
async function handlePull(ctx, _args = {}) {
|
|
44514
44897
|
const pulled = [];
|
|
44515
44898
|
const indexed = [];
|
|
44516
|
-
if (
|
|
44899
|
+
if (existsSync13(ctx.paths.communityDir)) {
|
|
44517
44900
|
const results = await communityPull({
|
|
44518
44901
|
communityDir: ctx.paths.communityDir,
|
|
44519
44902
|
logger: ctx.logger
|
|
@@ -44523,16 +44906,16 @@ async function handlePull(ctx, _args = {}) {
|
|
|
44523
44906
|
}
|
|
44524
44907
|
}
|
|
44525
44908
|
rebuildAll(ctx.db);
|
|
44526
|
-
if (
|
|
44909
|
+
if (existsSync13(ctx.paths.entriesDir)) {
|
|
44527
44910
|
const own = scanSource({ db: ctx.db, source: "own", entriesRoot: ctx.paths.entriesDir });
|
|
44528
44911
|
indexed.push({ source: "own", ...own });
|
|
44529
44912
|
}
|
|
44530
|
-
if (
|
|
44531
|
-
for (const entry of
|
|
44913
|
+
if (existsSync13(ctx.paths.communityDir)) {
|
|
44914
|
+
for (const entry of readdirSync8(ctx.paths.communityDir, { withFileTypes: true })) {
|
|
44532
44915
|
if (!entry.isDirectory()) continue;
|
|
44533
44916
|
const source = `community/${entry.name}`;
|
|
44534
|
-
const root =
|
|
44535
|
-
if (!
|
|
44917
|
+
const root = join16(ctx.paths.communityDir, entry.name, "entries");
|
|
44918
|
+
if (!existsSync13(root)) continue;
|
|
44536
44919
|
const scan = scanSource({ db: ctx.db, source, entriesRoot: root });
|
|
44537
44920
|
indexed.push({ source, ...scan });
|
|
44538
44921
|
}
|
|
@@ -44645,6 +45028,19 @@ async function runMcpServer() {
|
|
|
44645
45028
|
}
|
|
44646
45029
|
|
|
44647
45030
|
// src/commands/hookCmd.ts
|
|
45031
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
45032
|
+
import { existsSync as existsSync14, readFileSync as readFileSync9, unlinkSync as unlinkSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
45033
|
+
import { tmpdir } from "node:os";
|
|
45034
|
+
import { join as join17 } from "node:path";
|
|
45035
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
45036
|
+
var silentLogger = {
|
|
45037
|
+
info: () => {
|
|
45038
|
+
},
|
|
45039
|
+
warn: () => {
|
|
45040
|
+
},
|
|
45041
|
+
error: (m) => process.stderr.write(`[caveat:hook] ${m}
|
|
45042
|
+
`)
|
|
45043
|
+
};
|
|
44648
45044
|
async function readStdin() {
|
|
44649
45045
|
const chunks = [];
|
|
44650
45046
|
for await (const chunk of process.stdin) {
|
|
@@ -44663,7 +45059,163 @@ function parsePayload(raw2) {
|
|
|
44663
45059
|
return {};
|
|
44664
45060
|
}
|
|
44665
45061
|
}
|
|
44666
|
-
|
|
45062
|
+
function getSessionId(payload) {
|
|
45063
|
+
const v = payload.session_id ?? payload.sessionId;
|
|
45064
|
+
return typeof v === "string" && v.length > 0 ? v : "_unknown";
|
|
45065
|
+
}
|
|
45066
|
+
function buildContextSafely() {
|
|
45067
|
+
try {
|
|
45068
|
+
return buildContext(silentLogger);
|
|
45069
|
+
} catch (err) {
|
|
45070
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
45071
|
+
process.stderr.write(`[caveat:hook] context error: ${msg}
|
|
45072
|
+
`);
|
|
45073
|
+
return null;
|
|
45074
|
+
}
|
|
45075
|
+
}
|
|
45076
|
+
function searchCaveatsFromTextSafely(text2) {
|
|
45077
|
+
if (!text2) return [];
|
|
45078
|
+
let db;
|
|
45079
|
+
try {
|
|
45080
|
+
const ctx = buildContextSafely();
|
|
45081
|
+
if (!ctx || !existsSync14(ctx.paths.dbPath)) return [];
|
|
45082
|
+
db = openDb({ path: ctx.paths.dbPath });
|
|
45083
|
+
const hits = findCaveatsForPrompt(db, text2);
|
|
45084
|
+
if (hits.length > 0) {
|
|
45085
|
+
try {
|
|
45086
|
+
markHit(db, hits);
|
|
45087
|
+
} catch (err) {
|
|
45088
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
45089
|
+
process.stderr.write(`[caveat:hook] markHit error: ${msg}
|
|
45090
|
+
`);
|
|
45091
|
+
}
|
|
45092
|
+
}
|
|
45093
|
+
return hits;
|
|
45094
|
+
} catch (err) {
|
|
45095
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
45096
|
+
process.stderr.write(`[caveat:hook] search error: ${msg}
|
|
45097
|
+
`);
|
|
45098
|
+
return [];
|
|
45099
|
+
} finally {
|
|
45100
|
+
db?.close();
|
|
45101
|
+
}
|
|
45102
|
+
}
|
|
45103
|
+
function loadSignalsSafely(path) {
|
|
45104
|
+
try {
|
|
45105
|
+
return readSessionSignals(path);
|
|
45106
|
+
} catch (err) {
|
|
45107
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
45108
|
+
process.stderr.write(`[caveat:hook] transcript read error: ${msg}
|
|
45109
|
+
`);
|
|
45110
|
+
return null;
|
|
45111
|
+
}
|
|
45112
|
+
}
|
|
45113
|
+
function drainForSession(sessionId) {
|
|
45114
|
+
const ctx = buildContextSafely();
|
|
45115
|
+
if (!ctx) return;
|
|
45116
|
+
const reminders = drainPendingReminders(ctx.caveatHome, sessionId);
|
|
45117
|
+
for (const text2 of reminders) {
|
|
45118
|
+
process.stdout.write(`<system-reminder>${text2}</system-reminder>
|
|
45119
|
+
`);
|
|
45120
|
+
}
|
|
45121
|
+
}
|
|
45122
|
+
function extractToolResponseText(response) {
|
|
45123
|
+
if (typeof response === "string") return response;
|
|
45124
|
+
if (Array.isArray(response)) {
|
|
45125
|
+
const parts = [];
|
|
45126
|
+
for (const item of response) {
|
|
45127
|
+
if (typeof item === "string") parts.push(item);
|
|
45128
|
+
else if (item !== null && typeof item === "object" && typeof item.text === "string") {
|
|
45129
|
+
parts.push(item.text);
|
|
45130
|
+
}
|
|
45131
|
+
}
|
|
45132
|
+
return parts.join(" ");
|
|
45133
|
+
}
|
|
45134
|
+
if (response !== null && typeof response === "object") {
|
|
45135
|
+
const r2 = response;
|
|
45136
|
+
if (typeof r2.content === "string") return r2.content;
|
|
45137
|
+
if (Array.isArray(r2.content)) return extractToolResponseText(r2.content);
|
|
45138
|
+
if (typeof r2.output === "string") return r2.output;
|
|
45139
|
+
if (typeof r2.stdout === "string" || typeof r2.stderr === "string") {
|
|
45140
|
+
return [r2.stdout, r2.stderr].filter((x2) => typeof x2 === "string").join(" ");
|
|
45141
|
+
}
|
|
45142
|
+
}
|
|
45143
|
+
return "";
|
|
45144
|
+
}
|
|
45145
|
+
function isToolError(payload) {
|
|
45146
|
+
const resp = payload.tool_response ?? payload.toolResponse;
|
|
45147
|
+
if (resp !== null && typeof resp === "object" && !Array.isArray(resp)) {
|
|
45148
|
+
if (resp.is_error === true) return true;
|
|
45149
|
+
}
|
|
45150
|
+
if (payload.is_error === true) return true;
|
|
45151
|
+
return false;
|
|
45152
|
+
}
|
|
45153
|
+
function spawnWorker(job) {
|
|
45154
|
+
const workFile = join17(
|
|
45155
|
+
tmpdir(),
|
|
45156
|
+
`caveat-worker-${Date.now()}-${randomBytes3(4).toString("hex")}.json`
|
|
45157
|
+
);
|
|
45158
|
+
try {
|
|
45159
|
+
writeFileSync7(workFile, JSON.stringify(job), "utf-8");
|
|
45160
|
+
} catch (err) {
|
|
45161
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
45162
|
+
process.stderr.write(`[caveat:hook] worker writefile error: ${msg}
|
|
45163
|
+
`);
|
|
45164
|
+
return;
|
|
45165
|
+
}
|
|
45166
|
+
const cliScript = process.argv[1];
|
|
45167
|
+
if (!cliScript) return;
|
|
45168
|
+
try {
|
|
45169
|
+
const child = spawn2(
|
|
45170
|
+
process.execPath,
|
|
45171
|
+
["--disable-warning=ExperimentalWarning", cliScript, "hook", "worker", workFile],
|
|
45172
|
+
{ detached: true, stdio: "ignore", windowsHide: true }
|
|
45173
|
+
);
|
|
45174
|
+
child.unref();
|
|
45175
|
+
} catch (err) {
|
|
45176
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
45177
|
+
process.stderr.write(`[caveat:hook] worker spawn error: ${msg}
|
|
45178
|
+
`);
|
|
45179
|
+
try {
|
|
45180
|
+
unlinkSync2(workFile);
|
|
45181
|
+
} catch {
|
|
45182
|
+
}
|
|
45183
|
+
}
|
|
45184
|
+
}
|
|
45185
|
+
async function runWorker(workFile) {
|
|
45186
|
+
let raw2;
|
|
45187
|
+
try {
|
|
45188
|
+
raw2 = readFileSync9(workFile, "utf-8");
|
|
45189
|
+
} catch {
|
|
45190
|
+
process.exit(0);
|
|
45191
|
+
}
|
|
45192
|
+
try {
|
|
45193
|
+
unlinkSync2(workFile);
|
|
45194
|
+
} catch {
|
|
45195
|
+
}
|
|
45196
|
+
let job;
|
|
45197
|
+
try {
|
|
45198
|
+
job = JSON.parse(raw2);
|
|
45199
|
+
} catch {
|
|
45200
|
+
process.exit(0);
|
|
45201
|
+
}
|
|
45202
|
+
if (!job.searchText || !job.sessionId) process.exit(0);
|
|
45203
|
+
const hits = searchCaveatsFromTextSafely(job.searchText);
|
|
45204
|
+
if (hits.length === 0) process.exit(0);
|
|
45205
|
+
const ctx = buildContextSafely();
|
|
45206
|
+
if (!ctx) process.exit(0);
|
|
45207
|
+
try {
|
|
45208
|
+
appendPendingReminder(ctx.caveatHome, job.sessionId, toolErrorReminderText(hits));
|
|
45209
|
+
} catch {
|
|
45210
|
+
}
|
|
45211
|
+
process.exit(0);
|
|
45212
|
+
}
|
|
45213
|
+
async function runHook(name, arg) {
|
|
45214
|
+
if (name === "worker") {
|
|
45215
|
+
if (!arg) process.exit(0);
|
|
45216
|
+
await runWorker(arg);
|
|
45217
|
+
return;
|
|
45218
|
+
}
|
|
44667
45219
|
let raw2 = "";
|
|
44668
45220
|
try {
|
|
44669
45221
|
raw2 = await readStdin();
|
|
@@ -44674,22 +45226,37 @@ async function runHook(name) {
|
|
|
44674
45226
|
process.exit(0);
|
|
44675
45227
|
}
|
|
44676
45228
|
const payload = parsePayload(raw2);
|
|
45229
|
+
const sessionId = getSessionId(payload);
|
|
45230
|
+
drainForSession(sessionId);
|
|
44677
45231
|
if (name === "user-prompt-submit") {
|
|
44678
45232
|
const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
|
|
44679
|
-
|
|
45233
|
+
const hits = searchCaveatsFromTextSafely(prompt);
|
|
45234
|
+
if (hits.length > 0) {
|
|
44680
45235
|
process.stdout.write(
|
|
44681
|
-
`<system-reminder>${userPromptSubmitReminderText()}</system-reminder>
|
|
45236
|
+
`<system-reminder>${userPromptSubmitReminderText(hits)}</system-reminder>
|
|
44682
45237
|
`
|
|
44683
45238
|
);
|
|
44684
45239
|
}
|
|
44685
45240
|
process.exit(0);
|
|
44686
45241
|
}
|
|
44687
|
-
if (name === "
|
|
44688
|
-
if (payload.
|
|
44689
|
-
|
|
45242
|
+
if (name === "post-tool-use") {
|
|
45243
|
+
if (!isToolError(payload)) process.exit(0);
|
|
45244
|
+
const errText = extractToolResponseText(payload.tool_response ?? payload);
|
|
45245
|
+
if (errText) {
|
|
45246
|
+
spawnWorker({ sessionId, searchText: errText });
|
|
44690
45247
|
}
|
|
44691
|
-
process.
|
|
44692
|
-
|
|
45248
|
+
process.exit(0);
|
|
45249
|
+
}
|
|
45250
|
+
if (name === "stop") {
|
|
45251
|
+
if (payload.stop_hook_active === true) process.exit(0);
|
|
45252
|
+
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
|
|
45253
|
+
const signals = transcriptPath ? loadSignalsSafely(transcriptPath) : null;
|
|
45254
|
+
if (!signals || !hasAnyStruggleSignal(signals)) process.exit(0);
|
|
45255
|
+
const related = searchCaveatsFromTextSafely(struggleSearchText(signals));
|
|
45256
|
+
process.stdout.write(
|
|
45257
|
+
`<system-reminder>${stopReminderText(signals, related)}</system-reminder>
|
|
45258
|
+
`
|
|
45259
|
+
);
|
|
44693
45260
|
process.exit(0);
|
|
44694
45261
|
}
|
|
44695
45262
|
process.stderr.write(`[caveat:hook] unknown hook name: ${name}
|
|
@@ -44698,10 +45265,10 @@ async function runHook(name) {
|
|
|
44698
45265
|
}
|
|
44699
45266
|
|
|
44700
45267
|
// src/commands/pull.ts
|
|
44701
|
-
import { existsSync as
|
|
44702
|
-
import { join as
|
|
45268
|
+
import { existsSync as existsSync15, readdirSync as readdirSync9 } from "node:fs";
|
|
45269
|
+
import { join as join18 } from "node:path";
|
|
44703
45270
|
async function runPull(ctx) {
|
|
44704
|
-
if (!
|
|
45271
|
+
if (!existsSync15(ctx.paths.communityDir)) {
|
|
44705
45272
|
ctx.logger.info(
|
|
44706
45273
|
"no community repos yet \u2014 add one with `caveat community add <github-url>`."
|
|
44707
45274
|
);
|
|
@@ -44721,15 +45288,15 @@ async function runPull(ctx) {
|
|
|
44721
45288
|
const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
|
|
44722
45289
|
try {
|
|
44723
45290
|
rebuildAll(db);
|
|
44724
|
-
if (
|
|
45291
|
+
if (existsSync15(ctx.paths.entriesDir)) {
|
|
44725
45292
|
const own = scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
|
|
44726
45293
|
ctx.logger.info(`own: +${own.added}`);
|
|
44727
45294
|
}
|
|
44728
|
-
for (const entry of
|
|
45295
|
+
for (const entry of readdirSync9(ctx.paths.communityDir, { withFileTypes: true })) {
|
|
44729
45296
|
if (!entry.isDirectory()) continue;
|
|
44730
45297
|
const source = `community/${entry.name}`;
|
|
44731
|
-
const root =
|
|
44732
|
-
if (!
|
|
45298
|
+
const root = join18(ctx.paths.communityDir, entry.name, "entries");
|
|
45299
|
+
if (!existsSync15(root)) continue;
|
|
44733
45300
|
const scan = scanSource({ db, source, entriesRoot: root });
|
|
44734
45301
|
ctx.logger.info(`${source}: +${scan.added}`);
|
|
44735
45302
|
}
|
|
@@ -44853,6 +45420,13 @@ program.command("list").description("List caveats by updated_at DESC").option("-
|
|
|
44853
45420
|
const ctx = buildContext(stdoutLogger);
|
|
44854
45421
|
runList(ctx, { limit: opts.recent });
|
|
44855
45422
|
});
|
|
45423
|
+
program.command("stale").description(
|
|
45424
|
+
"List entries not surfaced by retrieval for N days (default 90). Use this to find private caveats that may be buried \u2014 if a 3-month-old private entry never surfaces, rewrite its body to include repo-specific identifiers, or delete it."
|
|
45425
|
+
).option("--days <n>", "age threshold in days", (v) => Number(v), 90).option("--visibility <v>", "public | private").option("--limit <n>", "max rows", (v) => Number(v), 50).action((opts) => {
|
|
45426
|
+
const ctx = buildContext(stdoutLogger);
|
|
45427
|
+
const vis = opts.visibility === "public" || opts.visibility === "private" ? opts.visibility : void 0;
|
|
45428
|
+
runStale(ctx, { days: opts.days, visibility: vis, limit: opts.limit });
|
|
45429
|
+
});
|
|
44856
45430
|
program.command("show").description("Show full caveat by id").argument("<id>", "entry id").option("--source <source>", "own or community/<handle>", "own").action((id, opts) => {
|
|
44857
45431
|
const ctx = buildContext(stdoutLogger);
|
|
44858
45432
|
runShow(ctx, { id, source: opts.source });
|
|
@@ -44873,8 +45447,10 @@ program.command("serve").description("Start the read-only web share portal").opt
|
|
|
44873
45447
|
program.command("mcp-server").description("Run the MCP stdio server (registered by `caveat init`)").action(async () => {
|
|
44874
45448
|
await runMcpServer();
|
|
44875
45449
|
});
|
|
44876
|
-
program.command("hook <name>").description(
|
|
44877
|
-
|
|
45450
|
+
program.command("hook <name> [arg]").description(
|
|
45451
|
+
"Run a Claude Code hook. name: user-prompt-submit | post-tool-use | stop | worker"
|
|
45452
|
+
).action(async (name, arg) => {
|
|
45453
|
+
await runHook(name, arg);
|
|
44878
45454
|
});
|
|
44879
45455
|
var community = program.command("community").description("Manage community caveat repos (shallow clones under <knowledgeRepo>/community/)");
|
|
44880
45456
|
community.command("add <url>").description("Shallow-clone a GitHub caveat repo into community/<handle>/").action(async (url) => {
|