caveat-cli 0.2.1 → 0.4.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, relative2, options2, skipNormalization) {
9764
+ function resolveComponent(base2, relative3, options2, skipNormalization) {
9765
9765
  const target = {};
9766
9766
  if (!skipNormalization) {
9767
9767
  base2 = parse4(serialize(base2, options2), options2);
9768
- relative2 = parse4(serialize(relative2, options2), options2);
9768
+ relative3 = parse4(serialize(relative3, options2), options2);
9769
9769
  }
9770
9770
  options2 = options2 || {};
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;
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;
9778
9778
  } else {
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;
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;
9785
9785
  } else {
9786
- if (!relative2.path) {
9786
+ if (!relative3.path) {
9787
9787
  target.path = base2.path;
9788
- if (relative2.query !== void 0) {
9789
- target.query = relative2.query;
9788
+ if (relative3.query !== void 0) {
9789
+ target.query = relative3.query;
9790
9790
  } else {
9791
9791
  target.query = base2.query;
9792
9792
  }
9793
9793
  } else {
9794
- if (relative2.path[0] === "/") {
9795
- target.path = removeDotSegments(relative2.path);
9794
+ if (relative3.path[0] === "/") {
9795
+ target.path = removeDotSegments(relative3.path);
9796
9796
  } else {
9797
9797
  if ((base2.userinfo !== void 0 || base2.host !== void 0 || base2.port !== void 0) && !base2.path) {
9798
- target.path = "/" + relative2.path;
9798
+ target.path = "/" + relative3.path;
9799
9799
  } else if (!base2.path) {
9800
- target.path = relative2.path;
9800
+ target.path = relative3.path;
9801
9801
  } else {
9802
- target.path = base2.path.slice(0, base2.path.lastIndexOf("/") + 1) + relative2.path;
9802
+ target.path = base2.path.slice(0, base2.path.lastIndexOf("/") + 1) + relative3.path;
9803
9803
  }
9804
9804
  target.path = removeDotSegments(target.path);
9805
9805
  }
9806
- target.query = relative2.query;
9806
+ target.query = relative3.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 = relative2.fragment;
9814
+ target.fragment = relative3.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 join7 } from "node:path";
12979
+ import { join as join9 } from "node:path";
12980
12980
 
12981
12981
  // ../../packages/core/dist/db.js
12982
12982
  import { DatabaseSync } from "node:sqlite";
@@ -16139,10 +16139,12 @@ function resolvePaths(caveatHome, knowledgeRepo, userHome) {
16139
16139
 
16140
16140
  // ../../packages/core/dist/config.js
16141
16141
  import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
16142
+ var SHARED_REPO_URL = "https://github.com/kitepon-rgb/caveats-quo";
16142
16143
  var DEFAULT_CONFIG = {
16143
16144
  knowledgeRepo: "own",
16144
16145
  semverKeys: ["driver", "cuda", "node"],
16145
- communitySources: []
16146
+ communitySources: [],
16147
+ sharedRepo: SHARED_REPO_URL
16146
16148
  };
16147
16149
  function loadConfig(userConfigPath) {
16148
16150
  const userCfg = existsSync5(userConfigPath) ? JSON.parse(readFileSync4(userConfigPath, "utf-8")) : {};
@@ -21226,15 +21228,246 @@ function stopReminderText() {
21226
21228
  return [
21227
21229
  "[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",
21228
21230
  "\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",
21229
- "\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"
21231
+ "\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",
21232
+ "\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"
21230
21233
  ].join("\n");
21231
21234
  }
21232
21235
 
21236
+ // ../../packages/core/dist/push.js
21237
+ import { spawnSync } from "node:child_process";
21238
+ import {
21239
+ copyFileSync,
21240
+ existsSync as existsSync7,
21241
+ mkdirSync as mkdirSync3,
21242
+ readdirSync as readdirSync4,
21243
+ readFileSync as readFileSync5
21244
+ } from "node:fs";
21245
+ import { dirname as dirname4, join as join7, relative as relative2 } from "node:path";
21246
+ async function pushEntry(opts) {
21247
+ if (!checkGhAvailable()) {
21248
+ return {
21249
+ status: "gh-missing",
21250
+ detail: "gh (GitHub CLI) is required. Install: https://cli.github.com/"
21251
+ };
21252
+ }
21253
+ if (!checkGhAuthed()) {
21254
+ return {
21255
+ status: "gh-unauthed",
21256
+ detail: "gh is not authenticated. Run `gh auth login`."
21257
+ };
21258
+ }
21259
+ const validation = validateCommunityUrl(opts.sharedRepoUrl);
21260
+ if (!validation.valid) {
21261
+ return { status: "failed", detail: `sharedRepo URL invalid: ${validation.reason}` };
21262
+ }
21263
+ const [sharedOwner, sharedName] = parseOwnerRepo(opts.sharedRepoUrl);
21264
+ if (!sharedOwner || !sharedName) {
21265
+ return { status: "failed", detail: `cannot parse owner/repo from ${opts.sharedRepoUrl}` };
21266
+ }
21267
+ const owned = findOwnedEntry(opts.entriesDir, opts.id);
21268
+ if (!owned) {
21269
+ return {
21270
+ status: "not-found",
21271
+ detail: `entry id=${opts.id} not found under ${opts.entriesDir}`
21272
+ };
21273
+ }
21274
+ const ghUser = resolveGhUser();
21275
+ if (!ghUser) {
21276
+ return { status: "failed", detail: "gh api user failed" };
21277
+ }
21278
+ if (opts.dryRun) {
21279
+ return {
21280
+ status: "dry-run",
21281
+ plannedSteps: [
21282
+ `fork ${sharedOwner}/${sharedName} as ${ghUser}/${sharedName}`,
21283
+ `clone fork \u2192 ${forkStagingDir(opts.caveatHome)}`,
21284
+ `copy entry \u2192 entries/${owned.relPath}`,
21285
+ "commit, push branch, open PR against main"
21286
+ ]
21287
+ };
21288
+ }
21289
+ try {
21290
+ ensureFork(sharedOwner, sharedName);
21291
+ const stagingDir = forkStagingDir(opts.caveatHome);
21292
+ ensureStagingClone(stagingDir, ghUser, sharedName, sharedOwner);
21293
+ const branch = `caveat-push-${owned.id}-${Date.now().toString(36)}`;
21294
+ run("git", ["checkout", "-b", branch], { cwd: stagingDir });
21295
+ const destPath = join7(stagingDir, "entries", owned.relPath);
21296
+ mkdirSync3(dirname4(destPath), { recursive: true });
21297
+ copyFileSync(owned.absPath, destPath);
21298
+ run("git", ["add", join7("entries", owned.relPath)], { cwd: stagingDir });
21299
+ const isUpdate = upstreamAlreadyHas(stagingDir, "entries/" + owned.relPath);
21300
+ const verb = isUpdate ? "update" : "add";
21301
+ const commitMsg = `${verb}: ${owned.title}`;
21302
+ run("git", ["commit", "-m", commitMsg], { cwd: stagingDir });
21303
+ run("git", ["push", "--set-upstream", "origin", branch], { cwd: stagingDir });
21304
+ const prTitle = `${verb}: ${owned.title}`;
21305
+ const prBody = [
21306
+ `Contribution of caveat \`${owned.id}\` via \`caveat push\`.`,
21307
+ "",
21308
+ `- entry path: \`entries/${owned.relPath}\``,
21309
+ `- action: ${verb}`,
21310
+ "",
21311
+ "Merged PRs will appear in subscribers' repos on their next `caveat pull`."
21312
+ ].join("\n");
21313
+ const pr = spawnSync(
21314
+ "gh",
21315
+ [
21316
+ "pr",
21317
+ "create",
21318
+ "--repo",
21319
+ `${sharedOwner}/${sharedName}`,
21320
+ "--base",
21321
+ "main",
21322
+ "--head",
21323
+ `${ghUser}:${branch}`,
21324
+ "--title",
21325
+ prTitle,
21326
+ "--body",
21327
+ prBody
21328
+ ],
21329
+ { cwd: stagingDir, encoding: "utf-8", shell: true }
21330
+ );
21331
+ if (pr.status !== 0) {
21332
+ return {
21333
+ status: "failed",
21334
+ detail: (typeof pr.stderr === "string" ? pr.stderr : String(pr.stderr ?? "")).trim() || "gh pr create failed"
21335
+ };
21336
+ }
21337
+ const prUrl = (typeof pr.stdout === "string" ? pr.stdout : String(pr.stdout ?? "")).trim();
21338
+ return { status: "ok", prUrl };
21339
+ } catch (err) {
21340
+ return {
21341
+ status: "failed",
21342
+ detail: err instanceof Error ? err.message : String(err)
21343
+ };
21344
+ }
21345
+ }
21346
+ function checkGhAvailable() {
21347
+ const r2 = spawnSync("gh", ["--version"], { encoding: "utf-8", shell: true });
21348
+ return r2.status === 0;
21349
+ }
21350
+ function checkGhAuthed() {
21351
+ const r2 = spawnSync("gh", ["auth", "status"], { encoding: "utf-8", shell: true });
21352
+ return r2.status === 0;
21353
+ }
21354
+ function resolveGhUser() {
21355
+ const r2 = spawnSync("gh", ["api", "user", "--jq", ".login"], {
21356
+ encoding: "utf-8",
21357
+ shell: true
21358
+ });
21359
+ if (r2.status !== 0) return void 0;
21360
+ return typeof r2.stdout === "string" ? r2.stdout.trim() : "";
21361
+ }
21362
+ function parseOwnerRepo(url) {
21363
+ const m = /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(\.git)?\/?$/.exec(url);
21364
+ if (!m) return [void 0, void 0];
21365
+ return [m[1], m[2]];
21366
+ }
21367
+ function findOwnedEntry(entriesDir, id) {
21368
+ if (!existsSync7(entriesDir)) return void 0;
21369
+ for (const categoryDirent of readdirSync4(entriesDir, { withFileTypes: true })) {
21370
+ if (!categoryDirent.isDirectory()) continue;
21371
+ const categoryDir = join7(entriesDir, categoryDirent.name);
21372
+ for (const fileDirent of readdirSync4(categoryDir, { withFileTypes: true })) {
21373
+ if (!fileDirent.isFile() || !fileDirent.name.endsWith(".md")) continue;
21374
+ const absPath = join7(categoryDir, fileDirent.name);
21375
+ const raw2 = readFileSync5(absPath, "utf-8");
21376
+ try {
21377
+ const parsed = parseMarkdown(raw2);
21378
+ if (parsed.frontmatter.id === id) {
21379
+ const relPath = relative2(entriesDir, absPath).replace(/\\/g, "/");
21380
+ return { id, title: parsed.frontmatter.title, absPath, relPath };
21381
+ }
21382
+ } catch {
21383
+ }
21384
+ }
21385
+ }
21386
+ return void 0;
21387
+ }
21388
+ function forkStagingDir(caveatHome) {
21389
+ return join7(caveatHome, "push-fork");
21390
+ }
21391
+ function ensureFork(owner, repo) {
21392
+ spawnSync(
21393
+ "gh",
21394
+ ["repo", "fork", `${owner}/${repo}`, "--clone=false", "--remote=false"],
21395
+ { encoding: "utf-8", shell: true }
21396
+ );
21397
+ }
21398
+ function ensureStagingClone(stagingDir, ghUser, sharedName, upstreamOwner) {
21399
+ if (!existsSync7(stagingDir)) {
21400
+ mkdirSync3(dirname4(stagingDir), { recursive: true });
21401
+ const forkUrl = `https://github.com/${ghUser}/${sharedName}.git`;
21402
+ run("git", ["clone", "--depth", "30", forkUrl, stagingDir]);
21403
+ run(
21404
+ "git",
21405
+ ["remote", "add", "upstream", `https://github.com/${upstreamOwner}/${sharedName}.git`],
21406
+ { cwd: stagingDir }
21407
+ );
21408
+ } else {
21409
+ run("git", ["checkout", "main"], { cwd: stagingDir });
21410
+ run("git", ["fetch", "upstream", "main"], { cwd: stagingDir });
21411
+ run("git", ["reset", "--hard", "upstream/main"], { cwd: stagingDir });
21412
+ run("git", ["push", "origin", "main", "--force-with-lease"], { cwd: stagingDir });
21413
+ }
21414
+ }
21415
+ function upstreamAlreadyHas(stagingDir, relFromRoot) {
21416
+ return existsSync7(join7(stagingDir, relFromRoot));
21417
+ }
21418
+ function run(command, args, opts = {}) {
21419
+ const r2 = spawnSync(command, args, {
21420
+ encoding: "utf-8",
21421
+ cwd: opts.cwd,
21422
+ shell: true
21423
+ });
21424
+ if (r2.status !== 0) {
21425
+ const stderr = typeof r2.stderr === "string" ? r2.stderr : String(r2.stderr ?? "");
21426
+ const stdout = typeof r2.stdout === "string" ? r2.stdout : String(r2.stdout ?? "");
21427
+ throw new Error(`${command} ${args.join(" ")} failed: ${(stderr || stdout || "unknown").trim()}`);
21428
+ }
21429
+ }
21430
+
21431
+ // ../../packages/core/dist/pullShared.js
21432
+ import { existsSync as existsSync8, readdirSync as readdirSync5 } from "node:fs";
21433
+ import { join as join8 } from "node:path";
21434
+ async function pullShared(opts) {
21435
+ const result = { pulled: [], indexed: [] };
21436
+ if (!existsSync8(opts.communityDir)) {
21437
+ return result;
21438
+ }
21439
+ const pulls = await communityPull({
21440
+ communityDir: opts.communityDir,
21441
+ logger: opts.logger
21442
+ });
21443
+ for (const p2 of pulls) {
21444
+ result.pulled.push({
21445
+ handle: p2.handle,
21446
+ status: p2.status,
21447
+ message: p2.message
21448
+ });
21449
+ }
21450
+ rebuildAll(opts.db);
21451
+ if (existsSync8(opts.entriesDir)) {
21452
+ const own = scanSource({ db: opts.db, source: "own", entriesRoot: opts.entriesDir });
21453
+ result.indexed.push({ source: "own", ...own });
21454
+ }
21455
+ for (const entry of readdirSync5(opts.communityDir, { withFileTypes: true })) {
21456
+ if (!entry.isDirectory()) continue;
21457
+ const source = `community/${entry.name}`;
21458
+ const root = join8(opts.communityDir, entry.name, "entries");
21459
+ if (!existsSync8(root)) continue;
21460
+ const scan = scanSource({ db: opts.db, source, entriesRoot: root });
21461
+ result.indexed.push({ source, ...scan });
21462
+ }
21463
+ return result;
21464
+ }
21465
+
21233
21466
  // src/context.ts
21234
21467
  function buildContext(logger, overrides = {}) {
21235
21468
  const userHome = overrides.userHome ?? homedir();
21236
21469
  const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
21237
- const userConfigPath = join7(userHome, ".caveatrc.json");
21470
+ const userConfigPath = join9(userHome, ".caveatrc.json");
21238
21471
  const config3 = loadConfig(userConfigPath);
21239
21472
  const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
21240
21473
  return { caveatHome, userHome, userConfigPath, config: config3, paths, logger };
@@ -21251,13 +21484,13 @@ var stdoutLogger = {
21251
21484
  };
21252
21485
 
21253
21486
  // src/commands/init.ts
21254
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "node:fs";
21255
- import { join as join9 } from "node:path";
21487
+ import { existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
21488
+ import { join as join11 } from "node:path";
21256
21489
 
21257
21490
  // src/claudeInstall.ts
21258
- import { spawnSync } from "node:child_process";
21259
- import { copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
21260
- import { dirname as dirname4, join as join8 } from "node:path";
21491
+ import { spawnSync as spawnSync2 } from "node:child_process";
21492
+ import { copyFileSync as copyFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
21493
+ import { dirname as dirname5, join as join10 } from "node:path";
21261
21494
  var EVENT_USER_PROMPT_SUBMIT = "UserPromptSubmit";
21262
21495
  var EVENT_STOP = "Stop";
21263
21496
  function quote(p2) {
@@ -21291,16 +21524,16 @@ function removeHook(settings, event, command) {
21291
21524
  return true;
21292
21525
  }
21293
21526
  function readSettings(path) {
21294
- if (!existsSync7(path)) return {};
21295
- return JSON.parse(readFileSync5(path, "utf-8"));
21527
+ if (!existsSync9(path)) return {};
21528
+ return JSON.parse(readFileSync6(path, "utf-8"));
21296
21529
  }
21297
21530
  function writeSettings(path, settings) {
21298
- const dir = dirname4(path);
21299
- if (!existsSync7(dir)) mkdirSync3(dir, { recursive: true });
21531
+ const dir = dirname5(path);
21532
+ if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
21300
21533
  let backupPath = "";
21301
- if (existsSync7(path)) {
21534
+ if (existsSync9(path)) {
21302
21535
  backupPath = `${path}.caveat-backup-${Date.now()}`;
21303
- copyFileSync(path, backupPath);
21536
+ copyFileSync2(path, backupPath);
21304
21537
  }
21305
21538
  writeFileSync4(path, JSON.stringify(settings, null, 2) + "\n", "utf-8");
21306
21539
  return backupPath;
@@ -21311,7 +21544,7 @@ function shellQuote(s) {
21311
21544
  }
21312
21545
  function runClaude(args) {
21313
21546
  const line = [CLAUDE_BIN, ...args].map(shellQuote).join(" ");
21314
- return spawnSync(line, { shell: true, encoding: "utf-8" });
21547
+ return spawnSync2(line, { shell: true, encoding: "utf-8" });
21315
21548
  }
21316
21549
  function registerMcp(nodePath, cliScriptPath, dryRun, logger) {
21317
21550
  const args = mcpArgs(cliScriptPath);
@@ -21361,7 +21594,7 @@ function unregisterMcp(dryRun, logger) {
21361
21594
  return { action: "skipped", detail: "not registered or removal failed" };
21362
21595
  }
21363
21596
  function installClaudeIntegration(opts) {
21364
- const settingsPath = join8(opts.claudeDir, "settings.json");
21597
+ const settingsPath = join10(opts.claudeDir, "settings.json");
21365
21598
  const settings = readSettings(settingsPath);
21366
21599
  const usCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "user-prompt-submit");
21367
21600
  const stopCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "stop");
@@ -21380,7 +21613,7 @@ function installClaudeIntegration(opts) {
21380
21613
  return { mcp, hooks: { userPromptSubmit, stop }, backupPath };
21381
21614
  }
21382
21615
  function uninstallClaudeIntegration(opts) {
21383
- const settingsPath = join8(opts.claudeDir, "settings.json");
21616
+ const settingsPath = join10(opts.claudeDir, "settings.json");
21384
21617
  const settings = readSettings(settingsPath);
21385
21618
  const usCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "user-prompt-submit");
21386
21619
  const stopCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "stop");
@@ -21417,23 +21650,33 @@ var KNOWLEDGE_GITIGNORE = [
21417
21650
  "community/",
21418
21651
  ""
21419
21652
  ].join("\n");
21420
- function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
21653
+ async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRun: false }) {
21421
21654
  ensureUserConfig(ctx.userConfigPath);
21422
21655
  ctx.logger.info(`user config: ${ctx.userConfigPath}`);
21423
- if (!existsSync8(ctx.paths.knowledgeRepo)) {
21424
- mkdirSync4(ctx.paths.knowledgeRepo, { recursive: true });
21425
- mkdirSync4(ctx.paths.entriesDir, { recursive: true });
21656
+ if (!existsSync10(ctx.paths.knowledgeRepo)) {
21657
+ mkdirSync5(ctx.paths.knowledgeRepo, { recursive: true });
21658
+ mkdirSync5(ctx.paths.entriesDir, { recursive: true });
21426
21659
  ctx.logger.info(`knowledge repo scaffolded: ${ctx.paths.knowledgeRepo}`);
21427
21660
  } else {
21428
21661
  ctx.logger.info(`knowledge repo: ${ctx.paths.knowledgeRepo}`);
21429
21662
  }
21430
- const gitignorePath = join9(ctx.paths.knowledgeRepo, ".gitignore");
21431
- if (!existsSync8(gitignorePath)) {
21663
+ const gitignorePath = join11(ctx.paths.knowledgeRepo, ".gitignore");
21664
+ if (!existsSync10(gitignorePath)) {
21432
21665
  writeFileSync5(gitignorePath, KNOWLEDGE_GITIGNORE, "utf-8");
21433
21666
  ctx.logger.info(`.gitignore created: ${gitignorePath}`);
21434
21667
  }
21435
21668
  const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
21436
- db.close();
21669
+ try {
21670
+ if (!opts.skipShared && !opts.dryRun) {
21671
+ await subscribeSharedRepo(ctx, db);
21672
+ } else if (opts.skipShared) {
21673
+ ctx.logger.info("shared community DB subscription skipped (--skip-shared)");
21674
+ } else if (opts.dryRun) {
21675
+ ctx.logger.info(`[dry-run] would subscribe to shared community DB: ${ctx.config.sharedRepo}`);
21676
+ }
21677
+ } finally {
21678
+ db.close();
21679
+ }
21437
21680
  ctx.logger.info(`db initialized: ${ctx.paths.dbPath}`);
21438
21681
  if (opts.skipClaude) {
21439
21682
  ctx.logger.info("Claude Code integration skipped (--skip-claude)");
@@ -21445,7 +21688,7 @@ function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
21445
21688
  return;
21446
21689
  }
21447
21690
  const result = installClaudeIntegration({
21448
- claudeDir: join9(ctx.userHome, ".claude"),
21691
+ claudeDir: join11(ctx.userHome, ".claude"),
21449
21692
  cliScriptPath,
21450
21693
  nodePath: process.execPath,
21451
21694
  dryRun: opts.dryRun,
@@ -21453,6 +21696,52 @@ function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
21453
21696
  });
21454
21697
  reportInstallResult(ctx, result, opts.dryRun);
21455
21698
  }
21699
+ async function subscribeSharedRepo(ctx, db) {
21700
+ const url = ctx.config.sharedRepo;
21701
+ const validation = validateCommunityUrl(url);
21702
+ if (!validation.valid) {
21703
+ ctx.logger.warn(
21704
+ `sharedRepo is not a valid GitHub URL \u2014 skipping: ${validation.reason}`
21705
+ );
21706
+ return;
21707
+ }
21708
+ const handle = validation.handle;
21709
+ const target = join11(ctx.paths.communityDir, handle);
21710
+ if (!existsSync10(target)) {
21711
+ if (!existsSync10(ctx.paths.communityDir)) {
21712
+ mkdirSync5(ctx.paths.communityDir, { recursive: true });
21713
+ }
21714
+ try {
21715
+ await communityAdd({
21716
+ url,
21717
+ communityDir: ctx.paths.communityDir,
21718
+ logger: ctx.logger
21719
+ });
21720
+ ctx.logger.info(`shared community DB subscribed: ${url} \u2192 community/${handle}/`);
21721
+ } catch (err) {
21722
+ ctx.logger.warn(
21723
+ `shared community DB subscription failed: ${err instanceof Error ? err.message : String(err)}`
21724
+ );
21725
+ return;
21726
+ }
21727
+ } else {
21728
+ ctx.logger.info(`shared community DB already subscribed: community/${handle}/`);
21729
+ }
21730
+ if (existsSync10(ctx.paths.communityDir)) {
21731
+ rebuildAll(db);
21732
+ if (existsSync10(ctx.paths.entriesDir)) {
21733
+ scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
21734
+ }
21735
+ for (const entry of readdirSync6(ctx.paths.communityDir, { withFileTypes: true })) {
21736
+ if (!entry.isDirectory()) continue;
21737
+ const source = `community/${entry.name}`;
21738
+ const root = join11(ctx.paths.communityDir, entry.name, "entries");
21739
+ if (!existsSync10(root)) continue;
21740
+ const result = scanSource({ db, source, entriesRoot: root });
21741
+ ctx.logger.info(`indexed ${source}: +${result.added}`);
21742
+ }
21743
+ }
21744
+ }
21456
21745
  function runUninstall(ctx, opts) {
21457
21746
  const cliScriptPath = process.argv[1];
21458
21747
  if (!cliScriptPath) {
@@ -21460,7 +21749,7 @@ function runUninstall(ctx, opts) {
21460
21749
  process.exit(1);
21461
21750
  }
21462
21751
  const result = uninstallClaudeIntegration({
21463
- claudeDir: join9(ctx.userHome, ".claude"),
21752
+ claudeDir: join11(ctx.userHome, ".claude"),
21464
21753
  cliScriptPath,
21465
21754
  nodePath: process.execPath,
21466
21755
  dryRun: opts.dryRun,
@@ -21497,29 +21786,29 @@ function reportInstallResult(ctx, result, dryRun) {
21497
21786
  }
21498
21787
 
21499
21788
  // src/commands/indexCmd.ts
21500
- import { existsSync as existsSync9, readdirSync as readdirSync4, mkdirSync as mkdirSync5 } from "node:fs";
21501
- import { dirname as dirname5, join as join10 } from "node:path";
21789
+ import { existsSync as existsSync11, readdirSync as readdirSync7, mkdirSync as mkdirSync6 } from "node:fs";
21790
+ import { dirname as dirname6, join as join12 } from "node:path";
21502
21791
  function runIndex(ctx, opts) {
21503
- const dbDir = dirname5(ctx.paths.dbPath);
21504
- if (!existsSync9(dbDir)) mkdirSync5(dbDir, { recursive: true });
21792
+ const dbDir = dirname6(ctx.paths.dbPath);
21793
+ if (!existsSync11(dbDir)) mkdirSync6(dbDir, { recursive: true });
21505
21794
  const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
21506
21795
  try {
21507
21796
  if (opts.full) {
21508
21797
  ctx.logger.info("full rebuild: DELETE FROM entries");
21509
21798
  rebuildAll(db);
21510
21799
  }
21511
- if (existsSync9(ctx.paths.entriesDir)) {
21800
+ if (existsSync11(ctx.paths.entriesDir)) {
21512
21801
  const result = scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
21513
21802
  ctx.logger.info(`own: +${result.added} ~${result.updated} -${result.deleted}`);
21514
21803
  } else {
21515
21804
  ctx.logger.warn(`entries dir not found: ${ctx.paths.entriesDir}`);
21516
21805
  }
21517
- if (existsSync9(ctx.paths.communityDir)) {
21518
- for (const entry of readdirSync4(ctx.paths.communityDir, { withFileTypes: true })) {
21806
+ if (existsSync11(ctx.paths.communityDir)) {
21807
+ for (const entry of readdirSync7(ctx.paths.communityDir, { withFileTypes: true })) {
21519
21808
  if (!entry.isDirectory()) continue;
21520
21809
  const source = `community/${entry.name}`;
21521
- const root = join10(ctx.paths.communityDir, entry.name, "entries");
21522
- if (!existsSync9(root)) continue;
21810
+ const root = join12(ctx.paths.communityDir, entry.name, "entries");
21811
+ if (!existsSync11(root)) continue;
21523
21812
  const result = scanSource({ db, source, entriesRoot: root });
21524
21813
  ctx.logger.info(`${source}: +${result.added} ~${result.updated} -${result.deleted}`);
21525
21814
  }
@@ -22293,11 +22582,11 @@ var serve = (options2, listeningListener) => {
22293
22582
 
22294
22583
  // ../web/dist/context.js
22295
22584
  import { homedir as homedir2 } from "node:os";
22296
- import { join as join11 } from "node:path";
22585
+ import { join as join13 } from "node:path";
22297
22586
  function buildWebContext(overrides = {}) {
22298
22587
  const userHome = overrides.userHome ?? homedir2();
22299
22588
  const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
22300
- const userConfigPath = join11(userHome, ".caveatrc.json");
22589
+ const userConfigPath = join13(userHome, ".caveatrc.json");
22301
22590
  const logger = overrides.logger ?? stderrLogger;
22302
22591
  const config3 = loadConfig(userConfigPath);
22303
22592
  const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
@@ -30024,14 +30313,14 @@ function createDetailRoute(ctx) {
30024
30313
  }
30025
30314
 
30026
30315
  // ../web/dist/routes/community.js
30027
- import { existsSync as existsSync10, readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
30028
- import { join as join12 } from "node:path";
30316
+ import { existsSync as existsSync12, readdirSync as readdirSync8, statSync as statSync4 } from "node:fs";
30317
+ import { join as join14 } from "node:path";
30029
30318
  function listCommunity(communityDir, db) {
30030
- if (!existsSync10(communityDir)) return [];
30319
+ if (!existsSync12(communityDir)) return [];
30031
30320
  const handles = [];
30032
- for (const entry of readdirSync5(communityDir, { withFileTypes: true })) {
30321
+ for (const entry of readdirSync8(communityDir, { withFileTypes: true })) {
30033
30322
  if (!entry.isDirectory()) continue;
30034
- const handlePath = join12(communityDir, entry.name);
30323
+ const handlePath = join14(communityDir, entry.name);
30035
30324
  const countRow = db.prepare("SELECT COUNT(*) AS n FROM entries WHERE source = ?").get(`community/${entry.name}`);
30036
30325
  handles.push({
30037
30326
  handle: entry.name,
@@ -44329,11 +44618,11 @@ var StdioServerTransport = class {
44329
44618
 
44330
44619
  // ../mcp/dist/context.js
44331
44620
  import { homedir as homedir3 } from "node:os";
44332
- import { join as join13 } from "node:path";
44621
+ import { join as join15 } from "node:path";
44333
44622
  function buildMcpContext(overrides = {}) {
44334
44623
  const userHome = overrides.userHome ?? homedir3();
44335
44624
  const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
44336
- const userConfigPath = join13(userHome, ".caveatrc.json");
44625
+ const userConfigPath = join15(userHome, ".caveatrc.json");
44337
44626
  const logger = overrides.logger ?? stderrLogger;
44338
44627
  const config3 = loadConfig(userConfigPath);
44339
44628
  const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
@@ -44482,6 +44771,48 @@ function handleIngestResearch(ctx, args) {
44482
44771
  );
44483
44772
  }
44484
44773
 
44774
+ // ../mcp/dist/tools/pull.js
44775
+ var pullInputShape = {};
44776
+ async function handlePull(ctx, _args = {}) {
44777
+ const result = await pullShared({
44778
+ communityDir: ctx.paths.communityDir,
44779
+ entriesDir: ctx.paths.entriesDir,
44780
+ db: ctx.db,
44781
+ logger: ctx.logger
44782
+ });
44783
+ return {
44784
+ pulled: result.pulled,
44785
+ indexed: result.indexed.map((i2) => ({
44786
+ source: i2.source,
44787
+ added: i2.added,
44788
+ updated: i2.updated,
44789
+ deleted: i2.deleted
44790
+ }))
44791
+ };
44792
+ }
44793
+
44794
+ // ../mcp/dist/tools/push.js
44795
+ var pushInputShape = {
44796
+ id: external_exports.string().min(1).describe("Entry id (frontmatter.id) to push to the shared community DB"),
44797
+ dry_run: external_exports.boolean().optional().describe("If true, compute the plan but do not modify GitHub or local git state")
44798
+ };
44799
+ async function handlePush(ctx, args) {
44800
+ const result = await pushEntry({
44801
+ entriesDir: ctx.paths.entriesDir,
44802
+ caveatHome: ctx.caveatHome,
44803
+ sharedRepoUrl: ctx.config.sharedRepo,
44804
+ id: args.id,
44805
+ dryRun: args.dry_run ?? false,
44806
+ logger: ctx.logger
44807
+ });
44808
+ return {
44809
+ status: result.status,
44810
+ detail: result.detail,
44811
+ pr_url: result.prUrl,
44812
+ planned_steps: result.plannedSteps
44813
+ };
44814
+ }
44815
+
44485
44816
  // ../mcp/dist/registerTools.js
44486
44817
  function jsonResult(data) {
44487
44818
  return {
@@ -44557,6 +44888,24 @@ function registerAllTools(server, ctx) {
44557
44888
  },
44558
44889
  async (args) => jsonResult(handleIngestResearch(ctx, args))
44559
44890
  );
44891
+ server.registerTool(
44892
+ "caveat_pull",
44893
+ {
44894
+ title: "caveat_pull",
44895
+ 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.",
44896
+ inputSchema: pullInputShape
44897
+ },
44898
+ async (args) => jsonResult(await handlePull(ctx, args))
44899
+ );
44900
+ server.registerTool(
44901
+ "caveat_push",
44902
+ {
44903
+ title: "caveat_push",
44904
+ 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.",
44905
+ inputSchema: pushInputShape
44906
+ },
44907
+ async (args) => jsonResult(await handlePush(ctx, args))
44908
+ );
44560
44909
  }
44561
44910
 
44562
44911
  // ../mcp/dist/server.js
@@ -44648,6 +44997,78 @@ async function runHook(name) {
44648
44997
  process.exit(0);
44649
44998
  }
44650
44999
 
45000
+ // src/commands/pull.ts
45001
+ import { existsSync as existsSync13 } from "node:fs";
45002
+ async function runPull(ctx) {
45003
+ if (!existsSync13(ctx.paths.communityDir)) {
45004
+ ctx.logger.info(
45005
+ "no community repos yet \u2014 did you run `caveat init`? You can also `caveat community add <url>` manually."
45006
+ );
45007
+ return;
45008
+ }
45009
+ const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
45010
+ try {
45011
+ const result = await pullShared({
45012
+ communityDir: ctx.paths.communityDir,
45013
+ entriesDir: ctx.paths.entriesDir,
45014
+ db,
45015
+ logger: ctx.logger
45016
+ });
45017
+ for (const p2 of result.pulled) {
45018
+ if (p2.status === "ok") {
45019
+ ctx.logger.info(`${p2.handle}: pulled`);
45020
+ } else {
45021
+ ctx.logger.warn(`${p2.handle}: FAILED \u2014 ${p2.message ?? "unknown"}`);
45022
+ }
45023
+ }
45024
+ for (const i2 of result.indexed) {
45025
+ ctx.logger.info(`${i2.source}: +${i2.added}`);
45026
+ }
45027
+ } finally {
45028
+ db.close();
45029
+ }
45030
+ }
45031
+
45032
+ // src/commands/push.ts
45033
+ async function runPush(ctx, opts) {
45034
+ const result = await pushEntry({
45035
+ entriesDir: ctx.paths.entriesDir,
45036
+ caveatHome: ctx.caveatHome,
45037
+ sharedRepoUrl: ctx.config.sharedRepo,
45038
+ id: opts.id,
45039
+ dryRun: opts.dryRun,
45040
+ logger: ctx.logger
45041
+ });
45042
+ switch (result.status) {
45043
+ case "ok":
45044
+ ctx.logger.info(`PR opened: ${result.prUrl ?? ""}`);
45045
+ ctx.logger.info(
45046
+ "Once merged, other subscribers will see your entry after their next `caveat pull`."
45047
+ );
45048
+ break;
45049
+ case "dry-run":
45050
+ for (const [i2, step] of (result.plannedSteps ?? []).entries()) {
45051
+ ctx.logger.info(`[dry-run] ${i2 + 1}. ${step}`);
45052
+ }
45053
+ break;
45054
+ case "not-found":
45055
+ ctx.logger.error(
45056
+ `${result.detail}. List entries with \`caveat list\` or write + index first.`
45057
+ );
45058
+ process.exitCode = 1;
45059
+ break;
45060
+ case "gh-missing":
45061
+ case "gh-unauthed":
45062
+ ctx.logger.error(result.detail ?? "gh CLI issue");
45063
+ process.exitCode = 1;
45064
+ break;
45065
+ case "failed":
45066
+ ctx.logger.error(result.detail ?? "unknown failure");
45067
+ process.exitCode = 1;
45068
+ break;
45069
+ }
45070
+ }
45071
+
44651
45072
  // src/commands/community.ts
44652
45073
  async function runCommunityAdd(ctx, url) {
44653
45074
  try {
@@ -44700,12 +45121,16 @@ function runCommunityList(ctx) {
44700
45121
 
44701
45122
  // src/index.ts
44702
45123
  var program = new Command();
44703
- program.name("caveat").description("External spec gotcha knowledge base CLI").version("0.2.1");
45124
+ program.name("caveat").description("External spec gotcha knowledge base CLI").version("0.4.0");
44704
45125
  program.command("init").description(
44705
- "Initialize ~/.caveatrc.json, ~/.caveat/, and register Claude Code integration"
44706
- ).option("--skip-claude", "skip Claude Code MCP + hook registration", false).option("--dry-run", "show planned changes without writing", false).action((opts) => {
45126
+ "Initialize ~/.caveatrc.json, ~/.caveat/, subscribe to the shared community DB, and register Claude Code integration"
45127
+ ).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) => {
44707
45128
  const ctx = buildContext(stdoutLogger);
44708
- runInit(ctx, { skipClaude: opts.skipClaude, dryRun: opts.dryRun });
45129
+ await runInit(ctx, {
45130
+ skipClaude: opts.skipClaude,
45131
+ skipShared: opts.skipShared,
45132
+ dryRun: opts.dryRun
45133
+ });
44709
45134
  });
44710
45135
  program.command("uninstall").description("Remove Claude Code MCP server and hooks registered by `caveat init`").option("--dry-run", "show planned changes without writing", false).action((opts) => {
44711
45136
  const ctx = buildContext(stdoutLogger);
@@ -44738,6 +45163,16 @@ program.command("stats").description("Show aggregate stats").action(() => {
44738
45163
  const ctx = buildContext(stdoutLogger);
44739
45164
  runStats(ctx);
44740
45165
  });
45166
+ program.command("pull").description(
45167
+ "Refresh all subscribed community repos (incl. shared DB) and re-index. Use this to receive contributions others have merged."
45168
+ ).action(async () => {
45169
+ const ctx = buildContext(stdoutLogger);
45170
+ await runPull(ctx);
45171
+ });
45172
+ 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) => {
45173
+ const ctx = buildContext(stdoutLogger);
45174
+ await runPush(ctx, { id, dryRun: opts.dryRun });
45175
+ });
44741
45176
  program.command("serve").description("Start the read-only web share portal").option("--port <n>", "port number", (v) => Number(v), 4242).action((opts) => {
44742
45177
  runServe({ port: opts.port });
44743
45178
  });