caveat-cli 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/caveat.js CHANGED
File without changes
package/dist/index.js CHANGED
@@ -16095,7 +16095,11 @@ function resolvePaths(caveatHome, knowledgeRepo, userHome) {
16095
16095
  knowledgeRepo: resolved,
16096
16096
  dbPath: join5(caveatHome, "index", "caveat.db"),
16097
16097
  entriesDir: join5(resolved, "entries"),
16098
- 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")
16099
16103
  };
16100
16104
  }
16101
16105
 
@@ -21233,6 +21237,12 @@ async function pushEntry(opts) {
21233
21237
  detail: `entry id=${opts.id} not found under ${opts.entriesDir}`
21234
21238
  };
21235
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
+ };
21245
+ }
21236
21246
  const ghUser = resolveGhUser();
21237
21247
  if (!ghUser) {
21238
21248
  return { status: "failed", detail: "gh api user failed" };
@@ -21253,16 +21263,16 @@ async function pushEntry(opts) {
21253
21263
  const stagingDir = forkStagingDir(opts.caveatHome);
21254
21264
  ensureStagingClone(stagingDir, ghUser, sharedName, sharedOwner);
21255
21265
  const branch = `caveat-push-${owned.id}-${Date.now().toString(36)}`;
21256
- run("git", ["checkout", "-b", branch], { cwd: stagingDir });
21266
+ runOrThrow("git", ["checkout", "-b", branch], { cwd: stagingDir });
21257
21267
  const destPath = join7(stagingDir, "entries", owned.relPath);
21258
21268
  mkdirSync3(dirname4(destPath), { recursive: true });
21259
21269
  copyFileSync(owned.absPath, destPath);
21260
- run("git", ["add", join7("entries", owned.relPath)], { cwd: stagingDir });
21270
+ runOrThrow("git", ["add", join7("entries", owned.relPath)], { cwd: stagingDir });
21261
21271
  const isUpdate = upstreamAlreadyHas(stagingDir, "entries/" + owned.relPath);
21262
21272
  const verb = isUpdate ? "update" : "add";
21263
21273
  const commitMsg = `${verb}: ${owned.title}`;
21264
- run("git", ["commit", "-m", commitMsg], { cwd: stagingDir });
21265
- run("git", ["push", "--set-upstream", "origin", branch], { cwd: stagingDir });
21274
+ runOrThrow("git", ["commit", "-m", commitMsg], { cwd: stagingDir });
21275
+ runOrThrow("git", ["push", "--set-upstream", "origin", branch], { cwd: stagingDir });
21266
21276
  const prTitle = `${verb}: ${owned.title}`;
21267
21277
  const prBody = [
21268
21278
  `Contribution of caveat \`${owned.id}\` via \`caveat push\`.`,
@@ -21272,7 +21282,7 @@ async function pushEntry(opts) {
21272
21282
  "",
21273
21283
  "Merged PRs will appear in subscribers' repos on their next `caveat pull`."
21274
21284
  ].join("\n");
21275
- const pr = spawnSync(
21285
+ const pr = runCapture(
21276
21286
  "gh",
21277
21287
  [
21278
21288
  "pr",
@@ -21288,16 +21298,15 @@ async function pushEntry(opts) {
21288
21298
  "--body",
21289
21299
  prBody
21290
21300
  ],
21291
- { cwd: stagingDir, encoding: "utf-8", shell: true }
21301
+ { cwd: stagingDir }
21292
21302
  );
21293
21303
  if (pr.status !== 0) {
21294
21304
  return {
21295
21305
  status: "failed",
21296
- detail: (typeof pr.stderr === "string" ? pr.stderr : String(pr.stderr ?? "")).trim() || "gh pr create failed"
21306
+ detail: (pr.stderr || "gh pr create failed").trim()
21297
21307
  };
21298
21308
  }
21299
- const prUrl = (typeof pr.stdout === "string" ? pr.stdout : String(pr.stdout ?? "")).trim();
21300
- return { status: "ok", prUrl };
21309
+ return { status: "ok", prUrl: pr.stdout.trim() };
21301
21310
  } catch (err) {
21302
21311
  return {
21303
21312
  status: "failed",
@@ -21305,21 +21314,41 @@ async function pushEntry(opts) {
21305
21314
  };
21306
21315
  }
21307
21316
  }
21317
+ function shellQuote(s) {
21318
+ if (!/[\s&|<>^()"`$!*?\[\]{}\\]/.test(s)) return s;
21319
+ return `"${s.replace(/"/g, '""')}"`;
21320
+ }
21321
+ function runCapture(command, args, opts = {}) {
21322
+ const line = [command, ...args].map(shellQuote).join(" ");
21323
+ const r2 = spawnSync(line, {
21324
+ encoding: "utf-8",
21325
+ cwd: opts.cwd,
21326
+ shell: true
21327
+ });
21328
+ return {
21329
+ status: r2.status,
21330
+ stdout: typeof r2.stdout === "string" ? r2.stdout : "",
21331
+ stderr: typeof r2.stderr === "string" ? r2.stderr : ""
21332
+ };
21333
+ }
21334
+ function runOrThrow(command, args, opts = {}) {
21335
+ const r2 = runCapture(command, args, opts);
21336
+ if (r2.status !== 0) {
21337
+ throw new Error(
21338
+ `${command} ${args.join(" ")} failed: ${(r2.stderr || r2.stdout || "unknown").trim()}`
21339
+ );
21340
+ }
21341
+ }
21308
21342
  function checkGhAvailable() {
21309
- const r2 = spawnSync("gh", ["--version"], { encoding: "utf-8", shell: true });
21310
- return r2.status === 0;
21343
+ return runCapture("gh", ["--version"]).status === 0;
21311
21344
  }
21312
21345
  function checkGhAuthed() {
21313
- const r2 = spawnSync("gh", ["auth", "status"], { encoding: "utf-8", shell: true });
21314
- return r2.status === 0;
21346
+ return runCapture("gh", ["auth", "status"]).status === 0;
21315
21347
  }
21316
21348
  function resolveGhUser() {
21317
- const r2 = spawnSync("gh", ["api", "user", "--jq", ".login"], {
21318
- encoding: "utf-8",
21319
- shell: true
21320
- });
21349
+ const r2 = runCapture("gh", ["api", "user", "--jq", ".login"]);
21321
21350
  if (r2.status !== 0) return void 0;
21322
- return typeof r2.stdout === "string" ? r2.stdout.trim() : "";
21351
+ return r2.stdout.trim() || void 0;
21323
21352
  }
21324
21353
  function parseOwnerRepo(url) {
21325
21354
  const m = /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(\.git)?\/?$/.exec(url);
@@ -21339,7 +21368,13 @@ function findOwnedEntry(entriesDir, id) {
21339
21368
  const parsed = parseMarkdown(raw2);
21340
21369
  if (parsed.frontmatter.id === id) {
21341
21370
  const relPath = relative2(entriesDir, absPath).replace(/\\/g, "/");
21342
- return { id, title: parsed.frontmatter.title, absPath, relPath };
21371
+ return {
21372
+ id,
21373
+ title: parsed.frontmatter.title,
21374
+ absPath,
21375
+ relPath,
21376
+ visibility: parsed.frontmatter.visibility
21377
+ };
21343
21378
  }
21344
21379
  } catch {
21345
21380
  }
@@ -21351,44 +21386,28 @@ function forkStagingDir(caveatHome) {
21351
21386
  return join7(caveatHome, "push-fork");
21352
21387
  }
21353
21388
  function ensureFork(owner, repo) {
21354
- spawnSync(
21355
- "gh",
21356
- ["repo", "fork", `${owner}/${repo}`, "--clone=false", "--remote=false"],
21357
- { encoding: "utf-8", shell: true }
21358
- );
21389
+ runCapture("gh", ["repo", "fork", `${owner}/${repo}`, "--clone=false", "--remote=false"]);
21359
21390
  }
21360
21391
  function ensureStagingClone(stagingDir, ghUser, sharedName, upstreamOwner) {
21361
21392
  if (!existsSync7(stagingDir)) {
21362
21393
  mkdirSync3(dirname4(stagingDir), { recursive: true });
21363
21394
  const forkUrl = `https://github.com/${ghUser}/${sharedName}.git`;
21364
- run("git", ["clone", "--depth", "30", forkUrl, stagingDir]);
21365
- run(
21395
+ runOrThrow("git", ["clone", "--depth", "30", forkUrl, stagingDir]);
21396
+ runOrThrow(
21366
21397
  "git",
21367
21398
  ["remote", "add", "upstream", `https://github.com/${upstreamOwner}/${sharedName}.git`],
21368
21399
  { cwd: stagingDir }
21369
21400
  );
21370
21401
  } else {
21371
- run("git", ["checkout", "main"], { cwd: stagingDir });
21372
- run("git", ["fetch", "upstream", "main"], { cwd: stagingDir });
21373
- run("git", ["reset", "--hard", "upstream/main"], { cwd: stagingDir });
21374
- run("git", ["push", "origin", "main", "--force-with-lease"], { cwd: stagingDir });
21402
+ runOrThrow("git", ["checkout", "main"], { cwd: stagingDir });
21403
+ runOrThrow("git", ["fetch", "upstream", "main"], { cwd: stagingDir });
21404
+ runOrThrow("git", ["reset", "--hard", "upstream/main"], { cwd: stagingDir });
21405
+ runOrThrow("git", ["push", "origin", "main", "--force-with-lease"], { cwd: stagingDir });
21375
21406
  }
21376
21407
  }
21377
21408
  function upstreamAlreadyHas(stagingDir, relFromRoot) {
21378
21409
  return existsSync7(join7(stagingDir, relFromRoot));
21379
21410
  }
21380
- function run(command, args, opts = {}) {
21381
- const r2 = spawnSync(command, args, {
21382
- encoding: "utf-8",
21383
- cwd: opts.cwd,
21384
- shell: true
21385
- });
21386
- if (r2.status !== 0) {
21387
- const stderr = typeof r2.stderr === "string" ? r2.stderr : String(r2.stderr ?? "");
21388
- const stdout = typeof r2.stdout === "string" ? r2.stdout : String(r2.stdout ?? "");
21389
- throw new Error(`${command} ${args.join(" ")} failed: ${(stderr || stdout || "unknown").trim()}`);
21390
- }
21391
- }
21392
21411
 
21393
21412
  // ../../packages/core/dist/pullShared.js
21394
21413
  import { existsSync as existsSync8, readdirSync as readdirSync5 } from "node:fs";
@@ -21435,6 +21454,21 @@ function buildContext(logger, overrides = {}) {
21435
21454
  return { caveatHome, userHome, userConfigPath, config: config3, paths, logger };
21436
21455
  }
21437
21456
 
21457
+ // src/version.ts
21458
+ import { readFileSync as readFileSync6 } from "node:fs";
21459
+ import { dirname as dirname5, join as join10 } from "node:path";
21460
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
21461
+ function resolveVersion() {
21462
+ try {
21463
+ const here2 = dirname5(fileURLToPath3(import.meta.url));
21464
+ const pkg = JSON.parse(readFileSync6(join10(here2, "..", "package.json"), "utf-8"));
21465
+ return typeof pkg.version === "string" ? pkg.version : "0.0.0";
21466
+ } catch {
21467
+ return "0.0.0";
21468
+ }
21469
+ }
21470
+ var CAVEAT_VERSION = resolveVersion();
21471
+
21438
21472
  // src/logger.ts
21439
21473
  var stdoutLogger = {
21440
21474
  info: (m) => process.stdout.write(`[caveat] ${m}
@@ -21446,13 +21480,13 @@ var stdoutLogger = {
21446
21480
  };
21447
21481
 
21448
21482
  // src/commands/init.ts
21449
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
21450
- import { join as join11 } from "node:path";
21483
+ import { existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync6, renameSync, rmdirSync, writeFileSync as writeFileSync5 } from "node:fs";
21484
+ import { join as join12 } from "node:path";
21451
21485
 
21452
21486
  // src/claudeInstall.ts
21453
21487
  import { spawnSync as spawnSync2 } from "node:child_process";
21454
- import { copyFileSync as copyFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
21455
- import { dirname as dirname5, join as join10 } from "node:path";
21488
+ import { copyFileSync as copyFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
21489
+ import { dirname as dirname6, join as join11 } from "node:path";
21456
21490
  var EVENT_USER_PROMPT_SUBMIT = "UserPromptSubmit";
21457
21491
  var EVENT_STOP = "Stop";
21458
21492
  function quote(p2) {
@@ -21487,10 +21521,10 @@ function removeHook(settings, event, command) {
21487
21521
  }
21488
21522
  function readSettings(path) {
21489
21523
  if (!existsSync9(path)) return {};
21490
- return JSON.parse(readFileSync6(path, "utf-8"));
21524
+ return JSON.parse(readFileSync7(path, "utf-8"));
21491
21525
  }
21492
21526
  function writeSettings(path, settings) {
21493
- const dir = dirname5(path);
21527
+ const dir = dirname6(path);
21494
21528
  if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
21495
21529
  let backupPath = "";
21496
21530
  if (existsSync9(path)) {
@@ -21501,11 +21535,11 @@ function writeSettings(path, settings) {
21501
21535
  return backupPath;
21502
21536
  }
21503
21537
  var CLAUDE_BIN = "claude";
21504
- function shellQuote(s) {
21538
+ function shellQuote2(s) {
21505
21539
  return /[\s&|<>^()]/.test(s) ? `"${s}"` : s;
21506
21540
  }
21507
21541
  function runClaude(args) {
21508
- const line = [CLAUDE_BIN, ...args].map(shellQuote).join(" ");
21542
+ const line = [CLAUDE_BIN, ...args].map(shellQuote2).join(" ");
21509
21543
  return spawnSync2(line, { shell: true, encoding: "utf-8" });
21510
21544
  }
21511
21545
  function registerMcp(nodePath, cliScriptPath, dryRun, logger) {
@@ -21556,7 +21590,7 @@ function unregisterMcp(dryRun, logger) {
21556
21590
  return { action: "skipped", detail: "not registered or removal failed" };
21557
21591
  }
21558
21592
  function installClaudeIntegration(opts) {
21559
- const settingsPath = join10(opts.claudeDir, "settings.json");
21593
+ const settingsPath = join11(opts.claudeDir, "settings.json");
21560
21594
  const settings = readSettings(settingsPath);
21561
21595
  const usCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "user-prompt-submit");
21562
21596
  const stopCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "stop");
@@ -21575,7 +21609,7 @@ function installClaudeIntegration(opts) {
21575
21609
  return { mcp, hooks: { userPromptSubmit, stop }, backupPath };
21576
21610
  }
21577
21611
  function uninstallClaudeIntegration(opts) {
21578
- const settingsPath = join10(opts.claudeDir, "settings.json");
21612
+ const settingsPath = join11(opts.claudeDir, "settings.json");
21579
21613
  const settings = readSettings(settingsPath);
21580
21614
  const usCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "user-prompt-submit");
21581
21615
  const stopCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "stop");
@@ -21605,11 +21639,6 @@ var KNOWLEDGE_GITIGNORE = [
21605
21639
  "",
21606
21640
  "# Obsidian per-user config: workspace layout, theme, plugin state, cache.",
21607
21641
  ".obsidian/",
21608
- "",
21609
- "# community/ is a local cache of shallow-cloned third-party caveat repos",
21610
- "# (populated by `caveat community add`). Each contains its own .git and is not",
21611
- "# part of this repo's tracked knowledge.",
21612
- "community/",
21613
21642
  ""
21614
21643
  ].join("\n");
21615
21644
  async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRun: false }) {
@@ -21622,7 +21651,8 @@ async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRu
21622
21651
  } else {
21623
21652
  ctx.logger.info(`knowledge repo: ${ctx.paths.knowledgeRepo}`);
21624
21653
  }
21625
- const gitignorePath = join11(ctx.paths.knowledgeRepo, ".gitignore");
21654
+ migrateLegacyCommunityDir(ctx);
21655
+ const gitignorePath = join12(ctx.paths.knowledgeRepo, ".gitignore");
21626
21656
  if (!existsSync10(gitignorePath)) {
21627
21657
  writeFileSync5(gitignorePath, KNOWLEDGE_GITIGNORE, "utf-8");
21628
21658
  ctx.logger.info(`.gitignore created: ${gitignorePath}`);
@@ -21650,7 +21680,7 @@ async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRu
21650
21680
  return;
21651
21681
  }
21652
21682
  const result = installClaudeIntegration({
21653
- claudeDir: join11(ctx.userHome, ".claude"),
21683
+ claudeDir: join12(ctx.userHome, ".claude"),
21654
21684
  cliScriptPath,
21655
21685
  nodePath: process.execPath,
21656
21686
  dryRun: opts.dryRun,
@@ -21658,6 +21688,28 @@ async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRu
21658
21688
  });
21659
21689
  reportInstallResult(ctx, result, opts.dryRun);
21660
21690
  }
21691
+ function migrateLegacyCommunityDir(ctx) {
21692
+ const legacy = join12(ctx.paths.knowledgeRepo, "community");
21693
+ const current = ctx.paths.communityDir;
21694
+ if (legacy === current) return;
21695
+ if (!existsSync10(legacy)) return;
21696
+ if (existsSync10(current)) {
21697
+ ctx.logger.warn(
21698
+ `legacy community dir still exists at ${legacy} \u2014 remove manually (new location in use)`
21699
+ );
21700
+ return;
21701
+ }
21702
+ mkdirSync5(current, { recursive: true });
21703
+ for (const entry of readdirSync6(legacy, { withFileTypes: true })) {
21704
+ if (!entry.isDirectory()) continue;
21705
+ renameSync(join12(legacy, entry.name), join12(current, entry.name));
21706
+ }
21707
+ try {
21708
+ rmdirSync(legacy);
21709
+ } catch {
21710
+ }
21711
+ ctx.logger.info(`migrated legacy community/ \u2192 ${current}`);
21712
+ }
21661
21713
  async function subscribeSharedRepo(ctx, db) {
21662
21714
  const url = ctx.config.sharedRepo;
21663
21715
  const validation = validateCommunityUrl(url);
@@ -21668,7 +21720,7 @@ async function subscribeSharedRepo(ctx, db) {
21668
21720
  return;
21669
21721
  }
21670
21722
  const handle = validation.handle;
21671
- const target = join11(ctx.paths.communityDir, handle);
21723
+ const target = join12(ctx.paths.communityDir, handle);
21672
21724
  if (!existsSync10(target)) {
21673
21725
  if (!existsSync10(ctx.paths.communityDir)) {
21674
21726
  mkdirSync5(ctx.paths.communityDir, { recursive: true });
@@ -21697,7 +21749,7 @@ async function subscribeSharedRepo(ctx, db) {
21697
21749
  for (const entry of readdirSync6(ctx.paths.communityDir, { withFileTypes: true })) {
21698
21750
  if (!entry.isDirectory()) continue;
21699
21751
  const source = `community/${entry.name}`;
21700
- const root = join11(ctx.paths.communityDir, entry.name, "entries");
21752
+ const root = join12(ctx.paths.communityDir, entry.name, "entries");
21701
21753
  if (!existsSync10(root)) continue;
21702
21754
  const result = scanSource({ db, source, entriesRoot: root });
21703
21755
  ctx.logger.info(`indexed ${source}: +${result.added}`);
@@ -21711,7 +21763,7 @@ function runUninstall(ctx, opts) {
21711
21763
  process.exit(1);
21712
21764
  }
21713
21765
  const result = uninstallClaudeIntegration({
21714
- claudeDir: join11(ctx.userHome, ".claude"),
21766
+ claudeDir: join12(ctx.userHome, ".claude"),
21715
21767
  cliScriptPath,
21716
21768
  nodePath: process.execPath,
21717
21769
  dryRun: opts.dryRun,
@@ -21749,9 +21801,9 @@ function reportInstallResult(ctx, result, dryRun) {
21749
21801
 
21750
21802
  // src/commands/indexCmd.ts
21751
21803
  import { existsSync as existsSync11, readdirSync as readdirSync7, mkdirSync as mkdirSync6 } from "node:fs";
21752
- import { dirname as dirname6, join as join12 } from "node:path";
21804
+ import { dirname as dirname7, join as join13 } from "node:path";
21753
21805
  function runIndex(ctx, opts) {
21754
- const dbDir = dirname6(ctx.paths.dbPath);
21806
+ const dbDir = dirname7(ctx.paths.dbPath);
21755
21807
  if (!existsSync11(dbDir)) mkdirSync6(dbDir, { recursive: true });
21756
21808
  const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
21757
21809
  try {
@@ -21769,7 +21821,7 @@ function runIndex(ctx, opts) {
21769
21821
  for (const entry of readdirSync7(ctx.paths.communityDir, { withFileTypes: true })) {
21770
21822
  if (!entry.isDirectory()) continue;
21771
21823
  const source = `community/${entry.name}`;
21772
- const root = join12(ctx.paths.communityDir, entry.name, "entries");
21824
+ const root = join13(ctx.paths.communityDir, entry.name, "entries");
21773
21825
  if (!existsSync11(root)) continue;
21774
21826
  const result = scanSource({ db, source, entriesRoot: root });
21775
21827
  ctx.logger.info(`${source}: +${result.added} ~${result.updated} -${result.deleted}`);
@@ -22544,11 +22596,11 @@ var serve = (options2, listeningListener) => {
22544
22596
 
22545
22597
  // ../web/dist/context.js
22546
22598
  import { homedir as homedir2 } from "node:os";
22547
- import { join as join13 } from "node:path";
22599
+ import { join as join14 } from "node:path";
22548
22600
  function buildWebContext(overrides = {}) {
22549
22601
  const userHome = overrides.userHome ?? homedir2();
22550
22602
  const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
22551
- const userConfigPath = join13(userHome, ".caveatrc.json");
22603
+ const userConfigPath = join14(userHome, ".caveatrc.json");
22552
22604
  const logger = overrides.logger ?? stderrLogger;
22553
22605
  const config3 = loadConfig(userConfigPath);
22554
22606
  const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
@@ -30276,13 +30328,13 @@ function createDetailRoute(ctx) {
30276
30328
 
30277
30329
  // ../web/dist/routes/community.js
30278
30330
  import { existsSync as existsSync12, readdirSync as readdirSync8, statSync as statSync4 } from "node:fs";
30279
- import { join as join14 } from "node:path";
30331
+ import { join as join15 } from "node:path";
30280
30332
  function listCommunity(communityDir, db) {
30281
30333
  if (!existsSync12(communityDir)) return [];
30282
30334
  const handles = [];
30283
30335
  for (const entry of readdirSync8(communityDir, { withFileTypes: true })) {
30284
30336
  if (!entry.isDirectory()) continue;
30285
- const handlePath = join14(communityDir, entry.name);
30337
+ const handlePath = join15(communityDir, entry.name);
30286
30338
  const countRow = db.prepare("SELECT COUNT(*) AS n FROM entries WHERE source = ?").get(`community/${entry.name}`);
30287
30339
  handles.push({
30288
30340
  handle: entry.name,
@@ -44580,11 +44632,11 @@ var StdioServerTransport = class {
44580
44632
 
44581
44633
  // ../mcp/dist/context.js
44582
44634
  import { homedir as homedir3 } from "node:os";
44583
- import { join as join15 } from "node:path";
44635
+ import { join as join16 } from "node:path";
44584
44636
  function buildMcpContext(overrides = {}) {
44585
44637
  const userHome = overrides.userHome ?? homedir3();
44586
44638
  const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
44587
- const userConfigPath = join15(userHome, ".caveatrc.json");
44639
+ const userConfigPath = join16(userHome, ".caveatrc.json");
44588
44640
  const logger = overrides.logger ?? stderrLogger;
44589
44641
  const config3 = loadConfig(userConfigPath);
44590
44642
  const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
@@ -44640,7 +44692,9 @@ var recordInputShape = {
44640
44692
  context: external_exports.string().optional(),
44641
44693
  confidence: confidenceSchema2.optional(),
44642
44694
  outcome: outcomeSchema.optional(),
44643
- visibility: visibilitySchema.optional(),
44695
+ visibility: visibilitySchema.describe(
44696
+ 'REQUIRED. Ask the user "public (share to community) or private (keep local only)?" before calling \u2014 never auto-classify. The user owns the knowledge; they decide its reach.'
44697
+ ),
44644
44698
  tags: external_exports.array(external_exports.string()).optional(),
44645
44699
  environment: external_exports.record(external_exports.string(), external_exports.string()).optional(),
44646
44700
  category: external_exports.string().optional().describe("Directory under entries/ (e.g., gpu, claude-code). Default: misc")
@@ -44748,7 +44802,7 @@ function registerAllTools(server, ctx) {
44748
44802
  "caveat_search",
44749
44803
  {
44750
44804
  title: "caveat_search",
44751
- description: "Full-text search across caveats (external spec gotchas). Returns summary; use caveat_get for body.",
44805
+ description: 'Search the "caveats" knowledge base \u2014 records of time-wasting traps in EXTERNAL specs (GPU/driver/CUDA versions, native-module builds, IDE/shell quirks, platform-specific behavior, library version incompatibilities) that someone already diagnosed. Call this FIRST when a problem smells environmental rather than a logic bug \u2014 before reading stack traces top-to-bottom or trying fixes. Query: 3+ chars, plain tokens only (no OR/NEAR/other FTS5 operators). Returns summary rows including `{id, source}` \u2014 pass BOTH to caveat_get for the full body.',
44752
44806
  inputSchema: searchInputShape
44753
44807
  },
44754
44808
  async (args) => jsonResult(handleSearch(ctx, args))
@@ -44757,7 +44811,7 @@ function registerAllTools(server, ctx) {
44757
44811
  "caveat_get",
44758
44812
  {
44759
44813
  title: "caveat_get",
44760
- description: "Fetch full caveat by id (frontmatter + sections + body).",
44814
+ description: 'Fetch the full body (frontmatter + H2 sections + text) of a caveat by id. IMPORTANT: when the id came from caveat_search, you MUST pass the `source` field from that same result (e.g., "community/Caveat"). The default source is "own" only; omitting source for a community entry returns not-found.',
44761
44815
  inputSchema: getInputShape
44762
44816
  },
44763
44817
  async (args) => jsonResult(handleGet(ctx, args))
@@ -44766,7 +44820,7 @@ function registerAllTools(server, ctx) {
44766
44820
  "caveat_record",
44767
44821
  {
44768
44822
  title: "caveat_record",
44769
- description: "Create a new caveat markdown file. Auto-fills source_session and environment fingerprint for unspecified keys. source_project is left null by design (publicly-shared knowledge should not leak per-user project names).",
44823
+ description: "Create a new caveat: a record of an external-spec trap that wasted real time (wrong driver, version mismatch, platform bug, IDE quirk, native-module issue, etc.) so future sessions can find it via caveat_search. REQUIRED BEFORE CALLING: (1) run caveat_search first to avoid duplicates, (2) ASK THE USER whether this should be `public` (shareable to the community DB) or `private` (kept local only) \u2014 never auto-classify visibility; the user owns the knowledge and decides its reach. Qualifies: specific symptom + diagnosed cause (or `outcome: impossible` verdict) + environment fingerprint. Does NOT qualify: project-internal bugs, user preferences, session summaries, ephemeral task notes. Auto-fills source_session and environment defaults; source_project is left null by design (shared knowledge must not leak per-user project names).",
44770
44824
  inputSchema: recordInputShape
44771
44825
  },
44772
44826
  async (args) => jsonResult(handleRecord(ctx, args))
@@ -44775,7 +44829,7 @@ function registerAllTools(server, ctx) {
44775
44829
  "caveat_update",
44776
44830
  {
44777
44831
  title: "caveat_update",
44778
- 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.",
44832
+ description: "Patch an existing caveat \u2014 use when newer evidence extends or corrects one that already exists. Frontmatter shallow-merges, but array fields (tags etc.) REPLACE rather than append \u2014 to add one tag, read the current list first, then patch with the full new array. Sections match by case-insensitive H2 heading. Immutable keys: id, created_at, source_session, source_project. Common uses: bump `last_verified` after re-confirming, add a resolution when it was `tentative`, flip `outcome` to `impossible`.",
44779
44833
  inputSchema: updateInputShape
44780
44834
  },
44781
44835
  async (args) => jsonResult(handleUpdate(ctx, args))
@@ -44784,7 +44838,7 @@ function registerAllTools(server, ctx) {
44784
44838
  "caveat_list_recent",
44785
44839
  {
44786
44840
  title: "caveat_list_recent",
44787
- description: "List caveats by updated_at DESC.",
44841
+ description: "List caveats ordered by updated_at DESC. Use for browsing recent additions \u2014 e.g., showing the user what is new after caveat_pull. Not for search; use caveat_search when you have a query.",
44788
44842
  inputSchema: listRecentInputShape
44789
44843
  },
44790
44844
  async (args) => jsonResult(handleListRecent(ctx, args))
@@ -44793,7 +44847,7 @@ function registerAllTools(server, ctx) {
44793
44847
  "caveat_pull",
44794
44848
  {
44795
44849
  title: "caveat_pull",
44796
- description: "git-pull all subscribed community caveat repos (incl. the shared DB) and re-index. Call this when the user asks if others have documented a similar trap, or at the start of a session that might benefit from fresh external knowledge. Safe and idempotent \u2014 re-running is cheap.",
44850
+ description: "git-pull the subscribed community caveat repos (the shared knowledge DB + any user-added remotes) 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 the DB 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.",
44797
44851
  inputSchema: pullInputShape
44798
44852
  },
44799
44853
  async (args) => jsonResult(await handlePull(ctx, args))
@@ -44802,7 +44856,7 @@ function registerAllTools(server, ctx) {
44802
44856
  "caveat_push",
44803
44857
  {
44804
44858
  title: "caveat_push",
44805
- description: "Contribute a user-owned caveat to the shared community DB via fork + PR. Call this after caveat_record when the entry looks genuinely reusable by others (not a one-off project tie-in, not duplicated by existing community entries). Requires the `gh` CLI on the user's machine; returns status=gh-missing or gh-unauthed when unavailable. Use dry_run=true to preview without touching GitHub.",
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.",
44806
44860
  inputSchema: pushInputShape
44807
44861
  },
44808
44862
  async (args) => jsonResult(await handlePush(ctx, args))
@@ -45022,7 +45076,7 @@ function runCommunityList(ctx) {
45022
45076
 
45023
45077
  // src/index.ts
45024
45078
  var program = new Command();
45025
- program.name("caveat").description("External spec gotcha knowledge base CLI").version("0.6.0");
45079
+ program.name("caveat").description("External spec gotcha knowledge base CLI").version(CAVEAT_VERSION);
45026
45080
  program.command("init").description(
45027
45081
  "Initialize ~/.caveatrc.json, ~/.caveat/, subscribe to the shared community DB, and register Claude Code integration"
45028
45082
  ).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) => {