caveat-cli 0.20.1 → 0.20.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -146,6 +146,14 @@ are `ack <cursor>`, `resolve <fingerprint>`, `reopen <fingerprint>`, and
146
146
  boolean `true` keeps collection disabled. Runtime state lives under Caveat's
147
147
  own state directory, independently of the knowledge index and host hook files.
148
148
 
149
+ Handled sync cancellation and remote failures retain local input and show a
150
+ non-zero result without automatic app repair registration. Failed recovery is
151
+ still reported. Severity uses observed impact (`warn` for a failed operation,
152
+ `high` for an unavailable feature or failed recovery, `fatal` for confirmed data
153
+ loss); occurrence counts do not raise it. Existing serious unresolved records
154
+ remain unchanged until reviewed. Cause and recovery need diagnostic evidence.
155
+ See the [reporting contract](https://github.com/kitepon/Caveat/blob/main/docs/01_plan.md#通信失敗の報告と重大度).
156
+
149
157
  Nothing is sent anywhere by default and the package contains no destination.
150
158
  To report to a collector you operate, also set
151
159
  `"runtimeErrorReportCredentialFile"` to the absolute path of the credential
@@ -8976,16 +8976,22 @@ function openDb(opts) {
8976
8976
  const parent = dirname(opts.path);
8977
8977
  if (!existsSync(parent)) mkdirSync(parent, { recursive: true });
8978
8978
  const db = new DatabaseSync(opts.path);
8979
- db.exec("PRAGMA journal_mode = WAL");
8980
- db.exec("PRAGMA foreign_keys = ON");
8981
- const { user_version } = db.prepare("PRAGMA user_version").get();
8982
- if (user_version === 0) {
8983
- db.exec(readFileSync(SCHEMA_PATH, "utf-8"));
8984
- } else {
8985
- applyMigrations(db, user_version);
8986
- backfillRoleTexts(db);
8979
+ try {
8980
+ db.exec("PRAGMA busy_timeout = 1000");
8981
+ db.exec("PRAGMA journal_mode = WAL");
8982
+ db.exec("PRAGMA foreign_keys = ON");
8983
+ const { user_version } = db.prepare("PRAGMA user_version").get();
8984
+ if (user_version === 0) {
8985
+ db.exec(readFileSync(SCHEMA_PATH, "utf-8"));
8986
+ } else {
8987
+ applyMigrations(db, user_version);
8988
+ backfillRoleTexts(db);
8989
+ }
8990
+ return db;
8991
+ } catch (error) {
8992
+ db.close();
8993
+ throw error;
8987
8994
  }
8988
- return db;
8989
8995
  }
8990
8996
  function backfillRoleTexts(db) {
8991
8997
  let rows;
@@ -15307,6 +15313,34 @@ var SyncError = class extends Error {
15307
15313
  }
15308
15314
  code;
15309
15315
  };
15316
+ var SyncRemoteError = class extends Error {
15317
+ constructor(phase, cause) {
15318
+ super(`sync ${phase} did not complete; local entries are retained; remote result may be unknown: ${errorMessage2(cause)}`, { cause });
15319
+ this.phase = phase;
15320
+ this.name = "SyncRemoteError";
15321
+ }
15322
+ phase;
15323
+ };
15324
+ var SyncRecoveryError = class extends Error {
15325
+ constructor(cause) {
15326
+ super("sync recovery failed; inspect the local rebase before retrying", { cause });
15327
+ this.name = "SyncRecoveryError";
15328
+ }
15329
+ };
15330
+ async function remoteOperation(phase, run) {
15331
+ try {
15332
+ return await run();
15333
+ } catch (cause) {
15334
+ throw new SyncRemoteError(phase, cause);
15335
+ }
15336
+ }
15337
+ async function activeRebase(git, ownDir) {
15338
+ for (const name of ["rebase-merge", "rebase-apply"]) {
15339
+ const path = (await git.raw(["rev-parse", "--git-path", name])).trim();
15340
+ if (existsSync8(resolve(ownDir, path))) return true;
15341
+ }
15342
+ return false;
15343
+ }
15310
15344
  function normalizePath(path) {
15311
15345
  return normalizePathCase(resolve(path).replace(/\\/g, "/"));
15312
15346
  }
@@ -15353,6 +15387,9 @@ async function preflightSync(ownDir, opts = {}) {
15353
15387
  if (normalizePath(root) !== normalizePath(requested)) {
15354
15388
  throw new SyncError("EXTERNAL_TOPLEVEL", `EXTERNAL_TOPLEVEL: own directory must be the repository root: ${requested} (root: ${root})`);
15355
15389
  }
15390
+ if (await activeRebase(git, requested)) {
15391
+ throw new SyncError("REBASE_IN_PROGRESS", "a rebase is already active; inspect and finish or abort it before syncing");
15392
+ }
15356
15393
  let branch;
15357
15394
  try {
15358
15395
  branch = (await git.raw(["symbolic-ref", "--short", "HEAD"])).trim();
@@ -15399,23 +15436,32 @@ async function syncOwn(opts) {
15399
15436
  await git.commit(`caveat sync: ${changed} changed file${changed === 1 ? "" : "s"}`);
15400
15437
  committed = true;
15401
15438
  }
15402
- const remoteBranch = (await git.raw(["ls-remote", "--heads", "origin", preflight.branch])).trim();
15439
+ const remoteBranch = (await remoteOperation("inspect", () => git.raw(["ls-remote", "--heads", "origin", preflight.branch]))).trim();
15403
15440
  let pulled = false;
15404
15441
  if (remoteBranch) {
15405
15442
  try {
15406
15443
  await git.pull("origin", preflight.branch, ["--rebase"]);
15407
15444
  pulled = true;
15408
15445
  } catch (err) {
15446
+ let rebasing;
15447
+ try {
15448
+ rebasing = await activeRebase(git, preflight.ownDir);
15449
+ } catch (cause) {
15450
+ throw new SyncRecoveryError(cause);
15451
+ }
15452
+ if (!rebasing) throw new SyncRemoteError("pull", err);
15409
15453
  try {
15410
15454
  await git.raw(["rebase", "--abort"]);
15411
- } catch {
15455
+ if (await activeRebase(git, preflight.ownDir)) throw Error("rebase still active");
15456
+ } catch (cause) {
15457
+ throw new SyncRecoveryError(cause);
15412
15458
  }
15413
15459
  const detail = err instanceof Error ? err.message : String(err);
15414
15460
  throw new SyncError("SYNC_CONFLICT", `sync rebase failed and was aborted: ${detail}`);
15415
15461
  }
15416
15462
  }
15417
15463
  await reindexAndMark(opts);
15418
- await git.push("origin", preflight.branch, ["-u"]);
15464
+ await remoteOperation("push", () => git.push("origin", preflight.branch, ["-u"]));
15419
15465
  return {
15420
15466
  ...preflight,
15421
15467
  committed,
@@ -15465,10 +15511,10 @@ function removeCanonicalScaffoldBeforeCheckout(ownDir) {
15465
15511
  }
15466
15512
  }
15467
15513
  async function defaultRemoteBranch(git, url) {
15468
- const symref = await git.raw(["ls-remote", "--symref", url, "HEAD"]);
15514
+ const symref = await remoteOperation("inspect", () => git.raw(["ls-remote", "--symref", url, "HEAD"]));
15469
15515
  const match = /^ref: refs\/heads\/([^\s]+)\s+HEAD$/m.exec(symref);
15470
15516
  if (match) return match[1];
15471
- const heads = await git.raw(["ls-remote", "--heads", url]);
15517
+ const heads = await remoteOperation("inspect", () => git.raw(["ls-remote", "--heads", url]));
15472
15518
  const first2 = /refs\/heads\/([^\s]+)\s*$/m.exec(heads);
15473
15519
  if (!first2) throw new Error(`remote has refs but no branch heads: ${url}`);
15474
15520
  return first2[1];
@@ -15481,7 +15527,7 @@ async function initOwnSync(opts) {
15481
15527
  throw new SyncError("OWN_REPO_EXISTS", `own knowledge directory is already a git repository: ${ownDir}`);
15482
15528
  }
15483
15529
  const inspector = createGit(ownDir);
15484
- const refs = (await inspector.raw(["ls-remote", "--heads", opts.url])).trim();
15530
+ const refs = (await remoteOperation("inspect", () => inspector.raw(["ls-remote", "--heads", opts.url]))).trim();
15485
15531
  const entryCount = countMarkdownEntries(ownDir);
15486
15532
  if (refs && entryCount > 0) {
15487
15533
  throw new SyncError("BOTH_HAVE_ENTRIES", "local and remote both contain entries; resolve the ownership conflict before initializing sync");
@@ -15497,12 +15543,12 @@ async function initOwnSync(opts) {
15497
15543
  await git.add("-A");
15498
15544
  await git.commit(`caveat sync: initial import (${entryCount} entries)`);
15499
15545
  const branch2 = (await git.revparse(["--abbrev-ref", "HEAD"])).trim();
15500
- await git.push("origin", branch2, ["-u"]);
15546
+ await remoteOperation("push", () => git.push("origin", branch2, ["-u"]));
15501
15547
  await reindexAndMark(opts);
15502
15548
  return { ownDir, branch: branch2, remoteWasEmpty: true };
15503
15549
  }
15504
15550
  const branch = await defaultRemoteBranch(git, opts.url);
15505
- await git.fetch("origin", branch);
15551
+ await remoteOperation("fetch", () => git.fetch("origin", branch));
15506
15552
  removeCanonicalScaffoldBeforeCheckout(ownDir);
15507
15553
  await git.checkout(["--track", "-B", branch, `origin/${branch}`]);
15508
15554
  await reindexAndMark(opts);
@@ -15510,7 +15556,8 @@ async function initOwnSync(opts) {
15510
15556
  } catch (err) {
15511
15557
  try {
15512
15558
  rmSync4(createdGitDir, { recursive: true, force: true });
15513
- } catch {
15559
+ } catch (cause) {
15560
+ throw new SyncRecoveryError(cause);
15514
15561
  }
15515
15562
  throw err;
15516
15563
  }
@@ -15594,13 +15641,20 @@ function classifyOwnSyncOutcome(err, lastProbe) {
15594
15641
  return "skip";
15595
15642
  case "REMOTE_PUBLIC":
15596
15643
  case "SYNC_CONFLICT":
15644
+ case "REBASE_IN_PROGRESS":
15597
15645
  return "fail";
15598
15646
  case "REMOTE_VISIBILITY_INDETERMINATE":
15599
15647
  return lastProbe?.kind === "indeterminate" && lastProbe.reason === PROBE_REQUEST_FAILED_REASON ? "network-skip" : "fail";
15600
15648
  }
15601
15649
  }
15650
+ function syncFailureCode(err) {
15651
+ if (err instanceof SyncError) return err.code;
15652
+ if (err instanceof SyncRemoteError) return `REMOTE_${err.phase.toUpperCase()}_FAILED`;
15653
+ if (err instanceof SyncRecoveryError) return "RECOVERY_FAILED";
15654
+ return "UNKNOWN";
15655
+ }
15602
15656
  function ownSyncFailureSignature(err) {
15603
- const code = err instanceof SyncError ? err.code : "UNKNOWN";
15657
+ const code = syncFailureCode(err);
15604
15658
  return sha256(code);
15605
15659
  }
15606
15660
  function autoSyncNotification(outcome) {
@@ -15673,8 +15727,8 @@ async function runAutoSync(opts) {
15673
15727
  ownSyncState = { consecutiveFailureSignature: null, consecutiveFailureCount: 0 };
15674
15728
  } catch (err) {
15675
15729
  const disposition = classifyOwnSyncOutcome(err, lastProbe);
15676
- const code = err instanceof SyncError ? err.code : void 0;
15677
- own = { disposition, code };
15730
+ const code = syncFailureCode(err);
15731
+ own = { disposition, code: code === "UNKNOWN" ? void 0 : code };
15678
15732
  if (disposition === "fail") {
15679
15733
  const failureSignature = ownSyncFailureSignature(err);
15680
15734
  const consecutiveFailureCount = ownSyncState.consecutiveFailureSignature === failureSignature ? ownSyncState.consecutiveFailureCount + 1 : 1;
@@ -17105,15 +17159,25 @@ var STATE_VERSION = "1.0";
17105
17159
  var MAX_RECORDS = 256;
17106
17160
  var RETENTION_MS = 30 * 864e5;
17107
17161
  var definitions = {
17108
- "CAVEAT.DATABASE_OPEN_FAILED": { component: "database", severity: "high", template: "Caveat database open failed" },
17109
- "CAVEAT.INDEX_FAILED": { component: "index", severity: "high", template: "Caveat index operation failed" },
17110
- "CAVEAT.SYNC_FAILED": { component: "sync", severity: "high", template: "Caveat own sync failed" },
17111
- "CAVEAT.MCP_SERVER_FAILED": { component: "mcp", severity: "high", template: "Caveat MCP server failed" },
17112
- "CAVEAT.MCP_TOOL_FAILED": { component: "mcp_tool", severity: "high", template: "Caveat MCP tool handler failed" },
17113
- "CAVEAT.CLAUDE_HOOK_FAILED": { component: "claude_hook", severity: "high", template: "Caveat Claude hook failed" },
17114
- "CAVEAT.CODEX_HOOK_FAILED": { component: "codex_hook", severity: "high", template: "Caveat Codex hook failed" },
17115
- "CAVEAT.CURSOR_HOOK_FAILED": { component: "cursor_hook", severity: "high", template: "Caveat Cursor hook failed" }
17162
+ "CAVEAT.DATABASE_OPEN_FAILED": { component: "database", template: "Caveat database open failed" },
17163
+ "CAVEAT.INDEX_FAILED": { component: "index", template: "Caveat index operation failed" },
17164
+ "CAVEAT.SYNC_FAILED": { component: "sync", template: "Caveat own sync failed" },
17165
+ "CAVEAT.MCP_SERVER_FAILED": { component: "mcp", template: "Caveat MCP server failed" },
17166
+ "CAVEAT.MCP_TOOL_FAILED": { component: "mcp_tool", template: "Caveat MCP tool handler failed" },
17167
+ "CAVEAT.CLAUDE_HOOK_FAILED": { component: "claude_hook", template: "Caveat Claude hook failed" },
17168
+ "CAVEAT.CODEX_HOOK_FAILED": { component: "codex_hook", template: "Caveat Codex hook failed" },
17169
+ "CAVEAT.CURSOR_HOOK_FAILED": { component: "cursor_hook", template: "Caveat Cursor hook failed" }
17116
17170
  };
17171
+ function runtimeErrorSeverity(impact) {
17172
+ if (impact === "data_lost") return "fatal";
17173
+ if (impact === "operation_failed") return "warn";
17174
+ if (impact === void 0 || impact === "feature_unavailable" || impact === "recovery_failed" || impact === "mutation_outcome_unverified") return "high";
17175
+ throw Error("invalid_runtime_impact");
17176
+ }
17177
+ function highestSeverity(previous, current) {
17178
+ const rank = ["info", "warn", "high", "fatal"];
17179
+ return rank.indexOf(previous) >= rank.indexOf(current) ? previous : current;
17180
+ }
17117
17181
  function normalizeOptions(options2 = {}) {
17118
17182
  return "env" in options2 || "configPath" in options2 || "storePath" in options2 || "version" in options2 || "isWindows" in options2 ? options2 : { env: options2 };
17119
17183
  }
@@ -17188,7 +17252,7 @@ function validate(store) {
17188
17252
  if (!plain(record) || !exact(record, ["product", "product_version", "component", "error_code", "message_template", "severity", "fingerprint", "count", "first_seen", "last_seen", "state_schema_version", "os", "arch", "status", "resolved_at", "reason_code", "sequence"])) throw Error("state_invalid");
17189
17253
  const code = typeof record.error_code === "string" ? record.error_code : void 0;
17190
17254
  const d = code && definitions[code];
17191
- if (!d || record.product !== "caveat" || !validVersion(record.product_version) || record.component !== d.component || record.message_template !== d.template || record.severity !== d.severity || record.fingerprint !== fingerprint(code) || seen.has(record.fingerprint) || !Number.isSafeInteger(record.count) || record.count < 1 || !validTime(record.first_seen) || !validTime(record.last_seen) || Date.parse(record.first_seen) > Date.parse(record.last_seen) || record.state_schema_version !== STATE_VERSION || !validOs(record.os) || !validArch(record.arch) || !Number.isSafeInteger(record.sequence) || record.sequence <= previous || record.sequence >= checked.next_sequence || !["open", "resolved"].includes(record.status) || record.status === "open" && (record.resolved_at !== null || record.reason_code !== null) || record.status === "resolved" && (typeof record.resolved_at !== "string" || !validTime(record.resolved_at) || Date.parse(record.resolved_at) < Date.parse(record.last_seen) || record.reason_code !== "operator_resolved")) throw Error("state_invalid");
17255
+ if (!d || record.product !== "caveat" || !validVersion(record.product_version) || record.component !== d.component || record.message_template !== d.template || !["fatal", "high", "warn", "info"].includes(record.severity) || record.fingerprint !== fingerprint(code) || seen.has(record.fingerprint) || !Number.isSafeInteger(record.count) || record.count < 1 || !validTime(record.first_seen) || !validTime(record.last_seen) || Date.parse(record.first_seen) > Date.parse(record.last_seen) || record.state_schema_version !== STATE_VERSION || !validOs(record.os) || !validArch(record.arch) || !Number.isSafeInteger(record.sequence) || record.sequence <= previous || record.sequence >= checked.next_sequence || !["open", "resolved"].includes(record.status) || record.status === "open" && (record.resolved_at !== null || record.reason_code !== null) || record.status === "resolved" && (typeof record.resolved_at !== "string" || !validTime(record.resolved_at) || Date.parse(record.resolved_at) < Date.parse(record.last_seen) || record.reason_code !== "operator_resolved")) throw Error("state_invalid");
17192
17256
  seen.add(record.fingerprint);
17193
17257
  previous = record.sequence;
17194
17258
  }
@@ -17296,6 +17360,7 @@ function recordRuntimeError(code, options2 = {}) {
17296
17360
  const definition = definitions[code];
17297
17361
  if (!definition) throw Error("unknown_runtime_code");
17298
17362
  const key = fingerprint(code);
17363
+ const severity = runtimeErrorSeverity(options2.impact);
17299
17364
  const sequence = store.next_sequence++;
17300
17365
  const time = now(options2);
17301
17366
  const existing = store.records.find((r2) => r2.fingerprint === key);
@@ -17308,6 +17373,7 @@ function recordRuntimeError(code, options2 = {}) {
17308
17373
  existing.os = os;
17309
17374
  existing.arch = arch;
17310
17375
  existing.count += 1;
17376
+ existing.severity = existing.status === "resolved" ? severity : highestSeverity(existing.severity, severity);
17311
17377
  existing.last_seen = time;
17312
17378
  existing.sequence = sequence;
17313
17379
  existing.status = "open";
@@ -17315,7 +17381,7 @@ function recordRuntimeError(code, options2 = {}) {
17315
17381
  existing.reason_code = null;
17316
17382
  } else {
17317
17383
  if (store.records.length >= MAX_RECORDS) throw Error("store_overflow");
17318
- store.records.push({ product: "caveat", product_version: version, component: definition.component, error_code: code, message_template: definition.template, severity: definition.severity, fingerprint: key, count: 1, first_seen: time, last_seen: time, state_schema_version: STATE_VERSION, os, arch, status: "open", resolved_at: null, reason_code: null, sequence });
17384
+ store.records.push({ product: "caveat", product_version: version, component: definition.component, error_code: code, message_template: definition.template, severity, fingerprint: key, count: 1, first_seen: time, last_seen: time, state_schema_version: STATE_VERSION, os, arch, status: "open", resolved_at: null, reason_code: null, sequence });
17319
17385
  }
17320
17386
  store.records.sort((a, b2) => a.sequence - b2.sequence);
17321
17387
  writeStore(path, store, isWin, options2);
@@ -17379,11 +17445,82 @@ function compactRuntimeErrors(options2 = {}) {
17379
17445
  }
17380
17446
  var runtimeErrorsInternal = { validate, definitions, collectionEnabled, optionsFor, lock, readStore, ensureSafeDir, ensureSafeFile, secureWindowsAcl, assertPosix, now, validVersion };
17381
17447
 
17448
+ // ../../packages/core/dist/hookDiagnostics.js
17449
+ import { existsSync as existsSync17, readFileSync as readFileSync15, renameSync as renameSync5, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "node:fs";
17450
+ import { dirname as dirname9, join as join14 } from "node:path";
17451
+ import { randomBytes as randomBytes4 } from "node:crypto";
17452
+ var phases = ["json parse error", "context error", "search error", "markHit error", "query log error"];
17453
+ var kinds = ["invalid_json", "sqlite_busy", "sqlite_readonly", "sqlite_corrupt", "permission_denied", "missing_path", "other"];
17454
+ var codes = ["CAVEAT.CLAUDE_HOOK_FAILED", "CAVEAT.CODEX_HOOK_FAILED", "CAVEAT.CURSOR_HOOK_FAILED"];
17455
+ var SCHEMA = "caveat.local_hook_diagnostics.v1";
17456
+ var MAX_DETAILS = 64;
17457
+ function hookDiagnosticKind(error) {
17458
+ if (error instanceof SyntaxError) return "invalid_json";
17459
+ if (typeof error !== "object" || error === null) return "other";
17460
+ const err = error;
17461
+ if (err.code === "ERR_SQLITE_ERROR" && Number.isInteger(err.errcode)) {
17462
+ const code = err.errcode & 255;
17463
+ if (code === 5 || code === 6) return "sqlite_busy";
17464
+ if (code === 8) return "sqlite_readonly";
17465
+ if (code === 11 || code === 26) return "sqlite_corrupt";
17466
+ }
17467
+ if (err.code === "EACCES" || err.code === "EPERM") return "permission_denied";
17468
+ if (err.code === "ENOENT") return "missing_path";
17469
+ return "other";
17470
+ }
17471
+ function readDetails(path, isWin, options2) {
17472
+ if (!existsSync17(path)) return [];
17473
+ runtimeErrorsInternal.ensureSafeFile(path, isWin, options2);
17474
+ const value = JSON.parse(readFileSync15(path, "utf8"));
17475
+ if (!value || Object.keys(value).sort().join(",") !== "records,schema" || value.schema !== SCHEMA || !Array.isArray(value.records) || value.records.length > MAX_DETAILS) throw Error("hook_diagnostics_invalid");
17476
+ for (const record of value.records) {
17477
+ if (!record || Object.keys(record).sort().join(",") !== "at,error_code,kind,phase,version" || !Number.isFinite(Date.parse(record.at)) || new Date(record.at).toISOString() !== record.at || !runtimeErrorsInternal.validVersion(record.version) || !codes.includes(record.error_code) || !phases.includes(record.phase) || !kinds.includes(record.kind)) throw Error("hook_diagnostics_invalid");
17478
+ }
17479
+ return value.records;
17480
+ }
17481
+ function observeHookDiagnostic(errorCode, phase, error, options2 = {}) {
17482
+ if (!runtimeErrorsInternal.collectionEnabled(options2)) return;
17483
+ try {
17484
+ if (!codes.includes(errorCode) || !phases.includes(phase)) throw Error("hook_diagnostics_invalid");
17485
+ const { path: storePath, isWin } = runtimeErrorsInternal.optionsFor(options2);
17486
+ const path = join14(dirname9(storePath), "hook-diagnostics.json");
17487
+ runtimeErrorsInternal.lock(path, isWin, () => {
17488
+ const records = readDetails(path, isWin, options2);
17489
+ const version = options2.version ?? "0.0.0";
17490
+ if (!runtimeErrorsInternal.validVersion(version)) throw Error("hook_diagnostics_invalid");
17491
+ records.push({ at: runtimeErrorsInternal.now(options2), version, error_code: errorCode, phase, kind: hookDiagnosticKind(error) });
17492
+ const temporary = join14(dirname9(path), `.hook-diagnostics-${process.pid}-${randomBytes4(6).toString("hex")}`);
17493
+ try {
17494
+ writeFileSync10(temporary, JSON.stringify({ schema: SCHEMA, records: records.slice(-MAX_DETAILS) }) + "\n", { mode: 384, flag: "wx" });
17495
+ if (isWin) runtimeErrorsInternal.secureWindowsAcl(temporary, options2.aclRunner, false, true);
17496
+ runtimeErrorsInternal.ensureSafeFile(temporary, isWin, options2);
17497
+ renameSync5(temporary, path);
17498
+ } finally {
17499
+ rmSync7(temporary, { force: true });
17500
+ }
17501
+ }, options2);
17502
+ } catch {
17503
+ try {
17504
+ process.stderr.write("[caveat:hook-diagnostics] unavailable\n");
17505
+ } catch {
17506
+ }
17507
+ }
17508
+ }
17509
+ function localHookDiagnostics(options2 = {}) {
17510
+ if (!runtimeErrorsInternal.collectionEnabled(options2)) return { schema: SCHEMA, status: "disabled", records: [] };
17511
+ try {
17512
+ const { path: storePath, isWin } = runtimeErrorsInternal.optionsFor(options2);
17513
+ return { schema: SCHEMA, status: "ready", records: readDetails(join14(dirname9(storePath), "hook-diagnostics.json"), isWin, options2) };
17514
+ } catch {
17515
+ return { schema: SCHEMA, status: "unavailable", records: [] };
17516
+ }
17517
+ }
17518
+
17382
17519
  // ../../packages/core/dist/runtimeErrorReport.js
17383
17520
  import { spawnSync as spawnSync2 } from "node:child_process";
17384
- import { createHash as createHash8, createHmac as createHmac2, randomBytes as randomBytes4, randomUUID, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
17385
- import { closeSync as closeSync2, constants, existsSync as existsSync17, fstatSync, lstatSync as lstatSync2, openSync as openSync2, readFileSync as readFileSync15, renameSync as renameSync5, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "node:fs";
17386
- import { dirname as dirname9, isAbsolute as isAbsolute2, join as join14 } from "node:path";
17521
+ import { createHash as createHash8, createHmac as createHmac2, randomBytes as randomBytes5, randomUUID, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
17522
+ import { closeSync as closeSync2, constants, existsSync as existsSync18, fstatSync, lstatSync as lstatSync2, openSync as openSync2, readFileSync as readFileSync16, renameSync as renameSync6, rmSync as rmSync8, writeFileSync as writeFileSync11 } from "node:fs";
17523
+ import { dirname as dirname10, isAbsolute as isAbsolute2, join as join15 } from "node:path";
17387
17524
  var RUNTIME_ERROR_REPORT_SCHEMA_VERSION = "1.0";
17388
17525
  var REPORT_STATE_SCHEMA = "caveat.runtime_error_report_state.v1";
17389
17526
  var REPORT_STATUS_SCHEMA = "caveat.runtime_error_report_status.v1";
@@ -17426,7 +17563,7 @@ var validTime2 = (v) => typeof v === "string" && Number.isFinite(Date.parse(v))
17426
17563
  var emptyState = () => ({ schema: REPORT_STATE_SCHEMA, last_attempt_at: null, last_outcome: null, last_accepted_at: null, next_attempt_at: null });
17427
17564
  var result = (status, outcome = null, counts = {}) => ({ schema: REPORT_RESULT_SCHEMA, status, outcome, runtime_error_count: counts.errors ?? 0, resolution_count: counts.resolutions ?? 0, acknowledged_through: counts.acknowledged ?? null });
17428
17565
  function reportStatePath(storePath) {
17429
- return join14(dirname9(storePath), "runtime-error-report.json");
17566
+ return join15(dirname10(storePath), "runtime-error-report.json");
17430
17567
  }
17431
17568
  function configuredCredentialPath(options2) {
17432
17569
  try {
@@ -17460,7 +17597,7 @@ function loadCredential(path, isWin, options2) {
17460
17597
  try {
17461
17598
  const after = fstatSync(fd);
17462
17599
  if (before.dev !== after.dev || before.ino !== after.ino || after.size > MAX_CREDENTIAL_BYTES) throw new ReportFailure("credential_unsafe");
17463
- text = readFileSync15(fd, "utf8");
17600
+ text = readFileSync16(fd, "utf8");
17464
17601
  } finally {
17465
17602
  closeSync2(fd);
17466
17603
  }
@@ -17499,7 +17636,7 @@ function sameHex(expected, actual) {
17499
17636
  }
17500
17637
  function readReportState(path) {
17501
17638
  try {
17502
- const value = JSON.parse(readFileSync15(path, "utf8"));
17639
+ const value = JSON.parse(readFileSync16(path, "utf8"));
17503
17640
  if (!plain2(value) || value.schema !== REPORT_STATE_SCHEMA) return emptyState();
17504
17641
  const time = (v) => validTime2(v) ? v : null;
17505
17642
  const outcome = typeof value.last_outcome === "string" && Object.hasOwn(OUTCOME_RETRY_MS, value.last_outcome) ? value.last_outcome : null;
@@ -17509,23 +17646,23 @@ function readReportState(path) {
17509
17646
  }
17510
17647
  }
17511
17648
  function writeReportState(path, state, isWin, options2) {
17512
- ensureSafeDir2(dirname9(path), isWin, options2);
17513
- const temporary = join14(dirname9(path), `.runtime-error-report-${process.pid}-${randomBytes4(6).toString("hex")}`);
17649
+ ensureSafeDir2(dirname10(path), isWin, options2);
17650
+ const temporary = join15(dirname10(path), `.runtime-error-report-${process.pid}-${randomBytes5(6).toString("hex")}`);
17514
17651
  try {
17515
- writeFileSync10(temporary, `${JSON.stringify(state)}
17652
+ writeFileSync11(temporary, `${JSON.stringify(state)}
17516
17653
  `, { mode: 384, flag: "wx" });
17517
17654
  if (isWin) secureWindowsAcl2(temporary, options2.aclRunner, false, true);
17518
17655
  else assertPosix2(lstatSync2(temporary), 384);
17519
- renameSync5(temporary, path);
17656
+ renameSync6(temporary, path);
17520
17657
  } finally {
17521
- rmSync7(temporary, { force: true });
17658
+ rmSync8(temporary, { force: true });
17522
17659
  }
17523
17660
  }
17524
17661
  function nextState(previous, outcome, at) {
17525
17662
  return { schema: REPORT_STATE_SCHEMA, last_attempt_at: at, last_outcome: outcome, last_accepted_at: outcome === "accepted" ? at : previous.last_accepted_at, next_attempt_at: new Date(Date.parse(at) + OUTCOME_RETRY_MS[outcome]).toISOString() };
17526
17663
  }
17527
17664
  function claimPending(storePath, isWin, at, options2) {
17528
- if (!existsSync17(storePath)) return "nothing_pending";
17665
+ if (!existsSync18(storePath)) return "nothing_pending";
17529
17666
  return lock2(storePath, isWin, () => {
17530
17667
  const statePath = reportStatePath(storePath);
17531
17668
  const state = readReportState(statePath);
@@ -17629,8 +17766,8 @@ function runtimeErrorReportDue(options2 = {}) {
17629
17766
  try {
17630
17767
  if (!collectionEnabled2(options2) || configuredCredentialPath(options2) === null) return false;
17631
17768
  const { path: storePath } = optionsFor2(options2);
17632
- if (!existsSync17(storePath)) return false;
17633
- const store = JSON.parse(readFileSync15(storePath, "utf8"));
17769
+ if (!existsSync18(storePath)) return false;
17770
+ const store = JSON.parse(readFileSync16(storePath, "utf8"));
17634
17771
  if (!plain2(store) || typeof store.next_sequence !== "number" || typeof store.acknowledged_through !== "number" || store.next_sequence - 1 <= store.acknowledged_through) return false;
17635
17772
  const next = readReportState(reportStatePath(storePath)).next_attempt_at;
17636
17773
  return next === null || Date.parse(now2(options2)) >= Date.parse(next);
@@ -17644,7 +17781,7 @@ function runtimeErrorReportStatus(options2 = {}) {
17644
17781
  if (configuredCredentialPath(options2) === null) return { ...base, reporting: "not_configured", status: "not_applicable" };
17645
17782
  try {
17646
17783
  const { path: storePath, isWin } = optionsFor2(options2);
17647
- const store = existsSync17(storePath) ? readStore2(storePath, isWin, options2) : null;
17784
+ const store = existsSync18(storePath) ? readStore2(storePath, isWin, options2) : null;
17648
17785
  const state = readReportState(reportStatePath(storePath));
17649
17786
  return { schema: REPORT_STATUS_SCHEMA, reporting: "enabled", status: "ready", pending_count: store ? store.records.filter((record) => record.sequence > store.acknowledged_through).length : 0, last_attempt_at: state.last_attempt_at, last_outcome: state.last_outcome, last_accepted_at: state.last_accepted_at, next_attempt_at: state.next_attempt_at };
17650
17787
  } catch {
@@ -17653,14 +17790,14 @@ function runtimeErrorReportStatus(options2 = {}) {
17653
17790
  }
17654
17791
 
17655
17792
  // ../../packages/core/dist/jevObservations.js
17656
- import { createHash as createHash9, randomBytes as randomBytes5 } from "node:crypto";
17657
- import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync16, readdirSync as readdirSync9, renameSync as renameSync6, rmSync as rmSync8, writeFileSync as writeFileSync11 } from "node:fs";
17658
- import { join as join15 } from "node:path";
17793
+ import { createHash as createHash9, randomBytes as randomBytes6 } from "node:crypto";
17794
+ import { existsSync as existsSync19, mkdirSync as mkdirSync9, readFileSync as readFileSync17, readdirSync as readdirSync9, renameSync as renameSync7, rmSync as rmSync9, writeFileSync as writeFileSync12 } from "node:fs";
17795
+ import { join as join16 } from "node:path";
17659
17796
  var JEV_OBSERVATION_SCHEMA = "caveat.jev_observation.v1";
17660
- var directory = (home) => join15(home, "jev-observations");
17797
+ var directory = (home) => join16(home, "jev-observations");
17661
17798
  var pathFor = (home, id) => {
17662
17799
  if (!/^[0-9a-f]{64}$/.test(id)) throw new Error("jev_observation_id_invalid");
17663
- return join15(directory(home), `${id}.json`);
17800
+ return join16(directory(home), `${id}.json`);
17664
17801
  };
17665
17802
  function jevObservationId(host, sessionId, turnKey) {
17666
17803
  return createHash9("sha256").update(`${host}\0${sessionId}\0${turnKey}`).digest("hex");
@@ -17671,14 +17808,14 @@ function validate2(value) {
17671
17808
  if (item.schema !== JEV_OBSERVATION_SCHEMA || typeof item.id !== "string" || !/^[0-9a-f]{64}$/.test(item.id) || !["claude", "codex"].includes(item.host) || typeof item.observedAt !== "string" || typeof item.sessionId !== "string" || typeof item.projectRoot !== "string" || typeof item.transcriptPath !== "string" || !Array.isArray(item.turns) || item.turns.some((turn) => typeof turn.originSessionId !== "string" || !Number.isSafeInteger(turn.turnNumber) || typeof turn.truncated !== "boolean") || typeof item.thinkingAvailable !== "boolean" || typeof item.model !== "string" || ![item.repeatedProblem, item.clearlyStuck, item.struggleThreshold, item.matchThreshold].every((score) => typeof score === "number" && Number.isFinite(score) && score >= 0 && score <= 1) || typeof item.struggling !== "boolean" || !Array.isArray(item.termChoices) || item.termChoices.some((choice) => typeof choice.term !== "string" || typeof choice.probability !== "number") || item.noneProbability !== null && typeof item.noneProbability !== "number" || !Array.isArray(item.selectedTerms) || item.selectedTerms.some((term) => typeof term !== "string") || typeof item.query !== "string" || !Array.isArray(item.ftsHits) || item.ftsHits.some((ref) => typeof ref !== "string") || !Array.isArray(item.candidates) || item.candidates.some((candidate) => typeof candidate.ref !== "string" || typeof candidate.score !== "number" || !Number.isFinite(candidate.score)) || item.selectedRef !== null && typeof item.selectedRef !== "string" || item.noticeText !== null && typeof item.noticeText !== "string" || !["below_struggle_threshold", "no_terms", "no_candidates", "below_match_threshold", "matched"].includes(item.decision) || !["none", "pending", "delivered", "suppressed"].includes(item.delivery) || item.review !== null && (!item.review || typeof item.review !== "object" || !["correct", "incorrect", "missed", "none", "unclear"].includes(item.review.verdict) || typeof item.review.note !== "string" || typeof item.review.reviewedAt !== "string")) throw new Error("jev_observation_invalid");
17672
17809
  }
17673
17810
  function readJevObservation(home, id) {
17674
- const value = JSON.parse(readFileSync16(pathFor(home, id), "utf8"));
17811
+ const value = JSON.parse(readFileSync17(pathFor(home, id), "utf8"));
17675
17812
  validate2(value);
17676
17813
  if (value.id !== id) throw new Error("jev_observation_id_mismatch");
17677
17814
  return value;
17678
17815
  }
17679
17816
  function listJevObservations(home) {
17680
17817
  const dir = directory(home);
17681
- if (!existsSync18(dir)) return [];
17818
+ if (!existsSync19(dir)) return [];
17682
17819
  return readdirSync9(dir).filter((name) => /^[0-9a-f]{64}\.json$/.test(name)).map((name) => readJevObservation(home, name.slice(0, -5))).sort((a, b2) => b2.observedAt.localeCompare(a.observedAt) || b2.id.localeCompare(a.id));
17683
17820
  }
17684
17821
  function writeJevObservation(home, item) {
@@ -17686,13 +17823,13 @@ function writeJevObservation(home, item) {
17686
17823
  const dir = directory(home);
17687
17824
  mkdirSync9(dir, { recursive: true, mode: 448 });
17688
17825
  const path = pathFor(home, item.id);
17689
- const temporary = `${path}.${randomBytes5(6).toString("hex")}.tmp`;
17826
+ const temporary = `${path}.${randomBytes6(6).toString("hex")}.tmp`;
17690
17827
  try {
17691
- writeFileSync11(temporary, `${JSON.stringify(item)}
17828
+ writeFileSync12(temporary, `${JSON.stringify(item)}
17692
17829
  `, { encoding: "utf8", mode: 384, flag: "wx" });
17693
- renameSync6(temporary, path);
17830
+ renameSync7(temporary, path);
17694
17831
  } finally {
17695
- rmSync8(temporary, { force: true });
17832
+ rmSync9(temporary, { force: true });
17696
17833
  }
17697
17834
  }
17698
17835
  function updateJevDelivery(home, id) {
@@ -17742,10 +17879,10 @@ function environmentAppliesToTask(environment, taskText, hostPlatform2) {
17742
17879
  }
17743
17880
 
17744
17881
  // ../../packages/core/dist/id.js
17745
- import { randomBytes as randomBytes6 } from "node:crypto";
17882
+ import { randomBytes as randomBytes7 } from "node:crypto";
17746
17883
  function randomHex(hexChars) {
17747
17884
  const bytes = Math.ceil(hexChars / 2);
17748
- return randomBytes6(bytes).toString("hex").slice(0, hexChars);
17885
+ return randomBytes7(bytes).toString("hex").slice(0, hexChars);
17749
17886
  }
17750
17887
  function slugify(title, now3 = () => /* @__PURE__ */ new Date()) {
17751
17888
  const lowered = title.toLowerCase();
@@ -17770,8 +17907,8 @@ function generateSourceSession(now3 = () => /* @__PURE__ */ new Date()) {
17770
17907
  }
17771
17908
 
17772
17909
  // ../../packages/core/dist/writer.js
17773
- import { existsSync as existsSync19, mkdirSync as mkdirSync10, writeFileSync as writeFileSync12 } from "node:fs";
17774
- import { dirname as dirname10 } from "node:path";
17910
+ import { existsSync as existsSync20, mkdirSync as mkdirSync10, writeFileSync as writeFileSync13 } from "node:fs";
17911
+ import { dirname as dirname11 } from "node:path";
17775
17912
  function buildEntry(frontmatter, sections) {
17776
17913
  const bodyParts = [];
17777
17914
  for (const [heading, content2] of Object.entries(sections)) {
@@ -17789,14 +17926,14 @@ ${body}${body ? "\n" : ""}`;
17789
17926
  return { content, body };
17790
17927
  }
17791
17928
  function writeEntryFile(filePath, content) {
17792
- const dir = dirname10(filePath);
17793
- if (!existsSync19(dir)) mkdirSync10(dir, { recursive: true });
17794
- writeFileSync12(filePath, content, "utf-8");
17929
+ const dir = dirname11(filePath);
17930
+ if (!existsSync20(dir)) mkdirSync10(dir, { recursive: true });
17931
+ writeFileSync13(filePath, content, "utf-8");
17795
17932
  }
17796
17933
 
17797
17934
  // ../../packages/core/dist/record.js
17798
17935
  import { statSync as statSync8 } from "node:fs";
17799
- import { join as join16 } from "node:path";
17936
+ import { join as join17 } from "node:path";
17800
17937
  var DEFAULT_CATEGORY = "misc";
17801
17938
  function recordEntry(input, opts) {
17802
17939
  const now3 = opts.now ?? (() => /* @__PURE__ */ new Date());
@@ -17832,7 +17969,7 @@ function recordEntry(input, opts) {
17832
17969
  const built = buildEntry(frontmatter, sections);
17833
17970
  const category = input.category ?? DEFAULT_CATEGORY;
17834
17971
  const relPath = `${category}/${id}.md`;
17835
- const filePath = join16(opts.entriesRoot, relPath);
17972
+ const filePath = join17(opts.entriesRoot, relPath);
17836
17973
  writeEntryFile(filePath, built.content);
17837
17974
  const stat = statSync8(filePath);
17838
17975
  upsertEntry(opts.db, {
@@ -17862,8 +17999,8 @@ function formatYmd(d) {
17862
17999
  }
17863
18000
 
17864
18001
  // ../../packages/core/dist/update.js
17865
- import { readFileSync as readFileSync17, statSync as statSync9, writeFileSync as writeFileSync13 } from "node:fs";
17866
- import { join as join17 } from "node:path";
18002
+ import { readFileSync as readFileSync18, statSync as statSync9, writeFileSync as writeFileSync14 } from "node:fs";
18003
+ import { join as join18 } from "node:path";
17867
18004
  var IMMUTABLE_KEYS = /* @__PURE__ */ new Set([
17868
18005
  "id",
17869
18006
  "created_at",
@@ -17882,8 +18019,8 @@ function updateEntry(id, patch, opts) {
17882
18019
  throw new Error(`caveat not found: id=${id} source=${source}`);
17883
18020
  }
17884
18021
  const relPath = row.path;
17885
- const filePath = join17(opts.entriesRoot, relPath);
17886
- const raw = readFileSync17(filePath, "utf-8");
18022
+ const filePath = join18(opts.entriesRoot, relPath);
18023
+ const raw = readFileSync18(filePath, "utf-8");
17887
18024
  const parsed = parseMarkdown(raw);
17888
18025
  if (patch.frontmatter) {
17889
18026
  for (const key of Object.keys(patch.frontmatter)) {
@@ -17924,7 +18061,7 @@ function updateEntry(id, patch, opts) {
17924
18061
  }
17925
18062
  }
17926
18063
  const built = buildEntry(mergedFrontmatter, mergedSections);
17927
- writeFileSync13(filePath, built.content, "utf-8");
18064
+ writeFileSync14(filePath, built.content, "utf-8");
17928
18065
  const stat = statSync9(filePath);
17929
18066
  upsertEntry(opts.db, {
17930
18067
  id,
@@ -17961,8 +18098,8 @@ function markHit(db, keys, now3 = () => (/* @__PURE__ */ new Date()).toISOString
17961
18098
  }
17962
18099
 
17963
18100
  // ../../packages/core/dist/hookQueryLog.js
17964
- import { appendFileSync, chmodSync as chmodSync2, mkdirSync as mkdirSync11, renameSync as renameSync7, statSync as statSync10, unlinkSync as unlinkSync3 } from "node:fs";
17965
- import { join as join18, resolve as resolve3 } from "node:path";
18101
+ import { appendFileSync, chmodSync as chmodSync2, mkdirSync as mkdirSync11, renameSync as renameSync8, statSync as statSync10, unlinkSync as unlinkSync3 } from "node:fs";
18102
+ import { join as join19, resolve as resolve3 } from "node:path";
17966
18103
  var CAVEAT_HOOK_QUERY_LOG_ENV = "CAVEAT_HOOK_QUERY_LOG";
17967
18104
  var HOOK_QUERY_LOG_MAX_BYTES = 1024 * 1024;
17968
18105
  var HOOK_QUERY_LOG_MAX_QUERY_CODE_UNITS = 1e3;
@@ -17970,7 +18107,7 @@ var fsDependencies = {
17970
18107
  appendFileSync,
17971
18108
  chmodSync: chmodSync2,
17972
18109
  mkdirSync: mkdirSync11,
17973
- renameSync: renameSync7,
18110
+ renameSync: renameSync8,
17974
18111
  statSync: statSync10,
17975
18112
  unlinkSync: unlinkSync3
17976
18113
  };
@@ -17986,8 +18123,8 @@ function chmodExistingFile(path, dependencies) {
17986
18123
  }
17987
18124
  function logHookQueryMiss(miss, dependencies = fsDependencies) {
17988
18125
  if (process.env[CAVEAT_HOOK_QUERY_LOG_ENV] !== "on") return;
17989
- const metricsDir = join18(resolve3(miss.caveatHome), "metrics");
17990
- const activePath = join18(metricsDir, "hook-search-misses.jsonl");
18126
+ const metricsDir = join19(resolve3(miss.caveatHome), "metrics");
18127
+ const activePath = join19(metricsDir, "hook-search-misses.jsonl");
17991
18128
  const backupPath = `${activePath}.1`;
17992
18129
  const record = JSON.stringify({
17993
18130
  at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -18070,6 +18207,9 @@ export {
18070
18207
  peekPendingRemindersDetailed,
18071
18208
  acknowledgePendingReminders,
18072
18209
  createKeyserverKeyProvider,
18210
+ SyncError,
18211
+ SyncRemoteError,
18212
+ SyncRecoveryError,
18073
18213
  syncOwn,
18074
18214
  KNOWLEDGE_GITIGNORE,
18075
18215
  initOwnSync,
@@ -18111,6 +18251,8 @@ export {
18111
18251
  acknowledgeRuntimeErrors,
18112
18252
  setRuntimeErrorStatus,
18113
18253
  compactRuntimeErrors,
18254
+ observeHookDiagnostic,
18255
+ localHookDiagnostics,
18114
18256
  sendRuntimeErrorReport,
18115
18257
  runtimeErrorReportDue,
18116
18258
  runtimeErrorReportStatus,
@@ -18141,4 +18283,4 @@ strip-bom-string/index.js:
18141
18283
  js-yaml/dist/js-yaml.mjs:
18142
18284
  (*! js-yaml 4.1.1 https://github.com/nodeca/js-yaml @license MIT *)
18143
18285
  */
18144
- //# sourceMappingURL=chunk-G52CLPRY.js.map
18286
+ //# sourceMappingURL=chunk-TGIVITP4.js.map