caveat-cli 0.19.8 → 0.19.10

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
@@ -37,6 +37,8 @@ import {
37
37
  isPrivateOwnerStat,
38
38
  isWindows,
39
39
  isWindowsEnv,
40
+ jevObservationId,
41
+ listJevObservations,
40
42
  listRecent,
41
43
  listStale,
42
44
  loadConfig,
@@ -59,6 +61,7 @@ import {
59
61
  releaseReindexLock,
60
62
  resetAutoSyncFailureState,
61
63
  resolvePaths,
64
+ reviewJevObservation,
62
65
  runAutoSync,
63
66
  runWindowsAcl,
64
67
  runtimeErrorsDiagnostics,
@@ -71,10 +74,12 @@ import {
71
74
  toolErrorReminderText,
72
75
  triggerAutoSync,
73
76
  updateEntry,
77
+ updateJevDelivery,
74
78
  userPromptSubmitReminderText,
75
79
  writeDigestMarker,
80
+ writeJevObservation,
76
81
  writeUserConfigPatch
77
- } from "./chunk-ITQLGM4K.js";
82
+ } from "./chunk-PFYFYJNS.js";
78
83
 
79
84
  // ../../node_modules/.pnpm/ajv@8.18.0/node_modules/ajv/dist/compile/codegen/code.js
80
85
  var require_code = __commonJS({
@@ -8390,7 +8395,7 @@ function runStats(ctx) {
8390
8395
 
8391
8396
  // src/commands/serve.ts
8392
8397
  async function runServe(opts) {
8393
- const { startServer } = await import("./server-H4BWLLZ2.js");
8398
+ const { startServer } = await import("./server-JXBPTQNU.js");
8394
8399
  const { port, host } = startServer({ port: opts.port });
8395
8400
  process.stdout.write(`[caveat] web portal: http://${host}:${port}/
8396
8401
  `);
@@ -33210,6 +33215,7 @@ var API_URL = "https://api.typesafe.ai/v1/systemone";
33210
33215
  var STRUGGLE_THRESHOLD = 0.85;
33211
33216
  var MATCH_THRESHOLD = 0.8;
33212
33217
  var MAX_TERMS = 48;
33218
+ var MAX_SEARCH_TERMS = 3;
33213
33219
  var MAX_CANDIDATES = 20;
33214
33220
  async function callJev(key, state, questions, fetcher = fetch) {
33215
33221
  const response = await fetcher(API_URL, {
@@ -33232,9 +33238,16 @@ function noul(answer) {
33232
33238
  }
33233
33239
  return answer.noul;
33234
33240
  }
33235
- function choice(answer, allowed) {
33236
- if (answer?.type !== "choice" || !allowed.has(answer.choice)) throw new Error("jev_choice_invalid");
33237
- return answer.choice;
33241
+ function searchTermChoices(answer, terms) {
33242
+ if (answer?.type !== "choice" || !isRecord(answer.probabilities)) throw new Error("jev_choice_invalid");
33243
+ const keys = terms.map((_, index) => `term_${index}`);
33244
+ const allowed = /* @__PURE__ */ new Set([...keys, "none"]);
33245
+ if (!allowed.has(answer.choice) || Object.keys(answer.probabilities).length !== allowed.size || Object.entries(answer.probabilities).some(([key, probability]) => !allowed.has(key) || typeof probability !== "number" || !Number.isFinite(probability) || probability < 0 || probability > 1)) {
33246
+ throw new Error("jev_choice_invalid");
33247
+ }
33248
+ if (answer.choice === "none") return [];
33249
+ const noneProbability = answer.probabilities.none;
33250
+ return keys.map((key, index) => ({ index, probability: answer.probabilities[key] })).filter(({ probability }) => probability > noneProbability).sort((a, b) => b.probability - a.probability || a.index - b.index).slice(0, MAX_SEARCH_TERMS).map(({ index }) => terms[index]);
33238
33251
  }
33239
33252
  function candidateTerms(turns) {
33240
33253
  const found = /* @__PURE__ */ new Map();
@@ -33262,17 +33275,27 @@ async function judgeStruggle(key, turns, ask = callJev) {
33262
33275
  };
33263
33276
  if (terms.length > 0) questions.search_term = {
33264
33277
  type: "choice",
33265
- instructions: "If the agent is struggling, which exact term from these options would best retrieve a useful Caveat knowledge entry about the concrete unresolved problem? Select none when no option identifies it.",
33278
+ instructions: "If the agent is struggling, which exact term from these options would best retrieve a useful Caveat knowledge entry about the concrete unresolved problem? Select none when no option identifies it. The search will use up to three of your highest-ranked terms together as an AND query.",
33266
33279
  criteria: { ...options, none: "No listed term would retrieve useful knowledge about this problem." }
33267
33280
  };
33268
33281
  const response = await ask(key, { turns }, questions);
33269
- const struggling = noul(response.answers.repeated_problem) >= STRUGGLE_THRESHOLD && noul(response.answers.clearly_stuck) >= STRUGGLE_THRESHOLD;
33270
- if (!struggling || terms.length === 0) return { struggling, term: null };
33271
- const selected = choice(response.answers.search_term, /* @__PURE__ */ new Set([...Object.keys(options), "none"]));
33272
- return { struggling, term: selected === "none" ? null : terms[Number(selected.slice(5))] ?? null };
33282
+ const repeatedProblem = noul(response.answers.repeated_problem);
33283
+ const clearlyStuck = noul(response.answers.clearly_stuck);
33284
+ const struggling = repeatedProblem >= STRUGGLE_THRESHOLD && clearlyStuck >= STRUGGLE_THRESHOLD;
33285
+ const choice = terms.length > 0 ? response.answers.search_term : void 0;
33286
+ const selected = terms.length > 0 ? searchTermChoices(choice, terms) : [];
33287
+ const termChoices = terms.map((term, index) => ({ term, probability: choice.probabilities[`term_${index}`] }));
33288
+ return {
33289
+ struggling,
33290
+ terms: struggling ? selected : [],
33291
+ repeatedProblem,
33292
+ clearlyStuck,
33293
+ termChoices,
33294
+ noneProbability: choice ? choice.probabilities.none : null
33295
+ };
33273
33296
  }
33274
33297
  async function rankKnowledge(key, turns, candidates, ask = callJev) {
33275
- if (candidates.length === 0) return null;
33298
+ if (candidates.length === 0) return { selected: null, scores: [] };
33276
33299
  const state = {
33277
33300
  turns,
33278
33301
  currentHostOs: fingerprint().os,
@@ -33295,14 +33318,16 @@ async function rankKnowledge(key, turns, candidates, ask = callJev) {
33295
33318
  const response = await ask(key, state, questions);
33296
33319
  let best = null;
33297
33320
  let bestScore = MATCH_THRESHOLD;
33321
+ const scores = [];
33298
33322
  for (let index = 0; index < candidates.length; index++) {
33299
33323
  const score = noul(response.answers[`candidate_${index}`]);
33324
+ scores.push(score);
33300
33325
  if (score >= MATCH_THRESHOLD && (best === null || score > bestScore)) {
33301
33326
  best = candidates[index];
33302
33327
  bestScore = score;
33303
33328
  }
33304
33329
  }
33305
- return best;
33330
+ return { selected: best, scores };
33306
33331
  }
33307
33332
  function quotePowerShell(value) {
33308
33333
  return `'${value.replaceAll("'", "''")}'`;
@@ -33367,39 +33392,88 @@ async function runJevStruggle(ctx, host, payload, deps = {}) {
33367
33392
  const key = (deps.readKey ?? readJevKey)(ctx.caveatHome);
33368
33393
  const ask = deps.ask ?? callJev;
33369
33394
  const judgment = await judgeStruggle(key, context.turns, ask);
33395
+ const observationId = jevObservationId(host, sessionId, turnKey);
33396
+ const base = {
33397
+ schema: "caveat.jev_observation.v1",
33398
+ id: observationId,
33399
+ observedAt: (/* @__PURE__ */ new Date()).toISOString(),
33400
+ host,
33401
+ sessionId,
33402
+ projectRoot,
33403
+ transcriptPath,
33404
+ turns: context.turns.map(({ originSessionId, turnNumber, truncated }) => ({ originSessionId, turnNumber, truncated })),
33405
+ thinkingAvailable: context.thinkingAvailable,
33406
+ model: MODEL,
33407
+ repeatedProblem: judgment.repeatedProblem,
33408
+ clearlyStuck: judgment.clearlyStuck,
33409
+ struggleThreshold: STRUGGLE_THRESHOLD,
33410
+ struggling: judgment.struggling,
33411
+ termChoices: judgment.termChoices,
33412
+ noneProbability: judgment.noneProbability,
33413
+ selectedTerms: judgment.terms,
33414
+ query: judgment.terms.join(" "),
33415
+ matchThreshold: MATCH_THRESHOLD,
33416
+ review: null
33417
+ };
33370
33418
  state.lastTurn = turnKey;
33371
33419
  if (!judgment.struggling) {
33420
+ writeJevObservation(ctx.caveatHome, {
33421
+ ...base,
33422
+ ftsHits: [],
33423
+ candidates: [],
33424
+ selectedRef: null,
33425
+ noticeText: null,
33426
+ decision: "below_struggle_threshold",
33427
+ delivery: "none"
33428
+ });
33372
33429
  saveState(ctx.caveatHome, sessionId, state);
33373
33430
  return null;
33374
33431
  }
33375
33432
  let candidates = [];
33376
- if (judgment.term) {
33433
+ const query = judgment.terms.join(" ");
33434
+ let ftsHits = [];
33435
+ if (query) {
33377
33436
  if (!existsSync14(ctx.paths.dbPath)) throw new Error("jev_knowledge_index_missing");
33378
33437
  const db = openDb({ path: ctx.paths.dbPath });
33379
33438
  try {
33380
33439
  const taskText = context.turns.map((turn) => `${turn.user}
33381
33440
  ${turn.assistant}
33382
33441
  ${turn.thinking}`).join("\n");
33383
- candidates = search(db, { query: judgment.term, limit: MAX_CANDIDATES }).filter((hit) => environmentAppliesToTask(hit.environment, taskText, fingerprint().os)).map((hit) => ({ hit, entry: get(db, hit.id, hit.source) })).filter((item) => item.entry !== null).filter((item) => Boolean(item.entry.sections.Resolution));
33442
+ const hits = search(db, { query, limit: MAX_CANDIDATES });
33443
+ ftsHits = hits.map((hit) => `${hit.source}/${hit.id}`);
33444
+ candidates = hits.filter((hit) => environmentAppliesToTask(hit.environment, taskText, fingerprint().os)).map((hit) => ({ hit, entry: get(db, hit.id, hit.source) })).filter((item) => item.entry !== null).filter((item) => Boolean(item.entry.sections.Resolution));
33384
33445
  } finally {
33385
33446
  db.close();
33386
33447
  }
33387
33448
  }
33388
- const selected = await rankKnowledge(key, context.turns, candidates, ask);
33449
+ const ranked = await rankKnowledge(key, context.turns, candidates, ask);
33450
+ const selected = ranked.selected;
33389
33451
  const id = selected ? `${selected.hit.source}/${selected.hit.id}` : null;
33390
- const term = judgment.term ?? "_none";
33391
- if (id && (state.deliveredIds.includes(id) || wasHitDelivered(ctx.caveatHome, sessionId, id)) || !id && state.notifiedTerms.includes(term)) {
33452
+ const term = query || "_none";
33453
+ const suppressed = Boolean(id && (state.deliveredIds.includes(id) || wasHitDelivered(ctx.caveatHome, sessionId, id)) || !id && state.notifiedTerms.includes(term));
33454
+ const noticeText = reminder(selected);
33455
+ writeJevObservation(ctx.caveatHome, {
33456
+ ...base,
33457
+ ftsHits,
33458
+ candidates: candidates.map((candidate, index) => ({ ref: `${candidate.hit.source}/${candidate.hit.id}`, score: ranked.scores[index] })),
33459
+ selectedRef: id,
33460
+ noticeText: suppressed ? null : noticeText,
33461
+ decision: id ? "matched" : !query ? "no_terms" : candidates.length === 0 ? "no_candidates" : "below_match_threshold",
33462
+ delivery: suppressed ? "suppressed" : "pending"
33463
+ });
33464
+ if (suppressed) {
33392
33465
  saveState(ctx.caveatHome, sessionId, state);
33393
33466
  return null;
33394
33467
  }
33395
33468
  return {
33396
- text: reminder(selected),
33469
+ text: noticeText,
33397
33470
  ref: id,
33398
33471
  delivered: () => {
33399
33472
  const current = loadState(ctx.caveatHome, sessionId);
33400
33473
  current.lastTurn = turnKey;
33401
33474
  if (id && !current.deliveredIds.includes(id)) current.deliveredIds.push(id);
33402
33475
  if (!id && !current.notifiedTerms.includes(term)) current.notifiedTerms.push(term);
33476
+ updateJevDelivery(ctx.caveatHome, observationId);
33403
33477
  saveState(ctx.caveatHome, sessionId, current);
33404
33478
  if (selected) {
33405
33479
  const db = openDb({ path: ctx.paths.dbPath });
@@ -34681,6 +34755,15 @@ function jevStatus(ctx) {
34681
34755
  })}
34682
34756
  `);
34683
34757
  }
34758
+ function listJevCases(ctx) {
34759
+ process.stdout.write(`${JSON.stringify({ schema: "caveat.jev_cases.v1", cases: listJevObservations(ctx.caveatHome) })}
34760
+ `);
34761
+ }
34762
+ function reviewJevCase(ctx, id, verdict, note) {
34763
+ const item = reviewJevObservation(ctx.caveatHome, id, verdict, note);
34764
+ process.stdout.write(`${JSON.stringify({ id: item.id, review: item.review })}
34765
+ `);
34766
+ }
34684
34767
 
34685
34768
  // src/index.ts
34686
34769
  var program = new Command();
@@ -34709,6 +34792,13 @@ jev.command("disable").action(() => {
34709
34792
  jev.command("status").action(() => {
34710
34793
  jevStatus(buildContext(stdoutLogger));
34711
34794
  });
34795
+ jev.command("cases").requiredOption("--json", "emit case records").action(() => {
34796
+ listJevCases(buildContext(stdoutLogger));
34797
+ });
34798
+ jev.command("review <id> <verdict>").description("Review one case: correct | incorrect | missed | none | unclear").option("--note <text>", "short reason for the verdict", "").action((id, verdict, opts) => {
34799
+ if (!["correct", "incorrect", "missed", "none", "unclear"].includes(verdict)) throw new Error("jev_review_verdict_invalid");
34800
+ reviewJevCase(buildContext(stdoutLogger), id, verdict, opts.note);
34801
+ });
34712
34802
  var parseLimit = (value) => {
34713
34803
  const parsed = parseCursor(value);
34714
34804
  if (parsed < 1 || parsed > 256) throw Error("invalid_limit");