caveat-cli 0.16.0 → 0.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14547,23 +14547,35 @@ var simpleGit = gitInstanceFactory;
14547
14547
 
14548
14548
  // ../../packages/core/dist/gitRuntime.js
14549
14549
  var FOREGROUND_GIT_TIMEOUT_MS = 3e5;
14550
+ var BACKGROUND_GIT_TIMEOUT_MS = 3e4;
14551
+ var HTTP_HELPER_PREEMPT_MARGIN_MS = 5e3;
14552
+ var MIN_GIT_TIMEOUT_MS = HTTP_HELPER_PREEMPT_MARGIN_MS + 1e3;
14550
14553
  function createGit(baseDir, opts) {
14551
14554
  const timeoutMs = opts?.timeoutMs ?? FOREGROUND_GIT_TIMEOUT_MS;
14555
+ if (!Number.isFinite(timeoutMs) || timeoutMs < MIN_GIT_TIMEOUT_MS) {
14556
+ throw new Error(`git timeoutMs must be at least ${MIN_GIT_TIMEOUT_MS}`);
14557
+ }
14558
+ const lowSpeedTimeSeconds = Math.floor((timeoutMs - HTTP_HELPER_PREEMPT_MARGIN_MS) / 1e3);
14552
14559
  const env = {
14553
14560
  ...process.env,
14554
14561
  GIT_TERMINAL_PROMPT: "0",
14555
- GCM_INTERACTIVE: "Never"
14562
+ GCM_INTERACTIVE: "Never",
14563
+ // Git's GIT_HTTP_LOW_SPEED_* environment variables override the matching
14564
+ // config keys. Set both after inherited env so a caller cannot silently
14565
+ // disable the helper-side no-response bound.
14566
+ GIT_HTTP_LOW_SPEED_LIMIT: "1",
14567
+ GIT_HTTP_LOW_SPEED_TIME: String(lowSpeedTimeSeconds)
14556
14568
  };
14557
14569
  const options2 = {
14558
14570
  maxConcurrentProcesses: 1,
14559
14571
  timeout: { block: timeoutMs },
14560
14572
  // On Windows, killing the direct `git` child does not necessarily kill its
14561
- // `git-remote-http` descendant. Give the HTTP helper the same bounded
14562
- // inactivity policy so it terminates itself instead of retaining handles
14563
- // indefinitely after simple-git has already rejected the task.
14573
+ // `git-remote-http` descendant. This earlier HTTP transfer-rate bound lets
14574
+ // Git unwind its own helper first; it is not a general process-tree or
14575
+ // total elapsed-time guarantee.
14564
14576
  config: [
14565
14577
  "http.lowSpeedLimit=1",
14566
- `http.lowSpeedTime=${Math.max(1, Math.ceil(timeoutMs / 1e3))}`
14578
+ `http.lowSpeedTime=${lowSpeedTimeSeconds}`
14567
14579
  ],
14568
14580
  unsafe: unsafeAllowancesForInheritedEnv(env)
14569
14581
  };
@@ -14625,8 +14637,8 @@ async function communityPull(opts) {
14625
14637
  for (const entry of readdirSync5(opts.communityDir, { withFileTypes: true })) {
14626
14638
  if (!entry.isDirectory()) continue;
14627
14639
  const path = join5(opts.communityDir, entry.name);
14628
- const git = createGit(path);
14629
14640
  try {
14641
+ const git = createGit(path, { timeoutMs: opts.gitTimeoutMs });
14630
14642
  await git.raw(["fetch", "origin", "--force", "--depth", "1"]);
14631
14643
  await git.raw(["reset", "--hard", "FETCH_HEAD"]);
14632
14644
  await git.raw(["clean", "-ffdx"]);
@@ -14687,6 +14699,10 @@ import {
14687
14699
  mkdirSync as mkdirSync3,
14688
14700
  readdirSync as readdirSync6,
14689
14701
  readFileSync as readFileSync5,
14702
+ linkSync,
14703
+ openSync,
14704
+ closeSync,
14705
+ chmodSync,
14690
14706
  rmSync as rmSync2,
14691
14707
  statSync as statSync5,
14692
14708
  unlinkSync as unlinkSync2,
@@ -14694,21 +14710,182 @@ import {
14694
14710
  } from "node:fs";
14695
14711
  import { join as join6 } from "node:path";
14696
14712
  import { randomBytes } from "node:crypto";
14713
+ import { createHash as createHash2 } from "node:crypto";
14714
+ import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
14697
14715
  function sanitizeSessionId(raw) {
14698
14716
  const clean = raw.replace(/[^A-Za-z0-9_-]/g, "");
14699
14717
  return clean.length > 0 ? clean : "_unknown";
14700
14718
  }
14701
14719
  var GLOBAL_PENDING_SESSION = "_global";
14720
+ var PENDING_LOCK_BUSY_TIMEOUT_MS = 1e3;
14721
+ function withPendingQueueLock(caveatHome, operation) {
14722
+ const root = join6(caveatHome, "pending");
14723
+ mkdirSync3(root, { recursive: true, mode: 448 });
14724
+ const lockPath2 = join6(root, ".queue-lock.sqlite");
14725
+ const db = new DatabaseSync2(lockPath2);
14726
+ let transactionOpen = false;
14727
+ try {
14728
+ chmodSync(lockPath2, 384);
14729
+ db.exec(`PRAGMA busy_timeout = ${PENDING_LOCK_BUSY_TIMEOUT_MS}`);
14730
+ db.exec("BEGIN IMMEDIATE");
14731
+ transactionOpen = true;
14732
+ const result = operation();
14733
+ db.exec("COMMIT");
14734
+ transactionOpen = false;
14735
+ return result;
14736
+ } catch (error) {
14737
+ if (transactionOpen) {
14738
+ try {
14739
+ db.exec("ROLLBACK");
14740
+ } catch {
14741
+ }
14742
+ }
14743
+ throw error;
14744
+ } finally {
14745
+ db.close();
14746
+ }
14747
+ }
14702
14748
  function pendingDirFor(caveatHome, sessionId) {
14703
14749
  return join6(caveatHome, "pending", sanitizeSessionId(sessionId));
14704
14750
  }
14705
14751
  function appendPendingReminder(caveatHome, sessionId, text) {
14706
- const dir = pendingDirFor(caveatHome, sessionId);
14707
- mkdirSync3(dir, { recursive: true });
14708
- const name = `${Date.now()}-${randomBytes(4).toString("hex")}.txt`;
14709
- const path = join6(dir, name);
14710
- writeFileSync2(path, text, "utf-8");
14711
- return path;
14752
+ return withPendingQueueLock(caveatHome, () => {
14753
+ const dir = pendingDirFor(caveatHome, sessionId);
14754
+ mkdirSync3(dir, { recursive: true, mode: 448 });
14755
+ const name = `${Date.now()}-${randomBytes(4).toString("hex")}.txt`;
14756
+ const path = join6(dir, name);
14757
+ writeFileSync2(path, text, { encoding: "utf-8", mode: 384, flag: "wx" });
14758
+ return path;
14759
+ });
14760
+ }
14761
+ function buildPendingSemanticKey(input) {
14762
+ const refs = [...input.refs].map(({ source, id }) => ({ source, id })).sort((a, b2) => a.source.localeCompare(b2.source) || a.id.localeCompare(b2.id)).filter((ref, index, all) => index === 0 || ref.source !== all[index - 1].source || ref.id !== all[index - 1].id).map(({ source, id }) => [source, id]);
14763
+ return createHash2("sha256").update(JSON.stringify({
14764
+ agent: input.agent,
14765
+ surface: input.surface,
14766
+ refs,
14767
+ stopSignalDigest: input.stopSignalDigest ?? null
14768
+ })).digest("hex");
14769
+ }
14770
+ var CLAIM_TTL_MS = 5 * 60 * 1e3;
14771
+ function hasNonExpiredPendingClaim(sessionDir, nowMs) {
14772
+ const claimsDir = join6(sessionDir, ".claims");
14773
+ let claims;
14774
+ try {
14775
+ claims = readdirSync6(claimsDir).filter((entry) => entry.endsWith(".claim"));
14776
+ } catch (error) {
14777
+ if (error && typeof error === "object" && error.code === "ENOENT") return false;
14778
+ return true;
14779
+ }
14780
+ for (const claim of claims) {
14781
+ const path = join6(claimsDir, claim);
14782
+ try {
14783
+ let createdAt = statSync5(path).mtimeMs;
14784
+ try {
14785
+ const parsed = JSON.parse(readFileSync5(path, "utf-8"));
14786
+ if (typeof parsed.createdAt === "number") createdAt = parsed.createdAt;
14787
+ } catch {
14788
+ }
14789
+ if (nowMs - createdAt <= CLAIM_TTL_MS) return true;
14790
+ } catch {
14791
+ return true;
14792
+ }
14793
+ }
14794
+ return false;
14795
+ }
14796
+ function acquirePendingClaim(caveatHome, sessionId, key, options2 = {}) {
14797
+ return withPendingQueueLock(caveatHome, () => {
14798
+ if (!/^[a-f0-9]{64}$/.test(key)) throw new Error("pending semantic key is invalid");
14799
+ if (existsSync6(join6(pendingDirFor(caveatHome, sessionId), `${key}.ready`))) return null;
14800
+ const dir = join6(pendingDirFor(caveatHome, sessionId), ".claims");
14801
+ mkdirSync3(dir, { recursive: true, mode: 448 });
14802
+ const path = join6(dir, `${key}.claim`);
14803
+ const now = options2.now ?? Date.now();
14804
+ const ttlMs = options2.ttlMs ?? CLAIM_TTL_MS;
14805
+ for (let attempt = 0; attempt < 2; attempt++) {
14806
+ const ownerToken = randomBytes(16).toString("hex");
14807
+ try {
14808
+ const fd = openSync(path, "wx", 384);
14809
+ try {
14810
+ writeFileSync2(fd, JSON.stringify({ ownerToken, createdAt: now }), "utf-8");
14811
+ } finally {
14812
+ closeSync(fd);
14813
+ }
14814
+ if (existsSync6(join6(pendingDirFor(caveatHome, sessionId), `${key}.ready`))) {
14815
+ unlinkSync2(path);
14816
+ return null;
14817
+ }
14818
+ return { caveatHome, key, ownerToken, path };
14819
+ } catch (error) {
14820
+ if (!(error && typeof error === "object" && error.code === "EEXIST")) throw error;
14821
+ try {
14822
+ let createdAt = statSync5(path).mtimeMs;
14823
+ try {
14824
+ const parsed = JSON.parse(readFileSync5(path, "utf-8"));
14825
+ if (typeof parsed.createdAt === "number") createdAt = parsed.createdAt;
14826
+ } catch {
14827
+ }
14828
+ if (now - createdAt <= ttlMs) return null;
14829
+ unlinkSync2(path);
14830
+ } catch (retryError) {
14831
+ if (retryError && typeof retryError === "object" && retryError.code === "ENOENT") continue;
14832
+ return null;
14833
+ }
14834
+ }
14835
+ }
14836
+ return null;
14837
+ });
14838
+ }
14839
+ function releasePendingClaim(claim) {
14840
+ withPendingQueueLock(claim.caveatHome, () => {
14841
+ try {
14842
+ const parsed = JSON.parse(readFileSync5(claim.path, "utf-8"));
14843
+ if (parsed.ownerToken !== claim.ownerToken) return;
14844
+ unlinkSync2(claim.path);
14845
+ } catch (error) {
14846
+ if (error && typeof error === "object" && error.code === "ENOENT") return;
14847
+ throw error;
14848
+ }
14849
+ });
14850
+ }
14851
+ function publishPendingReminder(caveatHome, sessionId, semanticKey, text) {
14852
+ if (!/^[a-f0-9]{64}$/.test(semanticKey)) throw new Error("pending semantic key is invalid");
14853
+ const ready = join6(pendingDirFor(caveatHome, sessionId), `${semanticKey}.ready`);
14854
+ if (existsSync6(ready)) return { path: ready, published: false };
14855
+ try {
14856
+ return withPendingQueueLock(caveatHome, () => {
14857
+ const dir = pendingDirFor(caveatHome, sessionId);
14858
+ mkdirSync3(dir, { recursive: true, mode: 448 });
14859
+ const temp = join6(dir, `.${semanticKey}.${randomBytes(12).toString("hex")}.tmp`);
14860
+ writeFileSync2(temp, text, { encoding: "utf-8", mode: 384, flag: "wx" });
14861
+ try {
14862
+ linkSync(temp, ready);
14863
+ return { path: ready, published: true };
14864
+ } catch (error) {
14865
+ if (error && typeof error === "object" && error.code === "EEXIST") return { path: ready, published: false };
14866
+ throw error;
14867
+ } finally {
14868
+ try {
14869
+ unlinkSync2(temp);
14870
+ } catch (error) {
14871
+ if (!(error && typeof error === "object" && error.code === "ENOENT")) throw error;
14872
+ }
14873
+ }
14874
+ });
14875
+ } catch (error) {
14876
+ if (existsSync6(ready)) return { path: ready, published: false };
14877
+ throw error;
14878
+ }
14879
+ }
14880
+ function buildAndPublishPendingReminder(caveatHome, sessionId, semanticKey, build) {
14881
+ const claim = acquirePendingClaim(caveatHome, sessionId, semanticKey);
14882
+ if (!claim) return { ran: false, published: false };
14883
+ try {
14884
+ const result = publishPendingReminder(caveatHome, sessionId, claim.key, build());
14885
+ return { ran: true, published: result.published };
14886
+ } finally {
14887
+ releasePendingClaim(claim);
14888
+ }
14712
14889
  }
14713
14890
  function appendGlobalPendingReminder(caveatHome, text) {
14714
14891
  return appendPendingReminder(caveatHome, GLOBAL_PENDING_SESSION, text);
@@ -14722,51 +14899,57 @@ function cleanupStalePendingDirs(caveatHome, options2 = {}) {
14722
14899
  const cutoffMs = now.getTime() - staleDays * 24 * 60 * 60 * 1e3;
14723
14900
  const root = join6(caveatHome, "pending");
14724
14901
  if (!existsSync6(root)) return { removed: [], kept: 0 };
14725
- let entries;
14726
- try {
14727
- entries = readdirSync6(root);
14728
- } catch {
14729
- return { removed: [], kept: 0 };
14730
- }
14731
- const removed = [];
14732
- let kept = 0;
14733
- for (const entry of entries) {
14734
- const sub = join6(root, entry);
14735
- let subStat;
14902
+ return withPendingQueueLock(caveatHome, () => {
14903
+ let entries;
14736
14904
  try {
14737
- subStat = statSync5(sub);
14905
+ entries = readdirSync6(root);
14738
14906
  } catch {
14739
- continue;
14907
+ return { removed: [], kept: 0 };
14740
14908
  }
14741
- if (!subStat.isDirectory()) continue;
14742
- let newest = subStat.mtimeMs;
14743
- let scanFailed = false;
14744
- try {
14745
- for (const f of readdirSync6(sub)) {
14746
- const fp = join6(sub, f);
14747
- try {
14748
- const fs = statSync5(fp);
14749
- if (fs.mtimeMs > newest) newest = fs.mtimeMs;
14750
- } catch {
14751
- scanFailed = true;
14752
- break;
14909
+ const removed = [];
14910
+ let kept = 0;
14911
+ for (const entry of entries) {
14912
+ const sub = join6(root, entry);
14913
+ let subStat;
14914
+ try {
14915
+ subStat = statSync5(sub);
14916
+ } catch {
14917
+ continue;
14918
+ }
14919
+ if (!subStat.isDirectory()) continue;
14920
+ if (hasNonExpiredPendingClaim(sub, now.getTime())) {
14921
+ kept++;
14922
+ continue;
14923
+ }
14924
+ let newest = subStat.mtimeMs;
14925
+ let scanFailed = false;
14926
+ try {
14927
+ for (const f of readdirSync6(sub)) {
14928
+ const fp = join6(sub, f);
14929
+ try {
14930
+ const fileStat = statSync5(fp);
14931
+ if (fileStat.mtimeMs > newest) newest = fileStat.mtimeMs;
14932
+ } catch {
14933
+ scanFailed = true;
14934
+ break;
14935
+ }
14753
14936
  }
14937
+ } catch {
14938
+ scanFailed = true;
14939
+ }
14940
+ if (scanFailed || newest >= cutoffMs) {
14941
+ kept++;
14942
+ continue;
14943
+ }
14944
+ try {
14945
+ rmSync2(sub, { recursive: true, force: true });
14946
+ removed.push(sub);
14947
+ } catch {
14948
+ kept++;
14754
14949
  }
14755
- } catch {
14756
- scanFailed = true;
14757
- }
14758
- if (scanFailed || newest >= cutoffMs) {
14759
- kept++;
14760
- continue;
14761
- }
14762
- try {
14763
- rmSync2(sub, { recursive: true, force: true });
14764
- removed.push(sub);
14765
- } catch {
14766
- kept++;
14767
14950
  }
14768
- }
14769
- return { removed, kept };
14951
+ return { removed, kept };
14952
+ });
14770
14953
  }
14771
14954
  function maybeSweepPendingDirs(caveatHome, options2 = {}) {
14772
14955
  if (process.env.CAVEAT_PENDING_SWEEP === "off") {
@@ -14798,36 +14981,51 @@ function maybeSweepPendingDirs(caveatHome, options2 = {}) {
14798
14981
  }
14799
14982
  return { swept: result };
14800
14983
  }
14801
- function drainPendingReminders(caveatHome, sessionId) {
14984
+ function drainPendingRemindersDetailed(caveatHome, sessionId, fs = {}) {
14802
14985
  const dir = pendingDirFor(caveatHome, sessionId);
14803
- if (!existsSync6(dir)) return [];
14804
- let entries;
14986
+ if (!existsSync6(dir)) return { reminders: [], cleanupFailures: [] };
14805
14987
  try {
14806
- entries = readdirSync6(dir).filter((f) => f.endsWith(".txt")).sort();
14988
+ return withPendingQueueLock(caveatHome, () => {
14989
+ let entries;
14990
+ try {
14991
+ entries = readdirSync6(dir).filter((f) => f.endsWith(".txt") || f.endsWith(".ready"));
14992
+ } catch {
14993
+ return { reminders: [], cleanupFailures: ["pending directory read failed"] };
14994
+ }
14995
+ const cleanupFailures = [];
14996
+ entries.sort((a, b2) => {
14997
+ try {
14998
+ const diff = statSync5(join6(dir, a)).mtimeMs - statSync5(join6(dir, b2)).mtimeMs;
14999
+ return diff || a.localeCompare(b2);
15000
+ } catch {
15001
+ return a.localeCompare(b2);
15002
+ }
15003
+ });
15004
+ const reminders = [];
15005
+ for (const entry of entries) {
15006
+ const path = join6(dir, entry);
15007
+ try {
15008
+ reminders.push(fs.read ? fs.read(path) : readFileSync5(path, "utf-8"));
15009
+ } catch {
15010
+ cleanupFailures.push(entry);
15011
+ continue;
15012
+ }
15013
+ try {
15014
+ if (fs.unlink) fs.unlink(path);
15015
+ else unlinkSync2(path);
15016
+ } catch {
15017
+ cleanupFailures.push(entry);
15018
+ }
15019
+ }
15020
+ return { reminders, cleanupFailures };
15021
+ });
14807
15022
  } catch {
14808
- return [];
14809
- }
14810
- const out = [];
14811
- for (const entry of entries) {
14812
- const path = join6(dir, entry);
14813
- try {
14814
- out.push(readFileSync5(path, "utf-8"));
14815
- } catch {
14816
- continue;
14817
- }
14818
- try {
14819
- unlinkSync2(path);
14820
- } catch {
14821
- }
15023
+ return { reminders: [], cleanupFailures: ["pending queue lock unavailable"] };
14822
15024
  }
14823
- return out;
14824
- }
14825
- function drainGlobalPendingReminders(caveatHome) {
14826
- return drainPendingReminders(caveatHome, GLOBAL_PENDING_SESSION);
14827
15025
  }
14828
15026
 
14829
15027
  // ../../packages/core/dist/sealedKeys.js
14830
- import { createHash as createHash2, randomBytes as randomBytes2 } from "node:crypto";
15028
+ import { createHash as createHash3, randomBytes as randomBytes2 } from "node:crypto";
14831
15029
  import {
14832
15030
  existsSync as existsSync7,
14833
15031
  mkdirSync as mkdirSync4,
@@ -14879,7 +15077,7 @@ function memoryKey(keyserverUrl, keyId) {
14879
15077
  ${keyId}`;
14880
15078
  }
14881
15079
  function cacheFilePath(caveatHome, keyserverUrl, keyId) {
14882
- const digest = createHash2("sha256").update(`${keyserverUrl}
15080
+ const digest = createHash3("sha256").update(`${keyserverUrl}
14883
15081
  ${keyId}`).digest("hex").slice(0, 32);
14884
15082
  return join7(caveatHome, "keys", `${digest}.json`);
14885
15083
  }
@@ -15067,7 +15265,7 @@ async function assertPrivateRemotes(remoteUrls, opts) {
15067
15265
  return worst;
15068
15266
  }
15069
15267
  async function preflightSync(ownDir, opts = {}) {
15070
- const git = createGit(ownDir);
15268
+ const git = createGit(ownDir, { timeoutMs: opts.gitTimeoutMs });
15071
15269
  if (!await git.checkIsRepo()) {
15072
15270
  throw new SyncError("NOT_A_REPO", "own knowledge directory is not a git repository; run `caveat sync --init` first");
15073
15271
  }
@@ -15103,7 +15301,7 @@ async function reindexAndMark(opts) {
15103
15301
  }
15104
15302
  async function syncOwn(opts) {
15105
15303
  const preflight = await preflightSync(opts.ownDir, opts);
15106
- const git = createGit(preflight.ownDir);
15304
+ const git = createGit(preflight.ownDir, { timeoutMs: opts.gitTimeoutMs });
15107
15305
  const status = await git.status();
15108
15306
  if (opts.dryRun) {
15109
15307
  return {
@@ -15236,7 +15434,7 @@ function errorMessage2(err) {
15236
15434
  }
15237
15435
 
15238
15436
  // ../../packages/core/dist/autoSync.js
15239
- import { createHash as createHash3 } from "node:crypto";
15437
+ import { createHash as createHash4 } from "node:crypto";
15240
15438
  import { mkdirSync as mkdirSync6, readFileSync as readFileSync7, renameSync as renameSync3, writeFileSync as writeFileSync5 } from "node:fs";
15241
15439
  import { dirname as dirname5, join as join9 } from "node:path";
15242
15440
  var AUTO_SYNC_DEBOUNCE_MS = 24 * 60 * 60 * 1e3;
@@ -15347,7 +15545,8 @@ async function runAutoSync(opts) {
15347
15545
  };
15348
15546
  const community = await communityPull({
15349
15547
  communityDir: opts.paths.communityDir,
15350
- logger: opts.logger
15548
+ logger: opts.logger,
15549
+ gitTimeoutMs: opts.gitTimeoutMs ?? BACKGROUND_GIT_TIMEOUT_MS
15351
15550
  });
15352
15551
  let own;
15353
15552
  if (ownSyncState.consecutiveFailureCount >= 3) {
@@ -15361,6 +15560,7 @@ async function runAutoSync(opts) {
15361
15560
  paths: opts.paths,
15362
15561
  logger: opts.logger,
15363
15562
  trustRemotePrivate: false,
15563
+ gitTimeoutMs: opts.gitTimeoutMs ?? BACKGROUND_GIT_TIMEOUT_MS,
15364
15564
  probeImpl: async (url) => {
15365
15565
  const probe = await probeAnonymousRead(url);
15366
15566
  lastProbe = probe;
@@ -15436,7 +15636,7 @@ async function reindexAndMark2(opts) {
15436
15636
  }
15437
15637
  }
15438
15638
  function sha256(input) {
15439
- return createHash3("sha256").update(input).digest("hex");
15639
+ return createHash4("sha256").update(input).digest("hex");
15440
15640
  }
15441
15641
  function errorMessage3(err) {
15442
15642
  return err instanceof Error ? err.message : String(err);
@@ -15693,7 +15893,40 @@ function toSearchResult2(row) {
15693
15893
  environment: fm.environment ?? {}
15694
15894
  };
15695
15895
  }
15696
- function findCaveatsForPrompt(db, prompt, opts = {}) {
15896
+ function findCaveatsForHook(db, input, opts = {}) {
15897
+ if (input.surface === "user_prompt") {
15898
+ const prompt = input.topicText || input.failureText;
15899
+ return findCaveatsForText(db, prompt, opts);
15900
+ }
15901
+ if (input.surface === "stop") {
15902
+ return findCaveatsForText(db, input.failureText, opts);
15903
+ }
15904
+ const failureTokens = new Set(
15905
+ buildPromptCandidates(input.failureText).map((candidate) => candidate.token.toLowerCase())
15906
+ );
15907
+ return findCaveatsForText(
15908
+ db,
15909
+ [input.topicText, input.failureText].filter(Boolean).join("\n"),
15910
+ opts,
15911
+ failureTokens
15912
+ );
15913
+ }
15914
+ function findCaveatsForHookSegments(db, inputs, opts = {}) {
15915
+ const limit = opts.limit ?? DEFAULT_REMINDER_HIT_LIMIT;
15916
+ const out = [];
15917
+ const seen = /* @__PURE__ */ new Set();
15918
+ for (const input of inputs) {
15919
+ for (const hit of findCaveatsForHook(db, input, { ...opts, limit })) {
15920
+ const key = `${hit.source}\0${hit.id}`;
15921
+ if (seen.has(key)) continue;
15922
+ seen.add(key);
15923
+ out.push(hit);
15924
+ if (out.length >= limit) return out;
15925
+ }
15926
+ }
15927
+ return out;
15928
+ }
15929
+ function findCaveatsForText(db, prompt, opts = {}, symptomEvidenceTokens) {
15697
15930
  if (typeof prompt !== "string" || prompt.length === 0) return [];
15698
15931
  const candidates = buildPromptCandidates(prompt);
15699
15932
  if (candidates.length === 0) return [];
@@ -15732,7 +15965,7 @@ function findCaveatsForPrompt(db, prompt, opts = {}) {
15732
15965
  perEntry.set(row.rowid, entry);
15733
15966
  }
15734
15967
  entry.groups.add(cand.group);
15735
- if (entry.symptomLower !== null && tokenAppearsIn(tokLower, entry.symptomLower)) {
15968
+ if (entry.symptomLower !== null && (symptomEvidenceTokens === void 0 || symptomEvidenceTokens.has(tokLower)) && tokenAppearsIn(tokLower, entry.symptomLower)) {
15736
15969
  entry.symptomTokens.add(tokLower);
15737
15970
  }
15738
15971
  if (entry.topicalLower !== null && tokenAppearsIn(tokLower, entry.topicalLower)) {
@@ -16150,14 +16383,14 @@ function markHit(db, keys, now = () => (/* @__PURE__ */ new Date()).toISOString(
16150
16383
  }
16151
16384
 
16152
16385
  // ../../packages/core/dist/hookQueryLog.js
16153
- import { appendFileSync, chmodSync, mkdirSync as mkdirSync7, renameSync as renameSync4, statSync as statSync6, unlinkSync as unlinkSync3 } from "node:fs";
16386
+ import { appendFileSync, chmodSync as chmodSync2, mkdirSync as mkdirSync7, renameSync as renameSync4, statSync as statSync6, unlinkSync as unlinkSync3 } from "node:fs";
16154
16387
  import { join as join11, resolve as resolve3 } from "node:path";
16155
16388
  var CAVEAT_HOOK_QUERY_LOG_ENV = "CAVEAT_HOOK_QUERY_LOG";
16156
16389
  var HOOK_QUERY_LOG_MAX_BYTES = 1024 * 1024;
16157
16390
  var HOOK_QUERY_LOG_MAX_QUERY_CODE_UNITS = 1e3;
16158
16391
  var fsDependencies = {
16159
16392
  appendFileSync,
16160
- chmodSync,
16393
+ chmodSync: chmodSync2,
16161
16394
  mkdirSync: mkdirSync7,
16162
16395
  renameSync: renameSync4,
16163
16396
  statSync: statSync6,
@@ -16247,7 +16480,7 @@ function listStale(db, opts = {}) {
16247
16480
  }
16248
16481
 
16249
16482
  // ../../packages/core/dist/publishScan.js
16250
- import { createHash as createHash4 } from "node:crypto";
16483
+ import { createHash as createHash5 } from "node:crypto";
16251
16484
  import { existsSync as existsSync13, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "node:fs";
16252
16485
  import { join as join12, basename as basename2 } from "node:path";
16253
16486
  import { homedir as homedir2, userInfo as userInfo2 } from "node:os";
@@ -16278,7 +16511,7 @@ var WIN_UNC_PATH_RE = /\\\\[^\\\s"'`<>()|]+\\[^\\\s"'`<>()|]+(?:\\[^\\\s"'`<>()|
16278
16511
  var PRIVATE_IP_RE = /\b(?:10\.(?:\d{1,3}\.){2}\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|127\.\d{1,3}\.\d{1,3}\.\d{1,3}|169\.254\.\d{1,3}\.\d{1,3})\b/g;
16279
16512
  var EMAIL_RE = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
16280
16513
  function sha2562(raw) {
16281
- return createHash4("sha256").update(raw).digest("hex");
16514
+ return createHash5("sha256").update(raw).digest("hex");
16282
16515
  }
16283
16516
  function findingDigest(opts) {
16284
16517
  const pemContext = opts.rule === "pem-private-key" ? `\0${opts.fileDigest}\0${opts.lineNumber}\0${opts.index}` : "";
@@ -16508,7 +16741,7 @@ var PublishScanError = class extends Error {
16508
16741
  };
16509
16742
 
16510
16743
  // ../../packages/core/dist/publish.js
16511
- import { createHash as createHash5 } from "node:crypto";
16744
+ import { createHash as createHash6 } from "node:crypto";
16512
16745
  import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as readFileSync12, readdirSync as readdirSync8, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "node:fs";
16513
16746
  import { dirname as dirname7, join as join13, relative as relative3 } from "node:path";
16514
16747
 
@@ -16647,7 +16880,7 @@ ${bad.join("\n")}`);
16647
16880
  return { fileCount: unsealed.files.length };
16648
16881
  }
16649
16882
  function sha2563(content) {
16650
- return createHash5("sha256").update(content).digest("hex");
16883
+ return createHash6("sha256").update(content).digest("hex");
16651
16884
  }
16652
16885
  function diffFiles(previous, next) {
16653
16886
  const before = new Map(previous.map((file) => [file.relPath, sha2563(file.content)]));
@@ -17226,15 +17459,15 @@ function formatYmd2(d) {
17226
17459
  }
17227
17460
 
17228
17461
  // ../../packages/core/dist/proposalEval.js
17229
- import { createHash as createHash6 } from "node:crypto";
17462
+ import { createHash as createHash7 } from "node:crypto";
17230
17463
  import { isAbsolute as isAbsolute2, relative as relative4, resolve as resolve4, sep } from "node:path";
17231
17464
 
17232
17465
  // ../../packages/core/dist/proposalExecution.js
17233
- import { createHash as createHash7 } from "node:crypto";
17466
+ import { createHash as createHash8 } from "node:crypto";
17234
17467
  import { basename as basename3, isAbsolute as isAbsolute3 } from "node:path";
17235
17468
 
17236
17469
  // ../../packages/core/dist/proposalExecutionCompiler.js
17237
- import { createHash as createHash8 } from "node:crypto";
17470
+ import { createHash as createHash9 } from "node:crypto";
17238
17471
 
17239
17472
  export {
17240
17473
  __commonJS,
@@ -17254,11 +17487,11 @@ export {
17254
17487
  communityPull,
17255
17488
  communityList,
17256
17489
  communityRemove,
17257
- appendPendingReminder,
17490
+ buildPendingSemanticKey,
17491
+ buildAndPublishPendingReminder,
17258
17492
  cleanupStalePendingDirs,
17259
17493
  maybeSweepPendingDirs,
17260
- drainPendingReminders,
17261
- drainGlobalPendingReminders,
17494
+ drainPendingRemindersDetailed,
17262
17495
  createKeyserverKeyProvider,
17263
17496
  SyncError,
17264
17497
  syncOwn,
@@ -17279,7 +17512,8 @@ export {
17279
17512
  ensureUserConfig,
17280
17513
  writeUserConfigPatch,
17281
17514
  defaultSelfIdentityTokens,
17282
- findCaveatsForPrompt,
17515
+ findCaveatsForHook,
17516
+ findCaveatsForHookSegments,
17283
17517
  toolErrorReminderText,
17284
17518
  userPromptSubmitReminderText,
17285
17519
  stopReminderText,
@@ -17320,4 +17554,4 @@ strip-bom-string/index.js:
17320
17554
  js-yaml/dist/js-yaml.mjs:
17321
17555
  (*! js-yaml 4.1.1 https://github.com/nodeca/js-yaml @license MIT *)
17322
17556
  */
17323
- //# sourceMappingURL=chunk-ZNAFNCPW.js.map
17557
+ //# sourceMappingURL=chunk-3MLJQLWY.js.map