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 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` creates `~/.caveat/`, registers the MCP server with Claude Code (via `claude mcp add --scope user`), and merges `UserPromptSubmit` / `Stop` hooks into `~/.claude/settings.json`. Existing hook entries are preserved; the settings file is backed up before any write. Run `caveat init --dry-run` to preview, or `caveat init --skip-claude` to skip Claude Code wiring.
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 against your knowledge repo
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> # shallow-clone another user's caveats repo
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
- - Optional: Claude Code installed for MCP / hooks integration. Without it, `caveat init --skip-claude` still provisions the local knowledge repo + index.
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
- communityDir: join5(resolved, "community")
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
- run("git", ["checkout", "-b", branch], { cwd: stagingDir });
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
- run("git", ["add", join7("entries", owned.relPath)], { cwd: stagingDir });
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
- run("git", ["commit", "-m", commitMsg], { cwd: stagingDir });
21303
- run("git", ["push", "--set-upstream", "origin", branch], { cwd: stagingDir });
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 = spawnSync(
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, encoding: "utf-8", shell: true }
21295
+ { cwd: stagingDir }
21330
21296
  );
21331
21297
  if (pr.status !== 0) {
21332
21298
  return {
21333
21299
  status: "failed",
21334
- detail: (typeof pr.stderr === "string" ? pr.stderr : String(pr.stderr ?? "")).trim() || "gh pr create failed"
21300
+ detail: (pr.stderr || "gh pr create failed").trim()
21335
21301
  };
21336
21302
  }
21337
- const prUrl = (typeof pr.stdout === "string" ? pr.stdout : String(pr.stdout ?? "")).trim();
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
- const r2 = spawnSync("gh", ["--version"], { encoding: "utf-8", shell: true });
21348
- return r2.status === 0;
21337
+ return runCapture("gh", ["--version"]).status === 0;
21349
21338
  }
21350
21339
  function checkGhAuthed() {
21351
- const r2 = spawnSync("gh", ["auth", "status"], { encoding: "utf-8", shell: true });
21352
- return r2.status === 0;
21340
+ return runCapture("gh", ["auth", "status"]).status === 0;
21353
21341
  }
21354
21342
  function resolveGhUser() {
21355
- const r2 = spawnSync("gh", ["api", "user", "--jq", ".login"], {
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 typeof r2.stdout === "string" ? r2.stdout.trim() : "";
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
- spawnSync(
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
- run("git", ["clone", "--depth", "30", forkUrl, stagingDir]);
21403
- run(
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
- run("git", ["checkout", "main"], { cwd: stagingDir });
21410
- run("git", ["fetch", "upstream", "main"], { cwd: stagingDir });
21411
- run("git", ["reset", "--hard", "upstream/main"], { cwd: stagingDir });
21412
- run("git", ["push", "origin", "main", "--force-with-lease"], { cwd: stagingDir });
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 join11 } from "node:path";
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 readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
21493
- import { dirname as dirname5, join as join10 } from "node:path";
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(readFileSync6(path, "utf-8"));
21512
+ return JSON.parse(readFileSync7(path, "utf-8"));
21529
21513
  }
21530
21514
  function writeSettings(path, settings) {
21531
- const dir = dirname5(path);
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 shellQuote(s) {
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(shellQuote).join(" ");
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 = join10(opts.claudeDir, "settings.json");
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 = join10(opts.claudeDir, "settings.json");
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
- const gitignorePath = join11(ctx.paths.knowledgeRepo, ".gitignore");
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: join11(ctx.userHome, ".claude"),
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 = join11(ctx.paths.communityDir, handle);
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 = join11(ctx.paths.communityDir, entry.name, "entries");
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: join11(ctx.userHome, ".claude"),
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 dirname6, join as join12 } from "node:path";
21792
+ import { dirname as dirname7, join as join13 } from "node:path";
21791
21793
  function runIndex(ctx, opts) {
21792
- const dbDir = dirname6(ctx.paths.dbPath);
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 = join12(ctx.paths.communityDir, entry.name, "entries");
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 join13 } from "node:path";
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 = join13(userHome, ".caveatrc.json");
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 join14 } from "node:path";
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 = join14(communityDir, entry.name);
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 join15 } from "node:path";
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 = join15(userHome, ".caveatrc.json");
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, brief_id.",
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("0.5.0");
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) => {