caveat-cli 0.14.9 → 0.15.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
@@ -2,9 +2,11 @@
2
2
  import { createRequire as __caveat_createRequire } from 'node:module';
3
3
  const require = __caveat_createRequire(import.meta.url);
4
4
  import {
5
+ KNOWLEDGE_GITIGNORE,
5
6
  __commonJS,
6
7
  __export,
7
8
  __toESM,
9
+ acquireReindexLock,
8
10
  appendPendingReminder,
9
11
  buildCodexSidecarDiagnosticsCommand,
10
12
  buildCodexSidecarReadOnlySmokeCommand,
@@ -15,6 +17,7 @@ import {
15
17
  communityList,
16
18
  communityPull,
17
19
  communityRemove,
20
+ computeEntriesDigest,
18
21
  decideCodexSidecarExecution,
19
22
  defaultSelfIdentityTokens,
20
23
  drainPendingReminders,
@@ -23,26 +26,34 @@ import {
23
26
  findCaveatsForPrompt,
24
27
  get,
25
28
  hasAnyStruggleSignal,
29
+ initOwnSync,
26
30
  listRecent,
27
31
  listStale,
28
32
  loadConfig,
29
33
  markHit,
30
34
  maybeSweepPendingDirs,
31
35
  openDb,
36
+ publishOwn,
32
37
  readCodexSessionSignals,
38
+ readDigestMarker,
33
39
  readSessionSignals,
34
40
  rebuildAll,
35
41
  recordEntry,
42
+ reindexAllSources,
43
+ releaseReindexLock,
36
44
  resolvePaths,
37
45
  scanSource,
38
46
  search,
39
47
  stderrLogger,
40
48
  stopReminderText,
41
49
  struggleSearchText,
50
+ syncOwn,
42
51
  toolErrorReminderText,
43
52
  updateEntry,
44
- userPromptSubmitReminderText
45
- } from "./chunk-ECC5LJZF.js";
53
+ userPromptSubmitReminderText,
54
+ writeDigestMarker,
55
+ writeUserConfigPatch
56
+ } from "./chunk-Z2XLYCR3.js";
46
57
 
47
58
  // ../../node_modules/.pnpm/ajv@8.18.0/node_modules/ajv/dist/compile/codegen/code.js
48
59
  var require_code = __commonJS({
@@ -7085,15 +7096,6 @@ function resolveHookNodePath({
7085
7096
  }
7086
7097
 
7087
7098
  // src/commands/init.ts
7088
- var KNOWLEDGE_GITIGNORE = [
7089
- "# Never commit entries flagged private (visibility: private is enforced by the",
7090
- "# pre-commit gate, but this is a filename-level backup guard).",
7091
- "*.private.md",
7092
- "",
7093
- "# Obsidian per-user config: workspace layout, theme, plugin state, cache.",
7094
- ".obsidian/",
7095
- ""
7096
- ].join("\n");
7097
7099
  async function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
7098
7100
  ensureUserConfig(ctx.userConfigPath);
7099
7101
  ctx.logger.info(`user config: ${ctx.userConfigPath}`);
@@ -7114,7 +7116,12 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
7114
7116
  const dbDir = dirname3(ctx.paths.dbPath);
7115
7117
  if (!existsSync3(dbDir)) mkdirSync2(dbDir, { recursive: true });
7116
7118
  const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
7117
- db.close();
7119
+ try {
7120
+ reindexAllSources({ db, paths: ctx.paths, logger: ctx.logger });
7121
+ writeDigestMarker(ctx.caveatHome, computeEntriesDigest(ctx.paths));
7122
+ } finally {
7123
+ db.close();
7124
+ }
7118
7125
  ctx.logger.info(`db initialized: ${ctx.paths.dbPath}`);
7119
7126
  } else {
7120
7127
  ctx.logger.info(`[dry-run] db path: ${ctx.paths.dbPath}`);
@@ -7227,8 +7234,8 @@ function reportInstallResult(ctx, result, dryRun) {
7227
7234
  }
7228
7235
 
7229
7236
  // src/commands/indexCmd.ts
7230
- import { existsSync as existsSync4, readdirSync as readdirSync2, mkdirSync as mkdirSync3 } from "node:fs";
7231
- import { dirname as dirname4, join as join6 } from "node:path";
7237
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3 } from "node:fs";
7238
+ import { dirname as dirname4 } from "node:path";
7232
7239
  function runIndex(ctx, opts) {
7233
7240
  const dbDir = dirname4(ctx.paths.dbPath);
7234
7241
  if (!existsSync4(dbDir)) mkdirSync3(dbDir, { recursive: true });
@@ -7238,22 +7245,15 @@ function runIndex(ctx, opts) {
7238
7245
  ctx.logger.info("full rebuild: DELETE FROM entries");
7239
7246
  rebuildAll(db);
7240
7247
  }
7241
- if (existsSync4(ctx.paths.entriesDir)) {
7242
- const result = scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
7243
- ctx.logger.info(`own: +${result.added} ~${result.updated} -${result.deleted}`);
7244
- } else {
7245
- ctx.logger.warn(`entries dir not found: ${ctx.paths.entriesDir}`);
7246
- }
7247
- if (existsSync4(ctx.paths.communityDir)) {
7248
- for (const entry of readdirSync2(ctx.paths.communityDir, { withFileTypes: true })) {
7249
- if (!entry.isDirectory()) continue;
7250
- const source = `community/${entry.name}`;
7251
- const root = join6(ctx.paths.communityDir, entry.name, "entries");
7252
- if (!existsSync4(root)) continue;
7253
- const result = scanSource({ db, source, entriesRoot: root });
7254
- ctx.logger.info(`${source}: +${result.added} ~${result.updated} -${result.deleted}`);
7248
+ const result = reindexAllSources({ db, paths: ctx.paths, logger: ctx.logger });
7249
+ for (const [source, scan] of Object.entries(result.perSource)) {
7250
+ if (source === "own") {
7251
+ ctx.logger.info(`own: +${scan.added} ~${scan.updated} -${scan.deleted}`);
7252
+ } else {
7253
+ ctx.logger.info(`${source}: +${scan.added} ~${scan.updated} -${scan.deleted}`);
7255
7254
  }
7256
7255
  }
7256
+ writeDigestMarker(ctx.caveatHome, computeEntriesDigest(ctx.paths));
7257
7257
  } finally {
7258
7258
  db.close();
7259
7259
  }
@@ -7405,7 +7405,7 @@ function runStats(ctx) {
7405
7405
 
7406
7406
  // src/commands/serve.ts
7407
7407
  async function runServe(opts) {
7408
- const { startServer } = await import("./server-YQ4DJ2H7.js");
7408
+ const { startServer } = await import("./server-DCE3ODHI.js");
7409
7409
  const { port, host } = startServer({ port: opts.port });
7410
7410
  process.stdout.write(`[caveat] web portal: http://${host}:${port}/
7411
7411
  `);
@@ -31477,11 +31477,11 @@ var StdioServerTransport = class {
31477
31477
 
31478
31478
  // ../mcp/dist/context.js
31479
31479
  import { homedir as homedir2 } from "node:os";
31480
- import { join as join7 } from "node:path";
31480
+ import { join as join6 } from "node:path";
31481
31481
  function buildMcpContext(overrides = {}) {
31482
31482
  const userHome = overrides.userHome ?? homedir2();
31483
31483
  const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
31484
- const userConfigPath = join7(userHome, ".caveatrc.json");
31484
+ const userConfigPath = join6(userHome, ".caveatrc.json");
31485
31485
  const logger = overrides.logger ?? stderrLogger;
31486
31486
  const config2 = loadConfig(userConfigPath);
31487
31487
  const paths = resolvePaths(caveatHome, config2.knowledgeRepo, userHome);
@@ -31616,8 +31616,8 @@ function handleListRecent(ctx, args) {
31616
31616
  }
31617
31617
 
31618
31618
  // ../mcp/dist/tools/pull.js
31619
- import { existsSync as existsSync5, readdirSync as readdirSync3 } from "node:fs";
31620
- import { join as join8 } from "node:path";
31619
+ import { existsSync as existsSync5, readdirSync as readdirSync2 } from "node:fs";
31620
+ import { join as join7 } from "node:path";
31621
31621
  var pullInputShape = {};
31622
31622
  async function handlePull(ctx, _args = {}) {
31623
31623
  const pulled = [];
@@ -31637,10 +31637,10 @@ async function handlePull(ctx, _args = {}) {
31637
31637
  indexed.push({ source: "own", ...own });
31638
31638
  }
31639
31639
  if (existsSync5(ctx.paths.communityDir)) {
31640
- for (const entry of readdirSync3(ctx.paths.communityDir, { withFileTypes: true })) {
31640
+ for (const entry of readdirSync2(ctx.paths.communityDir, { withFileTypes: true })) {
31641
31641
  if (!entry.isDirectory()) continue;
31642
31642
  const source = `community/${entry.name}`;
31643
- const root = join8(ctx.paths.communityDir, entry.name, "entries");
31643
+ const root = join7(ctx.paths.communityDir, entry.name, "entries");
31644
31644
  if (!existsSync5(root)) continue;
31645
31645
  const scan = scanSource({ db: ctx.db, source, entriesRoot: root });
31646
31646
  indexed.push({ source, ...scan });
@@ -31754,9 +31754,9 @@ async function runMcpServer() {
31754
31754
  }
31755
31755
 
31756
31756
  // src/commands/hookCmd.ts
31757
- import { spawn, spawnSync as spawnSync2 } from "node:child_process";
31757
+ import { spawn as spawn2, spawnSync as spawnSync2 } from "node:child_process";
31758
31758
  import {
31759
- existsSync as existsSync6,
31759
+ existsSync as existsSync7,
31760
31760
  mkdirSync as mkdirSync4,
31761
31761
  mkdtempSync,
31762
31762
  readFileSync as readFileSync3,
@@ -31767,6 +31767,31 @@ import {
31767
31767
  import { tmpdir } from "node:os";
31768
31768
  import { join as join9 } from "node:path";
31769
31769
  import { createHash, randomBytes } from "node:crypto";
31770
+
31771
+ // src/autoReindexTrigger.ts
31772
+ import { spawn } from "node:child_process";
31773
+ import { existsSync as existsSync6 } from "node:fs";
31774
+ import { join as join8 } from "node:path";
31775
+ function maybeTriggerAutoReindex(ctx) {
31776
+ if (process.env.CAVEAT_INDEX_AUTOSYNC === "off") return;
31777
+ if (!existsSync6(ctx.paths.dbPath)) return;
31778
+ if (existsSync6(join8(ctx.caveatHome, "index", ".reindex-lock"))) return;
31779
+ const current = computeEntriesDigest(ctx.paths);
31780
+ const marker = readDigestMarker(ctx.caveatHome);
31781
+ if (marker?.digest === current.digest && marker.fileCount === current.fileCount) return;
31782
+ const cliScript = process.argv[1];
31783
+ if (!cliScript) throw new Error("current Caveat CLI script path is unavailable");
31784
+ const child = spawn(
31785
+ process.execPath,
31786
+ // execArgv を継承して親と同じランタイム(例: テストハーネスの --import tsx)で
31787
+ // worker を起動する。素の node だと TS ソース実行のテスト環境で worker が即死する。
31788
+ [...process.execArgv, "--disable-warning=ExperimentalWarning", cliScript, "hook", "reindex"],
31789
+ { detached: true, stdio: "ignore", windowsHide: true }
31790
+ );
31791
+ child.unref();
31792
+ }
31793
+
31794
+ // src/commands/hookCmd.ts
31770
31795
  var silentLogger = {
31771
31796
  info: () => {
31772
31797
  },
@@ -31815,7 +31840,7 @@ function searchCaveatsFromTextSafely(text) {
31815
31840
  let db;
31816
31841
  try {
31817
31842
  const ctx = buildContextSafely();
31818
- if (!ctx || !existsSync6(ctx.paths.dbPath)) return [];
31843
+ if (!ctx || !existsSync7(ctx.paths.dbPath)) return [];
31819
31844
  db = openDb({ path: ctx.paths.dbPath });
31820
31845
  const hits = findCaveatsForPrompt(db, text, {
31821
31846
  selfIdentity: defaultSelfIdentityTokens()
@@ -31977,7 +32002,7 @@ function spawnWorker(job) {
31977
32002
  const cliScript = process.argv[1];
31978
32003
  if (!cliScript) return;
31979
32004
  try {
31980
- const child = spawn(
32005
+ const child = spawn2(
31981
32006
  process.execPath,
31982
32007
  ["--disable-warning=ExperimentalWarning", cliScript, "hook", "worker", workFile],
31983
32008
  { detached: true, stdio: "ignore", windowsHide: true }
@@ -32021,12 +32046,65 @@ async function runWorker(workFile) {
32021
32046
  }
32022
32047
  process.exit(0);
32023
32048
  }
32049
+ function writeLastReindex(caveatHome, value) {
32050
+ try {
32051
+ writeFileSync3(join9(caveatHome, "index", ".last-reindex.json"), JSON.stringify(value), "utf-8");
32052
+ } catch (err) {
32053
+ const msg = err instanceof Error ? err.message : String(err);
32054
+ process.stderr.write(`[caveat:hook] reindex status write error: ${msg}
32055
+ `);
32056
+ }
32057
+ }
32058
+ function runReindexWorker() {
32059
+ if (process.env.CAVEAT_INDEX_AUTOSYNC === "off") {
32060
+ process.stderr.write("[caveat:hook] auto reindex disabled by CAVEAT_INDEX_AUTOSYNC=off\n");
32061
+ return;
32062
+ }
32063
+ const ctx = buildContextSafely();
32064
+ if (!ctx || !existsSync7(ctx.paths.dbPath)) {
32065
+ process.stderr.write("[caveat:hook] auto reindex skipped: index database does not exist\n");
32066
+ return;
32067
+ }
32068
+ const lock = acquireReindexLock(ctx.caveatHome);
32069
+ if (!lock) return;
32070
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
32071
+ let db;
32072
+ try {
32073
+ const digest = computeEntriesDigest(ctx.paths);
32074
+ db = openDb({ path: ctx.paths.dbPath, logger: silentLogger });
32075
+ const result = reindexAllSources({ db, paths: ctx.paths, logger: silentLogger });
32076
+ writeDigestMarker(ctx.caveatHome, digest);
32077
+ writeLastReindex(ctx.caveatHome, {
32078
+ startedAt,
32079
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
32080
+ perSource: result.perSource
32081
+ });
32082
+ } catch (err) {
32083
+ const msg = err instanceof Error ? err.message : String(err);
32084
+ process.stderr.write(`[caveat:hook] reindex error: ${msg}
32085
+ `);
32086
+ writeLastReindex(ctx.caveatHome, {
32087
+ startedAt,
32088
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
32089
+ error: msg
32090
+ });
32091
+ } finally {
32092
+ db?.close();
32093
+ try {
32094
+ releaseReindexLock(lock);
32095
+ } catch (err) {
32096
+ const msg = err instanceof Error ? err.message : String(err);
32097
+ process.stderr.write(`[caveat:hook] reindex lock release error: ${msg}
32098
+ `);
32099
+ }
32100
+ }
32101
+ }
32024
32102
  function buildToolErrorReminder(job, hits) {
32025
32103
  const base = toolErrorReminderText(hits);
32026
32104
  const mode = hookCodexSidecarMode();
32027
32105
  if (mode === "off") return base;
32028
32106
  const projectRoot = process.cwd();
32029
- const hasSidecarConfig = existsSync6(join9(projectRoot, ".codex-sidecar.yml"));
32107
+ const hasSidecarConfig = existsSync7(join9(projectRoot, ".codex-sidecar.yml"));
32030
32108
  if (mode === "auto" && !hasSidecarConfig) return base;
32031
32109
  const advisory = runCodexSidecarAdvisory({
32032
32110
  searchText: job.searchText,
@@ -32063,7 +32141,7 @@ function buildStopReminder(signals, related) {
32063
32141
  const mode = hookCodexSidecarMode();
32064
32142
  if (mode === "off") return base;
32065
32143
  const projectRoot = process.cwd();
32066
- const hasSidecarConfig = existsSync6(join9(projectRoot, ".codex-sidecar.yml"));
32144
+ const hasSidecarConfig = existsSync7(join9(projectRoot, ".codex-sidecar.yml"));
32067
32145
  if (mode === "auto" && !hasSidecarConfig) return base;
32068
32146
  const advisory = runCodexSidecarAdvisory({
32069
32147
  searchText: struggleSearchText(signals),
@@ -32178,6 +32256,10 @@ function runCodexSidecarAdvisory(input) {
32178
32256
  }
32179
32257
  }
32180
32258
  async function runHook(name, arg) {
32259
+ if (name === "reindex") {
32260
+ runReindexWorker();
32261
+ process.exit(0);
32262
+ }
32181
32263
  if (name === "worker") {
32182
32264
  if (!arg) process.exit(0);
32183
32265
  await runWorker(arg);
@@ -32224,10 +32306,22 @@ async function runHook(name, arg) {
32224
32306
  process.exit(0);
32225
32307
  }
32226
32308
  if (name === "stop") {
32227
- try {
32228
- const ctx = buildContextSafely();
32229
- if (ctx) maybeSweepPendingDirs(ctx.caveatHome);
32230
- } catch {
32309
+ const ctx = buildContextSafely();
32310
+ if (ctx) {
32311
+ try {
32312
+ maybeSweepPendingDirs(ctx.caveatHome);
32313
+ } catch (err) {
32314
+ const msg = err instanceof Error ? err.message : String(err);
32315
+ process.stderr.write(`[caveat:hook] pending sweep error: ${msg}
32316
+ `);
32317
+ }
32318
+ try {
32319
+ maybeTriggerAutoReindex(ctx);
32320
+ } catch (err) {
32321
+ const msg = err instanceof Error ? err.message : String(err);
32322
+ process.stderr.write(`[caveat:hook] auto reindex trigger error: ${msg}
32323
+ `);
32324
+ }
32231
32325
  }
32232
32326
  if (payload.stop_hook_active === true) process.exit(0);
32233
32327
  const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
@@ -32243,14 +32337,14 @@ async function runHook(name, arg) {
32243
32337
  }
32244
32338
 
32245
32339
  // src/commands/codexHookCmd.ts
32246
- import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
32247
- import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "node:fs";
32340
+ import { spawn as spawn3, spawnSync as spawnSync3 } from "node:child_process";
32341
+ import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "node:fs";
32248
32342
  import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
32249
32343
  import { join as join11 } from "node:path";
32250
32344
  import { createHash as createHash2, randomBytes as randomBytes2 } from "node:crypto";
32251
32345
 
32252
32346
  // src/codexHookInstall.ts
32253
- import { copyFileSync as copyFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
32347
+ import { copyFileSync as copyFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
32254
32348
  import { dirname as dirname5, join as join10 } from "node:path";
32255
32349
  function quote2(p) {
32256
32350
  return p.includes(" ") ? `"${p}"` : p;
@@ -32274,14 +32368,14 @@ function hasCaveatHook(hooksJson, event, fragment) {
32274
32368
  ) ?? false;
32275
32369
  }
32276
32370
  function readHooks(path) {
32277
- if (!existsSync7(path)) return {};
32371
+ if (!existsSync8(path)) return {};
32278
32372
  return JSON.parse(readFileSync4(path, "utf-8"));
32279
32373
  }
32280
32374
  function writeJsonWithBackup(path, value) {
32281
32375
  const dir = dirname5(path);
32282
- if (!existsSync7(dir)) mkdirSync5(dir, { recursive: true });
32376
+ if (!existsSync8(dir)) mkdirSync5(dir, { recursive: true });
32283
32377
  let backupPath = "";
32284
- if (existsSync7(path)) {
32378
+ if (existsSync8(path)) {
32285
32379
  backupPath = `${path}.caveat-backup-${Date.now()}`;
32286
32380
  copyFileSync2(path, backupPath);
32287
32381
  }
@@ -32358,9 +32452,9 @@ codex_hooks = true
32358
32452
  }
32359
32453
  function writeConfigWithBackup(path, text) {
32360
32454
  const dir = dirname5(path);
32361
- if (!existsSync7(dir)) mkdirSync5(dir, { recursive: true });
32455
+ if (!existsSync8(dir)) mkdirSync5(dir, { recursive: true });
32362
32456
  let backupPath = "";
32363
- if (existsSync7(path)) {
32457
+ if (existsSync8(path)) {
32364
32458
  backupPath = `${path}.caveat-backup-${Date.now()}`;
32365
32459
  copyFileSync2(path, backupPath);
32366
32460
  }
@@ -32389,7 +32483,7 @@ function installCodexHooks(opts) {
32389
32483
  hookCommand2(opts.nodePath, opts.cliScriptPath, "stop"),
32390
32484
  "stop"
32391
32485
  );
32392
- const rawConfig = existsSync7(configPath) ? readFileSync4(configPath, "utf-8") : "";
32486
+ const rawConfig = existsSync8(configPath) ? readFileSync4(configPath, "utf-8") : "";
32393
32487
  const enabled = enableCodexHooksFeature(rawConfig);
32394
32488
  const anyHookAdded = userPromptSubmit === "added" || postToolUse === "added" || stop === "added";
32395
32489
  let backupPath;
@@ -32517,7 +32611,7 @@ function searchCaveatsFromTextSafely2(text) {
32517
32611
  let db;
32518
32612
  try {
32519
32613
  const ctx = buildContextSafely2();
32520
- if (!ctx || !existsSync8(ctx.paths.dbPath)) return [];
32614
+ if (!ctx || !existsSync9(ctx.paths.dbPath)) return [];
32521
32615
  db = openDb({ path: ctx.paths.dbPath });
32522
32616
  const hits = findCaveatsForPrompt(db, text, {
32523
32617
  selfIdentity: defaultSelfIdentityTokens()
@@ -32586,7 +32680,7 @@ function processExitCodeFromText(text) {
32586
32680
  return m ? Number(m[1]) : null;
32587
32681
  }
32588
32682
  function transcriptToolOutput(transcriptPath, toolUseId) {
32589
- if (!transcriptPath || !toolUseId || !existsSync8(transcriptPath)) return null;
32683
+ if (!transcriptPath || !toolUseId || !existsSync9(transcriptPath)) return null;
32590
32684
  let raw = "";
32591
32685
  try {
32592
32686
  raw = readFileSync5(transcriptPath, "utf-8");
@@ -32873,6 +32967,14 @@ async function runCodexHook(name, arg) {
32873
32967
  process.exit(0);
32874
32968
  }
32875
32969
  if (name === "stop") {
32970
+ try {
32971
+ const ctx = buildContextSafely2();
32972
+ if (ctx) maybeTriggerAutoReindex(ctx);
32973
+ } catch (err) {
32974
+ const msg = err instanceof Error ? err.message : String(err);
32975
+ process.stderr.write(`[caveat:codex-hook] auto reindex trigger error: ${msg}
32976
+ `);
32977
+ }
32876
32978
  if (payload.stop_hook_active === true) process.exit(0);
32877
32979
  const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
32878
32980
  const signals = transcriptPath ? loadSignalsSafely2(transcriptPath) : null;
@@ -32887,19 +32989,15 @@ async function runCodexHook(name, arg) {
32887
32989
  }
32888
32990
 
32889
32991
  // src/commands/pull.ts
32890
- import { existsSync as existsSync9, readdirSync as readdirSync4 } from "node:fs";
32891
- import { join as join12 } from "node:path";
32992
+ import { existsSync as existsSync10 } from "node:fs";
32892
32993
  async function runPull(ctx) {
32893
- if (!existsSync9(ctx.paths.communityDir)) {
32994
+ const hasCommunityDir = existsSync10(ctx.paths.communityDir);
32995
+ if (!hasCommunityDir) {
32894
32996
  ctx.logger.info(
32895
32997
  "no community repos yet \u2014 add one with `caveat community add <github-url>`."
32896
32998
  );
32897
- return;
32898
32999
  }
32899
- const pulls = await communityPull({
32900
- communityDir: ctx.paths.communityDir,
32901
- logger: ctx.logger
32902
- });
33000
+ const pulls = hasCommunityDir ? await communityPull({ communityDir: ctx.paths.communityDir, logger: ctx.logger }) : [];
32903
33001
  for (const p of pulls) {
32904
33002
  if (p.status === "ok") {
32905
33003
  ctx.logger.info(`${p.handle}: pulled`);
@@ -32910,28 +33008,160 @@ async function runPull(ctx) {
32910
33008
  const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
32911
33009
  try {
32912
33010
  rebuildAll(db);
32913
- if (existsSync9(ctx.paths.entriesDir)) {
32914
- const own = scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
32915
- ctx.logger.info(`own: +${own.added}`);
32916
- }
32917
- for (const entry of readdirSync4(ctx.paths.communityDir, { withFileTypes: true })) {
32918
- if (!entry.isDirectory()) continue;
32919
- const source = `community/${entry.name}`;
32920
- const root = join12(ctx.paths.communityDir, entry.name, "entries");
32921
- if (!existsSync9(root)) continue;
32922
- const scan = scanSource({ db, source, entriesRoot: root });
33011
+ const result = reindexAllSources({ db, paths: ctx.paths, logger: ctx.logger });
33012
+ for (const [source, scan] of Object.entries(result.perSource)) {
32923
33013
  ctx.logger.info(`${source}: +${scan.added}`);
32924
33014
  }
33015
+ writeDigestMarker(ctx.caveatHome, computeEntriesDigest(ctx.paths));
32925
33016
  } finally {
32926
33017
  db.close();
32927
33018
  }
32928
33019
  }
32929
33020
 
32930
- // src/commands/codexSidecar.ts
33021
+ // src/ghSetup.ts
32931
33022
  import { spawnSync as spawnSync4 } from "node:child_process";
33023
+ import { readSync } from "node:fs";
33024
+ function runGh(args) {
33025
+ const result = spawnSync4("gh", args, { encoding: "utf-8" });
33026
+ return { status: result.status, stdout: typeof result.stdout === "string" ? result.stdout : "", stderr: typeof result.stderr === "string" ? result.stderr : "" };
33027
+ }
33028
+ function commandError(result, fallback) {
33029
+ return new Error((result.stderr || result.stdout || fallback).trim());
33030
+ }
33031
+ function askOnce(question) {
33032
+ process.stdout.write(`${question} `);
33033
+ const buf = Buffer.alloc(256);
33034
+ const bytes = readSync(0, buf, 0, buf.length, null);
33035
+ return /^(?:y|yes)$/i.test(buf.toString("utf-8", 0, bytes).trim());
33036
+ }
33037
+
33038
+ // src/commands/sync.ts
33039
+ async function defaultPrivateRepoUrl(opts, deps) {
33040
+ const version2 = deps.ghRunner(["--version"]);
33041
+ if (version2.status !== 0) {
33042
+ throw new Error(
33043
+ "gh is not available. create a private repository named Caveat-Private on GitHub, then run:\n caveat sync --init <url>\n # or install gh and run caveat sync --init"
33044
+ );
33045
+ }
33046
+ const user = deps.ghRunner(["api", "user", "-q", ".login"]);
33047
+ if (user.status !== 0 || !user.stdout.trim()) {
33048
+ throw new Error("gh is not authenticated; run `gh auth login`, then retry `caveat sync --init`");
33049
+ }
33050
+ const login = user.stdout.trim();
33051
+ const name = `${login}/Caveat-Private`;
33052
+ const view = deps.ghRunner(["repo", "view", name]);
33053
+ if (view.status !== 0) {
33054
+ const question = `create private repo github.com/${name}? [y/N]`;
33055
+ if (!opts.yes) {
33056
+ if (!deps.isTty()) {
33057
+ throw new Error(`${question}; rerun with --yes to approve non-interactively`);
33058
+ }
33059
+ if (!deps.confirm(question)) throw new Error("private repository creation cancelled");
33060
+ }
33061
+ const create = deps.ghRunner(["repo", "create", "Caveat-Private", "--private"]);
33062
+ if (create.status !== 0) throw commandError(create, "gh repo create failed");
33063
+ }
33064
+ return `https://github.com/${name}.git`;
33065
+ }
33066
+ async function runSync(ctx, opts, dependencies = {}) {
33067
+ try {
33068
+ const ghRunner = dependencies.ghRunner ?? runGh;
33069
+ const isTty = dependencies.isTty ?? (() => Boolean(process.stdin.isTTY));
33070
+ const confirm = dependencies.confirm ?? askOnce;
33071
+ const initValue = opts.init;
33072
+ if (initValue !== void 0 && initValue !== false) {
33073
+ const url2 = opts.repo ?? (typeof initValue === "string" ? initValue : await defaultPrivateRepoUrl(opts, { ghRunner, isTty, confirm }));
33074
+ const result2 = await initOwnSync({
33075
+ ownDir: ctx.paths.knowledgeRepo,
33076
+ url: url2,
33077
+ caveatHome: ctx.caveatHome,
33078
+ paths: ctx.paths,
33079
+ logger: ctx.logger,
33080
+ trustRemotePrivate: opts.trustRemotePrivate
33081
+ });
33082
+ ctx.logger.info(
33083
+ result2.remoteWasEmpty ? `initialized private sync remote: ${url2}` : `checked out existing private remote: ${url2} (branch ${result2.branch})`
33084
+ );
33085
+ return;
33086
+ }
33087
+ if (opts.repo) throw new Error("--repo requires --init");
33088
+ const result = await syncOwn({
33089
+ ownDir: ctx.paths.knowledgeRepo,
33090
+ caveatHome: ctx.caveatHome,
33091
+ paths: ctx.paths,
33092
+ logger: ctx.logger,
33093
+ trustRemotePrivate: opts.trustRemotePrivate,
33094
+ dryRun: opts.dryRun
33095
+ });
33096
+ if (result.dryRun) {
33097
+ ctx.logger.info(
33098
+ `[dry-run] ${result.changedFiles} local change(s) pending; remote ${result.pushUrls.join(", ")} (${result.probe.kind})`
33099
+ );
33100
+ } else {
33101
+ const rebased = result.pulled ? ", rebased onto remote" : "";
33102
+ ctx.logger.info(`synced ${result.branch} \u2192 origin (${result.changedFiles} local change(s)${rebased})`);
33103
+ }
33104
+ } catch (err) {
33105
+ const message = err instanceof Error ? err.message : String(err);
33106
+ ctx.logger.error(message);
33107
+ process.exitCode = 1;
33108
+ }
33109
+ }
33110
+
33111
+ // src/commands/publish.ts
33112
+ var HTTPS_TARGET_RE = /^https:\/\/github\.com\/[^/]+\/[^/]+?(?:\.git)?\/?$/;
33113
+ var SCP_TARGET_RE = /^git@github\.com:[^/]+\/[^/]+?(?:\.git)?$/;
33114
+ function validatePublishTarget(url2) {
33115
+ if (!HTTPS_TARGET_RE.test(url2) && !SCP_TARGET_RE.test(url2)) throw new Error("invalid publish target: use https://github.com/<org>/<repo>(.git) or git@github.com:<org>/<repo>(.git)");
33116
+ return url2;
33117
+ }
33118
+ async function defaultPublicRepoUrl(opts, deps) {
33119
+ const version2 = deps.ghRunner(["--version"]);
33120
+ if (version2.status !== 0) throw new Error("gh is not available. create a public repository named Caveat-Public on GitHub, then run:\n caveat publish --init <url>\n # or install gh and run caveat publish");
33121
+ const user = deps.ghRunner(["api", "user", "-q", ".login"]);
33122
+ if (user.status !== 0 || !user.stdout.trim()) throw new Error("gh is not authenticated; run `gh auth login`, then retry `caveat publish`");
33123
+ const login = user.stdout.trim();
33124
+ const name = `${login}/Caveat-Public`;
33125
+ const view = deps.ghRunner(["repo", "view", name]);
33126
+ if (view.status !== 0) {
33127
+ const question = `create public repo github.com/${name}? [y/N]`;
33128
+ if (!opts.yes) {
33129
+ if (!deps.isTty()) throw new Error(`${question}; rerun with --yes to approve non-interactively`);
33130
+ if (!deps.confirm(question)) throw new Error("public repository creation cancelled");
33131
+ }
33132
+ const create = deps.ghRunner(["repo", "create", "Caveat-Public", "--public"]);
33133
+ if (create.status !== 0) throw commandError(create, "gh repo create failed");
33134
+ }
33135
+ return `https://github.com/${name}.git`;
33136
+ }
33137
+ async function runPublish(ctx, opts, dependencies = {}) {
33138
+ try {
33139
+ const ghRunner = dependencies.ghRunner ?? runGh;
33140
+ const isTty = dependencies.isTty ?? (() => Boolean(process.stdin.isTTY));
33141
+ const confirm = dependencies.confirm ?? askOnce;
33142
+ let target = ctx.config.publishTarget;
33143
+ if (opts.init !== void 0) {
33144
+ target = validatePublishTarget(opts.init);
33145
+ writeUserConfigPatch(ctx.userConfigPath, { publishTarget: target });
33146
+ }
33147
+ if (!target) {
33148
+ target = await defaultPublicRepoUrl(opts, { ghRunner, isTty, confirm });
33149
+ writeUserConfigPatch(ctx.userConfigPath, { publishTarget: target });
33150
+ }
33151
+ const result = await publishOwn({ paths: ctx.paths, config: { ...ctx.config, publishTarget: target }, logger: ctx.logger, confirmImpl: confirm, isTty, dryRun: opts.dryRun, yes: opts.yes });
33152
+ if (result.dryRun) ctx.logger.info(`[dry-run] ${result.fileCount} public entry(ies) ready to publish`);
33153
+ else if (result.changed) ctx.logger.info(`published ${result.fileCount} public entry(ies)`);
33154
+ } catch (err) {
33155
+ ctx.logger.error(err instanceof Error ? err.message : String(err));
33156
+ process.exitCode = 1;
33157
+ }
33158
+ }
33159
+
33160
+ // src/commands/codexSidecar.ts
33161
+ import { spawnSync as spawnSync5 } from "node:child_process";
32932
33162
  import { mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
32933
33163
  import { tmpdir as tmpdir3 } from "node:os";
32934
- import { dirname as dirname6, join as join13 } from "node:path";
33164
+ import { dirname as dirname6, join as join12 } from "node:path";
32935
33165
  import { cwd, exit } from "node:process";
32936
33166
  function runCodexSidecarDiagnostics(logger, opts) {
32937
33167
  const plan = buildCodexSidecarDiagnosticsCommand({
@@ -32963,8 +33193,8 @@ function runCodexSidecarWithCaveats(ctx, workflow, prompt, opts) {
32963
33193
  process.stdout.write(JSON.stringify({ status: "skipped", decision }, null, 2) + "\n");
32964
33194
  exit(0);
32965
33195
  }
32966
- const contextDir = mkdtempSync2(join13(tmpdir3(), "caveat-sidecar-context-"));
32967
- const contextFile = join13(contextDir, "context.json");
33196
+ const contextDir = mkdtempSync2(join12(tmpdir3(), "caveat-sidecar-context-"));
33197
+ const contextFile = join12(contextDir, "context.json");
32968
33198
  let status = 1;
32969
33199
  try {
32970
33200
  const blocks = collectCaveatContextBlocks(ctx, {
@@ -33028,7 +33258,7 @@ function defaultPreset(workflow) {
33028
33258
  }
33029
33259
  function executePlan(logger, command, args, options = {}) {
33030
33260
  logger.info(`[codex-sidecar] ${command} ${args.map(shellDisplayQuote).join(" ")}`);
33031
- const result = spawnSync4(command, args, { encoding: "utf-8", stdio: "pipe" });
33261
+ const result = spawnSync5(command, args, { encoding: "utf-8", stdio: "pipe" });
33032
33262
  if (result.stdout) process.stdout.write(result.stdout);
33033
33263
  if (result.stderr) process.stderr.write(result.stderr);
33034
33264
  if (options.saveResult && result.stdout) {
@@ -33198,6 +33428,14 @@ program.command("pull").description(
33198
33428
  const ctx = buildContext(stdoutLogger);
33199
33429
  await runPull(ctx);
33200
33430
  });
33431
+ program.command("sync").description("Sync own Caveat entries to a private git remote").option("--init [url]", "initialize private remote sync (optionally with its URL)").option("--repo <url>", "private remote URL (requires --init)").option("--dry-run", "show pending own-repo changes without writing", false).option("--trust-remote-private", "continue when anonymous remote visibility cannot be determined", false).option("--yes", "approve creating the default GitHub private repository", false).action(async (opts) => {
33432
+ const ctx = buildContext(stdoutLogger);
33433
+ await runSync(ctx, opts);
33434
+ });
33435
+ program.command("publish").description("Mirror own public Caveat entries to a public git repository").option("--init <url>", "set the public mirror target").option("--dry-run", "show public mirror changes without committing or pushing", false).option("--yes", "approve creating a default repository or publishing changes", false).action(async (opts) => {
33436
+ const ctx = buildContext(stdoutLogger);
33437
+ await runPublish(ctx, opts);
33438
+ });
33201
33439
  program.command("serve").description("Start the read-only web share portal").option("--port <n>", "port number", (v) => Number(v), 4242).action(async (opts) => {
33202
33440
  await runServe({ port: opts.port });
33203
33441
  });
@@ -33205,7 +33443,7 @@ program.command("mcp-server").description("Run the MCP stdio server (registered
33205
33443
  await runMcpServer();
33206
33444
  });
33207
33445
  program.command("hook <name> [arg]").description(
33208
- "Run a Claude Code hook. name: user-prompt-submit | post-tool-use | stop | worker"
33446
+ "Run a Claude Code hook. name: user-prompt-submit | post-tool-use | stop | worker | reindex"
33209
33447
  ).action(async (name, arg) => {
33210
33448
  await runHook(name, arg);
33211
33449
  });