caveat-cli 0.11.1 → 0.12.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/caveat.js CHANGED
File without changes
package/dist/index.js CHANGED
@@ -12975,7 +12975,7 @@ var require_dist3 = __commonJS({
12975
12975
  import { Command } from "commander";
12976
12976
 
12977
12977
  // src/context.ts
12978
- import { homedir } from "node:os";
12978
+ import { homedir as homedir2 } from "node:os";
12979
12979
  import { join as join8 } from "node:path";
12980
12980
 
12981
12981
  // ../../packages/core/dist/db.js
@@ -12983,42 +12983,6 @@ import { DatabaseSync } from "node:sqlite";
12983
12983
  import { readFileSync, readdirSync, existsSync, mkdirSync } from "node:fs";
12984
12984
  import { fileURLToPath } from "node:url";
12985
12985
  import { dirname, join } from "node:path";
12986
- var here = dirname(fileURLToPath(import.meta.url));
12987
- var SCHEMA_PATH = join(here, "schema.sql");
12988
- var MIGRATIONS_DIR = join(here, "migrations");
12989
- var stderrLogger = {
12990
- info: (m) => process.stderr.write(`[caveat] ${m}
12991
- `),
12992
- warn: (m) => process.stderr.write(`[caveat:warn] ${m}
12993
- `),
12994
- error: (m) => process.stderr.write(`[caveat:error] ${m}
12995
- `)
12996
- };
12997
- function openDb(opts) {
12998
- const parent = dirname(opts.path);
12999
- if (!existsSync(parent)) mkdirSync(parent, { recursive: true });
13000
- const db = new DatabaseSync(opts.path);
13001
- db.exec("PRAGMA journal_mode = WAL");
13002
- db.exec("PRAGMA foreign_keys = ON");
13003
- const { user_version } = db.prepare("PRAGMA user_version").get();
13004
- if (user_version === 0) {
13005
- db.exec(readFileSync(SCHEMA_PATH, "utf-8"));
13006
- } else {
13007
- applyMigrations(db, user_version);
13008
- }
13009
- return db;
13010
- }
13011
- function applyMigrations(db, currentVersion) {
13012
- if (!existsSync(MIGRATIONS_DIR)) return;
13013
- const files = readdirSync(MIGRATIONS_DIR).filter((f) => /^\d+_.+\.sql$/.test(f)).sort();
13014
- for (const file of files) {
13015
- const n = Number(file.split("_")[0]);
13016
- if (n <= currentVersion) continue;
13017
- const sql = readFileSync(join(MIGRATIONS_DIR, file), "utf-8");
13018
- db.exec(sql);
13019
- db.exec(`PRAGMA user_version = ${n}`);
13020
- }
13021
- }
13022
12986
 
13023
12987
  // ../../packages/core/dist/frontmatter.js
13024
12988
  var import_gray_matter = __toESM(require_gray_matter(), 1);
@@ -15675,6 +15639,100 @@ function extractSections(body) {
15675
15639
  if (heading2 !== null) result[heading2] = buf.join("\n").trim();
15676
15640
  return result;
15677
15641
  }
15642
+ function deriveRoleTexts(input) {
15643
+ const tagLabels = [];
15644
+ if (input.tags) {
15645
+ try {
15646
+ const parsed = JSON.parse(input.tags);
15647
+ if (Array.isArray(parsed)) {
15648
+ for (const t2 of parsed) if (typeof t2 === "string") tagLabels.push(t2);
15649
+ }
15650
+ } catch {
15651
+ }
15652
+ }
15653
+ const envValues = [];
15654
+ if (input.frontmatter_json) {
15655
+ try {
15656
+ const fm = JSON.parse(input.frontmatter_json);
15657
+ if (fm && typeof fm.environment === "object" && fm.environment !== null) {
15658
+ for (const v of Object.values(fm.environment)) {
15659
+ if (typeof v === "string") envValues.push(v);
15660
+ else if (typeof v === "number" || typeof v === "boolean") envValues.push(String(v));
15661
+ }
15662
+ }
15663
+ } catch {
15664
+ }
15665
+ }
15666
+ const topical = [input.title, ...tagLabels, ...envValues].join("\n");
15667
+ const sections = extractSections(input.body);
15668
+ const symptomKey = Object.keys(sections).find(
15669
+ (k2) => k2.trim().toLowerCase() === "symptom"
15670
+ );
15671
+ const symptom = symptomKey ? sections[symptomKey] ?? "" : "";
15672
+ return { topical, symptom };
15673
+ }
15674
+
15675
+ // ../../packages/core/dist/db.js
15676
+ var here = dirname(fileURLToPath(import.meta.url));
15677
+ var SCHEMA_PATH = join(here, "schema.sql");
15678
+ var MIGRATIONS_DIR = join(here, "migrations");
15679
+ var stderrLogger = {
15680
+ info: (m) => process.stderr.write(`[caveat] ${m}
15681
+ `),
15682
+ warn: (m) => process.stderr.write(`[caveat:warn] ${m}
15683
+ `),
15684
+ error: (m) => process.stderr.write(`[caveat:error] ${m}
15685
+ `)
15686
+ };
15687
+ function openDb(opts) {
15688
+ const parent = dirname(opts.path);
15689
+ if (!existsSync(parent)) mkdirSync(parent, { recursive: true });
15690
+ const db = new DatabaseSync(opts.path);
15691
+ db.exec("PRAGMA journal_mode = WAL");
15692
+ db.exec("PRAGMA foreign_keys = ON");
15693
+ const { user_version } = db.prepare("PRAGMA user_version").get();
15694
+ if (user_version === 0) {
15695
+ db.exec(readFileSync(SCHEMA_PATH, "utf-8"));
15696
+ } else {
15697
+ applyMigrations(db, user_version);
15698
+ backfillRoleTexts(db);
15699
+ }
15700
+ return db;
15701
+ }
15702
+ function backfillRoleTexts(db) {
15703
+ let rows;
15704
+ try {
15705
+ rows = db.prepare(
15706
+ "SELECT rowid, title, body, tags, frontmatter_json FROM entries WHERE topical_text IS NULL"
15707
+ ).all();
15708
+ } catch {
15709
+ return;
15710
+ }
15711
+ if (rows.length === 0) return;
15712
+ const upd = db.prepare(
15713
+ "UPDATE entries SET topical_text = ?, symptom_text = ? WHERE rowid = ?"
15714
+ );
15715
+ for (const r2 of rows) {
15716
+ const { topical, symptom } = deriveRoleTexts({
15717
+ title: r2.title,
15718
+ body: r2.body,
15719
+ tags: r2.tags,
15720
+ frontmatter_json: r2.frontmatter_json
15721
+ });
15722
+ upd.run(topical, symptom, r2.rowid);
15723
+ }
15724
+ }
15725
+ function applyMigrations(db, currentVersion) {
15726
+ if (!existsSync(MIGRATIONS_DIR)) return;
15727
+ const files = readdirSync(MIGRATIONS_DIR).filter((f) => /^\d+_.+\.sql$/.test(f)).sort();
15728
+ for (const file of files) {
15729
+ const n = Number(file.split("_")[0]);
15730
+ if (n <= currentVersion) continue;
15731
+ const sql = readFileSync(join(MIGRATIONS_DIR, file), "utf-8");
15732
+ db.exec(sql);
15733
+ db.exec(`PRAGMA user_version = ${n}`);
15734
+ }
15735
+ }
15678
15736
 
15679
15737
  // ../../packages/core/dist/env.js
15680
15738
  var import_semver = __toESM(require_semver2(), 1);
@@ -15735,11 +15793,18 @@ function scanSource(opts) {
15735
15793
  }
15736
15794
  function upsertEntry(db, row) {
15737
15795
  const existing = db.prepare("SELECT rowid FROM entries WHERE source = ? AND id = ?").get(row.source, row.id);
15796
+ const { topical, symptom } = deriveRoleTexts({
15797
+ title: row.title,
15798
+ body: row.body,
15799
+ tags: row.tags,
15800
+ frontmatter_json: row.frontmatter_json
15801
+ });
15738
15802
  if (existing) {
15739
15803
  db.prepare(
15740
15804
  `UPDATE entries
15741
15805
  SET path = ?, title = ?, body = ?, frontmatter_json = ?, tags = ?,
15742
- confidence = ?, visibility = ?, file_mtime = ?, indexed_at = ?
15806
+ confidence = ?, visibility = ?, file_mtime = ?, indexed_at = ?,
15807
+ topical_text = ?, symptom_text = ?
15743
15808
  WHERE source = ? AND id = ?`
15744
15809
  ).run(
15745
15810
  row.path,
@@ -15751,14 +15816,16 @@ function upsertEntry(db, row) {
15751
15816
  row.visibility,
15752
15817
  row.file_mtime,
15753
15818
  row.indexed_at,
15819
+ topical,
15820
+ symptom,
15754
15821
  row.source,
15755
15822
  row.id
15756
15823
  );
15757
15824
  return existing.rowid;
15758
15825
  }
15759
15826
  const info = db.prepare(
15760
- `INSERT INTO entries (id, source, path, title, body, frontmatter_json, tags, confidence, visibility, file_mtime, indexed_at)
15761
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
15827
+ `INSERT INTO entries (id, source, path, title, body, frontmatter_json, tags, confidence, visibility, file_mtime, indexed_at, topical_text, symptom_text)
15828
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
15762
15829
  ).run(
15763
15830
  row.id,
15764
15831
  row.source,
@@ -15770,7 +15837,9 @@ function upsertEntry(db, row) {
15770
15837
  row.confidence,
15771
15838
  row.visibility,
15772
15839
  row.file_mtime,
15773
- row.indexed_at
15840
+ row.indexed_at,
15841
+ topical,
15842
+ symptom
15774
15843
  );
15775
15844
  return Number(info.lastInsertRowid);
15776
15845
  }
@@ -21203,6 +21272,7 @@ function communityRemove(opts) {
21203
21272
  }
21204
21273
 
21205
21274
  // ../../packages/core/dist/claudeHooks.js
21275
+ import { homedir, userInfo } from "node:os";
21206
21276
  var PROMPT_TOKEN_MIN_LENGTH = 3;
21207
21277
  var PROMPT_MAX_CANDIDATE_TOKENS = 50;
21208
21278
  var DEFAULT_REMINDER_HIT_LIMIT = 5;
@@ -21210,35 +21280,75 @@ var SYMPTOM_EXCERPT_LENGTH2 = 200;
21210
21280
  var SYMPTOM_LINE_MAX = 120;
21211
21281
  var MIN_DISTINCT_TOKEN_MATCHES_CEILING = 2;
21212
21282
  var CJK_CHAR = /[぀-ゟ゠-ヿ一-鿿ヲ-゚]/;
21283
+ var HIRAGANA_ONLY = /^[぀-ゟ]+$/;
21213
21284
  function isCjkDominated(token) {
21214
21285
  return CJK_CHAR.test(token);
21215
21286
  }
21216
- function expandToken(token, out) {
21287
+ function isPureHiragana(token) {
21288
+ return HIRAGANA_ONLY.test(token);
21289
+ }
21290
+ function escapeForRegex(s) {
21291
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
21292
+ }
21293
+ function tokenAppearsIn(tokenLower, textLower) {
21294
+ if (textLower.length === 0) return false;
21295
+ if (CJK_CHAR.test(tokenLower)) return textLower.includes(tokenLower);
21296
+ const re = new RegExp(
21297
+ `(?:^|[^\\p{L}\\p{N}])${escapeForRegex(tokenLower)}(?:[^\\p{L}\\p{N}]|$)`,
21298
+ "u"
21299
+ );
21300
+ return re.test(textLower);
21301
+ }
21302
+ function expandToken(token, group, out) {
21217
21303
  if (isCjkDominated(token)) {
21218
21304
  if (token.length < PROMPT_TOKEN_MIN_LENGTH) return;
21219
21305
  for (let i2 = 0; i2 <= token.length - PROMPT_TOKEN_MIN_LENGTH; i2++) {
21220
- out.push(token.slice(i2, i2 + PROMPT_TOKEN_MIN_LENGTH));
21306
+ const tri = token.slice(i2, i2 + PROMPT_TOKEN_MIN_LENGTH);
21307
+ if (isPureHiragana(tri)) continue;
21308
+ out.push({ token: tri, group });
21221
21309
  }
21222
21310
  } else if (token.length >= PROMPT_TOKEN_MIN_LENGTH) {
21223
- out.push(token);
21311
+ out.push({ token, group });
21224
21312
  }
21225
21313
  }
21226
- function extractPromptCandidates(prompt) {
21227
- if (typeof prompt !== "string" || prompt.length === 0) return [];
21228
- const cleaned = prompt.replace(/[^\p{L}\p{N}\s]/gu, " ");
21314
+ function stripFsPaths(s) {
21315
+ return s.replace(/\\\\[^\s]+/g, " ").replace(/(^|\s)[A-Za-z]:[\\/][^\s]*/g, "$1 ").replace(/(^|\s)\/(?:[^\s/]+\/)+[^\s/]*/g, "$1 ");
21316
+ }
21317
+ function buildPromptCandidates(prompt) {
21318
+ const cleaned = stripFsPaths(prompt).replace(/[^\p{L}\p{N}\s]/gu, " ");
21229
21319
  const rawTokens = cleaned.split(/\s+/).filter((t2) => t2.length > 0);
21230
21320
  const expanded = [];
21231
- for (const t2 of rawTokens) expandToken(t2, expanded);
21321
+ for (let i2 = 0; i2 < rawTokens.length; i2++) {
21322
+ expandToken(rawTokens[i2], i2, expanded);
21323
+ }
21232
21324
  const seen = /* @__PURE__ */ new Set();
21233
21325
  const unique = [];
21234
- for (const t2 of expanded) {
21235
- const key = t2.toLowerCase();
21326
+ for (const c3 of expanded) {
21327
+ const key = c3.token.toLowerCase();
21236
21328
  if (seen.has(key)) continue;
21237
21329
  seen.add(key);
21238
- unique.push(t2);
21330
+ unique.push(c3);
21239
21331
  }
21240
21332
  return unique.slice(0, PROMPT_MAX_CANDIDATE_TOKENS);
21241
21333
  }
21334
+ function defaultSelfIdentityTokens() {
21335
+ const out = /* @__PURE__ */ new Set();
21336
+ try {
21337
+ const u = userInfo().username;
21338
+ if (u && u.length >= PROMPT_TOKEN_MIN_LENGTH) out.add(u.toLowerCase());
21339
+ } catch {
21340
+ }
21341
+ try {
21342
+ const h2 = homedir();
21343
+ if (h2) {
21344
+ for (const part of h2.split(/[\\/]/)) {
21345
+ if (part.length >= PROMPT_TOKEN_MIN_LENGTH) out.add(part.toLowerCase());
21346
+ }
21347
+ }
21348
+ } catch {
21349
+ }
21350
+ return out;
21351
+ }
21242
21352
  function toSearchResult2(row) {
21243
21353
  const fm = JSON.parse(row.frontmatter_json);
21244
21354
  const symptomMatch = /##\s+Symptom\s*\n([\s\S]*?)(?=\n##|\n*$)/.exec(row.body);
@@ -21254,28 +21364,58 @@ function toSearchResult2(row) {
21254
21364
  };
21255
21365
  }
21256
21366
  function findCaveatsForPrompt(db, prompt, opts = {}) {
21257
- const tokens = extractPromptCandidates(prompt);
21258
- if (tokens.length === 0) return [];
21259
- const minMatches = Math.min(MIN_DISTINCT_TOKEN_MATCHES_CEILING, tokens.length);
21367
+ if (typeof prompt !== "string" || prompt.length === 0) return [];
21368
+ const candidates = buildPromptCandidates(prompt);
21369
+ if (candidates.length === 0) return [];
21370
+ const selfIds = opts.selfIdentity;
21371
+ const filtered = selfIds && selfIds.size > 0 ? candidates.filter((c3) => !selfIds.has(c3.token.toLowerCase())) : candidates;
21372
+ if (filtered.length === 0) return [];
21373
+ const totalGroups = new Set(filtered.map((c3) => c3.group)).size;
21374
+ const minMatches = Math.min(MIN_DISTINCT_TOKEN_MATCHES_CEILING, totalGroups);
21260
21375
  const perEntry = /* @__PURE__ */ new Map();
21376
+ const tokenDf = /* @__PURE__ */ new Map();
21261
21377
  const stmt = db.prepare(
21262
21378
  "SELECT e.* FROM entries_fts f JOIN entries e ON e.rowid = f.rowid WHERE entries_fts MATCH ?"
21263
21379
  );
21264
- for (const tok of tokens) {
21380
+ for (const cand of filtered) {
21381
+ const tokLower = cand.token.toLowerCase();
21382
+ if (tokenDf.has(tokLower)) continue;
21265
21383
  let rows = [];
21266
21384
  try {
21267
- rows = stmt.all(`"${tok}"`);
21385
+ rows = stmt.all(`"${cand.token}"`);
21268
21386
  } catch {
21387
+ tokenDf.set(tokLower, 0);
21269
21388
  continue;
21270
21389
  }
21390
+ tokenDf.set(tokLower, rows.length);
21271
21391
  for (const row of rows) {
21272
- const existing = perEntry.get(row.rowid);
21273
- if (existing) existing.count += 1;
21274
- else perEntry.set(row.rowid, { count: 1, row });
21392
+ let entry = perEntry.get(row.rowid);
21393
+ if (!entry) {
21394
+ entry = {
21395
+ groups: /* @__PURE__ */ new Set(),
21396
+ symptomTokens: /* @__PURE__ */ new Set(),
21397
+ symptomLower: typeof row.symptom_text === "string" && row.symptom_text.length > 0 ? row.symptom_text.toLowerCase() : null,
21398
+ row
21399
+ };
21400
+ perEntry.set(row.rowid, entry);
21401
+ }
21402
+ entry.groups.add(cand.group);
21403
+ if (entry.symptomLower !== null && tokenAppearsIn(tokLower, entry.symptomLower)) {
21404
+ entry.symptomTokens.add(tokLower);
21405
+ }
21275
21406
  }
21276
21407
  }
21408
+ const validDfs = [...tokenDf.entries()].filter(([, df]) => df > 0);
21409
+ if (validDfs.length === 0) return [];
21410
+ let minDf = Infinity;
21411
+ for (const [, df] of validDfs) if (df < minDf) minDf = df;
21412
+ const rareTokens = new Set(validDfs.filter(([, df]) => df === minDf).map(([t2]) => t2));
21277
21413
  const limit = opts.limit ?? DEFAULT_REMINDER_HIT_LIMIT;
21278
- return [...perEntry.values()].filter(({ count }) => count >= minMatches).sort((a, b2) => b2.count - a.count).slice(0, limit).map(({ row }) => toSearchResult2(row));
21414
+ return [...perEntry.values()].filter(({ groups, symptomTokens }) => {
21415
+ if (groups.size < minMatches) return false;
21416
+ for (const t2 of symptomTokens) if (rareTokens.has(t2)) return true;
21417
+ return false;
21418
+ }).sort((a, b2) => b2.groups.size - a.groups.size).slice(0, limit).map(({ row }) => toSearchResult2(row));
21279
21419
  }
21280
21420
  function toolErrorReminderText(hits) {
21281
21421
  const lines = [];
@@ -21564,7 +21704,7 @@ function listStale(db, opts = {}) {
21564
21704
 
21565
21705
  // src/context.ts
21566
21706
  function buildContext(logger, overrides = {}) {
21567
- const userHome = overrides.userHome ?? homedir();
21707
+ const userHome = overrides.userHome ?? homedir2();
21568
21708
  const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
21569
21709
  const userConfigPath = join8(userHome, ".caveatrc.json");
21570
21710
  const config3 = loadConfig(userConfigPath);
@@ -22696,10 +22836,10 @@ var serve = (options2, listeningListener) => {
22696
22836
  };
22697
22837
 
22698
22838
  // ../web/dist/context.js
22699
- import { homedir as homedir2 } from "node:os";
22839
+ import { homedir as homedir3 } from "node:os";
22700
22840
  import { join as join13 } from "node:path";
22701
22841
  function buildWebContext(overrides = {}) {
22702
- const userHome = overrides.userHome ?? homedir2();
22842
+ const userHome = overrides.userHome ?? homedir3();
22703
22843
  const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
22704
22844
  const userConfigPath = join13(userHome, ".caveatrc.json");
22705
22845
  const logger = overrides.logger ?? stderrLogger;
@@ -44750,10 +44890,10 @@ var StdioServerTransport = class {
44750
44890
  };
44751
44891
 
44752
44892
  // ../mcp/dist/context.js
44753
- import { homedir as homedir3 } from "node:os";
44893
+ import { homedir as homedir4 } from "node:os";
44754
44894
  import { join as join15 } from "node:path";
44755
44895
  function buildMcpContext(overrides = {}) {
44756
- const userHome = overrides.userHome ?? homedir3();
44896
+ const userHome = overrides.userHome ?? homedir4();
44757
44897
  const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
44758
44898
  const userConfigPath = join15(userHome, ".caveatrc.json");
44759
44899
  const logger = overrides.logger ?? stderrLogger;
@@ -45080,7 +45220,9 @@ function searchCaveatsFromTextSafely(text2) {
45080
45220
  const ctx = buildContextSafely();
45081
45221
  if (!ctx || !existsSync14(ctx.paths.dbPath)) return [];
45082
45222
  db = openDb({ path: ctx.paths.dbPath });
45083
- const hits = findCaveatsForPrompt(db, text2);
45223
+ const hits = findCaveatsForPrompt(db, text2, {
45224
+ selfIdentity: defaultSelfIdentityTokens()
45225
+ });
45084
45226
  if (hits.length > 0) {
45085
45227
  try {
45086
45228
  markHit(db, hits);