caveat-cli 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -9761,49 +9761,49 @@ var require_fast_uri = __commonJS({
9761
9761
  schemelessOptions.skipEscape = true;
9762
9762
  return serialize(resolved, schemelessOptions);
9763
9763
  }
9764
- function resolveComponent(base2, relative3, options2, skipNormalization) {
9764
+ function resolveComponent(base2, relative2, options2, skipNormalization) {
9765
9765
  const target = {};
9766
9766
  if (!skipNormalization) {
9767
9767
  base2 = parse4(serialize(base2, options2), options2);
9768
- relative3 = parse4(serialize(relative3, options2), options2);
9768
+ relative2 = parse4(serialize(relative2, options2), options2);
9769
9769
  }
9770
9770
  options2 = options2 || {};
9771
- if (!options2.tolerant && relative3.scheme) {
9772
- target.scheme = relative3.scheme;
9773
- target.userinfo = relative3.userinfo;
9774
- target.host = relative3.host;
9775
- target.port = relative3.port;
9776
- target.path = removeDotSegments(relative3.path || "");
9777
- target.query = relative3.query;
9771
+ if (!options2.tolerant && relative2.scheme) {
9772
+ target.scheme = relative2.scheme;
9773
+ target.userinfo = relative2.userinfo;
9774
+ target.host = relative2.host;
9775
+ target.port = relative2.port;
9776
+ target.path = removeDotSegments(relative2.path || "");
9777
+ target.query = relative2.query;
9778
9778
  } else {
9779
- if (relative3.userinfo !== void 0 || relative3.host !== void 0 || relative3.port !== void 0) {
9780
- target.userinfo = relative3.userinfo;
9781
- target.host = relative3.host;
9782
- target.port = relative3.port;
9783
- target.path = removeDotSegments(relative3.path || "");
9784
- target.query = relative3.query;
9779
+ if (relative2.userinfo !== void 0 || relative2.host !== void 0 || relative2.port !== void 0) {
9780
+ target.userinfo = relative2.userinfo;
9781
+ target.host = relative2.host;
9782
+ target.port = relative2.port;
9783
+ target.path = removeDotSegments(relative2.path || "");
9784
+ target.query = relative2.query;
9785
9785
  } else {
9786
- if (!relative3.path) {
9786
+ if (!relative2.path) {
9787
9787
  target.path = base2.path;
9788
- if (relative3.query !== void 0) {
9789
- target.query = relative3.query;
9788
+ if (relative2.query !== void 0) {
9789
+ target.query = relative2.query;
9790
9790
  } else {
9791
9791
  target.query = base2.query;
9792
9792
  }
9793
9793
  } else {
9794
- if (relative3.path[0] === "/") {
9795
- target.path = removeDotSegments(relative3.path);
9794
+ if (relative2.path[0] === "/") {
9795
+ target.path = removeDotSegments(relative2.path);
9796
9796
  } else {
9797
9797
  if ((base2.userinfo !== void 0 || base2.host !== void 0 || base2.port !== void 0) && !base2.path) {
9798
- target.path = "/" + relative3.path;
9798
+ target.path = "/" + relative2.path;
9799
9799
  } else if (!base2.path) {
9800
- target.path = relative3.path;
9800
+ target.path = relative2.path;
9801
9801
  } else {
9802
- target.path = base2.path.slice(0, base2.path.lastIndexOf("/") + 1) + relative3.path;
9802
+ target.path = base2.path.slice(0, base2.path.lastIndexOf("/") + 1) + relative2.path;
9803
9803
  }
9804
9804
  target.path = removeDotSegments(target.path);
9805
9805
  }
9806
- target.query = relative3.query;
9806
+ target.query = relative2.query;
9807
9807
  }
9808
9808
  target.userinfo = base2.userinfo;
9809
9809
  target.host = base2.host;
@@ -9811,7 +9811,7 @@ var require_fast_uri = __commonJS({
9811
9811
  }
9812
9812
  target.scheme = base2.scheme;
9813
9813
  }
9814
- target.fragment = relative3.fragment;
9814
+ target.fragment = relative2.fragment;
9815
9815
  return target;
9816
9816
  }
9817
9817
  function equal(uriA, uriB, options2) {
@@ -12976,7 +12976,7 @@ import { Command } from "commander";
12976
12976
 
12977
12977
  // src/context.ts
12978
12978
  import { homedir } from "node:os";
12979
- import { join as join9 } from "node:path";
12979
+ import { join as join7 } from "node:path";
12980
12980
 
12981
12981
  // ../../packages/core/dist/db.js
12982
12982
  import { DatabaseSync } from "node:sqlite";
@@ -15819,7 +15819,14 @@ function search(db, opts = {}) {
15819
15819
  conditions.push(`e.confidence IN (${placeholders})`);
15820
15820
  params.push(...filters.confidence);
15821
15821
  }
15822
+ if (filters.visibility === "public" || filters.visibility === "private") {
15823
+ conditions.push(`e.visibility = ?`);
15824
+ params.push(filters.visibility);
15825
+ }
15822
15826
  if (conditions.length) sql += " AND " + conditions.join(" AND ");
15827
+ if (!ftsQuery) {
15828
+ sql += ` ORDER BY json_extract(e.frontmatter_json, '$.updated_at') DESC`;
15829
+ }
15823
15830
  sql += " LIMIT ?";
15824
15831
  params.push(limit);
15825
15832
  const rows = db.prepare(sql).all(...params);
@@ -15864,6 +15871,7 @@ function toSearchResult(row) {
15864
15871
  title: row.title,
15865
15872
  symptomExcerpt: symptom.slice(0, SYMPTOM_EXCERPT_LENGTH),
15866
15873
  confidence: row.confidence,
15874
+ visibility: row.visibility ?? "public",
15867
15875
  environment: fm.environment ?? {}
15868
15876
  };
15869
15877
  }
@@ -16105,12 +16113,10 @@ function resolvePaths(caveatHome, knowledgeRepo, userHome) {
16105
16113
 
16106
16114
  // ../../packages/core/dist/config.js
16107
16115
  import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
16108
- var SHARED_REPO_URL = "https://github.com/kitepon-rgb/Caveat";
16109
16116
  var DEFAULT_CONFIG = {
16110
16117
  knowledgeRepo: "own",
16111
16118
  semverKeys: ["driver", "cuda", "node"],
16112
- communitySources: [],
16113
- sharedRepo: SHARED_REPO_URL
16119
+ communitySources: []
16114
16120
  };
16115
16121
  function loadConfig(userConfigPath) {
16116
16122
  const userCfg = existsSync5(userConfigPath) ? JSON.parse(readFileSync4(userConfigPath, "utf-8")) : {};
@@ -16134,7 +16140,7 @@ function deepMerge(base2, overlay) {
16134
16140
  }
16135
16141
 
16136
16142
  // ../../packages/core/dist/community.js
16137
- import { existsSync as existsSync6, readdirSync as readdirSync3 } from "node:fs";
16143
+ import { existsSync as existsSync6, readdirSync as readdirSync3, rmSync, statSync as statSync4 } from "node:fs";
16138
16144
  import { join as join6 } from "node:path";
16139
16145
 
16140
16146
  // ../../node_modules/.pnpm/simple-git@3.36.0/node_modules/simple-git/dist/esm/index.js
@@ -21164,6 +21170,37 @@ function communityList(opts) {
21164
21170
  }
21165
21171
  return out;
21166
21172
  }
21173
+ function communityRemove(opts) {
21174
+ const handle = (opts.handle ?? "").trim();
21175
+ if (!handle) {
21176
+ throw new Error("handle is required");
21177
+ }
21178
+ if (handle === "." || handle === ".." || /[\\/]/.test(handle)) {
21179
+ throw new Error(`invalid handle (no path separators or relative segments): ${opts.handle}`);
21180
+ }
21181
+ const target = join6(opts.communityDir, handle);
21182
+ const dirExisted = existsSync6(target) && statSync4(target).isDirectory();
21183
+ const source = `community/${handle}`;
21184
+ const row = opts.db.prepare("SELECT COUNT(*) AS n FROM entries WHERE source = ?").get(source);
21185
+ const rowCount = row?.n ?? 0;
21186
+ if (opts.dryRun) {
21187
+ return { handle, path: target, dirExisted, rowCount, removed: false, dryRun: true };
21188
+ }
21189
+ if (dirExisted) {
21190
+ rmSync(target, { recursive: true, force: true });
21191
+ }
21192
+ if (rowCount > 0) {
21193
+ opts.db.prepare("DELETE FROM entries WHERE source = ?").run(source);
21194
+ }
21195
+ return {
21196
+ handle,
21197
+ path: target,
21198
+ dirExisted,
21199
+ rowCount,
21200
+ removed: dirExisted || rowCount > 0,
21201
+ dryRun: false
21202
+ };
21203
+ }
21167
21204
 
21168
21205
  // ../../packages/core/dist/claudeHooks.js
21169
21206
  var CAVEAT_TRIGGERS = [
@@ -21194,262 +21231,28 @@ function stopReminderText() {
21194
21231
  return [
21195
21232
  "[caveat] \u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u3067\u5916\u90E8\u4ED5\u69D8\u306E\u7F60\uFF08GPU/\u30C9\u30E9\u30A4\u30D0/\u30C4\u30FC\u30EB\u30D0\u30FC\u30B8\u30E7\u30F3/IDE/\u518D\u73FE\u6027\uFF09\u3092\u8E0F\u307F\u3001\u6B21\u56DE\u306E\u81EA\u5206\u306E\u8ABF\u67FB\u304C\u77ED\u7E2E\u3067\u304D\u305D\u3046\u306A\u3089 mcp__caveat__caveat_record \u3067\u767B\u9332\u3092\u691C\u8A0E\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
21196
21233
  "\u89E3\u6C7A\u3057\u305F\u30B1\u30FC\u30B9\u3060\u3051\u3067\u306A\u304F\u3001\u300C\u73FE\u72B6\u306E\u5236\u7D04\u3067\u306F\u4E0D\u53EF\u80FD\u3068\u5224\u5B9A\u3057\u305F\u300D\u7D50\u8AD6\u3082 outcome: impossible \u3068\u3057\u3066\u8A18\u9332\u5BFE\u8C61\u3002\u6B21\u56DE\u540C\u3058 3 \u6642\u9593\u3092\u6EB6\u304B\u3055\u306A\u3044\u305F\u3081\u306B\u3002",
21197
- "\u65E2\u306B\u540C\u7B49\u306E caveat \u304C\u3042\u308B\u5834\u5408\u306F caveat_update \u3067\u4E0A\u66F8\u304D\u3001\u307E\u305F\u306F last_verified \u3060\u3051\u66F4\u65B0\u3002",
21198
- "\u8A18\u9332\u3057\u305F\u7F60\u304C\u4ED6\u306E\u30E6\u30FC\u30B6\u306B\u3082\u518D\u5229\u7528\u4FA1\u5024\u304C\u3042\u308B\u306A\u3089 mcp__caveat__caveat_push \u3067\u5171\u6709 DB \u306B PR \u3092\u6295\u3052\u308B\u3053\u3068\u3082\u691C\u8A0E\uFF08\u500B\u4EBA\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u56FA\u6709\u306E\u7269\u306F push \u3057\u306A\u3044\uFF09\u3002"
21234
+ "\u65E2\u306B\u540C\u7B49\u306E caveat \u304C\u3042\u308B\u5834\u5408\u306F caveat_update \u3067\u4E0A\u66F8\u304D\u3001\u307E\u305F\u306F last_verified \u3060\u3051\u66F4\u65B0\u3002"
21199
21235
  ].join("\n");
21200
21236
  }
21201
21237
 
21202
- // ../../packages/core/dist/push.js
21203
- import { spawnSync } from "node:child_process";
21204
- import {
21205
- copyFileSync,
21206
- existsSync as existsSync7,
21207
- mkdirSync as mkdirSync3,
21208
- readdirSync as readdirSync4,
21209
- readFileSync as readFileSync5
21210
- } from "node:fs";
21211
- import { dirname as dirname4, join as join7, relative as relative2 } from "node:path";
21212
- async function pushEntry(opts) {
21213
- if (!checkGhAvailable()) {
21214
- return {
21215
- status: "gh-missing",
21216
- detail: "gh (GitHub CLI) is required. Install: https://cli.github.com/"
21217
- };
21218
- }
21219
- if (!checkGhAuthed()) {
21220
- return {
21221
- status: "gh-unauthed",
21222
- detail: "gh is not authenticated. Run `gh auth login`."
21223
- };
21224
- }
21225
- const validation = validateCommunityUrl(opts.sharedRepoUrl);
21226
- if (!validation.valid) {
21227
- return { status: "failed", detail: `sharedRepo URL invalid: ${validation.reason}` };
21228
- }
21229
- const [sharedOwner, sharedName] = parseOwnerRepo(opts.sharedRepoUrl);
21230
- if (!sharedOwner || !sharedName) {
21231
- return { status: "failed", detail: `cannot parse owner/repo from ${opts.sharedRepoUrl}` };
21232
- }
21233
- const owned = findOwnedEntry(opts.entriesDir, opts.id);
21234
- if (!owned) {
21235
- return {
21236
- status: "not-found",
21237
- detail: `entry id=${opts.id} not found under ${opts.entriesDir}`
21238
- };
21239
- }
21240
- const ghUser = resolveGhUser();
21241
- if (!ghUser) {
21242
- return { status: "failed", detail: "gh api user failed" };
21243
- }
21244
- if (opts.dryRun) {
21245
- return {
21246
- status: "dry-run",
21247
- plannedSteps: [
21248
- `fork ${sharedOwner}/${sharedName} as ${ghUser}/${sharedName}`,
21249
- `clone fork \u2192 ${forkStagingDir(opts.caveatHome)}`,
21250
- `copy entry \u2192 entries/${owned.relPath}`,
21251
- "commit, push branch, open PR against main"
21252
- ]
21253
- };
21254
- }
21255
- try {
21256
- ensureFork(sharedOwner, sharedName);
21257
- const stagingDir = forkStagingDir(opts.caveatHome);
21258
- ensureStagingClone(stagingDir, ghUser, sharedName, sharedOwner);
21259
- const branch = `caveat-push-${owned.id}-${Date.now().toString(36)}`;
21260
- runOrThrow("git", ["checkout", "-b", branch], { cwd: stagingDir });
21261
- const destPath = join7(stagingDir, "entries", owned.relPath);
21262
- mkdirSync3(dirname4(destPath), { recursive: true });
21263
- copyFileSync(owned.absPath, destPath);
21264
- runOrThrow("git", ["add", join7("entries", owned.relPath)], { cwd: stagingDir });
21265
- const isUpdate = upstreamAlreadyHas(stagingDir, "entries/" + owned.relPath);
21266
- const verb = isUpdate ? "update" : "add";
21267
- const commitMsg = `${verb}: ${owned.title}`;
21268
- runOrThrow("git", ["commit", "-m", commitMsg], { cwd: stagingDir });
21269
- runOrThrow("git", ["push", "--set-upstream", "origin", branch], { cwd: stagingDir });
21270
- const prTitle = `${verb}: ${owned.title}`;
21271
- const prBody = [
21272
- `Contribution of caveat \`${owned.id}\` via \`caveat push\`.`,
21273
- "",
21274
- `- entry path: \`entries/${owned.relPath}\``,
21275
- `- action: ${verb}`,
21276
- "",
21277
- "Merged PRs will appear in subscribers' repos on their next `caveat pull`."
21278
- ].join("\n");
21279
- const pr = runCapture(
21280
- "gh",
21281
- [
21282
- "pr",
21283
- "create",
21284
- "--repo",
21285
- `${sharedOwner}/${sharedName}`,
21286
- "--base",
21287
- "main",
21288
- "--head",
21289
- `${ghUser}:${branch}`,
21290
- "--title",
21291
- prTitle,
21292
- "--body",
21293
- prBody
21294
- ],
21295
- { cwd: stagingDir }
21296
- );
21297
- if (pr.status !== 0) {
21298
- return {
21299
- status: "failed",
21300
- detail: (pr.stderr || "gh pr create failed").trim()
21301
- };
21302
- }
21303
- return { status: "ok", prUrl: pr.stdout.trim() };
21304
- } catch (err) {
21305
- return {
21306
- status: "failed",
21307
- detail: err instanceof Error ? err.message : String(err)
21308
- };
21309
- }
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
- }
21336
- function checkGhAvailable() {
21337
- return runCapture("gh", ["--version"]).status === 0;
21338
- }
21339
- function checkGhAuthed() {
21340
- return runCapture("gh", ["auth", "status"]).status === 0;
21341
- }
21342
- function resolveGhUser() {
21343
- const r2 = runCapture("gh", ["api", "user", "--jq", ".login"]);
21344
- if (r2.status !== 0) return void 0;
21345
- return r2.stdout.trim() || void 0;
21346
- }
21347
- function parseOwnerRepo(url) {
21348
- const m = /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(\.git)?\/?$/.exec(url);
21349
- if (!m) return [void 0, void 0];
21350
- return [m[1], m[2]];
21351
- }
21352
- function findOwnedEntry(entriesDir, id) {
21353
- if (!existsSync7(entriesDir)) return void 0;
21354
- for (const categoryDirent of readdirSync4(entriesDir, { withFileTypes: true })) {
21355
- if (!categoryDirent.isDirectory()) continue;
21356
- const categoryDir = join7(entriesDir, categoryDirent.name);
21357
- for (const fileDirent of readdirSync4(categoryDir, { withFileTypes: true })) {
21358
- if (!fileDirent.isFile() || !fileDirent.name.endsWith(".md")) continue;
21359
- const absPath = join7(categoryDir, fileDirent.name);
21360
- const raw2 = readFileSync5(absPath, "utf-8");
21361
- try {
21362
- const parsed = parseMarkdown(raw2);
21363
- if (parsed.frontmatter.id === id) {
21364
- const relPath = relative2(entriesDir, absPath).replace(/\\/g, "/");
21365
- return { id, title: parsed.frontmatter.title, absPath, relPath };
21366
- }
21367
- } catch {
21368
- }
21369
- }
21370
- }
21371
- return void 0;
21372
- }
21373
- function forkStagingDir(caveatHome) {
21374
- return join7(caveatHome, "push-fork");
21375
- }
21376
- function ensureFork(owner, repo) {
21377
- runCapture("gh", ["repo", "fork", `${owner}/${repo}`, "--clone=false", "--remote=false"]);
21378
- }
21379
- function ensureStagingClone(stagingDir, ghUser, sharedName, upstreamOwner) {
21380
- if (!existsSync7(stagingDir)) {
21381
- mkdirSync3(dirname4(stagingDir), { recursive: true });
21382
- const forkUrl = `https://github.com/${ghUser}/${sharedName}.git`;
21383
- runOrThrow("git", ["clone", "--depth", "30", forkUrl, stagingDir]);
21384
- runOrThrow(
21385
- "git",
21386
- ["remote", "add", "upstream", `https://github.com/${upstreamOwner}/${sharedName}.git`],
21387
- { cwd: stagingDir }
21388
- );
21389
- } else {
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 });
21394
- }
21395
- }
21396
- function upstreamAlreadyHas(stagingDir, relFromRoot) {
21397
- return existsSync7(join7(stagingDir, relFromRoot));
21398
- }
21399
-
21400
- // ../../packages/core/dist/pullShared.js
21401
- import { existsSync as existsSync8, readdirSync as readdirSync5 } from "node:fs";
21402
- import { join as join8 } from "node:path";
21403
- async function pullShared(opts) {
21404
- const result = { pulled: [], indexed: [] };
21405
- if (!existsSync8(opts.communityDir)) {
21406
- return result;
21407
- }
21408
- const pulls = await communityPull({
21409
- communityDir: opts.communityDir,
21410
- logger: opts.logger
21411
- });
21412
- for (const p2 of pulls) {
21413
- result.pulled.push({
21414
- handle: p2.handle,
21415
- status: p2.status,
21416
- message: p2.message
21417
- });
21418
- }
21419
- rebuildAll(opts.db);
21420
- if (existsSync8(opts.entriesDir)) {
21421
- const own = scanSource({ db: opts.db, source: "own", entriesRoot: opts.entriesDir });
21422
- result.indexed.push({ source: "own", ...own });
21423
- }
21424
- for (const entry of readdirSync5(opts.communityDir, { withFileTypes: true })) {
21425
- if (!entry.isDirectory()) continue;
21426
- const source = `community/${entry.name}`;
21427
- const root = join8(opts.communityDir, entry.name, "entries");
21428
- if (!existsSync8(root)) continue;
21429
- const scan = scanSource({ db: opts.db, source, entriesRoot: root });
21430
- result.indexed.push({ source, ...scan });
21431
- }
21432
- return result;
21433
- }
21434
-
21435
21238
  // src/context.ts
21436
21239
  function buildContext(logger, overrides = {}) {
21437
21240
  const userHome = overrides.userHome ?? homedir();
21438
21241
  const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
21439
- const userConfigPath = join9(userHome, ".caveatrc.json");
21242
+ const userConfigPath = join7(userHome, ".caveatrc.json");
21440
21243
  const config3 = loadConfig(userConfigPath);
21441
21244
  const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
21442
21245
  return { caveatHome, userHome, userConfigPath, config: config3, paths, logger };
21443
21246
  }
21444
21247
 
21445
21248
  // src/version.ts
21446
- import { readFileSync as readFileSync6 } from "node:fs";
21447
- import { dirname as dirname5, join as join10 } from "node:path";
21249
+ import { readFileSync as readFileSync5 } from "node:fs";
21250
+ import { dirname as dirname4, join as join8 } from "node:path";
21448
21251
  import { fileURLToPath as fileURLToPath3 } from "node:url";
21449
21252
  function resolveVersion() {
21450
21253
  try {
21451
- const here2 = dirname5(fileURLToPath3(import.meta.url));
21452
- const pkg = JSON.parse(readFileSync6(join10(here2, "..", "package.json"), "utf-8"));
21254
+ const here2 = dirname4(fileURLToPath3(import.meta.url));
21255
+ const pkg = JSON.parse(readFileSync5(join8(here2, "..", "package.json"), "utf-8"));
21453
21256
  return typeof pkg.version === "string" ? pkg.version : "0.0.0";
21454
21257
  } catch {
21455
21258
  return "0.0.0";
@@ -21468,13 +21271,13 @@ var stdoutLogger = {
21468
21271
  };
21469
21272
 
21470
21273
  // src/commands/init.ts
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";
21274
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync4, renameSync, rmdirSync, writeFileSync as writeFileSync5 } from "node:fs";
21275
+ import { dirname as dirname6, join as join10 } from "node:path";
21473
21276
 
21474
21277
  // src/claudeInstall.ts
21475
- import { spawnSync as spawnSync2 } from "node:child_process";
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";
21278
+ import { spawnSync } from "node:child_process";
21279
+ import { copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
21280
+ import { dirname as dirname5, join as join9 } from "node:path";
21478
21281
  var EVENT_USER_PROMPT_SUBMIT = "UserPromptSubmit";
21479
21282
  var EVENT_STOP = "Stop";
21480
21283
  function quote(p2) {
@@ -21508,27 +21311,27 @@ function removeHook(settings, event, command) {
21508
21311
  return true;
21509
21312
  }
21510
21313
  function readSettings(path) {
21511
- if (!existsSync9(path)) return {};
21512
- return JSON.parse(readFileSync7(path, "utf-8"));
21314
+ if (!existsSync7(path)) return {};
21315
+ return JSON.parse(readFileSync6(path, "utf-8"));
21513
21316
  }
21514
21317
  function writeSettings(path, settings) {
21515
- const dir = dirname6(path);
21516
- if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
21318
+ const dir = dirname5(path);
21319
+ if (!existsSync7(dir)) mkdirSync3(dir, { recursive: true });
21517
21320
  let backupPath = "";
21518
- if (existsSync9(path)) {
21321
+ if (existsSync7(path)) {
21519
21322
  backupPath = `${path}.caveat-backup-${Date.now()}`;
21520
- copyFileSync2(path, backupPath);
21323
+ copyFileSync(path, backupPath);
21521
21324
  }
21522
21325
  writeFileSync4(path, JSON.stringify(settings, null, 2) + "\n", "utf-8");
21523
21326
  return backupPath;
21524
21327
  }
21525
21328
  var CLAUDE_BIN = "claude";
21526
- function shellQuote2(s) {
21329
+ function shellQuote(s) {
21527
21330
  return /[\s&|<>^()]/.test(s) ? `"${s}"` : s;
21528
21331
  }
21529
21332
  function runClaude(args) {
21530
- const line = [CLAUDE_BIN, ...args].map(shellQuote2).join(" ");
21531
- return spawnSync2(line, { shell: true, encoding: "utf-8" });
21333
+ const line = [CLAUDE_BIN, ...args].map(shellQuote).join(" ");
21334
+ return spawnSync(line, { shell: true, encoding: "utf-8" });
21532
21335
  }
21533
21336
  function registerMcp(nodePath, cliScriptPath, dryRun, logger) {
21534
21337
  const args = mcpArgs(cliScriptPath);
@@ -21578,7 +21381,7 @@ function unregisterMcp(dryRun, logger) {
21578
21381
  return { action: "skipped", detail: "not registered or removal failed" };
21579
21382
  }
21580
21383
  function installClaudeIntegration(opts) {
21581
- const settingsPath = join11(opts.claudeDir, "settings.json");
21384
+ const settingsPath = join9(opts.claudeDir, "settings.json");
21582
21385
  const settings = readSettings(settingsPath);
21583
21386
  const usCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "user-prompt-submit");
21584
21387
  const stopCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "stop");
@@ -21597,7 +21400,7 @@ function installClaudeIntegration(opts) {
21597
21400
  return { mcp, hooks: { userPromptSubmit, stop }, backupPath };
21598
21401
  }
21599
21402
  function uninstallClaudeIntegration(opts) {
21600
- const settingsPath = join11(opts.claudeDir, "settings.json");
21403
+ const settingsPath = join9(opts.claudeDir, "settings.json");
21601
21404
  const settings = readSettings(settingsPath);
21602
21405
  const usCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "user-prompt-submit");
21603
21406
  const stopCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "stop");
@@ -21629,35 +21432,34 @@ var KNOWLEDGE_GITIGNORE = [
21629
21432
  ".obsidian/",
21630
21433
  ""
21631
21434
  ].join("\n");
21632
- async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRun: false }) {
21435
+ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
21633
21436
  ensureUserConfig(ctx.userConfigPath);
21634
21437
  ctx.logger.info(`user config: ${ctx.userConfigPath}`);
21635
- if (!existsSync10(ctx.paths.knowledgeRepo)) {
21636
- mkdirSync5(ctx.paths.knowledgeRepo, { recursive: true });
21637
- mkdirSync5(ctx.paths.entriesDir, { recursive: true });
21438
+ if (!existsSync8(ctx.paths.knowledgeRepo)) {
21439
+ mkdirSync4(ctx.paths.knowledgeRepo, { recursive: true });
21440
+ mkdirSync4(ctx.paths.entriesDir, { recursive: true });
21638
21441
  ctx.logger.info(`knowledge repo scaffolded: ${ctx.paths.knowledgeRepo}`);
21639
21442
  } else {
21640
21443
  ctx.logger.info(`knowledge repo: ${ctx.paths.knowledgeRepo}`);
21641
21444
  }
21642
21445
  migrateLegacyCommunityDir(ctx);
21643
- const gitignorePath = join12(ctx.paths.knowledgeRepo, ".gitignore");
21644
- if (!existsSync10(gitignorePath)) {
21446
+ const gitignorePath = join10(ctx.paths.knowledgeRepo, ".gitignore");
21447
+ if (!existsSync8(gitignorePath)) {
21645
21448
  writeFileSync5(gitignorePath, KNOWLEDGE_GITIGNORE, "utf-8");
21646
21449
  ctx.logger.info(`.gitignore created: ${gitignorePath}`);
21647
21450
  }
21648
- const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
21649
- try {
21650
- if (!opts.skipShared && !opts.dryRun) {
21651
- await subscribeSharedRepo(ctx, db);
21652
- } else if (opts.skipShared) {
21653
- ctx.logger.info("shared community DB subscription skipped (--skip-shared)");
21654
- } else if (opts.dryRun) {
21655
- ctx.logger.info(`[dry-run] would subscribe to shared community DB: ${ctx.config.sharedRepo}`);
21656
- }
21657
- } finally {
21451
+ if (!opts.dryRun) {
21452
+ const dbDir = dirname6(ctx.paths.dbPath);
21453
+ if (!existsSync8(dbDir)) mkdirSync4(dbDir, { recursive: true });
21454
+ const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
21658
21455
  db.close();
21456
+ ctx.logger.info(`db initialized: ${ctx.paths.dbPath}`);
21457
+ } else {
21458
+ ctx.logger.info(`[dry-run] db path: ${ctx.paths.dbPath}`);
21659
21459
  }
21660
- ctx.logger.info(`db initialized: ${ctx.paths.dbPath}`);
21460
+ ctx.logger.info(
21461
+ "tip: subscribe to a group repo with `caveat community add <github-url>`, then `caveat pull`."
21462
+ );
21661
21463
  if (opts.skipClaude) {
21662
21464
  ctx.logger.info("Claude Code integration skipped (--skip-claude)");
21663
21465
  return;
@@ -21668,7 +21470,7 @@ async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRu
21668
21470
  return;
21669
21471
  }
21670
21472
  const result = installClaudeIntegration({
21671
- claudeDir: join12(ctx.userHome, ".claude"),
21473
+ claudeDir: join10(ctx.userHome, ".claude"),
21672
21474
  cliScriptPath,
21673
21475
  nodePath: process.execPath,
21674
21476
  dryRun: opts.dryRun,
@@ -21677,20 +21479,20 @@ async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRu
21677
21479
  reportInstallResult(ctx, result, opts.dryRun);
21678
21480
  }
21679
21481
  function migrateLegacyCommunityDir(ctx) {
21680
- const legacy = join12(ctx.paths.knowledgeRepo, "community");
21482
+ const legacy = join10(ctx.paths.knowledgeRepo, "community");
21681
21483
  const current = ctx.paths.communityDir;
21682
21484
  if (legacy === current) return;
21683
- if (!existsSync10(legacy)) return;
21684
- if (existsSync10(current)) {
21485
+ if (!existsSync8(legacy)) return;
21486
+ if (existsSync8(current)) {
21685
21487
  ctx.logger.warn(
21686
21488
  `legacy community dir still exists at ${legacy} \u2014 remove manually (new location in use)`
21687
21489
  );
21688
21490
  return;
21689
21491
  }
21690
- mkdirSync5(current, { recursive: true });
21691
- for (const entry of readdirSync6(legacy, { withFileTypes: true })) {
21492
+ mkdirSync4(current, { recursive: true });
21493
+ for (const entry of readdirSync4(legacy, { withFileTypes: true })) {
21692
21494
  if (!entry.isDirectory()) continue;
21693
- renameSync(join12(legacy, entry.name), join12(current, entry.name));
21495
+ renameSync(join10(legacy, entry.name), join10(current, entry.name));
21694
21496
  }
21695
21497
  try {
21696
21498
  rmdirSync(legacy);
@@ -21698,52 +21500,6 @@ function migrateLegacyCommunityDir(ctx) {
21698
21500
  }
21699
21501
  ctx.logger.info(`migrated legacy community/ \u2192 ${current}`);
21700
21502
  }
21701
- async function subscribeSharedRepo(ctx, db) {
21702
- const url = ctx.config.sharedRepo;
21703
- const validation = validateCommunityUrl(url);
21704
- if (!validation.valid) {
21705
- ctx.logger.warn(
21706
- `sharedRepo is not a valid GitHub URL \u2014 skipping: ${validation.reason}`
21707
- );
21708
- return;
21709
- }
21710
- const handle = validation.handle;
21711
- const target = join12(ctx.paths.communityDir, handle);
21712
- if (!existsSync10(target)) {
21713
- if (!existsSync10(ctx.paths.communityDir)) {
21714
- mkdirSync5(ctx.paths.communityDir, { recursive: true });
21715
- }
21716
- try {
21717
- await communityAdd({
21718
- url,
21719
- communityDir: ctx.paths.communityDir,
21720
- logger: ctx.logger
21721
- });
21722
- ctx.logger.info(`shared community DB subscribed: ${url} \u2192 community/${handle}/`);
21723
- } catch (err) {
21724
- ctx.logger.warn(
21725
- `shared community DB subscription failed: ${err instanceof Error ? err.message : String(err)}`
21726
- );
21727
- return;
21728
- }
21729
- } else {
21730
- ctx.logger.info(`shared community DB already subscribed: community/${handle}/`);
21731
- }
21732
- if (existsSync10(ctx.paths.communityDir)) {
21733
- rebuildAll(db);
21734
- if (existsSync10(ctx.paths.entriesDir)) {
21735
- scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
21736
- }
21737
- for (const entry of readdirSync6(ctx.paths.communityDir, { withFileTypes: true })) {
21738
- if (!entry.isDirectory()) continue;
21739
- const source = `community/${entry.name}`;
21740
- const root = join12(ctx.paths.communityDir, entry.name, "entries");
21741
- if (!existsSync10(root)) continue;
21742
- const result = scanSource({ db, source, entriesRoot: root });
21743
- ctx.logger.info(`indexed ${source}: +${result.added}`);
21744
- }
21745
- }
21746
- }
21747
21503
  function runUninstall(ctx, opts) {
21748
21504
  const cliScriptPath = process.argv[1];
21749
21505
  if (!cliScriptPath) {
@@ -21751,7 +21507,7 @@ function runUninstall(ctx, opts) {
21751
21507
  process.exit(1);
21752
21508
  }
21753
21509
  const result = uninstallClaudeIntegration({
21754
- claudeDir: join12(ctx.userHome, ".claude"),
21510
+ claudeDir: join10(ctx.userHome, ".claude"),
21755
21511
  cliScriptPath,
21756
21512
  nodePath: process.execPath,
21757
21513
  dryRun: opts.dryRun,
@@ -21788,29 +21544,29 @@ function reportInstallResult(ctx, result, dryRun) {
21788
21544
  }
21789
21545
 
21790
21546
  // src/commands/indexCmd.ts
21791
- import { existsSync as existsSync11, readdirSync as readdirSync7, mkdirSync as mkdirSync6 } from "node:fs";
21792
- import { dirname as dirname7, join as join13 } from "node:path";
21547
+ import { existsSync as existsSync9, readdirSync as readdirSync5, mkdirSync as mkdirSync5 } from "node:fs";
21548
+ import { dirname as dirname7, join as join11 } from "node:path";
21793
21549
  function runIndex(ctx, opts) {
21794
21550
  const dbDir = dirname7(ctx.paths.dbPath);
21795
- if (!existsSync11(dbDir)) mkdirSync6(dbDir, { recursive: true });
21551
+ if (!existsSync9(dbDir)) mkdirSync5(dbDir, { recursive: true });
21796
21552
  const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
21797
21553
  try {
21798
21554
  if (opts.full) {
21799
21555
  ctx.logger.info("full rebuild: DELETE FROM entries");
21800
21556
  rebuildAll(db);
21801
21557
  }
21802
- if (existsSync11(ctx.paths.entriesDir)) {
21558
+ if (existsSync9(ctx.paths.entriesDir)) {
21803
21559
  const result = scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
21804
21560
  ctx.logger.info(`own: +${result.added} ~${result.updated} -${result.deleted}`);
21805
21561
  } else {
21806
21562
  ctx.logger.warn(`entries dir not found: ${ctx.paths.entriesDir}`);
21807
21563
  }
21808
- if (existsSync11(ctx.paths.communityDir)) {
21809
- for (const entry of readdirSync7(ctx.paths.communityDir, { withFileTypes: true })) {
21564
+ if (existsSync9(ctx.paths.communityDir)) {
21565
+ for (const entry of readdirSync5(ctx.paths.communityDir, { withFileTypes: true })) {
21810
21566
  if (!entry.isDirectory()) continue;
21811
21567
  const source = `community/${entry.name}`;
21812
- const root = join13(ctx.paths.communityDir, entry.name, "entries");
21813
- if (!existsSync11(root)) continue;
21568
+ const root = join11(ctx.paths.communityDir, entry.name, "entries");
21569
+ if (!existsSync9(root)) continue;
21814
21570
  const result = scanSource({ db, source, entriesRoot: root });
21815
21571
  ctx.logger.info(`${source}: +${result.added} ~${result.updated} -${result.deleted}`);
21816
21572
  }
@@ -22584,11 +22340,11 @@ var serve = (options2, listeningListener) => {
22584
22340
 
22585
22341
  // ../web/dist/context.js
22586
22342
  import { homedir as homedir2 } from "node:os";
22587
- import { join as join14 } from "node:path";
22343
+ import { join as join12 } from "node:path";
22588
22344
  function buildWebContext(overrides = {}) {
22589
22345
  const userHome = overrides.userHome ?? homedir2();
22590
22346
  const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
22591
- const userConfigPath = join14(userHome, ".caveatrc.json");
22347
+ const userConfigPath = join12(userHome, ".caveatrc.json");
22592
22348
  const logger = overrides.logger ?? stderrLogger;
22593
22349
  const config3 = loadConfig(userConfigPath);
22594
22350
  const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
@@ -24693,7 +24449,7 @@ ul.entries li { padding: 0.5rem 0; border-bottom: 1px solid #eee; }
24693
24449
  ul.entries a.title { font-weight: 600; color: #0a5fff; text-decoration: none; }
24694
24450
  ul.entries a.title:hover { text-decoration: underline; }
24695
24451
  ul.entries .meta { font-size: 0.85rem; color: #666; margin-top: 0.2rem; }
24696
- ul.entries .meta .badge {
24452
+ .badge {
24697
24453
  display: inline-block;
24698
24454
  padding: 0 0.4rem;
24699
24455
  margin-right: 0.3rem;
@@ -24701,10 +24457,12 @@ ul.entries .meta .badge {
24701
24457
  background: #f0f0f0;
24702
24458
  font-size: 0.8rem;
24703
24459
  }
24704
- ul.entries .meta .badge.confirmed { background: #d7f4dc; color: #16703a; }
24705
- ul.entries .meta .badge.reproduced { background: #e6eeff; color: #234ea3; }
24706
- ul.entries .meta .badge.tentative { background: #fff4d1; color: #8a5a00; }
24707
- ul.entries .meta .badge.impossible { background: #ffd9d9; color: #8a1a1a; }
24460
+ .badge.confirmed { background: #d7f4dc; color: #16703a; }
24461
+ .badge.reproduced { background: #e6eeff; color: #234ea3; }
24462
+ .badge.tentative { background: #fff4d1; color: #8a5a00; }
24463
+ .badge.impossible { background: #ffd9d9; color: #8a1a1a; }
24464
+ .badge.public { background: #eef2f5; color: #5a6470; }
24465
+ .badge.private { background: #fde7e7; color: #a02525; font-weight: 600; }
24708
24466
  ul.entries .excerpt { margin-top: 0.3rem; color: #444; font-size: 0.92rem; }
24709
24467
  article h2 { border-bottom: 1px solid #eee; padding-bottom: 0.2rem; margin-top: 1.6rem; }
24710
24468
  article pre {
@@ -24779,6 +24537,7 @@ function createIndexRoute(ctx) {
24779
24537
  const q2 = c3.req.query("q")?.trim() ?? "";
24780
24538
  const sourceParam = c3.req.query("source");
24781
24539
  const tagParam = c3.req.query("tag");
24540
+ const visibilityParam = c3.req.query("visibility");
24782
24541
  const filters = {};
24783
24542
  if (sourceParam === "own" || sourceParam === "community" || sourceParam === "all") {
24784
24543
  filters.source = sourceParam;
@@ -24786,17 +24545,22 @@ function createIndexRoute(ctx) {
24786
24545
  if (tagParam) {
24787
24546
  filters.tags = [tagParam];
24788
24547
  }
24789
- const results = q2 ? search(ctx.db, { query: q2, filters, limit: 50 }) : listRecent(ctx.db, 50);
24548
+ if (visibilityParam === "public" || visibilityParam === "private") {
24549
+ filters.visibility = visibilityParam;
24550
+ }
24551
+ const results = search(ctx.db, { query: q2, filters, limit: 50 });
24790
24552
  const rows = results.map((r2) => {
24791
24553
  const envEntries = Object.entries(r2.environment ?? {});
24792
24554
  const envSummary = envEntries.slice(0, 3).map(([k2, v]) => `${escapeHtml(k2)}:${escapeHtml(String(v))}`).join(" ");
24793
24555
  const hrefSource = r2.source === "own" ? "" : `?source=${encodeURIComponent(r2.source)}`;
24556
+ const visibilityBadge = r2.visibility === "private" ? `<span class="badge private" title="local-only \u2014 never pushed to community DB">\u{1F512} private</span>` : `<span class="badge public">public</span>`;
24794
24557
  return `
24795
24558
  <li>
24796
24559
  <a class="title" href="/g/${encodeURIComponent(r2.id)}${hrefSource}">${escapeHtml(r2.title)}</a>
24797
24560
  <div class="meta">
24798
24561
  <span class="badge ${escapeHtml(r2.confidence ?? "")}">${escapeHtml(r2.confidence ?? "")}</span>
24799
24562
  <span class="badge">${escapeHtml(r2.source)}</span>
24563
+ ${visibilityBadge}
24800
24564
  ${envSummary ? `<span>${envSummary}</span>` : ""}
24801
24565
  </div>
24802
24566
  ${r2.symptomExcerpt ? `<div class="excerpt">${escapeHtml(r2.symptomExcerpt)}</div>` : ""}
@@ -24810,9 +24574,18 @@ function createIndexRoute(ctx) {
24810
24574
  <option value="own" ${sourceParam === "own" ? "selected" : ""}>own</option>
24811
24575
  <option value="community" ${sourceParam === "community" ? "selected" : ""}>community</option>
24812
24576
  </select>
24577
+ <select name="visibility" title="visibility filter \u2014 useful when sharing screen">
24578
+ <option value="" ${!visibilityParam ? "selected" : ""}>any visibility</option>
24579
+ <option value="public" ${visibilityParam === "public" ? "selected" : ""}>public only</option>
24580
+ <option value="private" ${visibilityParam === "private" ? "selected" : ""}>private only</option>
24581
+ </select>
24813
24582
  <button type="submit">search</button>
24814
24583
  </form>
24815
- ${results.length > 0 ? `<ul class="entries">${rows}</ul>` : `<div class="empty">${q2 ? `no results for "${escapeHtml(q2)}"` : "no entries yet \u2014 run <code>caveat index</code> after adding md files"}</div>`}`;
24584
+ ${results.length > 0 ? `<ul class="entries">${rows}</ul>` : (() => {
24585
+ if (q2) return `<div class="empty">no results for "${escapeHtml(q2)}"</div>`;
24586
+ const hasFilter = !!(sourceParam || tagParam || visibilityParam);
24587
+ return hasFilter ? `<div class="empty">no entries match the active filters</div>` : `<div class="empty">no entries yet \u2014 run <code>caveat index</code> after adding md files</div>`;
24588
+ })()}`;
24816
24589
  const title = q2 ? `search: ${q2} \xB7 Caveat` : "Caveat";
24817
24590
  return c3.html(layout(title, body));
24818
24591
  });
@@ -30278,10 +30051,11 @@ function createDetailRoute(ctx) {
30278
30051
  }
30279
30052
  const fm = entry.frontmatter;
30280
30053
  const envRows = Object.entries(fm.environment ?? {}).map(([k2, v]) => `<dt>${escapeHtml(k2)}</dt><dd>${escapeHtml(String(v))}</dd>`).join("");
30054
+ const visibilityBadge = fm.visibility === "private" ? `<span class="badge private" title="local-only \u2014 never pushed to community DB">\u{1F512} private</span>` : `<span class="badge public">public</span>`;
30281
30055
  const metaRows = [
30282
30056
  `<dt>id</dt><dd><code>${escapeHtml(fm.id)}</code></dd>`,
30283
30057
  `<dt>source</dt><dd>${escapeHtml(entry.source)}</dd>`,
30284
- `<dt>visibility</dt><dd>${escapeHtml(fm.visibility)}</dd>`,
30058
+ `<dt>visibility</dt><dd>${visibilityBadge}</dd>`,
30285
30059
  `<dt>confidence</dt><dd><span class="badge ${escapeHtml(fm.confidence)}">${escapeHtml(fm.confidence)}</span></dd>`
30286
30060
  ];
30287
30061
  if (fm.outcome) {
@@ -30315,14 +30089,14 @@ function createDetailRoute(ctx) {
30315
30089
  }
30316
30090
 
30317
30091
  // ../web/dist/routes/community.js
30318
- import { existsSync as existsSync12, readdirSync as readdirSync8, statSync as statSync4 } from "node:fs";
30319
- import { join as join15 } from "node:path";
30092
+ import { existsSync as existsSync10, readdirSync as readdirSync6, statSync as statSync5 } from "node:fs";
30093
+ import { join as join13 } from "node:path";
30320
30094
  function listCommunity(communityDir, db) {
30321
- if (!existsSync12(communityDir)) return [];
30095
+ if (!existsSync10(communityDir)) return [];
30322
30096
  const handles = [];
30323
- for (const entry of readdirSync8(communityDir, { withFileTypes: true })) {
30097
+ for (const entry of readdirSync6(communityDir, { withFileTypes: true })) {
30324
30098
  if (!entry.isDirectory()) continue;
30325
- const handlePath = join15(communityDir, entry.name);
30099
+ const handlePath = join13(communityDir, entry.name);
30326
30100
  const countRow = db.prepare("SELECT COUNT(*) AS n FROM entries WHERE source = ?").get(`community/${entry.name}`);
30327
30101
  handles.push({
30328
30102
  handle: entry.name,
@@ -30339,7 +30113,7 @@ function createCommunityRoute(ctx) {
30339
30113
  const list2 = handles.map((h2) => {
30340
30114
  let mtime = "";
30341
30115
  try {
30342
- mtime = statSync4(h2.path).mtime.toISOString().slice(0, 10);
30116
+ mtime = statSync5(h2.path).mtime.toISOString().slice(0, 10);
30343
30117
  } catch {
30344
30118
  mtime = "";
30345
30119
  }
@@ -44620,11 +44394,11 @@ var StdioServerTransport = class {
44620
44394
 
44621
44395
  // ../mcp/dist/context.js
44622
44396
  import { homedir as homedir3 } from "node:os";
44623
- import { join as join16 } from "node:path";
44397
+ import { join as join14 } from "node:path";
44624
44398
  function buildMcpContext(overrides = {}) {
44625
44399
  const userHome = overrides.userHome ?? homedir3();
44626
44400
  const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
44627
- const userConfigPath = join16(userHome, ".caveatrc.json");
44401
+ const userConfigPath = join14(userHome, ".caveatrc.json");
44628
44402
  const logger = overrides.logger ?? stderrLogger;
44629
44403
  const config3 = loadConfig(userConfigPath);
44630
44404
  const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
@@ -44680,7 +44454,9 @@ var recordInputShape = {
44680
44454
  context: external_exports.string().optional(),
44681
44455
  confidence: confidenceSchema2.optional(),
44682
44456
  outcome: outcomeSchema.optional(),
44683
- visibility: visibilitySchema.optional(),
44457
+ visibility: visibilitySchema.describe(
44458
+ '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.'
44459
+ ),
44684
44460
  tags: external_exports.array(external_exports.string()).optional(),
44685
44461
  environment: external_exports.record(external_exports.string(), external_exports.string()).optional(),
44686
44462
  category: external_exports.string().optional().describe("Directory under entries/ (e.g., gpu, claude-code). Default: misc")
@@ -44731,45 +44507,37 @@ function handleListRecent(ctx, args) {
44731
44507
  }
44732
44508
 
44733
44509
  // ../mcp/dist/tools/pull.js
44510
+ import { existsSync as existsSync11, readdirSync as readdirSync7 } from "node:fs";
44511
+ import { join as join15 } from "node:path";
44734
44512
  var pullInputShape = {};
44735
44513
  async function handlePull(ctx, _args = {}) {
44736
- const result = await pullShared({
44737
- communityDir: ctx.paths.communityDir,
44738
- entriesDir: ctx.paths.entriesDir,
44739
- db: ctx.db,
44740
- logger: ctx.logger
44741
- });
44742
- return {
44743
- pulled: result.pulled,
44744
- indexed: result.indexed.map((i2) => ({
44745
- source: i2.source,
44746
- added: i2.added,
44747
- updated: i2.updated,
44748
- deleted: i2.deleted
44749
- }))
44750
- };
44751
- }
44752
-
44753
- // ../mcp/dist/tools/push.js
44754
- var pushInputShape = {
44755
- id: external_exports.string().min(1).describe("Entry id (frontmatter.id) to push to the shared community DB"),
44756
- dry_run: external_exports.boolean().optional().describe("If true, compute the plan but do not modify GitHub or local git state")
44757
- };
44758
- async function handlePush(ctx, args) {
44759
- const result = await pushEntry({
44760
- entriesDir: ctx.paths.entriesDir,
44761
- caveatHome: ctx.caveatHome,
44762
- sharedRepoUrl: ctx.config.sharedRepo,
44763
- id: args.id,
44764
- dryRun: args.dry_run ?? false,
44765
- logger: ctx.logger
44766
- });
44767
- return {
44768
- status: result.status,
44769
- detail: result.detail,
44770
- pr_url: result.prUrl,
44771
- planned_steps: result.plannedSteps
44772
- };
44514
+ const pulled = [];
44515
+ const indexed = [];
44516
+ if (existsSync11(ctx.paths.communityDir)) {
44517
+ const results = await communityPull({
44518
+ communityDir: ctx.paths.communityDir,
44519
+ logger: ctx.logger
44520
+ });
44521
+ for (const r2 of results) {
44522
+ pulled.push({ handle: r2.handle, status: r2.status, message: r2.message });
44523
+ }
44524
+ }
44525
+ rebuildAll(ctx.db);
44526
+ if (existsSync11(ctx.paths.entriesDir)) {
44527
+ const own = scanSource({ db: ctx.db, source: "own", entriesRoot: ctx.paths.entriesDir });
44528
+ indexed.push({ source: "own", ...own });
44529
+ }
44530
+ if (existsSync11(ctx.paths.communityDir)) {
44531
+ for (const entry of readdirSync7(ctx.paths.communityDir, { withFileTypes: true })) {
44532
+ if (!entry.isDirectory()) continue;
44533
+ const source = `community/${entry.name}`;
44534
+ const root = join15(ctx.paths.communityDir, entry.name, "entries");
44535
+ if (!existsSync11(root)) continue;
44536
+ const scan = scanSource({ db: ctx.db, source, entriesRoot: root });
44537
+ indexed.push({ source, ...scan });
44538
+ }
44539
+ }
44540
+ return { pulled, indexed };
44773
44541
  }
44774
44542
 
44775
44543
  // ../mcp/dist/registerTools.js
@@ -44788,7 +44556,7 @@ function registerAllTools(server, ctx) {
44788
44556
  "caveat_search",
44789
44557
  {
44790
44558
  title: "caveat_search",
44791
- description: "Full-text search across caveats (external spec gotchas). Returns summary; use caveat_get for body.",
44559
+ 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.',
44792
44560
  inputSchema: searchInputShape
44793
44561
  },
44794
44562
  async (args) => jsonResult(handleSearch(ctx, args))
@@ -44797,7 +44565,7 @@ function registerAllTools(server, ctx) {
44797
44565
  "caveat_get",
44798
44566
  {
44799
44567
  title: "caveat_get",
44800
- description: "Fetch full caveat by id (frontmatter + sections + body).",
44568
+ 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.',
44801
44569
  inputSchema: getInputShape
44802
44570
  },
44803
44571
  async (args) => jsonResult(handleGet(ctx, args))
@@ -44806,7 +44574,7 @@ function registerAllTools(server, ctx) {
44806
44574
  "caveat_record",
44807
44575
  {
44808
44576
  title: "caveat_record",
44809
- 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).",
44577
+ 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).",
44810
44578
  inputSchema: recordInputShape
44811
44579
  },
44812
44580
  async (args) => jsonResult(handleRecord(ctx, args))
@@ -44815,7 +44583,7 @@ function registerAllTools(server, ctx) {
44815
44583
  "caveat_update",
44816
44584
  {
44817
44585
  title: "caveat_update",
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.",
44586
+ 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`.",
44819
44587
  inputSchema: updateInputShape
44820
44588
  },
44821
44589
  async (args) => jsonResult(handleUpdate(ctx, args))
@@ -44824,7 +44592,7 @@ function registerAllTools(server, ctx) {
44824
44592
  "caveat_list_recent",
44825
44593
  {
44826
44594
  title: "caveat_list_recent",
44827
- description: "List caveats by updated_at DESC.",
44595
+ 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.",
44828
44596
  inputSchema: listRecentInputShape
44829
44597
  },
44830
44598
  async (args) => jsonResult(handleListRecent(ctx, args))
@@ -44833,20 +44601,11 @@ function registerAllTools(server, ctx) {
44833
44601
  "caveat_pull",
44834
44602
  {
44835
44603
  title: "caveat_pull",
44836
- 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.",
44604
+ description: "git-pull every subscribed community caveat repo (added via `caveat community add`) and re-index. Call when: (a) the user explicitly asks about others' knowledge on a topic, or (b) caveat_search returned empty for a query that feels like it should have hits and a subscribed repo might be stale. Do NOT call reflexively at session start \u2014 it is cheap but not free, and stale-by-minutes is acceptable. Safe and idempotent.",
44837
44605
  inputSchema: pullInputShape
44838
44606
  },
44839
44607
  async (args) => jsonResult(await handlePull(ctx, args))
44840
44608
  );
44841
- server.registerTool(
44842
- "caveat_push",
44843
- {
44844
- title: "caveat_push",
44845
- 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.",
44846
- inputSchema: pushInputShape
44847
- },
44848
- async (args) => jsonResult(await handlePush(ctx, args))
44849
- );
44850
44609
  }
44851
44610
 
44852
44611
  // ../mcp/dist/server.js
@@ -44939,77 +44698,46 @@ async function runHook(name) {
44939
44698
  }
44940
44699
 
44941
44700
  // src/commands/pull.ts
44942
- import { existsSync as existsSync13 } from "node:fs";
44701
+ import { existsSync as existsSync12, readdirSync as readdirSync8 } from "node:fs";
44702
+ import { join as join16 } from "node:path";
44943
44703
  async function runPull(ctx) {
44944
- if (!existsSync13(ctx.paths.communityDir)) {
44704
+ if (!existsSync12(ctx.paths.communityDir)) {
44945
44705
  ctx.logger.info(
44946
- "no community repos yet \u2014 did you run `caveat init`? You can also `caveat community add <url>` manually."
44706
+ "no community repos yet \u2014 add one with `caveat community add <github-url>`."
44947
44707
  );
44948
44708
  return;
44949
44709
  }
44710
+ const pulls = await communityPull({
44711
+ communityDir: ctx.paths.communityDir,
44712
+ logger: ctx.logger
44713
+ });
44714
+ for (const p2 of pulls) {
44715
+ if (p2.status === "ok") {
44716
+ ctx.logger.info(`${p2.handle}: pulled`);
44717
+ } else {
44718
+ ctx.logger.warn(`${p2.handle}: FAILED \u2014 ${p2.message ?? "unknown"}`);
44719
+ }
44720
+ }
44950
44721
  const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
44951
44722
  try {
44952
- const result = await pullShared({
44953
- communityDir: ctx.paths.communityDir,
44954
- entriesDir: ctx.paths.entriesDir,
44955
- db,
44956
- logger: ctx.logger
44957
- });
44958
- for (const p2 of result.pulled) {
44959
- if (p2.status === "ok") {
44960
- ctx.logger.info(`${p2.handle}: pulled`);
44961
- } else {
44962
- ctx.logger.warn(`${p2.handle}: FAILED \u2014 ${p2.message ?? "unknown"}`);
44963
- }
44723
+ rebuildAll(db);
44724
+ if (existsSync12(ctx.paths.entriesDir)) {
44725
+ const own = scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
44726
+ ctx.logger.info(`own: +${own.added}`);
44964
44727
  }
44965
- for (const i2 of result.indexed) {
44966
- ctx.logger.info(`${i2.source}: +${i2.added}`);
44728
+ for (const entry of readdirSync8(ctx.paths.communityDir, { withFileTypes: true })) {
44729
+ if (!entry.isDirectory()) continue;
44730
+ const source = `community/${entry.name}`;
44731
+ const root = join16(ctx.paths.communityDir, entry.name, "entries");
44732
+ if (!existsSync12(root)) continue;
44733
+ const scan = scanSource({ db, source, entriesRoot: root });
44734
+ ctx.logger.info(`${source}: +${scan.added}`);
44967
44735
  }
44968
44736
  } finally {
44969
44737
  db.close();
44970
44738
  }
44971
44739
  }
44972
44740
 
44973
- // src/commands/push.ts
44974
- async function runPush(ctx, opts) {
44975
- const result = await pushEntry({
44976
- entriesDir: ctx.paths.entriesDir,
44977
- caveatHome: ctx.caveatHome,
44978
- sharedRepoUrl: ctx.config.sharedRepo,
44979
- id: opts.id,
44980
- dryRun: opts.dryRun,
44981
- logger: ctx.logger
44982
- });
44983
- switch (result.status) {
44984
- case "ok":
44985
- ctx.logger.info(`PR opened: ${result.prUrl ?? ""}`);
44986
- ctx.logger.info(
44987
- "Once merged, other subscribers will see your entry after their next `caveat pull`."
44988
- );
44989
- break;
44990
- case "dry-run":
44991
- for (const [i2, step] of (result.plannedSteps ?? []).entries()) {
44992
- ctx.logger.info(`[dry-run] ${i2 + 1}. ${step}`);
44993
- }
44994
- break;
44995
- case "not-found":
44996
- ctx.logger.error(
44997
- `${result.detail}. List entries with \`caveat list\` or write + index first.`
44998
- );
44999
- process.exitCode = 1;
45000
- break;
45001
- case "gh-missing":
45002
- case "gh-unauthed":
45003
- ctx.logger.error(result.detail ?? "gh CLI issue");
45004
- process.exitCode = 1;
45005
- break;
45006
- case "failed":
45007
- ctx.logger.error(result.detail ?? "unknown failure");
45008
- process.exitCode = 1;
45009
- break;
45010
- }
45011
- }
45012
-
45013
44741
  // src/commands/community.ts
45014
44742
  async function runCommunityAdd(ctx, url) {
45015
44743
  try {
@@ -45059,17 +44787,46 @@ function runCommunityList(ctx) {
45059
44787
  db.close();
45060
44788
  }
45061
44789
  }
44790
+ function runCommunityRemove(ctx, handle, opts) {
44791
+ const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
44792
+ try {
44793
+ const result = communityRemove({
44794
+ communityDir: ctx.paths.communityDir,
44795
+ handle,
44796
+ db,
44797
+ dryRun: opts.dryRun,
44798
+ logger: ctx.logger
44799
+ });
44800
+ if (!result.dirExisted && result.rowCount === 0) {
44801
+ ctx.logger.warn(`no community subscription named "${handle}" \u2014 nothing to remove`);
44802
+ return;
44803
+ }
44804
+ const prefix = result.dryRun ? "[dry-run] would " : "";
44805
+ if (result.dirExisted) {
44806
+ ctx.logger.info(`${prefix}remove dir: ${result.path}`);
44807
+ }
44808
+ if (result.rowCount > 0) {
44809
+ ctx.logger.info(
44810
+ `${prefix}delete ${result.rowCount} db row(s) for source=community/${handle}`
44811
+ );
44812
+ }
44813
+ } catch (err) {
44814
+ ctx.logger.error(err instanceof Error ? err.message : String(err));
44815
+ process.exitCode = 1;
44816
+ } finally {
44817
+ db.close();
44818
+ }
44819
+ }
45062
44820
 
45063
44821
  // src/index.ts
45064
44822
  var program = new Command();
45065
44823
  program.name("caveat").description("External spec gotcha knowledge base CLI").version(CAVEAT_VERSION);
45066
44824
  program.command("init").description(
45067
- "Initialize ~/.caveatrc.json, ~/.caveat/, subscribe to the shared community DB, and register Claude Code integration"
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) => {
44825
+ "Initialize ~/.caveatrc.json, ~/.caveat/, and register Claude Code integration. Add knowledge sources later with `caveat community add <github-url>`."
44826
+ ).option("--skip-claude", "skip Claude Code MCP + hook registration", false).option("--dry-run", "show planned changes without writing", false).action(async (opts) => {
45069
44827
  const ctx = buildContext(stdoutLogger);
45070
44828
  await runInit(ctx, {
45071
44829
  skipClaude: opts.skipClaude,
45072
- skipShared: opts.skipShared,
45073
44830
  dryRun: opts.dryRun
45074
44831
  });
45075
44832
  });
@@ -45105,15 +44862,11 @@ program.command("stats").description("Show aggregate stats").action(() => {
45105
44862
  runStats(ctx);
45106
44863
  });
45107
44864
  program.command("pull").description(
45108
- "Refresh all subscribed community repos (incl. shared DB) and re-index. Use this to receive contributions others have merged."
44865
+ "git-pull every subscribed community repo and re-index. Use this to receive updates from group/teammate repos."
45109
44866
  ).action(async () => {
45110
44867
  const ctx = buildContext(stdoutLogger);
45111
44868
  await runPull(ctx);
45112
44869
  });
45113
- program.command("push").description("Contribute a caveat to the shared community DB via fork + PR (requires gh CLI).").argument("<id>", "entry id (from `caveat list`)").option("--dry-run", "show planned steps without making GitHub changes", false).action(async (id, opts) => {
45114
- const ctx = buildContext(stdoutLogger);
45115
- await runPush(ctx, { id, dryRun: opts.dryRun });
45116
- });
45117
44870
  program.command("serve").description("Start the read-only web share portal").option("--port <n>", "port number", (v) => Number(v), 4242).action((opts) => {
45118
44871
  runServe({ port: opts.port });
45119
44872
  });
@@ -45136,6 +44889,10 @@ community.command("list").description("List imported community repos with entry
45136
44889
  const ctx = buildContext(stdoutLogger);
45137
44890
  runCommunityList(ctx);
45138
44891
  });
44892
+ community.command("remove <handle>").description("Unsubscribe from a community repo: delete community/<handle>/ and purge its DB rows").option("--dry-run", "show what would be removed without touching disk or db", false).action((handle, opts) => {
44893
+ const ctx = buildContext(stdoutLogger);
44894
+ runCommunityRemove(ctx, handle, { dryRun: opts.dryRun });
44895
+ });
45139
44896
  program.parseAsync(process.argv).catch((err) => {
45140
44897
  const msg = err instanceof Error ? err.message : String(err);
45141
44898
  process.stderr.write(`[caveat:error] ${msg}