caveat-cli 0.10.0 → 0.11.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
@@ -21339,6 +21339,10 @@ function stopReminderText(signals, related) {
21339
21339
  if (signals.durationMinutes > 0) {
21340
21340
  lines.push(`- \u7D4C\u904E\u6642\u9593: ${signals.durationMinutes} \u5206`);
21341
21341
  }
21342
+ const externalLookup = signals.webSearchCount + signals.webFetchCount > 0;
21343
+ lines.push(
21344
+ `- \u5206\u985E\u30D2\u30F3\u30C8: ${externalLookup ? "\u5916\u90E8\u4ED5\u69D8\u8ABF\u67FB\u3042\u308A \u2192 public \u5BC4\u308A" : "\u5916\u90E8\u8ABF\u67FB\u306A\u3057 \u2192 private \u5BC4\u308A"}`
21345
+ );
21342
21346
  lines.push("");
21343
21347
  if (related.length > 0) {
21344
21348
  lines.push(
@@ -21356,6 +21360,9 @@ function stopReminderText(signals, related) {
21356
21360
  "\u65E2\u5B58\u7F60\u306B\u8A72\u5F53\u306A\u3057\u3002\u5916\u90E8\u4ED5\u69D8\u306E\u7F60\u306B\u82E6\u6226\u3057\u3066\u3044\u305F\u306A\u3089 mcp__caveat__caveat_record \u3067\u767B\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002outcome: impossible \u3082\u8A18\u9332\u5BFE\u8C61\u3002"
21357
21361
  );
21358
21362
  }
21363
+ lines.push(
21364
+ "\u8A18\u9332\u6642\u306F tool \u8AAC\u660E\u306E\u4E8C\u9805\u57FA\u6E96\u3067 visibility \u3092\u9078\u3076\uFF08public = \u7B2C\u4E09\u8005\u518D\u73FE\u53EF\u80FD / private = repo \u56FA\u6709\uFF09\u3002\u8FF7\u3063\u305F\u3089 private\u3002"
21365
+ );
21359
21366
  return lines.join("\n");
21360
21367
  }
21361
21368
 
@@ -21513,6 +21520,48 @@ function drainPendingReminders(caveatHome, sessionId) {
21513
21520
  return out;
21514
21521
  }
21515
21522
 
21523
+ // ../../packages/core/dist/markHit.js
21524
+ function markHit(db, keys, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
21525
+ if (keys.length === 0) return;
21526
+ const ts = now();
21527
+ const stmt = db.prepare(
21528
+ "UPDATE entries SET last_hit_at = ? WHERE source = ? AND id = ?"
21529
+ );
21530
+ for (const k2 of keys) {
21531
+ stmt.run(ts, k2.source, k2.id);
21532
+ }
21533
+ }
21534
+
21535
+ // ../../packages/core/dist/stale.js
21536
+ function listStale(db, opts = {}) {
21537
+ const days = opts.days ?? 90;
21538
+ const limit = opts.limit ?? 50;
21539
+ const now = (opts.now ?? (() => /* @__PURE__ */ new Date()))();
21540
+ const cutoff = new Date(now.getTime() - days * 24 * 60 * 60 * 1e3).toISOString();
21541
+ const conditions = ["(last_hit_at IS NULL OR last_hit_at < ?)"];
21542
+ const params = [cutoff];
21543
+ if (opts.visibility === "public" || opts.visibility === "private") {
21544
+ conditions.push("visibility = ?");
21545
+ params.push(opts.visibility);
21546
+ }
21547
+ const sql = `
21548
+ SELECT id, source, title, visibility, last_hit_at
21549
+ FROM entries
21550
+ WHERE ${conditions.join(" AND ")}
21551
+ ORDER BY last_hit_at IS NULL DESC, last_hit_at ASC
21552
+ LIMIT ?
21553
+ `;
21554
+ params.push(limit);
21555
+ const rows = db.prepare(sql).all(...params);
21556
+ return rows.map((r2) => ({
21557
+ id: r2.id,
21558
+ source: r2.source,
21559
+ title: r2.title,
21560
+ visibility: r2.visibility ?? "public",
21561
+ last_hit_at: r2.last_hit_at
21562
+ }));
21563
+ }
21564
+
21516
21565
  // src/context.ts
21517
21566
  function buildContext(logger, overrides = {}) {
21518
21567
  const userHome = overrides.userHome ?? homedir();
@@ -21906,6 +21955,29 @@ function runList(ctx, opts) {
21906
21955
  }
21907
21956
  }
21908
21957
 
21958
+ // src/commands/stale.ts
21959
+ function runStale(ctx, opts) {
21960
+ const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
21961
+ try {
21962
+ const rows = listStale(db, {
21963
+ days: opts.days,
21964
+ visibility: opts.visibility,
21965
+ limit: opts.limit
21966
+ });
21967
+ if (rows.length === 0) {
21968
+ process.stdout.write("(no stale entries)\n");
21969
+ return;
21970
+ }
21971
+ for (const r2 of rows) {
21972
+ const age = r2.last_hit_at ?? "never";
21973
+ process.stdout.write(`${r2.id} [${r2.source}] (${r2.visibility}) ${age} \u2014 ${r2.title}
21974
+ `);
21975
+ }
21976
+ } finally {
21977
+ db.close();
21978
+ }
21979
+ }
21980
+
21909
21981
  // src/commands/show.ts
21910
21982
  function runShow(ctx, opts) {
21911
21983
  const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
@@ -44694,12 +44766,24 @@ function buildMcpContext(overrides = {}) {
44694
44766
  // ../mcp/dist/tools/search.js
44695
44767
  var sourceFilter = external_exports.enum(["own", "community", "all"]);
44696
44768
  var confidenceSchema = external_exports.enum(["confirmed", "reproduced", "tentative"]);
44769
+ var visibilityFilter = external_exports.enum(["public", "private", "all"]);
44697
44770
  var searchInputShape = {
44698
44771
  query: external_exports.string().describe("FTS query (3+ chars for trigram). Empty string lists without text filter."),
44699
44772
  filters: external_exports.object({
44700
44773
  tags: external_exports.array(external_exports.string()).optional(),
44701
44774
  confidence: external_exports.array(confidenceSchema).optional(),
44702
- source: sourceFilter.optional()
44775
+ source: sourceFilter.optional(),
44776
+ visibility: visibilityFilter.optional().describe(
44777
+ [
44778
+ "Narrow by publish tier.",
44779
+ "'public' = external-spec gotchas reproducible by any third party (PyInstaller, Stripe, Claude Code hook behavior, etc).",
44780
+ "'private' = your own cross-project notes (repo-specific, your workflow, intentional non-standard design).",
44781
+ "'all' (or omit) = both tiers.",
44782
+ "Use 'public' when drafting externally-visible output (PR descriptions, public docs, answers to third parties) so private notes do not bleed into external content.",
44783
+ "Use 'private' when specifically recalling your own past decisions.",
44784
+ "Default to omitting this filter \u2014 narrowing too aggressively hides relevant entries."
44785
+ ].join(" ")
44786
+ )
44703
44787
  }).optional(),
44704
44788
  limit: external_exports.number().int().min(1).max(200).optional()
44705
44789
  };
@@ -44709,6 +44793,7 @@ function handleSearch(ctx, args) {
44709
44793
  filters: args.filters,
44710
44794
  limit: args.limit
44711
44795
  });
44796
+ if (results.length > 0) markHit(ctx.db, results);
44712
44797
  return results;
44713
44798
  }
44714
44799
 
@@ -44740,7 +44825,14 @@ var recordInputShape = {
44740
44825
  confidence: confidenceSchema2.optional(),
44741
44826
  outcome: outcomeSchema.optional(),
44742
44827
  visibility: visibilitySchema.describe(
44743
- 'REQUIRED. Ask the user "public (share to community) or private (keep local only)?" before calling \u2014 never auto-classify. The user owns the knowledge; they decide its reach.'
44828
+ [
44829
+ "REQUIRED. Classify using this binary criterion:",
44830
+ "- 'public' if a third party running the same external tool/spec could reproduce this gotcha (external-spec trap, e.g. PyInstaller/Stripe/Podman/Claude Code hook behavior).",
44831
+ "- 'private' if it is specific to your repo, your workflow, an intentional non-standard design, or context that only exists in this project.",
44832
+ "- When unclear, prefer 'private' (leak-safety).",
44833
+ `Exception: if the user explicitly asks to record it as private/public (e.g. "save this as private", "\u3053\u308C\u306F\u81EA\u5206\u7528\u306B\u30E1\u30E2\u3057\u3066"), follow the user's instruction regardless of the criterion \u2014 explicit user intent overrides auto-classification.`,
44834
+ "When recording with visibility: 'private', always include repo-specific identifiers (function names, file paths, class names, custom terminology) in the body so the entry can be retrieved later by co-occurrence FTS when you touch that area again."
44835
+ ].join(" ")
44744
44836
  ),
44745
44837
  tags: external_exports.array(external_exports.string()).optional(),
44746
44838
  environment: external_exports.record(external_exports.string(), external_exports.string()).optional(),
@@ -44761,7 +44853,13 @@ var patchFrontmatterSchema = external_exports.object({
44761
44853
  title: external_exports.string().optional(),
44762
44854
  confidence: confidenceSchema3.optional(),
44763
44855
  outcome: outcomeSchema2.optional(),
44764
- visibility: visibilitySchema2.optional(),
44856
+ visibility: visibilitySchema2.optional().describe(
44857
+ [
44858
+ "Change the publish tier. Use the same binary criterion as caveat_record:",
44859
+ "'public' if third-party reproducible, 'private' if repo-specific/your-workflow-specific.",
44860
+ "When unclear, prefer 'private'. Explicit user instruction overrides auto-classification."
44861
+ ].join(" ")
44862
+ ),
44765
44863
  tags: external_exports.array(external_exports.string()).optional(),
44766
44864
  environment: external_exports.record(external_exports.string(), external_exports.string()).optional(),
44767
44865
  last_verified: external_exports.string().optional()
@@ -44982,7 +45080,17 @@ function searchCaveatsFromTextSafely(text2) {
44982
45080
  const ctx = buildContextSafely();
44983
45081
  if (!ctx || !existsSync14(ctx.paths.dbPath)) return [];
44984
45082
  db = openDb({ path: ctx.paths.dbPath });
44985
- return findCaveatsForPrompt(db, text2);
45083
+ const hits = findCaveatsForPrompt(db, text2);
45084
+ if (hits.length > 0) {
45085
+ try {
45086
+ markHit(db, hits);
45087
+ } catch (err) {
45088
+ const msg = err instanceof Error ? err.message : String(err);
45089
+ process.stderr.write(`[caveat:hook] markHit error: ${msg}
45090
+ `);
45091
+ }
45092
+ }
45093
+ return hits;
44986
45094
  } catch (err) {
44987
45095
  const msg = err instanceof Error ? err.message : String(err);
44988
45096
  process.stderr.write(`[caveat:hook] search error: ${msg}
@@ -45312,6 +45420,13 @@ program.command("list").description("List caveats by updated_at DESC").option("-
45312
45420
  const ctx = buildContext(stdoutLogger);
45313
45421
  runList(ctx, { limit: opts.recent });
45314
45422
  });
45423
+ program.command("stale").description(
45424
+ "List entries not surfaced by retrieval for N days (default 90). Use this to find private caveats that may be buried \u2014 if a 3-month-old private entry never surfaces, rewrite its body to include repo-specific identifiers, or delete it."
45425
+ ).option("--days <n>", "age threshold in days", (v) => Number(v), 90).option("--visibility <v>", "public | private").option("--limit <n>", "max rows", (v) => Number(v), 50).action((opts) => {
45426
+ const ctx = buildContext(stdoutLogger);
45427
+ const vis = opts.visibility === "public" || opts.visibility === "private" ? opts.visibility : void 0;
45428
+ runStale(ctx, { days: opts.days, visibility: vis, limit: opts.limit });
45429
+ });
45315
45430
  program.command("show").description("Show full caveat by id").argument("<id>", "entry id").option("--source <source>", "own or community/<handle>", "own").action((id, opts) => {
45316
45431
  const ctx = buildContext(stdoutLogger);
45317
45432
  runShow(ctx, { id, source: opts.source });