caveat-cli 0.5.0 → 0.6.1
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 +31 -5
- package/dist/index.js +116 -175
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# caveat-cli
|
|
2
2
|
|
|
3
|
-
External spec gotcha knowledge base CLI — markdown + SQLite FTS5 + MCP server + Claude Code hooks.
|
|
3
|
+
External spec gotcha knowledge base CLI — markdown + SQLite FTS5 + MCP server + Claude Code hooks, with a shared community knowledge DB out of the box.
|
|
4
4
|
|
|
5
5
|
**Source / full docs**: https://github.com/kitepon-rgb/Caveat
|
|
6
6
|
|
|
@@ -11,22 +11,48 @@ npm install -g caveat-cli
|
|
|
11
11
|
caveat init
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
`caveat init`
|
|
14
|
+
`caveat init` (idempotent, `--dry-run` supported) does 4 things:
|
|
15
|
+
|
|
16
|
+
1. Scaffolds `~/.caveat/own/` (your personal knowledge repo) + `~/.caveat/index/caveat.db`
|
|
17
|
+
2. **Subscribes you to the shared community DB** (`kitepon-rgb/Caveat` by default) — shallow-clones into `~/.caveat/community/Caveat/` and indexes so other users' caveats are immediately searchable
|
|
18
|
+
3. Registers the MCP server with Claude Code (`claude mcp add --scope user`)
|
|
19
|
+
4. Merges `UserPromptSubmit` / `Stop` hooks into `~/.claude/settings.json` (existing entries preserved, backup written before any change)
|
|
20
|
+
|
|
21
|
+
Opt-out flags: `--skip-claude`, `--skip-shared`. `caveat uninstall` reverses the Claude Code changes without touching `~/.caveat/`.
|
|
15
22
|
|
|
16
23
|
## Basic usage
|
|
17
24
|
|
|
18
25
|
```sh
|
|
19
|
-
caveat search "rtx" # FTS
|
|
26
|
+
caveat search "rtx" # FTS across your own entries + the shared community DB
|
|
20
27
|
caveat list # recent entries
|
|
28
|
+
caveat pull # fetch new community contributions + re-index
|
|
29
|
+
caveat push <entry-id> # contribute your entry via fork + PR (requires gh CLI)
|
|
21
30
|
caveat serve # http://localhost:4242 read-only portal
|
|
22
|
-
caveat community add <url> #
|
|
31
|
+
caveat community add <url> # subscribe to an additional community repo
|
|
23
32
|
caveat uninstall # reverse `caveat init` Claude integration
|
|
24
33
|
```
|
|
25
34
|
|
|
35
|
+
## MCP tools (7)
|
|
36
|
+
|
|
37
|
+
Exposed to Claude Code via the MCP server that `caveat init` registers:
|
|
38
|
+
|
|
39
|
+
`caveat_search`, `caveat_get`, `caveat_record`, `caveat_update`, `caveat_list_recent`, `caveat_pull`, `caveat_push`.
|
|
40
|
+
|
|
41
|
+
Claude can autonomously pull community updates (safe, idempotent) and push your recorded caveats (gated by Claude Code's tool permission prompt for user consent — contribution is a public action).
|
|
42
|
+
|
|
43
|
+
## Pointing at a different shared DB
|
|
44
|
+
|
|
45
|
+
For an internal/enterprise shared knowledge pool, override in `~/.caveatrc.json`:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{ "sharedRepo": "https://github.com/your-org/your-caveats" }
|
|
49
|
+
```
|
|
50
|
+
|
|
26
51
|
## Requirements
|
|
27
52
|
|
|
28
53
|
- Node 22.5+ (for built-in `node:sqlite`)
|
|
29
|
-
-
|
|
54
|
+
- `gh` CLI authenticated (`gh auth login`) if you want to use `caveat push`
|
|
55
|
+
- Claude Code installed if you want MCP / hooks integration. Without it, `caveat init --skip-claude` still provisions local state + shared DB
|
|
30
56
|
|
|
31
57
|
## License
|
|
32
58
|
|
package/dist/index.js
CHANGED
|
@@ -15948,8 +15948,7 @@ function recordEntry(input, opts) {
|
|
|
15948
15948
|
source_session: generateSourceSession(now),
|
|
15949
15949
|
created_at: ymd,
|
|
15950
15950
|
updated_at: ymd,
|
|
15951
|
-
last_verified: ymd
|
|
15952
|
-
...input.brief_id !== void 0 ? { brief_id: input.brief_id } : {}
|
|
15951
|
+
last_verified: ymd
|
|
15953
15952
|
};
|
|
15954
15953
|
const sections = {};
|
|
15955
15954
|
if (input.context !== void 0) sections["Context"] = input.context;
|
|
@@ -15996,8 +15995,7 @@ var IMMUTABLE_KEYS = /* @__PURE__ */ new Set([
|
|
|
15996
15995
|
"id",
|
|
15997
15996
|
"created_at",
|
|
15998
15997
|
"source_session",
|
|
15999
|
-
"source_project"
|
|
16000
|
-
"brief_id"
|
|
15998
|
+
"source_project"
|
|
16001
15999
|
]);
|
|
16002
16000
|
function updateEntry(id, patch, opts) {
|
|
16003
16001
|
const source = opts.source ?? "own";
|
|
@@ -16035,9 +16033,6 @@ function updateEntry(id, patch, opts) {
|
|
|
16035
16033
|
if (patch.frontmatter?.tags !== void 0) {
|
|
16036
16034
|
mergedFrontmatter.tags = patch.frontmatter.tags;
|
|
16037
16035
|
}
|
|
16038
|
-
if (parsed.frontmatter.brief_id !== void 0) {
|
|
16039
|
-
mergedFrontmatter.brief_id = parsed.frontmatter.brief_id;
|
|
16040
|
-
}
|
|
16041
16036
|
const mergedSections = { ...parsed.sections };
|
|
16042
16037
|
if (patch.sections) {
|
|
16043
16038
|
for (const [heading2, content] of Object.entries(patch.sections)) {
|
|
@@ -16077,39 +16072,6 @@ function formatYmd2(d) {
|
|
|
16077
16072
|
return `${yyyy}-${mm}-${dd}`;
|
|
16078
16073
|
}
|
|
16079
16074
|
|
|
16080
|
-
// ../../packages/core/dist/brief.js
|
|
16081
|
-
function generateBrief(db, topic, limit = 10) {
|
|
16082
|
-
const related = topic.trim().length > 0 ? search(db, { query: topic, limit }) : [];
|
|
16083
|
-
const brief_id = `brf-${Date.now().toString(36)}-${randomHex(8)}`;
|
|
16084
|
-
const lines = [];
|
|
16085
|
-
lines.push(`# \u8ABF\u67FB\u4F9D\u983C: ${topic}`);
|
|
16086
|
-
lines.push("");
|
|
16087
|
-
lines.push("## \u4F9D\u983C\u306E\u80CC\u666F");
|
|
16088
|
-
lines.push(
|
|
16089
|
-
`\u4EE5\u4E0B\u306E\u8A71\u984C\u306B\u3064\u3044\u3066\u3001\u4E00\u6B21\u30BD\u30FC\u30B9\u4E2D\u5FC3\u3067\u8ABF\u67FB\u3057\u3066\u304F\u3060\u3055\u3044: ${topic}`
|
|
16090
|
-
);
|
|
16091
|
-
lines.push("");
|
|
16092
|
-
if (related.length > 0) {
|
|
16093
|
-
lines.push("## \u65E2\u5B58\u306E\u95A2\u9023 caveat\uFF08\u53C2\u8003\uFF09");
|
|
16094
|
-
for (const r2 of related) {
|
|
16095
|
-
lines.push(`- \`${r2.id}\` (${r2.confidence}) ${r2.title}`);
|
|
16096
|
-
}
|
|
16097
|
-
lines.push("");
|
|
16098
|
-
}
|
|
16099
|
-
lines.push("## \u6C42\u3081\u305F\u3044\u60C5\u5831");
|
|
16100
|
-
lines.push("1. \u516C\u5F0F\u4ED5\u69D8 (RFC / \u4ED5\u69D8\u66F8 / \u30D9\u30F3\u30C0\u30FC\u516C\u5F0F\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8) \u2014 \u30D0\u30FC\u30B8\u30E7\u30F3\u660E\u8A18");
|
|
16101
|
-
lines.push("2. \u65E2\u77E5\u306E\u843D\u3068\u3057\u7A74 (\u30D0\u30FC\u30B8\u30E7\u30F3\u4F9D\u5B58\u3001\u30D7\u30E9\u30C3\u30C8\u30D5\u30A9\u30FC\u30E0\u5DEE\u7570\u3001\u65E2\u5831\u306E\u4E0D\u5177\u5408)");
|
|
16102
|
-
lines.push("3. \u56DE\u907F\u7B56\u3068\u4EE3\u66FF\u624B\u6BB5\uFF08\u52B9\u679C\u306E\u88CF\u53D6\u308A\u3064\u304D\uFF09");
|
|
16103
|
-
lines.push("4. \u4E00\u6B21\u30BD\u30FC\u30B9 URL\u3002\u4E8C\u6B21\u30BD\u30FC\u30B9\uFF08\u30D6\u30ED\u30B0\u7B49\uFF09\u306F\u533A\u5225\u3057\u3066\u8A18\u8F09");
|
|
16104
|
-
lines.push("");
|
|
16105
|
-
lines.push("## \u51FA\u529B\u306E\u5F62");
|
|
16106
|
-
lines.push("Symptom / Cause / Resolution / Evidence \u306E 4 \u30BB\u30AF\u30B7\u30E7\u30F3\u3067\u3001\u5F8C\u7D9A\u306E `ingest_research` \u306B\u6E21\u305B\u308B\u5F62\u5F0F\u3067\u8FD4\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
|
|
16107
|
-
lines.push("");
|
|
16108
|
-
lines.push("---");
|
|
16109
|
-
lines.push(`brief_id: ${brief_id}`);
|
|
16110
|
-
return { brief_id, text: lines.join("\n") };
|
|
16111
|
-
}
|
|
16112
|
-
|
|
16113
16075
|
// ../../packages/core/dist/paths.js
|
|
16114
16076
|
import { existsSync as existsSync4 } from "node:fs";
|
|
16115
16077
|
import { dirname as dirname3, isAbsolute, join as join5, resolve } from "node:path";
|
|
@@ -16133,7 +16095,11 @@ function resolvePaths(caveatHome, knowledgeRepo, userHome) {
|
|
|
16133
16095
|
knowledgeRepo: resolved,
|
|
16134
16096
|
dbPath: join5(caveatHome, "index", "caveat.db"),
|
|
16135
16097
|
entriesDir: join5(resolved, "entries"),
|
|
16136
|
-
|
|
16098
|
+
// community/ lives at caveatHome level, NOT inside knowledgeRepo. Community
|
|
16099
|
+
// clones are external knowledge caches — not semantically "owned" by the
|
|
16100
|
+
// user — and they embed their own .git dirs which would otherwise nest
|
|
16101
|
+
// inside the user's git-tracked knowledge repo.
|
|
16102
|
+
communityDir: join5(caveatHome, "community")
|
|
16137
16103
|
};
|
|
16138
16104
|
}
|
|
16139
16105
|
|
|
@@ -21291,16 +21257,16 @@ async function pushEntry(opts) {
|
|
|
21291
21257
|
const stagingDir = forkStagingDir(opts.caveatHome);
|
|
21292
21258
|
ensureStagingClone(stagingDir, ghUser, sharedName, sharedOwner);
|
|
21293
21259
|
const branch = `caveat-push-${owned.id}-${Date.now().toString(36)}`;
|
|
21294
|
-
|
|
21260
|
+
runOrThrow("git", ["checkout", "-b", branch], { cwd: stagingDir });
|
|
21295
21261
|
const destPath = join7(stagingDir, "entries", owned.relPath);
|
|
21296
21262
|
mkdirSync3(dirname4(destPath), { recursive: true });
|
|
21297
21263
|
copyFileSync(owned.absPath, destPath);
|
|
21298
|
-
|
|
21264
|
+
runOrThrow("git", ["add", join7("entries", owned.relPath)], { cwd: stagingDir });
|
|
21299
21265
|
const isUpdate = upstreamAlreadyHas(stagingDir, "entries/" + owned.relPath);
|
|
21300
21266
|
const verb = isUpdate ? "update" : "add";
|
|
21301
21267
|
const commitMsg = `${verb}: ${owned.title}`;
|
|
21302
|
-
|
|
21303
|
-
|
|
21268
|
+
runOrThrow("git", ["commit", "-m", commitMsg], { cwd: stagingDir });
|
|
21269
|
+
runOrThrow("git", ["push", "--set-upstream", "origin", branch], { cwd: stagingDir });
|
|
21304
21270
|
const prTitle = `${verb}: ${owned.title}`;
|
|
21305
21271
|
const prBody = [
|
|
21306
21272
|
`Contribution of caveat \`${owned.id}\` via \`caveat push\`.`,
|
|
@@ -21310,7 +21276,7 @@ async function pushEntry(opts) {
|
|
|
21310
21276
|
"",
|
|
21311
21277
|
"Merged PRs will appear in subscribers' repos on their next `caveat pull`."
|
|
21312
21278
|
].join("\n");
|
|
21313
|
-
const pr =
|
|
21279
|
+
const pr = runCapture(
|
|
21314
21280
|
"gh",
|
|
21315
21281
|
[
|
|
21316
21282
|
"pr",
|
|
@@ -21326,16 +21292,15 @@ async function pushEntry(opts) {
|
|
|
21326
21292
|
"--body",
|
|
21327
21293
|
prBody
|
|
21328
21294
|
],
|
|
21329
|
-
{ cwd: stagingDir
|
|
21295
|
+
{ cwd: stagingDir }
|
|
21330
21296
|
);
|
|
21331
21297
|
if (pr.status !== 0) {
|
|
21332
21298
|
return {
|
|
21333
21299
|
status: "failed",
|
|
21334
|
-
detail: (
|
|
21300
|
+
detail: (pr.stderr || "gh pr create failed").trim()
|
|
21335
21301
|
};
|
|
21336
21302
|
}
|
|
21337
|
-
|
|
21338
|
-
return { status: "ok", prUrl };
|
|
21303
|
+
return { status: "ok", prUrl: pr.stdout.trim() };
|
|
21339
21304
|
} catch (err) {
|
|
21340
21305
|
return {
|
|
21341
21306
|
status: "failed",
|
|
@@ -21343,21 +21308,41 @@ async function pushEntry(opts) {
|
|
|
21343
21308
|
};
|
|
21344
21309
|
}
|
|
21345
21310
|
}
|
|
21311
|
+
function shellQuote(s) {
|
|
21312
|
+
if (!/[\s&|<>^()"`$!*?\[\]{}\\]/.test(s)) return s;
|
|
21313
|
+
return `"${s.replace(/"/g, '""')}"`;
|
|
21314
|
+
}
|
|
21315
|
+
function runCapture(command, args, opts = {}) {
|
|
21316
|
+
const line = [command, ...args].map(shellQuote).join(" ");
|
|
21317
|
+
const r2 = spawnSync(line, {
|
|
21318
|
+
encoding: "utf-8",
|
|
21319
|
+
cwd: opts.cwd,
|
|
21320
|
+
shell: true
|
|
21321
|
+
});
|
|
21322
|
+
return {
|
|
21323
|
+
status: r2.status,
|
|
21324
|
+
stdout: typeof r2.stdout === "string" ? r2.stdout : "",
|
|
21325
|
+
stderr: typeof r2.stderr === "string" ? r2.stderr : ""
|
|
21326
|
+
};
|
|
21327
|
+
}
|
|
21328
|
+
function runOrThrow(command, args, opts = {}) {
|
|
21329
|
+
const r2 = runCapture(command, args, opts);
|
|
21330
|
+
if (r2.status !== 0) {
|
|
21331
|
+
throw new Error(
|
|
21332
|
+
`${command} ${args.join(" ")} failed: ${(r2.stderr || r2.stdout || "unknown").trim()}`
|
|
21333
|
+
);
|
|
21334
|
+
}
|
|
21335
|
+
}
|
|
21346
21336
|
function checkGhAvailable() {
|
|
21347
|
-
|
|
21348
|
-
return r2.status === 0;
|
|
21337
|
+
return runCapture("gh", ["--version"]).status === 0;
|
|
21349
21338
|
}
|
|
21350
21339
|
function checkGhAuthed() {
|
|
21351
|
-
|
|
21352
|
-
return r2.status === 0;
|
|
21340
|
+
return runCapture("gh", ["auth", "status"]).status === 0;
|
|
21353
21341
|
}
|
|
21354
21342
|
function resolveGhUser() {
|
|
21355
|
-
const r2 =
|
|
21356
|
-
encoding: "utf-8",
|
|
21357
|
-
shell: true
|
|
21358
|
-
});
|
|
21343
|
+
const r2 = runCapture("gh", ["api", "user", "--jq", ".login"]);
|
|
21359
21344
|
if (r2.status !== 0) return void 0;
|
|
21360
|
-
return
|
|
21345
|
+
return r2.stdout.trim() || void 0;
|
|
21361
21346
|
}
|
|
21362
21347
|
function parseOwnerRepo(url) {
|
|
21363
21348
|
const m = /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(\.git)?\/?$/.exec(url);
|
|
@@ -21389,44 +21374,28 @@ function forkStagingDir(caveatHome) {
|
|
|
21389
21374
|
return join7(caveatHome, "push-fork");
|
|
21390
21375
|
}
|
|
21391
21376
|
function ensureFork(owner, repo) {
|
|
21392
|
-
|
|
21393
|
-
"gh",
|
|
21394
|
-
["repo", "fork", `${owner}/${repo}`, "--clone=false", "--remote=false"],
|
|
21395
|
-
{ encoding: "utf-8", shell: true }
|
|
21396
|
-
);
|
|
21377
|
+
runCapture("gh", ["repo", "fork", `${owner}/${repo}`, "--clone=false", "--remote=false"]);
|
|
21397
21378
|
}
|
|
21398
21379
|
function ensureStagingClone(stagingDir, ghUser, sharedName, upstreamOwner) {
|
|
21399
21380
|
if (!existsSync7(stagingDir)) {
|
|
21400
21381
|
mkdirSync3(dirname4(stagingDir), { recursive: true });
|
|
21401
21382
|
const forkUrl = `https://github.com/${ghUser}/${sharedName}.git`;
|
|
21402
|
-
|
|
21403
|
-
|
|
21383
|
+
runOrThrow("git", ["clone", "--depth", "30", forkUrl, stagingDir]);
|
|
21384
|
+
runOrThrow(
|
|
21404
21385
|
"git",
|
|
21405
21386
|
["remote", "add", "upstream", `https://github.com/${upstreamOwner}/${sharedName}.git`],
|
|
21406
21387
|
{ cwd: stagingDir }
|
|
21407
21388
|
);
|
|
21408
21389
|
} else {
|
|
21409
|
-
|
|
21410
|
-
|
|
21411
|
-
|
|
21412
|
-
|
|
21390
|
+
runOrThrow("git", ["checkout", "main"], { cwd: stagingDir });
|
|
21391
|
+
runOrThrow("git", ["fetch", "upstream", "main"], { cwd: stagingDir });
|
|
21392
|
+
runOrThrow("git", ["reset", "--hard", "upstream/main"], { cwd: stagingDir });
|
|
21393
|
+
runOrThrow("git", ["push", "origin", "main", "--force-with-lease"], { cwd: stagingDir });
|
|
21413
21394
|
}
|
|
21414
21395
|
}
|
|
21415
21396
|
function upstreamAlreadyHas(stagingDir, relFromRoot) {
|
|
21416
21397
|
return existsSync7(join7(stagingDir, relFromRoot));
|
|
21417
21398
|
}
|
|
21418
|
-
function run(command, args, opts = {}) {
|
|
21419
|
-
const r2 = spawnSync(command, args, {
|
|
21420
|
-
encoding: "utf-8",
|
|
21421
|
-
cwd: opts.cwd,
|
|
21422
|
-
shell: true
|
|
21423
|
-
});
|
|
21424
|
-
if (r2.status !== 0) {
|
|
21425
|
-
const stderr = typeof r2.stderr === "string" ? r2.stderr : String(r2.stderr ?? "");
|
|
21426
|
-
const stdout = typeof r2.stdout === "string" ? r2.stdout : String(r2.stdout ?? "");
|
|
21427
|
-
throw new Error(`${command} ${args.join(" ")} failed: ${(stderr || stdout || "unknown").trim()}`);
|
|
21428
|
-
}
|
|
21429
|
-
}
|
|
21430
21399
|
|
|
21431
21400
|
// ../../packages/core/dist/pullShared.js
|
|
21432
21401
|
import { existsSync as existsSync8, readdirSync as readdirSync5 } from "node:fs";
|
|
@@ -21473,6 +21442,21 @@ function buildContext(logger, overrides = {}) {
|
|
|
21473
21442
|
return { caveatHome, userHome, userConfigPath, config: config3, paths, logger };
|
|
21474
21443
|
}
|
|
21475
21444
|
|
|
21445
|
+
// src/version.ts
|
|
21446
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
21447
|
+
import { dirname as dirname5, join as join10 } from "node:path";
|
|
21448
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
21449
|
+
function resolveVersion() {
|
|
21450
|
+
try {
|
|
21451
|
+
const here2 = dirname5(fileURLToPath3(import.meta.url));
|
|
21452
|
+
const pkg = JSON.parse(readFileSync6(join10(here2, "..", "package.json"), "utf-8"));
|
|
21453
|
+
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
21454
|
+
} catch {
|
|
21455
|
+
return "0.0.0";
|
|
21456
|
+
}
|
|
21457
|
+
}
|
|
21458
|
+
var CAVEAT_VERSION = resolveVersion();
|
|
21459
|
+
|
|
21476
21460
|
// src/logger.ts
|
|
21477
21461
|
var stdoutLogger = {
|
|
21478
21462
|
info: (m) => process.stdout.write(`[caveat] ${m}
|
|
@@ -21484,13 +21468,13 @@ var stdoutLogger = {
|
|
|
21484
21468
|
};
|
|
21485
21469
|
|
|
21486
21470
|
// src/commands/init.ts
|
|
21487
|
-
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
21488
|
-
import { join as
|
|
21471
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync6, renameSync, rmdirSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
21472
|
+
import { join as join12 } from "node:path";
|
|
21489
21473
|
|
|
21490
21474
|
// src/claudeInstall.ts
|
|
21491
21475
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
21492
|
-
import { copyFileSync as copyFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as
|
|
21493
|
-
import { dirname as
|
|
21476
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
21477
|
+
import { dirname as dirname6, join as join11 } from "node:path";
|
|
21494
21478
|
var EVENT_USER_PROMPT_SUBMIT = "UserPromptSubmit";
|
|
21495
21479
|
var EVENT_STOP = "Stop";
|
|
21496
21480
|
function quote(p2) {
|
|
@@ -21525,10 +21509,10 @@ function removeHook(settings, event, command) {
|
|
|
21525
21509
|
}
|
|
21526
21510
|
function readSettings(path) {
|
|
21527
21511
|
if (!existsSync9(path)) return {};
|
|
21528
|
-
return JSON.parse(
|
|
21512
|
+
return JSON.parse(readFileSync7(path, "utf-8"));
|
|
21529
21513
|
}
|
|
21530
21514
|
function writeSettings(path, settings) {
|
|
21531
|
-
const dir =
|
|
21515
|
+
const dir = dirname6(path);
|
|
21532
21516
|
if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
|
|
21533
21517
|
let backupPath = "";
|
|
21534
21518
|
if (existsSync9(path)) {
|
|
@@ -21539,11 +21523,11 @@ function writeSettings(path, settings) {
|
|
|
21539
21523
|
return backupPath;
|
|
21540
21524
|
}
|
|
21541
21525
|
var CLAUDE_BIN = "claude";
|
|
21542
|
-
function
|
|
21526
|
+
function shellQuote2(s) {
|
|
21543
21527
|
return /[\s&|<>^()]/.test(s) ? `"${s}"` : s;
|
|
21544
21528
|
}
|
|
21545
21529
|
function runClaude(args) {
|
|
21546
|
-
const line = [CLAUDE_BIN, ...args].map(
|
|
21530
|
+
const line = [CLAUDE_BIN, ...args].map(shellQuote2).join(" ");
|
|
21547
21531
|
return spawnSync2(line, { shell: true, encoding: "utf-8" });
|
|
21548
21532
|
}
|
|
21549
21533
|
function registerMcp(nodePath, cliScriptPath, dryRun, logger) {
|
|
@@ -21594,7 +21578,7 @@ function unregisterMcp(dryRun, logger) {
|
|
|
21594
21578
|
return { action: "skipped", detail: "not registered or removal failed" };
|
|
21595
21579
|
}
|
|
21596
21580
|
function installClaudeIntegration(opts) {
|
|
21597
|
-
const settingsPath =
|
|
21581
|
+
const settingsPath = join11(opts.claudeDir, "settings.json");
|
|
21598
21582
|
const settings = readSettings(settingsPath);
|
|
21599
21583
|
const usCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "user-prompt-submit");
|
|
21600
21584
|
const stopCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "stop");
|
|
@@ -21613,7 +21597,7 @@ function installClaudeIntegration(opts) {
|
|
|
21613
21597
|
return { mcp, hooks: { userPromptSubmit, stop }, backupPath };
|
|
21614
21598
|
}
|
|
21615
21599
|
function uninstallClaudeIntegration(opts) {
|
|
21616
|
-
const settingsPath =
|
|
21600
|
+
const settingsPath = join11(opts.claudeDir, "settings.json");
|
|
21617
21601
|
const settings = readSettings(settingsPath);
|
|
21618
21602
|
const usCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "user-prompt-submit");
|
|
21619
21603
|
const stopCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "stop");
|
|
@@ -21643,11 +21627,6 @@ var KNOWLEDGE_GITIGNORE = [
|
|
|
21643
21627
|
"",
|
|
21644
21628
|
"# Obsidian per-user config: workspace layout, theme, plugin state, cache.",
|
|
21645
21629
|
".obsidian/",
|
|
21646
|
-
"",
|
|
21647
|
-
"# community/ is a local cache of shallow-cloned third-party caveat repos",
|
|
21648
|
-
"# (populated by `caveat community add`). Each contains its own .git and is not",
|
|
21649
|
-
"# part of this repo's tracked knowledge.",
|
|
21650
|
-
"community/",
|
|
21651
21630
|
""
|
|
21652
21631
|
].join("\n");
|
|
21653
21632
|
async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRun: false }) {
|
|
@@ -21660,7 +21639,8 @@ async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRu
|
|
|
21660
21639
|
} else {
|
|
21661
21640
|
ctx.logger.info(`knowledge repo: ${ctx.paths.knowledgeRepo}`);
|
|
21662
21641
|
}
|
|
21663
|
-
|
|
21642
|
+
migrateLegacyCommunityDir(ctx);
|
|
21643
|
+
const gitignorePath = join12(ctx.paths.knowledgeRepo, ".gitignore");
|
|
21664
21644
|
if (!existsSync10(gitignorePath)) {
|
|
21665
21645
|
writeFileSync5(gitignorePath, KNOWLEDGE_GITIGNORE, "utf-8");
|
|
21666
21646
|
ctx.logger.info(`.gitignore created: ${gitignorePath}`);
|
|
@@ -21688,7 +21668,7 @@ async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRu
|
|
|
21688
21668
|
return;
|
|
21689
21669
|
}
|
|
21690
21670
|
const result = installClaudeIntegration({
|
|
21691
|
-
claudeDir:
|
|
21671
|
+
claudeDir: join12(ctx.userHome, ".claude"),
|
|
21692
21672
|
cliScriptPath,
|
|
21693
21673
|
nodePath: process.execPath,
|
|
21694
21674
|
dryRun: opts.dryRun,
|
|
@@ -21696,6 +21676,28 @@ async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRu
|
|
|
21696
21676
|
});
|
|
21697
21677
|
reportInstallResult(ctx, result, opts.dryRun);
|
|
21698
21678
|
}
|
|
21679
|
+
function migrateLegacyCommunityDir(ctx) {
|
|
21680
|
+
const legacy = join12(ctx.paths.knowledgeRepo, "community");
|
|
21681
|
+
const current = ctx.paths.communityDir;
|
|
21682
|
+
if (legacy === current) return;
|
|
21683
|
+
if (!existsSync10(legacy)) return;
|
|
21684
|
+
if (existsSync10(current)) {
|
|
21685
|
+
ctx.logger.warn(
|
|
21686
|
+
`legacy community dir still exists at ${legacy} \u2014 remove manually (new location in use)`
|
|
21687
|
+
);
|
|
21688
|
+
return;
|
|
21689
|
+
}
|
|
21690
|
+
mkdirSync5(current, { recursive: true });
|
|
21691
|
+
for (const entry of readdirSync6(legacy, { withFileTypes: true })) {
|
|
21692
|
+
if (!entry.isDirectory()) continue;
|
|
21693
|
+
renameSync(join12(legacy, entry.name), join12(current, entry.name));
|
|
21694
|
+
}
|
|
21695
|
+
try {
|
|
21696
|
+
rmdirSync(legacy);
|
|
21697
|
+
} catch {
|
|
21698
|
+
}
|
|
21699
|
+
ctx.logger.info(`migrated legacy community/ \u2192 ${current}`);
|
|
21700
|
+
}
|
|
21699
21701
|
async function subscribeSharedRepo(ctx, db) {
|
|
21700
21702
|
const url = ctx.config.sharedRepo;
|
|
21701
21703
|
const validation = validateCommunityUrl(url);
|
|
@@ -21706,7 +21708,7 @@ async function subscribeSharedRepo(ctx, db) {
|
|
|
21706
21708
|
return;
|
|
21707
21709
|
}
|
|
21708
21710
|
const handle = validation.handle;
|
|
21709
|
-
const target =
|
|
21711
|
+
const target = join12(ctx.paths.communityDir, handle);
|
|
21710
21712
|
if (!existsSync10(target)) {
|
|
21711
21713
|
if (!existsSync10(ctx.paths.communityDir)) {
|
|
21712
21714
|
mkdirSync5(ctx.paths.communityDir, { recursive: true });
|
|
@@ -21735,7 +21737,7 @@ async function subscribeSharedRepo(ctx, db) {
|
|
|
21735
21737
|
for (const entry of readdirSync6(ctx.paths.communityDir, { withFileTypes: true })) {
|
|
21736
21738
|
if (!entry.isDirectory()) continue;
|
|
21737
21739
|
const source = `community/${entry.name}`;
|
|
21738
|
-
const root =
|
|
21740
|
+
const root = join12(ctx.paths.communityDir, entry.name, "entries");
|
|
21739
21741
|
if (!existsSync10(root)) continue;
|
|
21740
21742
|
const result = scanSource({ db, source, entriesRoot: root });
|
|
21741
21743
|
ctx.logger.info(`indexed ${source}: +${result.added}`);
|
|
@@ -21749,7 +21751,7 @@ function runUninstall(ctx, opts) {
|
|
|
21749
21751
|
process.exit(1);
|
|
21750
21752
|
}
|
|
21751
21753
|
const result = uninstallClaudeIntegration({
|
|
21752
|
-
claudeDir:
|
|
21754
|
+
claudeDir: join12(ctx.userHome, ".claude"),
|
|
21753
21755
|
cliScriptPath,
|
|
21754
21756
|
nodePath: process.execPath,
|
|
21755
21757
|
dryRun: opts.dryRun,
|
|
@@ -21787,9 +21789,9 @@ function reportInstallResult(ctx, result, dryRun) {
|
|
|
21787
21789
|
|
|
21788
21790
|
// src/commands/indexCmd.ts
|
|
21789
21791
|
import { existsSync as existsSync11, readdirSync as readdirSync7, mkdirSync as mkdirSync6 } from "node:fs";
|
|
21790
|
-
import { dirname as
|
|
21792
|
+
import { dirname as dirname7, join as join13 } from "node:path";
|
|
21791
21793
|
function runIndex(ctx, opts) {
|
|
21792
|
-
const dbDir =
|
|
21794
|
+
const dbDir = dirname7(ctx.paths.dbPath);
|
|
21793
21795
|
if (!existsSync11(dbDir)) mkdirSync6(dbDir, { recursive: true });
|
|
21794
21796
|
const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
|
|
21795
21797
|
try {
|
|
@@ -21807,7 +21809,7 @@ function runIndex(ctx, opts) {
|
|
|
21807
21809
|
for (const entry of readdirSync7(ctx.paths.communityDir, { withFileTypes: true })) {
|
|
21808
21810
|
if (!entry.isDirectory()) continue;
|
|
21809
21811
|
const source = `community/${entry.name}`;
|
|
21810
|
-
const root =
|
|
21812
|
+
const root = join13(ctx.paths.communityDir, entry.name, "entries");
|
|
21811
21813
|
if (!existsSync11(root)) continue;
|
|
21812
21814
|
const result = scanSource({ db, source, entriesRoot: root });
|
|
21813
21815
|
ctx.logger.info(`${source}: +${result.added} ~${result.updated} -${result.deleted}`);
|
|
@@ -22582,11 +22584,11 @@ var serve = (options2, listeningListener) => {
|
|
|
22582
22584
|
|
|
22583
22585
|
// ../web/dist/context.js
|
|
22584
22586
|
import { homedir as homedir2 } from "node:os";
|
|
22585
|
-
import { join as
|
|
22587
|
+
import { join as join14 } from "node:path";
|
|
22586
22588
|
function buildWebContext(overrides = {}) {
|
|
22587
22589
|
const userHome = overrides.userHome ?? homedir2();
|
|
22588
22590
|
const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
|
|
22589
|
-
const userConfigPath =
|
|
22591
|
+
const userConfigPath = join14(userHome, ".caveatrc.json");
|
|
22590
22592
|
const logger = overrides.logger ?? stderrLogger;
|
|
22591
22593
|
const config3 = loadConfig(userConfigPath);
|
|
22592
22594
|
const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
|
|
@@ -30314,13 +30316,13 @@ function createDetailRoute(ctx) {
|
|
|
30314
30316
|
|
|
30315
30317
|
// ../web/dist/routes/community.js
|
|
30316
30318
|
import { existsSync as existsSync12, readdirSync as readdirSync8, statSync as statSync4 } from "node:fs";
|
|
30317
|
-
import { join as
|
|
30319
|
+
import { join as join15 } from "node:path";
|
|
30318
30320
|
function listCommunity(communityDir, db) {
|
|
30319
30321
|
if (!existsSync12(communityDir)) return [];
|
|
30320
30322
|
const handles = [];
|
|
30321
30323
|
for (const entry of readdirSync8(communityDir, { withFileTypes: true })) {
|
|
30322
30324
|
if (!entry.isDirectory()) continue;
|
|
30323
|
-
const handlePath =
|
|
30325
|
+
const handlePath = join15(communityDir, entry.name);
|
|
30324
30326
|
const countRow = db.prepare("SELECT COUNT(*) AS n FROM entries WHERE source = ?").get(`community/${entry.name}`);
|
|
30325
30327
|
handles.push({
|
|
30326
30328
|
handle: entry.name,
|
|
@@ -44618,11 +44620,11 @@ var StdioServerTransport = class {
|
|
|
44618
44620
|
|
|
44619
44621
|
// ../mcp/dist/context.js
|
|
44620
44622
|
import { homedir as homedir3 } from "node:os";
|
|
44621
|
-
import { join as
|
|
44623
|
+
import { join as join16 } from "node:path";
|
|
44622
44624
|
function buildMcpContext(overrides = {}) {
|
|
44623
44625
|
const userHome = overrides.userHome ?? homedir3();
|
|
44624
44626
|
const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
|
|
44625
|
-
const userConfigPath =
|
|
44627
|
+
const userConfigPath = join16(userHome, ".caveatrc.json");
|
|
44626
44628
|
const logger = overrides.logger ?? stderrLogger;
|
|
44627
44629
|
const config3 = loadConfig(userConfigPath);
|
|
44628
44630
|
const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
|
|
@@ -44681,8 +44683,7 @@ var recordInputShape = {
|
|
|
44681
44683
|
visibility: visibilitySchema.optional(),
|
|
44682
44684
|
tags: external_exports.array(external_exports.string()).optional(),
|
|
44683
44685
|
environment: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
44684
|
-
category: external_exports.string().optional().describe("Directory under entries/ (e.g., gpu, claude-code). Default: misc")
|
|
44685
|
-
brief_id: external_exports.string().optional()
|
|
44686
|
+
category: external_exports.string().optional().describe("Directory under entries/ (e.g., gpu, claude-code). Default: misc")
|
|
44686
44687
|
};
|
|
44687
44688
|
function handleRecord(ctx, args) {
|
|
44688
44689
|
return recordEntry(args, {
|
|
@@ -44729,48 +44730,6 @@ function handleListRecent(ctx, args) {
|
|
|
44729
44730
|
return listRecent(ctx.db, args.limit ?? 20);
|
|
44730
44731
|
}
|
|
44731
44732
|
|
|
44732
|
-
// ../mcp/dist/tools/nlmBriefFor.js
|
|
44733
|
-
var nlmBriefForInputShape = {
|
|
44734
|
-
topic: external_exports.string().min(1),
|
|
44735
|
-
limit: external_exports.number().int().min(1).max(50).optional()
|
|
44736
|
-
};
|
|
44737
|
-
function handleNlmBriefFor(ctx, args) {
|
|
44738
|
-
return generateBrief(ctx.db, args.topic, args.limit);
|
|
44739
|
-
}
|
|
44740
|
-
|
|
44741
|
-
// ../mcp/dist/tools/ingestResearch.js
|
|
44742
|
-
var ingestResearchInputShape = {
|
|
44743
|
-
title: external_exports.string().min(1),
|
|
44744
|
-
symptom: external_exports.string().min(1),
|
|
44745
|
-
cause: external_exports.string().optional(),
|
|
44746
|
-
resolution: external_exports.string().optional(),
|
|
44747
|
-
evidence: external_exports.array(external_exports.string()).optional(),
|
|
44748
|
-
brief_id: external_exports.string().optional(),
|
|
44749
|
-
tags: external_exports.array(external_exports.string()).optional(),
|
|
44750
|
-
category: external_exports.string().optional()
|
|
44751
|
-
};
|
|
44752
|
-
function handleIngestResearch(ctx, args) {
|
|
44753
|
-
const evidenceText = args.evidence ? args.evidence.map((line) => `- ${line}`).join("\n") : "";
|
|
44754
|
-
return recordEntry(
|
|
44755
|
-
{
|
|
44756
|
-
title: args.title,
|
|
44757
|
-
symptom: args.symptom,
|
|
44758
|
-
cause: args.cause ?? "",
|
|
44759
|
-
resolution: args.resolution ?? "",
|
|
44760
|
-
evidence: evidenceText,
|
|
44761
|
-
confidence: "tentative",
|
|
44762
|
-
outcome: "resolved",
|
|
44763
|
-
tags: args.tags,
|
|
44764
|
-
category: args.category,
|
|
44765
|
-
brief_id: args.brief_id
|
|
44766
|
-
},
|
|
44767
|
-
{
|
|
44768
|
-
db: ctx.db,
|
|
44769
|
-
entriesRoot: ctx.paths.entriesDir
|
|
44770
|
-
}
|
|
44771
|
-
);
|
|
44772
|
-
}
|
|
44773
|
-
|
|
44774
44733
|
// ../mcp/dist/tools/pull.js
|
|
44775
44734
|
var pullInputShape = {};
|
|
44776
44735
|
async function handlePull(ctx, _args = {}) {
|
|
@@ -44856,7 +44815,7 @@ function registerAllTools(server, ctx) {
|
|
|
44856
44815
|
"caveat_update",
|
|
44857
44816
|
{
|
|
44858
44817
|
title: "caveat_update",
|
|
44859
|
-
description: "Patch an existing caveat. Frontmatter shallow-merges (arrays replace). Sections matched by case-insensitive H2 heading. Immutable keys: id, created_at, source_session, source_project
|
|
44818
|
+
description: "Patch an existing caveat. Frontmatter shallow-merges (arrays replace). Sections matched by case-insensitive H2 heading. Immutable keys: id, created_at, source_session, source_project.",
|
|
44860
44819
|
inputSchema: updateInputShape
|
|
44861
44820
|
},
|
|
44862
44821
|
async (args) => jsonResult(handleUpdate(ctx, args))
|
|
@@ -44870,24 +44829,6 @@ function registerAllTools(server, ctx) {
|
|
|
44870
44829
|
},
|
|
44871
44830
|
async (args) => jsonResult(handleListRecent(ctx, args))
|
|
44872
44831
|
);
|
|
44873
|
-
server.registerTool(
|
|
44874
|
-
"nlm_brief_for",
|
|
44875
|
-
{
|
|
44876
|
-
title: "nlm_brief_for",
|
|
44877
|
-
description: "Generate a NotebookLM research brief for a topic. Returns {brief_id, text}. brief_id can be passed to ingest_research when the NLM output is ready. Stateless \u2014 brief_id is not persisted until ingest_research fires.",
|
|
44878
|
-
inputSchema: nlmBriefForInputShape
|
|
44879
|
-
},
|
|
44880
|
-
async (args) => jsonResult(handleNlmBriefFor(ctx, args))
|
|
44881
|
-
);
|
|
44882
|
-
server.registerTool(
|
|
44883
|
-
"ingest_research",
|
|
44884
|
-
{
|
|
44885
|
-
title: "ingest_research",
|
|
44886
|
-
description: "Create a caveat from NotebookLM research output. Always records with confidence: tentative. Pass brief_id to link back to the originating brief.",
|
|
44887
|
-
inputSchema: ingestResearchInputShape
|
|
44888
|
-
},
|
|
44889
|
-
async (args) => jsonResult(handleIngestResearch(ctx, args))
|
|
44890
|
-
);
|
|
44891
44832
|
server.registerTool(
|
|
44892
44833
|
"caveat_pull",
|
|
44893
44834
|
{
|
|
@@ -45121,7 +45062,7 @@ function runCommunityList(ctx) {
|
|
|
45121
45062
|
|
|
45122
45063
|
// src/index.ts
|
|
45123
45064
|
var program = new Command();
|
|
45124
|
-
program.name("caveat").description("External spec gotcha knowledge base CLI").version(
|
|
45065
|
+
program.name("caveat").description("External spec gotcha knowledge base CLI").version(CAVEAT_VERSION);
|
|
45125
45066
|
program.command("init").description(
|
|
45126
45067
|
"Initialize ~/.caveatrc.json, ~/.caveat/, subscribe to the shared community DB, and register Claude Code integration"
|
|
45127
45068
|
).option("--skip-claude", "skip Claude Code MCP + hook registration", false).option("--skip-shared", "skip subscribing to the shared community DB", false).option("--dry-run", "show planned changes without writing", false).action(async (opts) => {
|