caveat-cli 0.6.2 → 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/README.md +32 -21
- package/dist/index.js +719 -515
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9761,49 +9761,49 @@ var require_fast_uri = __commonJS({
|
|
|
9761
9761
|
schemelessOptions.skipEscape = true;
|
|
9762
9762
|
return serialize(resolved, schemelessOptions);
|
|
9763
9763
|
}
|
|
9764
|
-
function resolveComponent(base2,
|
|
9764
|
+
function resolveComponent(base2, relative2, options2, skipNormalization) {
|
|
9765
9765
|
const target = {};
|
|
9766
9766
|
if (!skipNormalization) {
|
|
9767
9767
|
base2 = parse4(serialize(base2, options2), options2);
|
|
9768
|
-
|
|
9768
|
+
relative2 = parse4(serialize(relative2, options2), options2);
|
|
9769
9769
|
}
|
|
9770
9770
|
options2 = options2 || {};
|
|
9771
|
-
if (!options2.tolerant &&
|
|
9772
|
-
target.scheme =
|
|
9773
|
-
target.userinfo =
|
|
9774
|
-
target.host =
|
|
9775
|
-
target.port =
|
|
9776
|
-
target.path = removeDotSegments(
|
|
9777
|
-
target.query =
|
|
9771
|
+
if (!options2.tolerant && relative2.scheme) {
|
|
9772
|
+
target.scheme = relative2.scheme;
|
|
9773
|
+
target.userinfo = relative2.userinfo;
|
|
9774
|
+
target.host = relative2.host;
|
|
9775
|
+
target.port = relative2.port;
|
|
9776
|
+
target.path = removeDotSegments(relative2.path || "");
|
|
9777
|
+
target.query = relative2.query;
|
|
9778
9778
|
} else {
|
|
9779
|
-
if (
|
|
9780
|
-
target.userinfo =
|
|
9781
|
-
target.host =
|
|
9782
|
-
target.port =
|
|
9783
|
-
target.path = removeDotSegments(
|
|
9784
|
-
target.query =
|
|
9779
|
+
if (relative2.userinfo !== void 0 || relative2.host !== void 0 || relative2.port !== void 0) {
|
|
9780
|
+
target.userinfo = relative2.userinfo;
|
|
9781
|
+
target.host = relative2.host;
|
|
9782
|
+
target.port = relative2.port;
|
|
9783
|
+
target.path = removeDotSegments(relative2.path || "");
|
|
9784
|
+
target.query = relative2.query;
|
|
9785
9785
|
} else {
|
|
9786
|
-
if (!
|
|
9786
|
+
if (!relative2.path) {
|
|
9787
9787
|
target.path = base2.path;
|
|
9788
|
-
if (
|
|
9789
|
-
target.query =
|
|
9788
|
+
if (relative2.query !== void 0) {
|
|
9789
|
+
target.query = relative2.query;
|
|
9790
9790
|
} else {
|
|
9791
9791
|
target.query = base2.query;
|
|
9792
9792
|
}
|
|
9793
9793
|
} else {
|
|
9794
|
-
if (
|
|
9795
|
-
target.path = removeDotSegments(
|
|
9794
|
+
if (relative2.path[0] === "/") {
|
|
9795
|
+
target.path = removeDotSegments(relative2.path);
|
|
9796
9796
|
} else {
|
|
9797
9797
|
if ((base2.userinfo !== void 0 || base2.host !== void 0 || base2.port !== void 0) && !base2.path) {
|
|
9798
|
-
target.path = "/" +
|
|
9798
|
+
target.path = "/" + relative2.path;
|
|
9799
9799
|
} else if (!base2.path) {
|
|
9800
|
-
target.path =
|
|
9800
|
+
target.path = relative2.path;
|
|
9801
9801
|
} else {
|
|
9802
|
-
target.path = base2.path.slice(0, base2.path.lastIndexOf("/") + 1) +
|
|
9802
|
+
target.path = base2.path.slice(0, base2.path.lastIndexOf("/") + 1) + relative2.path;
|
|
9803
9803
|
}
|
|
9804
9804
|
target.path = removeDotSegments(target.path);
|
|
9805
9805
|
}
|
|
9806
|
-
target.query =
|
|
9806
|
+
target.query = relative2.query;
|
|
9807
9807
|
}
|
|
9808
9808
|
target.userinfo = base2.userinfo;
|
|
9809
9809
|
target.host = base2.host;
|
|
@@ -9811,7 +9811,7 @@ var require_fast_uri = __commonJS({
|
|
|
9811
9811
|
}
|
|
9812
9812
|
target.scheme = base2.scheme;
|
|
9813
9813
|
}
|
|
9814
|
-
target.fragment =
|
|
9814
|
+
target.fragment = relative2.fragment;
|
|
9815
9815
|
return target;
|
|
9816
9816
|
}
|
|
9817
9817
|
function equal(uriA, uriB, options2) {
|
|
@@ -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";
|
|
@@ -15819,7 +15819,14 @@ function search(db, opts = {}) {
|
|
|
15819
15819
|
conditions.push(`e.confidence IN (${placeholders})`);
|
|
15820
15820
|
params.push(...filters.confidence);
|
|
15821
15821
|
}
|
|
15822
|
+
if (filters.visibility === "public" || filters.visibility === "private") {
|
|
15823
|
+
conditions.push(`e.visibility = ?`);
|
|
15824
|
+
params.push(filters.visibility);
|
|
15825
|
+
}
|
|
15822
15826
|
if (conditions.length) sql += " AND " + conditions.join(" AND ");
|
|
15827
|
+
if (!ftsQuery) {
|
|
15828
|
+
sql += ` ORDER BY json_extract(e.frontmatter_json, '$.updated_at') DESC`;
|
|
15829
|
+
}
|
|
15823
15830
|
sql += " LIMIT ?";
|
|
15824
15831
|
params.push(limit);
|
|
15825
15832
|
const rows = db.prepare(sql).all(...params);
|
|
@@ -15864,6 +15871,7 @@ function toSearchResult(row) {
|
|
|
15864
15871
|
title: row.title,
|
|
15865
15872
|
symptomExcerpt: symptom.slice(0, SYMPTOM_EXCERPT_LENGTH),
|
|
15866
15873
|
confidence: row.confidence,
|
|
15874
|
+
visibility: row.visibility ?? "public",
|
|
15867
15875
|
environment: fm.environment ?? {}
|
|
15868
15876
|
};
|
|
15869
15877
|
}
|
|
@@ -16105,12 +16113,10 @@ function resolvePaths(caveatHome, knowledgeRepo, userHome) {
|
|
|
16105
16113
|
|
|
16106
16114
|
// ../../packages/core/dist/config.js
|
|
16107
16115
|
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
16108
|
-
var SHARED_REPO_URL = "https://github.com/kitepon-rgb/Caveat";
|
|
16109
16116
|
var DEFAULT_CONFIG = {
|
|
16110
16117
|
knowledgeRepo: "own",
|
|
16111
16118
|
semverKeys: ["driver", "cuda", "node"],
|
|
16112
|
-
communitySources: []
|
|
16113
|
-
sharedRepo: SHARED_REPO_URL
|
|
16119
|
+
communitySources: []
|
|
16114
16120
|
};
|
|
16115
16121
|
function loadConfig(userConfigPath) {
|
|
16116
16122
|
const userCfg = existsSync5(userConfigPath) ? JSON.parse(readFileSync4(userConfigPath, "utf-8")) : {};
|
|
@@ -16134,7 +16140,7 @@ function deepMerge(base2, overlay) {
|
|
|
16134
16140
|
}
|
|
16135
16141
|
|
|
16136
16142
|
// ../../packages/core/dist/community.js
|
|
16137
|
-
import { existsSync as existsSync6, readdirSync as readdirSync3 } from "node:fs";
|
|
16143
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3, rmSync, statSync as statSync4 } from "node:fs";
|
|
16138
16144
|
import { join as join6 } from "node:path";
|
|
16139
16145
|
|
|
16140
16146
|
// ../../node_modules/.pnpm/simple-git@3.36.0/node_modules/simple-git/dist/esm/index.js
|
|
@@ -21164,304 +21170,367 @@ function communityList(opts) {
|
|
|
21164
21170
|
}
|
|
21165
21171
|
return out;
|
|
21166
21172
|
}
|
|
21167
|
-
|
|
21168
|
-
|
|
21169
|
-
|
|
21170
|
-
|
|
21171
|
-
/\bdriver\b/i,
|
|
21172
|
-
/ドライバ/,
|
|
21173
|
-
/\b(vscode|vs\s?code|jetbrains|intellij|pycharm)\b/i,
|
|
21174
|
-
/\bclaude\s?code\b/i,
|
|
21175
|
-
/\b(flaky|intermittent|reproducib|reproduce|repro)\b/i,
|
|
21176
|
-
/再現(しない|性|できない)/,
|
|
21177
|
-
/挙動が(違|おかしい|変)/,
|
|
21178
|
-
/\b(node|python|cuda|cudnn)\s*\d+(\.\d+)?/i,
|
|
21179
|
-
/バージョン(依存|違い|差)/,
|
|
21180
|
-
/\b(prebuild|native\s+module|msvc|gyp)\b/i
|
|
21181
|
-
];
|
|
21182
|
-
function detectCaveatTrigger(prompt) {
|
|
21183
|
-
if (typeof prompt !== "string" || prompt.length === 0) return false;
|
|
21184
|
-
return CAVEAT_TRIGGERS.some((re) => re.test(prompt));
|
|
21185
|
-
}
|
|
21186
|
-
function userPromptSubmitReminderText() {
|
|
21187
|
-
return [
|
|
21188
|
-
"[caveat] \u3053\u306E\u30D7\u30ED\u30F3\u30D7\u30C8\u306F\u5916\u90E8\u4ED5\u69D8\u306E\u7F60\uFF08GPU/\u30C9\u30E9\u30A4\u30D0/\u30C4\u30FC\u30EB\u30D0\u30FC\u30B8\u30E7\u30F3/IDE/\u518D\u73FE\u6027\u306E\u4F4E\u3044\u6319\u52D5\uFF09\u306B\u89E6\u308C\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002",
|
|
21189
|
-
"\u5B9F\u88C5\u3092\u59CB\u3081\u308B\u524D\u306B mcp__caveat__caveat_search \u3092\u547C\u3093\u3067\u65E2\u5B58\u306E\u7F60\u30E1\u30E2\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
21190
|
-
"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"
|
|
21191
|
-
].join("\n");
|
|
21192
|
-
}
|
|
21193
|
-
function stopReminderText() {
|
|
21194
|
-
return [
|
|
21195
|
-
"[caveat] \u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u3067\u5916\u90E8\u4ED5\u69D8\u306E\u7F60\uFF08GPU/\u30C9\u30E9\u30A4\u30D0/\u30C4\u30FC\u30EB\u30D0\u30FC\u30B8\u30E7\u30F3/IDE/\u518D\u73FE\u6027\uFF09\u3092\u8E0F\u307F\u3001\u6B21\u56DE\u306E\u81EA\u5206\u306E\u8ABF\u67FB\u304C\u77ED\u7E2E\u3067\u304D\u305D\u3046\u306A\u3089 mcp__caveat__caveat_record \u3067\u767B\u9332\u3092\u691C\u8A0E\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
21196
|
-
"\u89E3\u6C7A\u3057\u305F\u30B1\u30FC\u30B9\u3060\u3051\u3067\u306A\u304F\u3001\u300C\u73FE\u72B6\u306E\u5236\u7D04\u3067\u306F\u4E0D\u53EF\u80FD\u3068\u5224\u5B9A\u3057\u305F\u300D\u7D50\u8AD6\u3082 outcome: impossible \u3068\u3057\u3066\u8A18\u9332\u5BFE\u8C61\u3002\u6B21\u56DE\u540C\u3058 3 \u6642\u9593\u3092\u6EB6\u304B\u3055\u306A\u3044\u305F\u3081\u306B\u3002",
|
|
21197
|
-
"\u65E2\u306B\u540C\u7B49\u306E caveat \u304C\u3042\u308B\u5834\u5408\u306F caveat_update \u3067\u4E0A\u66F8\u304D\u3001\u307E\u305F\u306F last_verified \u3060\u3051\u66F4\u65B0\u3002",
|
|
21198
|
-
"\u8A18\u9332\u3057\u305F\u7F60\u304C\u4ED6\u306E\u30E6\u30FC\u30B6\u306B\u3082\u518D\u5229\u7528\u4FA1\u5024\u304C\u3042\u308B\u306A\u3089 mcp__caveat__caveat_push \u3067\u5171\u6709 DB \u306B PR \u3092\u6295\u3052\u308B\u3053\u3068\u3082\u691C\u8A0E\uFF08\u500B\u4EBA\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u56FA\u6709\u306E\u7269\u306F push \u3057\u306A\u3044\uFF09\u3002"
|
|
21199
|
-
].join("\n");
|
|
21200
|
-
}
|
|
21201
|
-
|
|
21202
|
-
// ../../packages/core/dist/push.js
|
|
21203
|
-
import { spawnSync } from "node:child_process";
|
|
21204
|
-
import {
|
|
21205
|
-
copyFileSync,
|
|
21206
|
-
existsSync as existsSync7,
|
|
21207
|
-
mkdirSync as mkdirSync3,
|
|
21208
|
-
readdirSync as readdirSync4,
|
|
21209
|
-
readFileSync as readFileSync5
|
|
21210
|
-
} from "node:fs";
|
|
21211
|
-
import { dirname as dirname4, join as join7, relative as relative2 } from "node:path";
|
|
21212
|
-
async function pushEntry(opts) {
|
|
21213
|
-
if (!checkGhAvailable()) {
|
|
21214
|
-
return {
|
|
21215
|
-
status: "gh-missing",
|
|
21216
|
-
detail: "gh (GitHub CLI) is required. Install: https://cli.github.com/"
|
|
21217
|
-
};
|
|
21218
|
-
}
|
|
21219
|
-
if (!checkGhAuthed()) {
|
|
21220
|
-
return {
|
|
21221
|
-
status: "gh-unauthed",
|
|
21222
|
-
detail: "gh is not authenticated. Run `gh auth login`."
|
|
21223
|
-
};
|
|
21224
|
-
}
|
|
21225
|
-
const validation = validateCommunityUrl(opts.sharedRepoUrl);
|
|
21226
|
-
if (!validation.valid) {
|
|
21227
|
-
return { status: "failed", detail: `sharedRepo URL invalid: ${validation.reason}` };
|
|
21228
|
-
}
|
|
21229
|
-
const [sharedOwner, sharedName] = parseOwnerRepo(opts.sharedRepoUrl);
|
|
21230
|
-
if (!sharedOwner || !sharedName) {
|
|
21231
|
-
return { status: "failed", detail: `cannot parse owner/repo from ${opts.sharedRepoUrl}` };
|
|
21232
|
-
}
|
|
21233
|
-
const owned = findOwnedEntry(opts.entriesDir, opts.id);
|
|
21234
|
-
if (!owned) {
|
|
21235
|
-
return {
|
|
21236
|
-
status: "not-found",
|
|
21237
|
-
detail: `entry id=${opts.id} not found under ${opts.entriesDir}`
|
|
21238
|
-
};
|
|
21239
|
-
}
|
|
21240
|
-
if (owned.visibility === "private") {
|
|
21241
|
-
return {
|
|
21242
|
-
status: "visibility-private",
|
|
21243
|
-
detail: `entry id=${opts.id} has visibility: private and cannot be pushed to the public community DB. Update the entry to visibility: public first (the user declared this entry local-only).`
|
|
21244
|
-
};
|
|
21173
|
+
function communityRemove(opts) {
|
|
21174
|
+
const handle = (opts.handle ?? "").trim();
|
|
21175
|
+
if (!handle) {
|
|
21176
|
+
throw new Error("handle is required");
|
|
21245
21177
|
}
|
|
21246
|
-
|
|
21247
|
-
|
|
21248
|
-
return { status: "failed", detail: "gh api user failed" };
|
|
21178
|
+
if (handle === "." || handle === ".." || /[\\/]/.test(handle)) {
|
|
21179
|
+
throw new Error(`invalid handle (no path separators or relative segments): ${opts.handle}`);
|
|
21249
21180
|
}
|
|
21181
|
+
const target = join6(opts.communityDir, handle);
|
|
21182
|
+
const dirExisted = existsSync6(target) && statSync4(target).isDirectory();
|
|
21183
|
+
const source = `community/${handle}`;
|
|
21184
|
+
const row = opts.db.prepare("SELECT COUNT(*) AS n FROM entries WHERE source = ?").get(source);
|
|
21185
|
+
const rowCount = row?.n ?? 0;
|
|
21250
21186
|
if (opts.dryRun) {
|
|
21251
|
-
return {
|
|
21252
|
-
status: "dry-run",
|
|
21253
|
-
plannedSteps: [
|
|
21254
|
-
`fork ${sharedOwner}/${sharedName} as ${ghUser}/${sharedName}`,
|
|
21255
|
-
`clone fork \u2192 ${forkStagingDir(opts.caveatHome)}`,
|
|
21256
|
-
`copy entry \u2192 entries/${owned.relPath}`,
|
|
21257
|
-
"commit, push branch, open PR against main"
|
|
21258
|
-
]
|
|
21259
|
-
};
|
|
21187
|
+
return { handle, path: target, dirExisted, rowCount, removed: false, dryRun: true };
|
|
21260
21188
|
}
|
|
21261
|
-
|
|
21262
|
-
|
|
21263
|
-
const stagingDir = forkStagingDir(opts.caveatHome);
|
|
21264
|
-
ensureStagingClone(stagingDir, ghUser, sharedName, sharedOwner);
|
|
21265
|
-
const branch = `caveat-push-${owned.id}-${Date.now().toString(36)}`;
|
|
21266
|
-
runOrThrow("git", ["checkout", "-b", branch], { cwd: stagingDir });
|
|
21267
|
-
const destPath = join7(stagingDir, "entries", owned.relPath);
|
|
21268
|
-
mkdirSync3(dirname4(destPath), { recursive: true });
|
|
21269
|
-
copyFileSync(owned.absPath, destPath);
|
|
21270
|
-
runOrThrow("git", ["add", join7("entries", owned.relPath)], { cwd: stagingDir });
|
|
21271
|
-
const isUpdate = upstreamAlreadyHas(stagingDir, "entries/" + owned.relPath);
|
|
21272
|
-
const verb = isUpdate ? "update" : "add";
|
|
21273
|
-
const commitMsg = `${verb}: ${owned.title}`;
|
|
21274
|
-
runOrThrow("git", ["commit", "-m", commitMsg], { cwd: stagingDir });
|
|
21275
|
-
runOrThrow("git", ["push", "--set-upstream", "origin", branch], { cwd: stagingDir });
|
|
21276
|
-
const prTitle = `${verb}: ${owned.title}`;
|
|
21277
|
-
const prBody = [
|
|
21278
|
-
`Contribution of caveat \`${owned.id}\` via \`caveat push\`.`,
|
|
21279
|
-
"",
|
|
21280
|
-
`- entry path: \`entries/${owned.relPath}\``,
|
|
21281
|
-
`- action: ${verb}`,
|
|
21282
|
-
"",
|
|
21283
|
-
"Merged PRs will appear in subscribers' repos on their next `caveat pull`."
|
|
21284
|
-
].join("\n");
|
|
21285
|
-
const pr = runCapture(
|
|
21286
|
-
"gh",
|
|
21287
|
-
[
|
|
21288
|
-
"pr",
|
|
21289
|
-
"create",
|
|
21290
|
-
"--repo",
|
|
21291
|
-
`${sharedOwner}/${sharedName}`,
|
|
21292
|
-
"--base",
|
|
21293
|
-
"main",
|
|
21294
|
-
"--head",
|
|
21295
|
-
`${ghUser}:${branch}`,
|
|
21296
|
-
"--title",
|
|
21297
|
-
prTitle,
|
|
21298
|
-
"--body",
|
|
21299
|
-
prBody
|
|
21300
|
-
],
|
|
21301
|
-
{ cwd: stagingDir }
|
|
21302
|
-
);
|
|
21303
|
-
if (pr.status !== 0) {
|
|
21304
|
-
return {
|
|
21305
|
-
status: "failed",
|
|
21306
|
-
detail: (pr.stderr || "gh pr create failed").trim()
|
|
21307
|
-
};
|
|
21308
|
-
}
|
|
21309
|
-
return { status: "ok", prUrl: pr.stdout.trim() };
|
|
21310
|
-
} catch (err) {
|
|
21311
|
-
return {
|
|
21312
|
-
status: "failed",
|
|
21313
|
-
detail: err instanceof Error ? err.message : String(err)
|
|
21314
|
-
};
|
|
21189
|
+
if (dirExisted) {
|
|
21190
|
+
rmSync(target, { recursive: true, force: true });
|
|
21315
21191
|
}
|
|
21192
|
+
if (rowCount > 0) {
|
|
21193
|
+
opts.db.prepare("DELETE FROM entries WHERE source = ?").run(source);
|
|
21194
|
+
}
|
|
21195
|
+
return {
|
|
21196
|
+
handle,
|
|
21197
|
+
path: target,
|
|
21198
|
+
dirExisted,
|
|
21199
|
+
rowCount,
|
|
21200
|
+
removed: dirExisted || rowCount > 0,
|
|
21201
|
+
dryRun: false
|
|
21202
|
+
};
|
|
21316
21203
|
}
|
|
21317
|
-
|
|
21318
|
-
|
|
21319
|
-
|
|
21320
|
-
|
|
21321
|
-
|
|
21322
|
-
|
|
21323
|
-
|
|
21324
|
-
|
|
21325
|
-
|
|
21326
|
-
|
|
21327
|
-
|
|
21204
|
+
|
|
21205
|
+
// ../../packages/core/dist/claudeHooks.js
|
|
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;
|
|
21328
21246
|
return {
|
|
21329
|
-
|
|
21330
|
-
|
|
21331
|
-
|
|
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 ?? {}
|
|
21332
21254
|
};
|
|
21333
21255
|
}
|
|
21334
|
-
function
|
|
21335
|
-
const
|
|
21336
|
-
if (
|
|
21337
|
-
|
|
21338
|
-
|
|
21339
|
-
|
|
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
|
+
}
|
|
21340
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));
|
|
21341
21279
|
}
|
|
21342
|
-
function
|
|
21343
|
-
|
|
21344
|
-
|
|
21345
|
-
|
|
21346
|
-
|
|
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");
|
|
21347
21296
|
}
|
|
21348
|
-
function
|
|
21349
|
-
const
|
|
21350
|
-
|
|
21351
|
-
|
|
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");
|
|
21352
21313
|
}
|
|
21353
|
-
function
|
|
21354
|
-
const
|
|
21355
|
-
|
|
21356
|
-
return [m[1], m[2]];
|
|
21314
|
+
function shortPath(p2) {
|
|
21315
|
+
const parts = p2.split(/[\\/]/);
|
|
21316
|
+
return parts[parts.length - 1] ?? p2;
|
|
21357
21317
|
}
|
|
21358
|
-
function
|
|
21359
|
-
|
|
21360
|
-
|
|
21361
|
-
|
|
21362
|
-
|
|
21363
|
-
for (const fileDirent of readdirSync4(categoryDir, { withFileTypes: true })) {
|
|
21364
|
-
if (!fileDirent.isFile() || !fileDirent.name.endsWith(".md")) continue;
|
|
21365
|
-
const absPath = join7(categoryDir, fileDirent.name);
|
|
21366
|
-
const raw2 = readFileSync5(absPath, "utf-8");
|
|
21367
|
-
try {
|
|
21368
|
-
const parsed = parseMarkdown(raw2);
|
|
21369
|
-
if (parsed.frontmatter.id === id) {
|
|
21370
|
-
const relPath = relative2(entriesDir, absPath).replace(/\\/g, "/");
|
|
21371
|
-
return {
|
|
21372
|
-
id,
|
|
21373
|
-
title: parsed.frontmatter.title,
|
|
21374
|
-
absPath,
|
|
21375
|
-
relPath,
|
|
21376
|
-
visibility: parsed.frontmatter.visibility
|
|
21377
|
-
};
|
|
21378
|
-
}
|
|
21379
|
-
} catch {
|
|
21380
|
-
}
|
|
21381
|
-
}
|
|
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`);
|
|
21382
21323
|
}
|
|
21383
|
-
|
|
21384
|
-
}
|
|
21385
|
-
|
|
21386
|
-
|
|
21387
|
-
|
|
21388
|
-
|
|
21389
|
-
|
|
21390
|
-
}
|
|
21391
|
-
|
|
21392
|
-
if (
|
|
21393
|
-
|
|
21394
|
-
|
|
21395
|
-
|
|
21396
|
-
|
|
21397
|
-
|
|
21398
|
-
|
|
21399
|
-
|
|
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"
|
|
21400
21353
|
);
|
|
21401
21354
|
} else {
|
|
21402
|
-
|
|
21403
|
-
|
|
21404
|
-
|
|
21405
|
-
runOrThrow("git", ["push", "origin", "main", "--force-with-lease"], { cwd: stagingDir });
|
|
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
|
+
);
|
|
21406
21358
|
}
|
|
21407
|
-
|
|
21408
|
-
function upstreamAlreadyHas(stagingDir, relFromRoot) {
|
|
21409
|
-
return existsSync7(join7(stagingDir, relFromRoot));
|
|
21359
|
+
return lines.join("\n");
|
|
21410
21360
|
}
|
|
21411
21361
|
|
|
21412
|
-
// ../../packages/core/dist/
|
|
21413
|
-
import { existsSync as
|
|
21414
|
-
|
|
21415
|
-
|
|
21416
|
-
|
|
21417
|
-
|
|
21418
|
-
|
|
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(" ");
|
|
21419
21380
|
}
|
|
21420
|
-
|
|
21421
|
-
|
|
21422
|
-
|
|
21423
|
-
|
|
21424
|
-
|
|
21425
|
-
|
|
21426
|
-
|
|
21427
|
-
|
|
21428
|
-
|
|
21429
|
-
|
|
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;
|
|
21430
21395
|
}
|
|
21431
|
-
|
|
21432
|
-
|
|
21433
|
-
|
|
21434
|
-
|
|
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
|
+
};
|
|
21464
|
+
}
|
|
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 [];
|
|
21435
21499
|
}
|
|
21436
|
-
|
|
21437
|
-
|
|
21438
|
-
const
|
|
21439
|
-
|
|
21440
|
-
|
|
21441
|
-
|
|
21442
|
-
|
|
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
|
+
}
|
|
21443
21512
|
}
|
|
21444
|
-
return
|
|
21513
|
+
return out;
|
|
21445
21514
|
}
|
|
21446
21515
|
|
|
21447
21516
|
// src/context.ts
|
|
21448
21517
|
function buildContext(logger, overrides = {}) {
|
|
21449
21518
|
const userHome = overrides.userHome ?? homedir();
|
|
21450
21519
|
const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
|
|
21451
|
-
const userConfigPath =
|
|
21520
|
+
const userConfigPath = join8(userHome, ".caveatrc.json");
|
|
21452
21521
|
const config3 = loadConfig(userConfigPath);
|
|
21453
21522
|
const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
|
|
21454
21523
|
return { caveatHome, userHome, userConfigPath, config: config3, paths, logger };
|
|
21455
21524
|
}
|
|
21456
21525
|
|
|
21457
21526
|
// src/version.ts
|
|
21458
|
-
import { readFileSync as
|
|
21459
|
-
import { dirname as
|
|
21527
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
21528
|
+
import { dirname as dirname4, join as join9 } from "node:path";
|
|
21460
21529
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
21461
21530
|
function resolveVersion() {
|
|
21462
21531
|
try {
|
|
21463
|
-
const here2 =
|
|
21464
|
-
const pkg = JSON.parse(
|
|
21532
|
+
const here2 = dirname4(fileURLToPath3(import.meta.url));
|
|
21533
|
+
const pkg = JSON.parse(readFileSync7(join9(here2, "..", "package.json"), "utf-8"));
|
|
21465
21534
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
21466
21535
|
} catch {
|
|
21467
21536
|
return "0.0.0";
|
|
@@ -21480,14 +21549,15 @@ var stdoutLogger = {
|
|
|
21480
21549
|
};
|
|
21481
21550
|
|
|
21482
21551
|
// src/commands/init.ts
|
|
21483
|
-
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as
|
|
21484
|
-
import { 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";
|
|
21485
21554
|
|
|
21486
21555
|
// src/claudeInstall.ts
|
|
21487
|
-
import { spawnSync
|
|
21488
|
-
import { copyFileSync
|
|
21489
|
-
import { dirname as
|
|
21556
|
+
import { spawnSync } from "node:child_process";
|
|
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";
|
|
21490
21559
|
var EVENT_USER_PROMPT_SUBMIT = "UserPromptSubmit";
|
|
21560
|
+
var EVENT_POST_TOOL_USE = "PostToolUse";
|
|
21491
21561
|
var EVENT_STOP = "Stop";
|
|
21492
21562
|
function quote(p2) {
|
|
21493
21563
|
return p2.includes(" ") ? `"${p2}"` : p2;
|
|
@@ -21521,26 +21591,26 @@ function removeHook(settings, event, command) {
|
|
|
21521
21591
|
}
|
|
21522
21592
|
function readSettings(path) {
|
|
21523
21593
|
if (!existsSync9(path)) return {};
|
|
21524
|
-
return JSON.parse(
|
|
21594
|
+
return JSON.parse(readFileSync8(path, "utf-8"));
|
|
21525
21595
|
}
|
|
21526
21596
|
function writeSettings(path, settings) {
|
|
21527
|
-
const dir =
|
|
21597
|
+
const dir = dirname5(path);
|
|
21528
21598
|
if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
|
|
21529
21599
|
let backupPath = "";
|
|
21530
21600
|
if (existsSync9(path)) {
|
|
21531
21601
|
backupPath = `${path}.caveat-backup-${Date.now()}`;
|
|
21532
|
-
|
|
21602
|
+
copyFileSync(path, backupPath);
|
|
21533
21603
|
}
|
|
21534
|
-
|
|
21604
|
+
writeFileSync5(path, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
21535
21605
|
return backupPath;
|
|
21536
21606
|
}
|
|
21537
21607
|
var CLAUDE_BIN = "claude";
|
|
21538
|
-
function
|
|
21608
|
+
function shellQuote(s) {
|
|
21539
21609
|
return /[\s&|<>^()]/.test(s) ? `"${s}"` : s;
|
|
21540
21610
|
}
|
|
21541
21611
|
function runClaude(args) {
|
|
21542
|
-
const line = [CLAUDE_BIN, ...args].map(
|
|
21543
|
-
return
|
|
21612
|
+
const line = [CLAUDE_BIN, ...args].map(shellQuote).join(" ");
|
|
21613
|
+
return spawnSync(line, { shell: true, encoding: "utf-8" });
|
|
21544
21614
|
}
|
|
21545
21615
|
function registerMcp(nodePath, cliScriptPath, dryRun, logger) {
|
|
21546
21616
|
const args = mcpArgs(cliScriptPath);
|
|
@@ -21590,33 +21660,38 @@ function unregisterMcp(dryRun, logger) {
|
|
|
21590
21660
|
return { action: "skipped", detail: "not registered or removal failed" };
|
|
21591
21661
|
}
|
|
21592
21662
|
function installClaudeIntegration(opts) {
|
|
21593
|
-
const settingsPath =
|
|
21663
|
+
const settingsPath = join10(opts.claudeDir, "settings.json");
|
|
21594
21664
|
const settings = readSettings(settingsPath);
|
|
21595
21665
|
const usCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "user-prompt-submit");
|
|
21666
|
+
const ptCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "post-tool-use");
|
|
21596
21667
|
const stopCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "stop");
|
|
21597
21668
|
const userPromptSubmit = upsertHook(settings, EVENT_USER_PROMPT_SUBMIT, usCmd);
|
|
21669
|
+
const postToolUse = upsertHook(settings, EVENT_POST_TOOL_USE, ptCmd);
|
|
21598
21670
|
const stop = upsertHook(settings, EVENT_STOP, stopCmd);
|
|
21599
21671
|
let backupPath;
|
|
21600
|
-
|
|
21672
|
+
const anyAdded = userPromptSubmit === "added" || postToolUse === "added" || stop === "added";
|
|
21673
|
+
if (!opts.dryRun && anyAdded) {
|
|
21601
21674
|
const backup = writeSettings(settingsPath, settings);
|
|
21602
21675
|
if (backup) backupPath = backup;
|
|
21603
21676
|
} else if (opts.dryRun) {
|
|
21604
21677
|
opts.logger.info(
|
|
21605
|
-
`[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}`
|
|
21606
21679
|
);
|
|
21607
21680
|
}
|
|
21608
21681
|
const mcp = opts.skipMcpRegistration ? { action: "skipped", detail: "skipped by caller" } : registerMcp(opts.nodePath, opts.cliScriptPath, opts.dryRun, opts.logger);
|
|
21609
|
-
return { mcp, hooks: { userPromptSubmit, stop }, backupPath };
|
|
21682
|
+
return { mcp, hooks: { userPromptSubmit, postToolUse, stop }, backupPath };
|
|
21610
21683
|
}
|
|
21611
21684
|
function uninstallClaudeIntegration(opts) {
|
|
21612
|
-
const settingsPath =
|
|
21685
|
+
const settingsPath = join10(opts.claudeDir, "settings.json");
|
|
21613
21686
|
const settings = readSettings(settingsPath);
|
|
21614
21687
|
const usCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "user-prompt-submit");
|
|
21688
|
+
const ptCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "post-tool-use");
|
|
21615
21689
|
const stopCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "stop");
|
|
21616
21690
|
const removedUs = removeHook(settings, EVENT_USER_PROMPT_SUBMIT, usCmd);
|
|
21691
|
+
const removedPt = removeHook(settings, EVENT_POST_TOOL_USE, ptCmd);
|
|
21617
21692
|
const removedStop = removeHook(settings, EVENT_STOP, stopCmd);
|
|
21618
21693
|
let backupPath;
|
|
21619
|
-
if (!opts.dryRun && (removedUs || removedStop)) {
|
|
21694
|
+
if (!opts.dryRun && (removedUs || removedPt || removedStop)) {
|
|
21620
21695
|
const backup = writeSettings(settingsPath, settings);
|
|
21621
21696
|
if (backup) backupPath = backup;
|
|
21622
21697
|
}
|
|
@@ -21625,6 +21700,7 @@ function uninstallClaudeIntegration(opts) {
|
|
|
21625
21700
|
mcp,
|
|
21626
21701
|
hooks: {
|
|
21627
21702
|
userPromptSubmit: removedUs ? "added" : "unchanged",
|
|
21703
|
+
postToolUse: removedPt ? "added" : "unchanged",
|
|
21628
21704
|
stop: removedStop ? "added" : "unchanged"
|
|
21629
21705
|
},
|
|
21630
21706
|
backupPath
|
|
@@ -21641,7 +21717,7 @@ var KNOWLEDGE_GITIGNORE = [
|
|
|
21641
21717
|
".obsidian/",
|
|
21642
21718
|
""
|
|
21643
21719
|
].join("\n");
|
|
21644
|
-
async function runInit(ctx, opts = { skipClaude: false,
|
|
21720
|
+
async function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
|
|
21645
21721
|
ensureUserConfig(ctx.userConfigPath);
|
|
21646
21722
|
ctx.logger.info(`user config: ${ctx.userConfigPath}`);
|
|
21647
21723
|
if (!existsSync10(ctx.paths.knowledgeRepo)) {
|
|
@@ -21652,24 +21728,23 @@ async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRu
|
|
|
21652
21728
|
ctx.logger.info(`knowledge repo: ${ctx.paths.knowledgeRepo}`);
|
|
21653
21729
|
}
|
|
21654
21730
|
migrateLegacyCommunityDir(ctx);
|
|
21655
|
-
const gitignorePath =
|
|
21731
|
+
const gitignorePath = join11(ctx.paths.knowledgeRepo, ".gitignore");
|
|
21656
21732
|
if (!existsSync10(gitignorePath)) {
|
|
21657
|
-
|
|
21733
|
+
writeFileSync6(gitignorePath, KNOWLEDGE_GITIGNORE, "utf-8");
|
|
21658
21734
|
ctx.logger.info(`.gitignore created: ${gitignorePath}`);
|
|
21659
21735
|
}
|
|
21660
|
-
|
|
21661
|
-
|
|
21662
|
-
if (!
|
|
21663
|
-
|
|
21664
|
-
} else if (opts.skipShared) {
|
|
21665
|
-
ctx.logger.info("shared community DB subscription skipped (--skip-shared)");
|
|
21666
|
-
} else if (opts.dryRun) {
|
|
21667
|
-
ctx.logger.info(`[dry-run] would subscribe to shared community DB: ${ctx.config.sharedRepo}`);
|
|
21668
|
-
}
|
|
21669
|
-
} finally {
|
|
21736
|
+
if (!opts.dryRun) {
|
|
21737
|
+
const dbDir = dirname6(ctx.paths.dbPath);
|
|
21738
|
+
if (!existsSync10(dbDir)) mkdirSync5(dbDir, { recursive: true });
|
|
21739
|
+
const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
|
|
21670
21740
|
db.close();
|
|
21741
|
+
ctx.logger.info(`db initialized: ${ctx.paths.dbPath}`);
|
|
21742
|
+
} else {
|
|
21743
|
+
ctx.logger.info(`[dry-run] db path: ${ctx.paths.dbPath}`);
|
|
21671
21744
|
}
|
|
21672
|
-
ctx.logger.info(
|
|
21745
|
+
ctx.logger.info(
|
|
21746
|
+
"tip: subscribe to a group repo with `caveat community add <github-url>`, then `caveat pull`."
|
|
21747
|
+
);
|
|
21673
21748
|
if (opts.skipClaude) {
|
|
21674
21749
|
ctx.logger.info("Claude Code integration skipped (--skip-claude)");
|
|
21675
21750
|
return;
|
|
@@ -21680,7 +21755,7 @@ async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRu
|
|
|
21680
21755
|
return;
|
|
21681
21756
|
}
|
|
21682
21757
|
const result = installClaudeIntegration({
|
|
21683
|
-
claudeDir:
|
|
21758
|
+
claudeDir: join11(ctx.userHome, ".claude"),
|
|
21684
21759
|
cliScriptPath,
|
|
21685
21760
|
nodePath: process.execPath,
|
|
21686
21761
|
dryRun: opts.dryRun,
|
|
@@ -21689,7 +21764,7 @@ async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRu
|
|
|
21689
21764
|
reportInstallResult(ctx, result, opts.dryRun);
|
|
21690
21765
|
}
|
|
21691
21766
|
function migrateLegacyCommunityDir(ctx) {
|
|
21692
|
-
const legacy =
|
|
21767
|
+
const legacy = join11(ctx.paths.knowledgeRepo, "community");
|
|
21693
21768
|
const current = ctx.paths.communityDir;
|
|
21694
21769
|
if (legacy === current) return;
|
|
21695
21770
|
if (!existsSync10(legacy)) return;
|
|
@@ -21700,9 +21775,9 @@ function migrateLegacyCommunityDir(ctx) {
|
|
|
21700
21775
|
return;
|
|
21701
21776
|
}
|
|
21702
21777
|
mkdirSync5(current, { recursive: true });
|
|
21703
|
-
for (const entry of
|
|
21778
|
+
for (const entry of readdirSync5(legacy, { withFileTypes: true })) {
|
|
21704
21779
|
if (!entry.isDirectory()) continue;
|
|
21705
|
-
renameSync(
|
|
21780
|
+
renameSync(join11(legacy, entry.name), join11(current, entry.name));
|
|
21706
21781
|
}
|
|
21707
21782
|
try {
|
|
21708
21783
|
rmdirSync(legacy);
|
|
@@ -21710,52 +21785,6 @@ function migrateLegacyCommunityDir(ctx) {
|
|
|
21710
21785
|
}
|
|
21711
21786
|
ctx.logger.info(`migrated legacy community/ \u2192 ${current}`);
|
|
21712
21787
|
}
|
|
21713
|
-
async function subscribeSharedRepo(ctx, db) {
|
|
21714
|
-
const url = ctx.config.sharedRepo;
|
|
21715
|
-
const validation = validateCommunityUrl(url);
|
|
21716
|
-
if (!validation.valid) {
|
|
21717
|
-
ctx.logger.warn(
|
|
21718
|
-
`sharedRepo is not a valid GitHub URL \u2014 skipping: ${validation.reason}`
|
|
21719
|
-
);
|
|
21720
|
-
return;
|
|
21721
|
-
}
|
|
21722
|
-
const handle = validation.handle;
|
|
21723
|
-
const target = join12(ctx.paths.communityDir, handle);
|
|
21724
|
-
if (!existsSync10(target)) {
|
|
21725
|
-
if (!existsSync10(ctx.paths.communityDir)) {
|
|
21726
|
-
mkdirSync5(ctx.paths.communityDir, { recursive: true });
|
|
21727
|
-
}
|
|
21728
|
-
try {
|
|
21729
|
-
await communityAdd({
|
|
21730
|
-
url,
|
|
21731
|
-
communityDir: ctx.paths.communityDir,
|
|
21732
|
-
logger: ctx.logger
|
|
21733
|
-
});
|
|
21734
|
-
ctx.logger.info(`shared community DB subscribed: ${url} \u2192 community/${handle}/`);
|
|
21735
|
-
} catch (err) {
|
|
21736
|
-
ctx.logger.warn(
|
|
21737
|
-
`shared community DB subscription failed: ${err instanceof Error ? err.message : String(err)}`
|
|
21738
|
-
);
|
|
21739
|
-
return;
|
|
21740
|
-
}
|
|
21741
|
-
} else {
|
|
21742
|
-
ctx.logger.info(`shared community DB already subscribed: community/${handle}/`);
|
|
21743
|
-
}
|
|
21744
|
-
if (existsSync10(ctx.paths.communityDir)) {
|
|
21745
|
-
rebuildAll(db);
|
|
21746
|
-
if (existsSync10(ctx.paths.entriesDir)) {
|
|
21747
|
-
scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
|
|
21748
|
-
}
|
|
21749
|
-
for (const entry of readdirSync6(ctx.paths.communityDir, { withFileTypes: true })) {
|
|
21750
|
-
if (!entry.isDirectory()) continue;
|
|
21751
|
-
const source = `community/${entry.name}`;
|
|
21752
|
-
const root = join12(ctx.paths.communityDir, entry.name, "entries");
|
|
21753
|
-
if (!existsSync10(root)) continue;
|
|
21754
|
-
const result = scanSource({ db, source, entriesRoot: root });
|
|
21755
|
-
ctx.logger.info(`indexed ${source}: +${result.added}`);
|
|
21756
|
-
}
|
|
21757
|
-
}
|
|
21758
|
-
}
|
|
21759
21788
|
function runUninstall(ctx, opts) {
|
|
21760
21789
|
const cliScriptPath = process.argv[1];
|
|
21761
21790
|
if (!cliScriptPath) {
|
|
@@ -21763,7 +21792,7 @@ function runUninstall(ctx, opts) {
|
|
|
21763
21792
|
process.exit(1);
|
|
21764
21793
|
}
|
|
21765
21794
|
const result = uninstallClaudeIntegration({
|
|
21766
|
-
claudeDir:
|
|
21795
|
+
claudeDir: join11(ctx.userHome, ".claude"),
|
|
21767
21796
|
cliScriptPath,
|
|
21768
21797
|
nodePath: process.execPath,
|
|
21769
21798
|
dryRun: opts.dryRun,
|
|
@@ -21800,8 +21829,8 @@ function reportInstallResult(ctx, result, dryRun) {
|
|
|
21800
21829
|
}
|
|
21801
21830
|
|
|
21802
21831
|
// src/commands/indexCmd.ts
|
|
21803
|
-
import { existsSync as existsSync11, readdirSync as
|
|
21804
|
-
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";
|
|
21805
21834
|
function runIndex(ctx, opts) {
|
|
21806
21835
|
const dbDir = dirname7(ctx.paths.dbPath);
|
|
21807
21836
|
if (!existsSync11(dbDir)) mkdirSync6(dbDir, { recursive: true });
|
|
@@ -21818,10 +21847,10 @@ function runIndex(ctx, opts) {
|
|
|
21818
21847
|
ctx.logger.warn(`entries dir not found: ${ctx.paths.entriesDir}`);
|
|
21819
21848
|
}
|
|
21820
21849
|
if (existsSync11(ctx.paths.communityDir)) {
|
|
21821
|
-
for (const entry of
|
|
21850
|
+
for (const entry of readdirSync6(ctx.paths.communityDir, { withFileTypes: true })) {
|
|
21822
21851
|
if (!entry.isDirectory()) continue;
|
|
21823
21852
|
const source = `community/${entry.name}`;
|
|
21824
|
-
const root =
|
|
21853
|
+
const root = join12(ctx.paths.communityDir, entry.name, "entries");
|
|
21825
21854
|
if (!existsSync11(root)) continue;
|
|
21826
21855
|
const result = scanSource({ db, source, entriesRoot: root });
|
|
21827
21856
|
ctx.logger.info(`${source}: +${result.added} ~${result.updated} -${result.deleted}`);
|
|
@@ -22596,11 +22625,11 @@ var serve = (options2, listeningListener) => {
|
|
|
22596
22625
|
|
|
22597
22626
|
// ../web/dist/context.js
|
|
22598
22627
|
import { homedir as homedir2 } from "node:os";
|
|
22599
|
-
import { join as
|
|
22628
|
+
import { join as join13 } from "node:path";
|
|
22600
22629
|
function buildWebContext(overrides = {}) {
|
|
22601
22630
|
const userHome = overrides.userHome ?? homedir2();
|
|
22602
22631
|
const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
|
|
22603
|
-
const userConfigPath =
|
|
22632
|
+
const userConfigPath = join13(userHome, ".caveatrc.json");
|
|
22604
22633
|
const logger = overrides.logger ?? stderrLogger;
|
|
22605
22634
|
const config3 = loadConfig(userConfigPath);
|
|
22606
22635
|
const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
|
|
@@ -24705,7 +24734,7 @@ ul.entries li { padding: 0.5rem 0; border-bottom: 1px solid #eee; }
|
|
|
24705
24734
|
ul.entries a.title { font-weight: 600; color: #0a5fff; text-decoration: none; }
|
|
24706
24735
|
ul.entries a.title:hover { text-decoration: underline; }
|
|
24707
24736
|
ul.entries .meta { font-size: 0.85rem; color: #666; margin-top: 0.2rem; }
|
|
24708
|
-
|
|
24737
|
+
.badge {
|
|
24709
24738
|
display: inline-block;
|
|
24710
24739
|
padding: 0 0.4rem;
|
|
24711
24740
|
margin-right: 0.3rem;
|
|
@@ -24713,10 +24742,12 @@ ul.entries .meta .badge {
|
|
|
24713
24742
|
background: #f0f0f0;
|
|
24714
24743
|
font-size: 0.8rem;
|
|
24715
24744
|
}
|
|
24716
|
-
|
|
24717
|
-
|
|
24718
|
-
|
|
24719
|
-
|
|
24745
|
+
.badge.confirmed { background: #d7f4dc; color: #16703a; }
|
|
24746
|
+
.badge.reproduced { background: #e6eeff; color: #234ea3; }
|
|
24747
|
+
.badge.tentative { background: #fff4d1; color: #8a5a00; }
|
|
24748
|
+
.badge.impossible { background: #ffd9d9; color: #8a1a1a; }
|
|
24749
|
+
.badge.public { background: #eef2f5; color: #5a6470; }
|
|
24750
|
+
.badge.private { background: #fde7e7; color: #a02525; font-weight: 600; }
|
|
24720
24751
|
ul.entries .excerpt { margin-top: 0.3rem; color: #444; font-size: 0.92rem; }
|
|
24721
24752
|
article h2 { border-bottom: 1px solid #eee; padding-bottom: 0.2rem; margin-top: 1.6rem; }
|
|
24722
24753
|
article pre {
|
|
@@ -24791,6 +24822,7 @@ function createIndexRoute(ctx) {
|
|
|
24791
24822
|
const q2 = c3.req.query("q")?.trim() ?? "";
|
|
24792
24823
|
const sourceParam = c3.req.query("source");
|
|
24793
24824
|
const tagParam = c3.req.query("tag");
|
|
24825
|
+
const visibilityParam = c3.req.query("visibility");
|
|
24794
24826
|
const filters = {};
|
|
24795
24827
|
if (sourceParam === "own" || sourceParam === "community" || sourceParam === "all") {
|
|
24796
24828
|
filters.source = sourceParam;
|
|
@@ -24798,17 +24830,22 @@ function createIndexRoute(ctx) {
|
|
|
24798
24830
|
if (tagParam) {
|
|
24799
24831
|
filters.tags = [tagParam];
|
|
24800
24832
|
}
|
|
24801
|
-
|
|
24833
|
+
if (visibilityParam === "public" || visibilityParam === "private") {
|
|
24834
|
+
filters.visibility = visibilityParam;
|
|
24835
|
+
}
|
|
24836
|
+
const results = search(ctx.db, { query: q2, filters, limit: 50 });
|
|
24802
24837
|
const rows = results.map((r2) => {
|
|
24803
24838
|
const envEntries = Object.entries(r2.environment ?? {});
|
|
24804
24839
|
const envSummary = envEntries.slice(0, 3).map(([k2, v]) => `${escapeHtml(k2)}:${escapeHtml(String(v))}`).join(" ");
|
|
24805
24840
|
const hrefSource = r2.source === "own" ? "" : `?source=${encodeURIComponent(r2.source)}`;
|
|
24841
|
+
const visibilityBadge = r2.visibility === "private" ? `<span class="badge private" title="local-only \u2014 never pushed to community DB">\u{1F512} private</span>` : `<span class="badge public">public</span>`;
|
|
24806
24842
|
return `
|
|
24807
24843
|
<li>
|
|
24808
24844
|
<a class="title" href="/g/${encodeURIComponent(r2.id)}${hrefSource}">${escapeHtml(r2.title)}</a>
|
|
24809
24845
|
<div class="meta">
|
|
24810
24846
|
<span class="badge ${escapeHtml(r2.confidence ?? "")}">${escapeHtml(r2.confidence ?? "")}</span>
|
|
24811
24847
|
<span class="badge">${escapeHtml(r2.source)}</span>
|
|
24848
|
+
${visibilityBadge}
|
|
24812
24849
|
${envSummary ? `<span>${envSummary}</span>` : ""}
|
|
24813
24850
|
</div>
|
|
24814
24851
|
${r2.symptomExcerpt ? `<div class="excerpt">${escapeHtml(r2.symptomExcerpt)}</div>` : ""}
|
|
@@ -24822,9 +24859,18 @@ function createIndexRoute(ctx) {
|
|
|
24822
24859
|
<option value="own" ${sourceParam === "own" ? "selected" : ""}>own</option>
|
|
24823
24860
|
<option value="community" ${sourceParam === "community" ? "selected" : ""}>community</option>
|
|
24824
24861
|
</select>
|
|
24862
|
+
<select name="visibility" title="visibility filter \u2014 useful when sharing screen">
|
|
24863
|
+
<option value="" ${!visibilityParam ? "selected" : ""}>any visibility</option>
|
|
24864
|
+
<option value="public" ${visibilityParam === "public" ? "selected" : ""}>public only</option>
|
|
24865
|
+
<option value="private" ${visibilityParam === "private" ? "selected" : ""}>private only</option>
|
|
24866
|
+
</select>
|
|
24825
24867
|
<button type="submit">search</button>
|
|
24826
24868
|
</form>
|
|
24827
|
-
${results.length > 0 ? `<ul class="entries">${rows}</ul>` :
|
|
24869
|
+
${results.length > 0 ? `<ul class="entries">${rows}</ul>` : (() => {
|
|
24870
|
+
if (q2) return `<div class="empty">no results for "${escapeHtml(q2)}"</div>`;
|
|
24871
|
+
const hasFilter = !!(sourceParam || tagParam || visibilityParam);
|
|
24872
|
+
return hasFilter ? `<div class="empty">no entries match the active filters</div>` : `<div class="empty">no entries yet \u2014 run <code>caveat index</code> after adding md files</div>`;
|
|
24873
|
+
})()}`;
|
|
24828
24874
|
const title = q2 ? `search: ${q2} \xB7 Caveat` : "Caveat";
|
|
24829
24875
|
return c3.html(layout(title, body));
|
|
24830
24876
|
});
|
|
@@ -30290,10 +30336,11 @@ function createDetailRoute(ctx) {
|
|
|
30290
30336
|
}
|
|
30291
30337
|
const fm = entry.frontmatter;
|
|
30292
30338
|
const envRows = Object.entries(fm.environment ?? {}).map(([k2, v]) => `<dt>${escapeHtml(k2)}</dt><dd>${escapeHtml(String(v))}</dd>`).join("");
|
|
30339
|
+
const visibilityBadge = fm.visibility === "private" ? `<span class="badge private" title="local-only \u2014 never pushed to community DB">\u{1F512} private</span>` : `<span class="badge public">public</span>`;
|
|
30293
30340
|
const metaRows = [
|
|
30294
30341
|
`<dt>id</dt><dd><code>${escapeHtml(fm.id)}</code></dd>`,
|
|
30295
30342
|
`<dt>source</dt><dd>${escapeHtml(entry.source)}</dd>`,
|
|
30296
|
-
`<dt>visibility</dt><dd>${
|
|
30343
|
+
`<dt>visibility</dt><dd>${visibilityBadge}</dd>`,
|
|
30297
30344
|
`<dt>confidence</dt><dd><span class="badge ${escapeHtml(fm.confidence)}">${escapeHtml(fm.confidence)}</span></dd>`
|
|
30298
30345
|
];
|
|
30299
30346
|
if (fm.outcome) {
|
|
@@ -30327,14 +30374,14 @@ function createDetailRoute(ctx) {
|
|
|
30327
30374
|
}
|
|
30328
30375
|
|
|
30329
30376
|
// ../web/dist/routes/community.js
|
|
30330
|
-
import { existsSync as existsSync12, readdirSync as
|
|
30331
|
-
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";
|
|
30332
30379
|
function listCommunity(communityDir, db) {
|
|
30333
30380
|
if (!existsSync12(communityDir)) return [];
|
|
30334
30381
|
const handles = [];
|
|
30335
|
-
for (const entry of
|
|
30382
|
+
for (const entry of readdirSync7(communityDir, { withFileTypes: true })) {
|
|
30336
30383
|
if (!entry.isDirectory()) continue;
|
|
30337
|
-
const handlePath =
|
|
30384
|
+
const handlePath = join14(communityDir, entry.name);
|
|
30338
30385
|
const countRow = db.prepare("SELECT COUNT(*) AS n FROM entries WHERE source = ?").get(`community/${entry.name}`);
|
|
30339
30386
|
handles.push({
|
|
30340
30387
|
handle: entry.name,
|
|
@@ -30351,7 +30398,7 @@ function createCommunityRoute(ctx) {
|
|
|
30351
30398
|
const list2 = handles.map((h2) => {
|
|
30352
30399
|
let mtime = "";
|
|
30353
30400
|
try {
|
|
30354
|
-
mtime =
|
|
30401
|
+
mtime = statSync5(h2.path).mtime.toISOString().slice(0, 10);
|
|
30355
30402
|
} catch {
|
|
30356
30403
|
mtime = "";
|
|
30357
30404
|
}
|
|
@@ -44632,11 +44679,11 @@ var StdioServerTransport = class {
|
|
|
44632
44679
|
|
|
44633
44680
|
// ../mcp/dist/context.js
|
|
44634
44681
|
import { homedir as homedir3 } from "node:os";
|
|
44635
|
-
import { join as
|
|
44682
|
+
import { join as join15 } from "node:path";
|
|
44636
44683
|
function buildMcpContext(overrides = {}) {
|
|
44637
44684
|
const userHome = overrides.userHome ?? homedir3();
|
|
44638
44685
|
const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
|
|
44639
|
-
const userConfigPath =
|
|
44686
|
+
const userConfigPath = join15(userHome, ".caveatrc.json");
|
|
44640
44687
|
const logger = overrides.logger ?? stderrLogger;
|
|
44641
44688
|
const config3 = loadConfig(userConfigPath);
|
|
44642
44689
|
const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
|
|
@@ -44745,45 +44792,37 @@ function handleListRecent(ctx, args) {
|
|
|
44745
44792
|
}
|
|
44746
44793
|
|
|
44747
44794
|
// ../mcp/dist/tools/pull.js
|
|
44795
|
+
import { existsSync as existsSync13, readdirSync as readdirSync8 } from "node:fs";
|
|
44796
|
+
import { join as join16 } from "node:path";
|
|
44748
44797
|
var pullInputShape = {};
|
|
44749
44798
|
async function handlePull(ctx, _args = {}) {
|
|
44750
|
-
const
|
|
44751
|
-
|
|
44752
|
-
|
|
44753
|
-
|
|
44754
|
-
|
|
44755
|
-
|
|
44756
|
-
|
|
44757
|
-
|
|
44758
|
-
|
|
44759
|
-
|
|
44760
|
-
|
|
44761
|
-
|
|
44762
|
-
|
|
44763
|
-
})
|
|
44764
|
-
|
|
44765
|
-
}
|
|
44766
|
-
|
|
44767
|
-
|
|
44768
|
-
|
|
44769
|
-
|
|
44770
|
-
|
|
44771
|
-
|
|
44772
|
-
|
|
44773
|
-
|
|
44774
|
-
|
|
44775
|
-
|
|
44776
|
-
|
|
44777
|
-
id: args.id,
|
|
44778
|
-
dryRun: args.dry_run ?? false,
|
|
44779
|
-
logger: ctx.logger
|
|
44780
|
-
});
|
|
44781
|
-
return {
|
|
44782
|
-
status: result.status,
|
|
44783
|
-
detail: result.detail,
|
|
44784
|
-
pr_url: result.prUrl,
|
|
44785
|
-
planned_steps: result.plannedSteps
|
|
44786
|
-
};
|
|
44799
|
+
const pulled = [];
|
|
44800
|
+
const indexed = [];
|
|
44801
|
+
if (existsSync13(ctx.paths.communityDir)) {
|
|
44802
|
+
const results = await communityPull({
|
|
44803
|
+
communityDir: ctx.paths.communityDir,
|
|
44804
|
+
logger: ctx.logger
|
|
44805
|
+
});
|
|
44806
|
+
for (const r2 of results) {
|
|
44807
|
+
pulled.push({ handle: r2.handle, status: r2.status, message: r2.message });
|
|
44808
|
+
}
|
|
44809
|
+
}
|
|
44810
|
+
rebuildAll(ctx.db);
|
|
44811
|
+
if (existsSync13(ctx.paths.entriesDir)) {
|
|
44812
|
+
const own = scanSource({ db: ctx.db, source: "own", entriesRoot: ctx.paths.entriesDir });
|
|
44813
|
+
indexed.push({ source: "own", ...own });
|
|
44814
|
+
}
|
|
44815
|
+
if (existsSync13(ctx.paths.communityDir)) {
|
|
44816
|
+
for (const entry of readdirSync8(ctx.paths.communityDir, { withFileTypes: true })) {
|
|
44817
|
+
if (!entry.isDirectory()) continue;
|
|
44818
|
+
const source = `community/${entry.name}`;
|
|
44819
|
+
const root = join16(ctx.paths.communityDir, entry.name, "entries");
|
|
44820
|
+
if (!existsSync13(root)) continue;
|
|
44821
|
+
const scan = scanSource({ db: ctx.db, source, entriesRoot: root });
|
|
44822
|
+
indexed.push({ source, ...scan });
|
|
44823
|
+
}
|
|
44824
|
+
}
|
|
44825
|
+
return { pulled, indexed };
|
|
44787
44826
|
}
|
|
44788
44827
|
|
|
44789
44828
|
// ../mcp/dist/registerTools.js
|
|
@@ -44847,20 +44886,11 @@ function registerAllTools(server, ctx) {
|
|
|
44847
44886
|
"caveat_pull",
|
|
44848
44887
|
{
|
|
44849
44888
|
title: "caveat_pull",
|
|
44850
|
-
description: "git-pull
|
|
44889
|
+
description: "git-pull every subscribed community caveat repo (added via `caveat community add`) and re-index. Call when: (a) the user explicitly asks about others' knowledge on a topic, or (b) caveat_search returned empty for a query that feels like it should have hits and a subscribed repo might be stale. Do NOT call reflexively at session start \u2014 it is cheap but not free, and stale-by-minutes is acceptable. Safe and idempotent.",
|
|
44851
44890
|
inputSchema: pullInputShape
|
|
44852
44891
|
},
|
|
44853
44892
|
async (args) => jsonResult(await handlePull(ctx, args))
|
|
44854
44893
|
);
|
|
44855
|
-
server.registerTool(
|
|
44856
|
-
"caveat_push",
|
|
44857
|
-
{
|
|
44858
|
-
title: "caveat_push",
|
|
44859
|
-
description: "Contribute a user-owned caveat to the public shared DB via fork + PR on GitHub. This is a PUBLIC, externally-visible action (the PR appears on GitHub and is hard to fully retract). Confirm with the user before calling without dry_run \u2014 or call with dry_run=true first to show the plan. Only push entries that document a trap genuinely reusable by others (not project-specific ties, not duplicated by existing community entries). Entries with `visibility: private` are rejected (`status=visibility-private`) \u2014 the user has declared them local-only. Requires `gh` CLI authenticated; returns `status=gh-missing` / `gh-unauthed` on failure.",
|
|
44860
|
-
inputSchema: pushInputShape
|
|
44861
|
-
},
|
|
44862
|
-
async (args) => jsonResult(await handlePush(ctx, args))
|
|
44863
|
-
);
|
|
44864
44894
|
}
|
|
44865
44895
|
|
|
44866
44896
|
// ../mcp/dist/server.js
|
|
@@ -44900,6 +44930,19 @@ async function runMcpServer() {
|
|
|
44900
44930
|
}
|
|
44901
44931
|
|
|
44902
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
|
+
};
|
|
44903
44946
|
async function readStdin() {
|
|
44904
44947
|
const chunks = [];
|
|
44905
44948
|
for await (const chunk of process.stdin) {
|
|
@@ -44918,7 +44961,153 @@ function parsePayload(raw2) {
|
|
|
44918
44961
|
return {};
|
|
44919
44962
|
}
|
|
44920
44963
|
}
|
|
44921
|
-
|
|
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
|
+
}
|
|
44922
45111
|
let raw2 = "";
|
|
44923
45112
|
try {
|
|
44924
45113
|
raw2 = await readStdin();
|
|
@@ -44929,22 +45118,37 @@ async function runHook(name) {
|
|
|
44929
45118
|
process.exit(0);
|
|
44930
45119
|
}
|
|
44931
45120
|
const payload = parsePayload(raw2);
|
|
45121
|
+
const sessionId = getSessionId(payload);
|
|
45122
|
+
drainForSession(sessionId);
|
|
44932
45123
|
if (name === "user-prompt-submit") {
|
|
44933
45124
|
const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
|
|
44934
|
-
|
|
45125
|
+
const hits = searchCaveatsFromTextSafely(prompt);
|
|
45126
|
+
if (hits.length > 0) {
|
|
44935
45127
|
process.stdout.write(
|
|
44936
|
-
`<system-reminder>${userPromptSubmitReminderText()}</system-reminder>
|
|
45128
|
+
`<system-reminder>${userPromptSubmitReminderText(hits)}</system-reminder>
|
|
44937
45129
|
`
|
|
44938
45130
|
);
|
|
44939
45131
|
}
|
|
44940
45132
|
process.exit(0);
|
|
44941
45133
|
}
|
|
44942
|
-
if (name === "
|
|
44943
|
-
if (payload.
|
|
44944
|
-
|
|
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 });
|
|
44945
45139
|
}
|
|
44946
|
-
process.
|
|
44947
|
-
|
|
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
|
+
);
|
|
44948
45152
|
process.exit(0);
|
|
44949
45153
|
}
|
|
44950
45154
|
process.stderr.write(`[caveat:hook] unknown hook name: ${name}
|
|
@@ -44953,77 +45157,46 @@ async function runHook(name) {
|
|
|
44953
45157
|
}
|
|
44954
45158
|
|
|
44955
45159
|
// src/commands/pull.ts
|
|
44956
|
-
import { existsSync as
|
|
45160
|
+
import { existsSync as existsSync15, readdirSync as readdirSync9 } from "node:fs";
|
|
45161
|
+
import { join as join18 } from "node:path";
|
|
44957
45162
|
async function runPull(ctx) {
|
|
44958
|
-
if (!
|
|
45163
|
+
if (!existsSync15(ctx.paths.communityDir)) {
|
|
44959
45164
|
ctx.logger.info(
|
|
44960
|
-
"no community repos yet \u2014
|
|
45165
|
+
"no community repos yet \u2014 add one with `caveat community add <github-url>`."
|
|
44961
45166
|
);
|
|
44962
45167
|
return;
|
|
44963
45168
|
}
|
|
45169
|
+
const pulls = await communityPull({
|
|
45170
|
+
communityDir: ctx.paths.communityDir,
|
|
45171
|
+
logger: ctx.logger
|
|
45172
|
+
});
|
|
45173
|
+
for (const p2 of pulls) {
|
|
45174
|
+
if (p2.status === "ok") {
|
|
45175
|
+
ctx.logger.info(`${p2.handle}: pulled`);
|
|
45176
|
+
} else {
|
|
45177
|
+
ctx.logger.warn(`${p2.handle}: FAILED \u2014 ${p2.message ?? "unknown"}`);
|
|
45178
|
+
}
|
|
45179
|
+
}
|
|
44964
45180
|
const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
|
|
44965
45181
|
try {
|
|
44966
|
-
|
|
44967
|
-
|
|
44968
|
-
|
|
44969
|
-
|
|
44970
|
-
logger: ctx.logger
|
|
44971
|
-
});
|
|
44972
|
-
for (const p2 of result.pulled) {
|
|
44973
|
-
if (p2.status === "ok") {
|
|
44974
|
-
ctx.logger.info(`${p2.handle}: pulled`);
|
|
44975
|
-
} else {
|
|
44976
|
-
ctx.logger.warn(`${p2.handle}: FAILED \u2014 ${p2.message ?? "unknown"}`);
|
|
44977
|
-
}
|
|
45182
|
+
rebuildAll(db);
|
|
45183
|
+
if (existsSync15(ctx.paths.entriesDir)) {
|
|
45184
|
+
const own = scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
|
|
45185
|
+
ctx.logger.info(`own: +${own.added}`);
|
|
44978
45186
|
}
|
|
44979
|
-
for (const
|
|
44980
|
-
|
|
45187
|
+
for (const entry of readdirSync9(ctx.paths.communityDir, { withFileTypes: true })) {
|
|
45188
|
+
if (!entry.isDirectory()) continue;
|
|
45189
|
+
const source = `community/${entry.name}`;
|
|
45190
|
+
const root = join18(ctx.paths.communityDir, entry.name, "entries");
|
|
45191
|
+
if (!existsSync15(root)) continue;
|
|
45192
|
+
const scan = scanSource({ db, source, entriesRoot: root });
|
|
45193
|
+
ctx.logger.info(`${source}: +${scan.added}`);
|
|
44981
45194
|
}
|
|
44982
45195
|
} finally {
|
|
44983
45196
|
db.close();
|
|
44984
45197
|
}
|
|
44985
45198
|
}
|
|
44986
45199
|
|
|
44987
|
-
// src/commands/push.ts
|
|
44988
|
-
async function runPush(ctx, opts) {
|
|
44989
|
-
const result = await pushEntry({
|
|
44990
|
-
entriesDir: ctx.paths.entriesDir,
|
|
44991
|
-
caveatHome: ctx.caveatHome,
|
|
44992
|
-
sharedRepoUrl: ctx.config.sharedRepo,
|
|
44993
|
-
id: opts.id,
|
|
44994
|
-
dryRun: opts.dryRun,
|
|
44995
|
-
logger: ctx.logger
|
|
44996
|
-
});
|
|
44997
|
-
switch (result.status) {
|
|
44998
|
-
case "ok":
|
|
44999
|
-
ctx.logger.info(`PR opened: ${result.prUrl ?? ""}`);
|
|
45000
|
-
ctx.logger.info(
|
|
45001
|
-
"Once merged, other subscribers will see your entry after their next `caveat pull`."
|
|
45002
|
-
);
|
|
45003
|
-
break;
|
|
45004
|
-
case "dry-run":
|
|
45005
|
-
for (const [i2, step] of (result.plannedSteps ?? []).entries()) {
|
|
45006
|
-
ctx.logger.info(`[dry-run] ${i2 + 1}. ${step}`);
|
|
45007
|
-
}
|
|
45008
|
-
break;
|
|
45009
|
-
case "not-found":
|
|
45010
|
-
ctx.logger.error(
|
|
45011
|
-
`${result.detail}. List entries with \`caveat list\` or write + index first.`
|
|
45012
|
-
);
|
|
45013
|
-
process.exitCode = 1;
|
|
45014
|
-
break;
|
|
45015
|
-
case "gh-missing":
|
|
45016
|
-
case "gh-unauthed":
|
|
45017
|
-
ctx.logger.error(result.detail ?? "gh CLI issue");
|
|
45018
|
-
process.exitCode = 1;
|
|
45019
|
-
break;
|
|
45020
|
-
case "failed":
|
|
45021
|
-
ctx.logger.error(result.detail ?? "unknown failure");
|
|
45022
|
-
process.exitCode = 1;
|
|
45023
|
-
break;
|
|
45024
|
-
}
|
|
45025
|
-
}
|
|
45026
|
-
|
|
45027
45200
|
// src/commands/community.ts
|
|
45028
45201
|
async function runCommunityAdd(ctx, url) {
|
|
45029
45202
|
try {
|
|
@@ -45073,17 +45246,46 @@ function runCommunityList(ctx) {
|
|
|
45073
45246
|
db.close();
|
|
45074
45247
|
}
|
|
45075
45248
|
}
|
|
45249
|
+
function runCommunityRemove(ctx, handle, opts) {
|
|
45250
|
+
const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
|
|
45251
|
+
try {
|
|
45252
|
+
const result = communityRemove({
|
|
45253
|
+
communityDir: ctx.paths.communityDir,
|
|
45254
|
+
handle,
|
|
45255
|
+
db,
|
|
45256
|
+
dryRun: opts.dryRun,
|
|
45257
|
+
logger: ctx.logger
|
|
45258
|
+
});
|
|
45259
|
+
if (!result.dirExisted && result.rowCount === 0) {
|
|
45260
|
+
ctx.logger.warn(`no community subscription named "${handle}" \u2014 nothing to remove`);
|
|
45261
|
+
return;
|
|
45262
|
+
}
|
|
45263
|
+
const prefix = result.dryRun ? "[dry-run] would " : "";
|
|
45264
|
+
if (result.dirExisted) {
|
|
45265
|
+
ctx.logger.info(`${prefix}remove dir: ${result.path}`);
|
|
45266
|
+
}
|
|
45267
|
+
if (result.rowCount > 0) {
|
|
45268
|
+
ctx.logger.info(
|
|
45269
|
+
`${prefix}delete ${result.rowCount} db row(s) for source=community/${handle}`
|
|
45270
|
+
);
|
|
45271
|
+
}
|
|
45272
|
+
} catch (err) {
|
|
45273
|
+
ctx.logger.error(err instanceof Error ? err.message : String(err));
|
|
45274
|
+
process.exitCode = 1;
|
|
45275
|
+
} finally {
|
|
45276
|
+
db.close();
|
|
45277
|
+
}
|
|
45278
|
+
}
|
|
45076
45279
|
|
|
45077
45280
|
// src/index.ts
|
|
45078
45281
|
var program = new Command();
|
|
45079
45282
|
program.name("caveat").description("External spec gotcha knowledge base CLI").version(CAVEAT_VERSION);
|
|
45080
45283
|
program.command("init").description(
|
|
45081
|
-
"Initialize ~/.caveatrc.json, ~/.caveat/,
|
|
45082
|
-
).option("--skip-claude", "skip Claude Code MCP + hook registration", false).option("--
|
|
45284
|
+
"Initialize ~/.caveatrc.json, ~/.caveat/, and register Claude Code integration. Add knowledge sources later with `caveat community add <github-url>`."
|
|
45285
|
+
).option("--skip-claude", "skip Claude Code MCP + hook registration", false).option("--dry-run", "show planned changes without writing", false).action(async (opts) => {
|
|
45083
45286
|
const ctx = buildContext(stdoutLogger);
|
|
45084
45287
|
await runInit(ctx, {
|
|
45085
45288
|
skipClaude: opts.skipClaude,
|
|
45086
|
-
skipShared: opts.skipShared,
|
|
45087
45289
|
dryRun: opts.dryRun
|
|
45088
45290
|
});
|
|
45089
45291
|
});
|
|
@@ -45119,23 +45321,21 @@ program.command("stats").description("Show aggregate stats").action(() => {
|
|
|
45119
45321
|
runStats(ctx);
|
|
45120
45322
|
});
|
|
45121
45323
|
program.command("pull").description(
|
|
45122
|
-
"
|
|
45324
|
+
"git-pull every subscribed community repo and re-index. Use this to receive updates from group/teammate repos."
|
|
45123
45325
|
).action(async () => {
|
|
45124
45326
|
const ctx = buildContext(stdoutLogger);
|
|
45125
45327
|
await runPull(ctx);
|
|
45126
45328
|
});
|
|
45127
|
-
program.command("push").description("Contribute a caveat to the shared community DB via fork + PR (requires gh CLI).").argument("<id>", "entry id (from `caveat list`)").option("--dry-run", "show planned steps without making GitHub changes", false).action(async (id, opts) => {
|
|
45128
|
-
const ctx = buildContext(stdoutLogger);
|
|
45129
|
-
await runPush(ctx, { id, dryRun: opts.dryRun });
|
|
45130
|
-
});
|
|
45131
45329
|
program.command("serve").description("Start the read-only web share portal").option("--port <n>", "port number", (v) => Number(v), 4242).action((opts) => {
|
|
45132
45330
|
runServe({ port: opts.port });
|
|
45133
45331
|
});
|
|
45134
45332
|
program.command("mcp-server").description("Run the MCP stdio server (registered by `caveat init`)").action(async () => {
|
|
45135
45333
|
await runMcpServer();
|
|
45136
45334
|
});
|
|
45137
|
-
program.command("hook <name>").description(
|
|
45138
|
-
|
|
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);
|
|
45139
45339
|
});
|
|
45140
45340
|
var community = program.command("community").description("Manage community caveat repos (shallow clones under <knowledgeRepo>/community/)");
|
|
45141
45341
|
community.command("add <url>").description("Shallow-clone a GitHub caveat repo into community/<handle>/").action(async (url) => {
|
|
@@ -45150,6 +45350,10 @@ community.command("list").description("List imported community repos with entry
|
|
|
45150
45350
|
const ctx = buildContext(stdoutLogger);
|
|
45151
45351
|
runCommunityList(ctx);
|
|
45152
45352
|
});
|
|
45353
|
+
community.command("remove <handle>").description("Unsubscribe from a community repo: delete community/<handle>/ and purge its DB rows").option("--dry-run", "show what would be removed without touching disk or db", false).action((handle, opts) => {
|
|
45354
|
+
const ctx = buildContext(stdoutLogger);
|
|
45355
|
+
runCommunityRemove(ctx, handle, { dryRun: opts.dryRun });
|
|
45356
|
+
});
|
|
45153
45357
|
program.parseAsync(process.argv).catch((err) => {
|
|
45154
45358
|
const msg = err instanceof Error ? err.message : String(err);
|
|
45155
45359
|
process.stderr.write(`[caveat:error] ${msg}
|