caveat-cli 0.19.9 → 0.19.11

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({
@@ -453,11 +458,11 @@ var require_codegen = __commonJS({
453
458
  const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
454
459
  return `${varKind} ${this.name}${rhs};` + _n;
455
460
  }
456
- optimizeNames(names, constants4) {
461
+ optimizeNames(names, constants5) {
457
462
  if (!names[this.name.str])
458
463
  return;
459
464
  if (this.rhs)
460
- this.rhs = optimizeExpr(this.rhs, names, constants4);
465
+ this.rhs = optimizeExpr(this.rhs, names, constants5);
461
466
  return this;
462
467
  }
463
468
  get names() {
@@ -474,10 +479,10 @@ var require_codegen = __commonJS({
474
479
  render({ _n }) {
475
480
  return `${this.lhs} = ${this.rhs};` + _n;
476
481
  }
477
- optimizeNames(names, constants4) {
482
+ optimizeNames(names, constants5) {
478
483
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
479
484
  return;
480
- this.rhs = optimizeExpr(this.rhs, names, constants4);
485
+ this.rhs = optimizeExpr(this.rhs, names, constants5);
481
486
  return this;
482
487
  }
483
488
  get names() {
@@ -538,8 +543,8 @@ var require_codegen = __commonJS({
538
543
  optimizeNodes() {
539
544
  return `${this.code}` ? this : void 0;
540
545
  }
541
- optimizeNames(names, constants4) {
542
- this.code = optimizeExpr(this.code, names, constants4);
546
+ optimizeNames(names, constants5) {
547
+ this.code = optimizeExpr(this.code, names, constants5);
543
548
  return this;
544
549
  }
545
550
  get names() {
@@ -568,12 +573,12 @@ var require_codegen = __commonJS({
568
573
  }
569
574
  return nodes.length > 0 ? this : void 0;
570
575
  }
571
- optimizeNames(names, constants4) {
576
+ optimizeNames(names, constants5) {
572
577
  const { nodes } = this;
573
578
  let i = nodes.length;
574
579
  while (i--) {
575
580
  const n = nodes[i];
576
- if (n.optimizeNames(names, constants4))
581
+ if (n.optimizeNames(names, constants5))
577
582
  continue;
578
583
  subtractNames(names, n.names);
579
584
  nodes.splice(i, 1);
@@ -626,12 +631,12 @@ var require_codegen = __commonJS({
626
631
  return void 0;
627
632
  return this;
628
633
  }
629
- optimizeNames(names, constants4) {
634
+ optimizeNames(names, constants5) {
630
635
  var _a3;
631
- this.else = (_a3 = this.else) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants4);
632
- if (!(super.optimizeNames(names, constants4) || this.else))
636
+ this.else = (_a3 = this.else) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants5);
637
+ if (!(super.optimizeNames(names, constants5) || this.else))
633
638
  return;
634
- this.condition = optimizeExpr(this.condition, names, constants4);
639
+ this.condition = optimizeExpr(this.condition, names, constants5);
635
640
  return this;
636
641
  }
637
642
  get names() {
@@ -654,10 +659,10 @@ var require_codegen = __commonJS({
654
659
  render(opts) {
655
660
  return `for(${this.iteration})` + super.render(opts);
656
661
  }
657
- optimizeNames(names, constants4) {
658
- if (!super.optimizeNames(names, constants4))
662
+ optimizeNames(names, constants5) {
663
+ if (!super.optimizeNames(names, constants5))
659
664
  return;
660
- this.iteration = optimizeExpr(this.iteration, names, constants4);
665
+ this.iteration = optimizeExpr(this.iteration, names, constants5);
661
666
  return this;
662
667
  }
663
668
  get names() {
@@ -693,10 +698,10 @@ var require_codegen = __commonJS({
693
698
  render(opts) {
694
699
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
695
700
  }
696
- optimizeNames(names, constants4) {
697
- if (!super.optimizeNames(names, constants4))
701
+ optimizeNames(names, constants5) {
702
+ if (!super.optimizeNames(names, constants5))
698
703
  return;
699
- this.iterable = optimizeExpr(this.iterable, names, constants4);
704
+ this.iterable = optimizeExpr(this.iterable, names, constants5);
700
705
  return this;
701
706
  }
702
707
  get names() {
@@ -738,11 +743,11 @@ var require_codegen = __commonJS({
738
743
  (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNodes();
739
744
  return this;
740
745
  }
741
- optimizeNames(names, constants4) {
746
+ optimizeNames(names, constants5) {
742
747
  var _a3, _b;
743
- super.optimizeNames(names, constants4);
744
- (_a3 = this.catch) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants4);
745
- (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants4);
748
+ super.optimizeNames(names, constants5);
749
+ (_a3 = this.catch) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants5);
750
+ (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants5);
746
751
  return this;
747
752
  }
748
753
  get names() {
@@ -1043,7 +1048,7 @@ var require_codegen = __commonJS({
1043
1048
  function addExprNames(names, from) {
1044
1049
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
1045
1050
  }
1046
- function optimizeExpr(expr, names, constants4) {
1051
+ function optimizeExpr(expr, names, constants5) {
1047
1052
  if (expr instanceof code_1.Name)
1048
1053
  return replaceName(expr);
1049
1054
  if (!canOptimize(expr))
@@ -1058,14 +1063,14 @@ var require_codegen = __commonJS({
1058
1063
  return items;
1059
1064
  }, []));
1060
1065
  function replaceName(n) {
1061
- const c = constants4[n.str];
1066
+ const c = constants5[n.str];
1062
1067
  if (c === void 0 || names[n.str] !== 1)
1063
1068
  return n;
1064
1069
  delete names[n.str];
1065
1070
  return c;
1066
1071
  }
1067
1072
  function canOptimize(e) {
1068
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants4[c.str] !== void 0);
1073
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants5[c.str] !== void 0);
1069
1074
  }
1070
1075
  }
1071
1076
  function subtractNames(names, from) {
@@ -6891,7 +6896,7 @@ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readdirSync, rename
6891
6896
  import { dirname as dirname3, join as join8 } from "node:path";
6892
6897
 
6893
6898
  // src/claudeInstall.ts
6894
- import { constants, existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
6899
+ import { constants as constants2, existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
6895
6900
  import { join as join4 } from "node:path";
6896
6901
 
6897
6902
  // src/installShared.ts
@@ -6952,9 +6957,13 @@ function claudeMcpConfigPath(claudeDir, env = process.env) {
6952
6957
  }
6953
6958
 
6954
6959
  // src/mcpInstall.ts
6955
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
6960
+ import { constants, existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
6956
6961
  import { isDeepStrictEqual } from "node:util";
6957
6962
  import { parse, stringify } from "smol-toml";
6963
+ function isCaveatStdioMcpRegistration(value, nodePath, cliScriptPath) {
6964
+ if (!object(value)) return false;
6965
+ return isCanonicalAsset(value.command, nodePath, constants.X_OK) && Array.isArray(value.args) && value.args.length === 3 && value.args[0] === "--disable-warning=ExperimentalWarning" && isCanonicalAsset(value.args[1], cliScriptPath, constants.R_OK) && value.args[2] === "mcp-server";
6966
+ }
6958
6967
  function object(value) {
6959
6968
  return value !== null && typeof value === "object" && !Array.isArray(value);
6960
6969
  }
@@ -7058,12 +7067,12 @@ function isCanonicalCaveatClaudeHookCommand(actual, event, nodePath, cliScriptPa
7058
7067
  const [actualNodePath, actualCliScriptPath, subcommand, actualEvent] = tokens ?? [];
7059
7068
  if (tokens?.length !== 4 || actualNodePath === void 0 || actualCliScriptPath === void 0 || subcommand !== "hook" || actualEvent !== event) return false;
7060
7069
  if (actual !== hookCommand(actualNodePath, actualCliScriptPath, event)) return false;
7061
- return isCanonicalAsset(actualNodePath, nodePath, constants.X_OK) && isCanonicalAsset(actualCliScriptPath, cliScriptPath, constants.R_OK);
7070
+ return isCanonicalAsset(actualNodePath, nodePath, constants2.X_OK) && isCanonicalAsset(actualCliScriptPath, cliScriptPath, constants2.R_OK);
7062
7071
  }
7063
7072
  function isCaveatClaudeMcpRegistration(value, nodePath, cliScriptPath) {
7064
7073
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
7065
7074
  const server = value;
7066
- return ["args", "command", "env", "type"].every((key) => Object.hasOwn(server, key)) && server.type === "stdio" && isCanonicalAsset(server.command, nodePath, constants.X_OK) && Array.isArray(server.args) && server.args.length === 3 && server.args[0] === "--disable-warning=ExperimentalWarning" && isCanonicalAsset(server.args[1], cliScriptPath, constants.R_OK) && server.args[2] === "mcp-server" && server.env !== null && typeof server.env === "object" && !Array.isArray(server.env) && Object.values(server.env).every((value2) => typeof value2 === "string");
7075
+ return ["args", "command", "env", "type"].every((key) => Object.hasOwn(server, key)) && server.type === "stdio" && isCaveatStdioMcpRegistration(server, nodePath, cliScriptPath) && server.env !== null && typeof server.env === "object" && !Array.isArray(server.env) && Object.values(server.env).every((value2) => typeof value2 === "string");
7067
7076
  }
7068
7077
  function readSettings(path) {
7069
7078
  if (!existsSync3(path)) return {};
@@ -7145,7 +7154,7 @@ function uninstallClaudeIntegration(opts) {
7145
7154
  }
7146
7155
 
7147
7156
  // src/codexHookInstall.ts
7148
- import { constants as constants2, existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
7157
+ import { constants as constants3, existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
7149
7158
  import { join as join5 } from "node:path";
7150
7159
  import { parse as parseToml } from "smol-toml";
7151
7160
  function hookCommand2(nodePath, cliScriptPath, event, platform = process.platform) {
@@ -7170,7 +7179,7 @@ function isCaveatCodexHookCommand(actual, event) {
7170
7179
  function isCanonicalCaveatCodexHookCommand(actual, event, nodePath, cliScriptPath) {
7171
7180
  const parsed = commandTokens(actual);
7172
7181
  const tokens = parsed?.[0] === "&" ? parsed.slice(1) : parsed;
7173
- return tokens?.length === 4 && isCanonicalAsset(tokens[0], nodePath, constants2.X_OK) && isCanonicalAsset(tokens[1], cliScriptPath, constants2.R_OK) && tokens[2] === "codex-hook" && tokens[3] === event;
7182
+ return tokens?.length === 4 && isCanonicalAsset(tokens[0], nodePath, constants3.X_OK) && isCanonicalAsset(tokens[1], cliScriptPath, constants3.R_OK) && tokens[2] === "codex-hook" && tokens[3] === event;
7174
7183
  }
7175
7184
  function isCanonicalCaveatCodexHookEntry(value, event, nodePath, cliScriptPath) {
7176
7185
  if (!isPlainRecord(value) || typeof value.type !== "string" || typeof value.command !== "string") return false;
@@ -7529,7 +7538,7 @@ function detectCodexHookInstallation(codexHome) {
7529
7538
  // src/cursorInstall.ts
7530
7539
  import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "node:fs";
7531
7540
  import { join as join6 } from "node:path";
7532
- import { constants as constants3 } from "node:fs";
7541
+ import { constants as constants4 } from "node:fs";
7533
7542
  var TIMEOUT_SEC = 10;
7534
7543
  function hookCommand3(nodePath, cliScriptPath, event) {
7535
7544
  return `${quoteCommandPath(nodePath)} ${quoteCommandPath(cliScriptPath)} cursor-hook ${event}`;
@@ -7543,7 +7552,7 @@ function isCaveatCursorHookCommand(actual, event) {
7543
7552
  }
7544
7553
  function isCanonicalCaveatCursorHookCommand(actual, event, nodePath, cliScriptPath) {
7545
7554
  const tokens = commandTokens(actual);
7546
- return tokens?.length === 4 && isCanonicalAsset(tokens[0], nodePath, constants3.X_OK) && isCanonicalAsset(tokens[1], cliScriptPath, constants3.R_OK) && tokens[2] === "cursor-hook" && tokens[3] === event;
7555
+ return tokens?.length === 4 && isCanonicalAsset(tokens[0], nodePath, constants4.X_OK) && isCanonicalAsset(tokens[1], cliScriptPath, constants4.R_OK) && tokens[2] === "cursor-hook" && tokens[3] === event;
7547
7556
  }
7548
7557
  function isCanonicalCaveatCursorHookEntry(value, event, nodePath, cliScriptPath) {
7549
7558
  if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
@@ -8390,7 +8399,7 @@ function runStats(ctx) {
8390
8399
 
8391
8400
  // src/commands/serve.ts
8392
8401
  async function runServe(opts) {
8393
- const { startServer } = await import("./server-H4BWLLZ2.js");
8402
+ const { startServer } = await import("./server-JXBPTQNU.js");
8394
8403
  const { port, host } = startServer({ port: opts.port });
8395
8404
  process.stdout.write(`[caveat] web portal: http://${host}:${port}/
8396
8405
  `);
@@ -33274,12 +33283,23 @@ async function judgeStruggle(key, turns, ask = callJev) {
33274
33283
  criteria: { ...options, none: "No listed term would retrieve useful knowledge about this problem." }
33275
33284
  };
33276
33285
  const response = await ask(key, { turns }, questions);
33277
- const struggling = noul(response.answers.repeated_problem) >= STRUGGLE_THRESHOLD && noul(response.answers.clearly_stuck) >= STRUGGLE_THRESHOLD;
33278
- if (!struggling || terms.length === 0) return { struggling, terms: [] };
33279
- return { struggling, terms: searchTermChoices(response.answers.search_term, terms) };
33286
+ const repeatedProblem = noul(response.answers.repeated_problem);
33287
+ const clearlyStuck = noul(response.answers.clearly_stuck);
33288
+ const struggling = repeatedProblem >= STRUGGLE_THRESHOLD && clearlyStuck >= STRUGGLE_THRESHOLD;
33289
+ const choice = terms.length > 0 ? response.answers.search_term : void 0;
33290
+ const selected = terms.length > 0 ? searchTermChoices(choice, terms) : [];
33291
+ const termChoices = terms.map((term, index) => ({ term, probability: choice.probabilities[`term_${index}`] }));
33292
+ return {
33293
+ struggling,
33294
+ terms: struggling ? selected : [],
33295
+ repeatedProblem,
33296
+ clearlyStuck,
33297
+ termChoices,
33298
+ noneProbability: choice ? choice.probabilities.none : null
33299
+ };
33280
33300
  }
33281
33301
  async function rankKnowledge(key, turns, candidates, ask = callJev) {
33282
- if (candidates.length === 0) return null;
33302
+ if (candidates.length === 0) return { selected: null, scores: [] };
33283
33303
  const state = {
33284
33304
  turns,
33285
33305
  currentHostOs: fingerprint().os,
@@ -33302,14 +33322,16 @@ async function rankKnowledge(key, turns, candidates, ask = callJev) {
33302
33322
  const response = await ask(key, state, questions);
33303
33323
  let best = null;
33304
33324
  let bestScore = MATCH_THRESHOLD;
33325
+ const scores = [];
33305
33326
  for (let index = 0; index < candidates.length; index++) {
33306
33327
  const score = noul(response.answers[`candidate_${index}`]);
33328
+ scores.push(score);
33307
33329
  if (score >= MATCH_THRESHOLD && (best === null || score > bestScore)) {
33308
33330
  best = candidates[index];
33309
33331
  bestScore = score;
33310
33332
  }
33311
33333
  }
33312
- return best;
33334
+ return { selected: best, scores };
33313
33335
  }
33314
33336
  function quotePowerShell(value) {
33315
33337
  return `'${value.replaceAll("'", "''")}'`;
@@ -33374,13 +33396,46 @@ async function runJevStruggle(ctx, host, payload, deps = {}) {
33374
33396
  const key = (deps.readKey ?? readJevKey)(ctx.caveatHome);
33375
33397
  const ask = deps.ask ?? callJev;
33376
33398
  const judgment = await judgeStruggle(key, context.turns, ask);
33399
+ const observationId = jevObservationId(host, sessionId, turnKey);
33400
+ const base = {
33401
+ schema: "caveat.jev_observation.v1",
33402
+ id: observationId,
33403
+ observedAt: (/* @__PURE__ */ new Date()).toISOString(),
33404
+ host,
33405
+ sessionId,
33406
+ projectRoot,
33407
+ transcriptPath,
33408
+ turns: context.turns.map(({ originSessionId, turnNumber, truncated }) => ({ originSessionId, turnNumber, truncated })),
33409
+ thinkingAvailable: context.thinkingAvailable,
33410
+ model: MODEL,
33411
+ repeatedProblem: judgment.repeatedProblem,
33412
+ clearlyStuck: judgment.clearlyStuck,
33413
+ struggleThreshold: STRUGGLE_THRESHOLD,
33414
+ struggling: judgment.struggling,
33415
+ termChoices: judgment.termChoices,
33416
+ noneProbability: judgment.noneProbability,
33417
+ selectedTerms: judgment.terms,
33418
+ query: judgment.terms.join(" "),
33419
+ matchThreshold: MATCH_THRESHOLD,
33420
+ review: null
33421
+ };
33377
33422
  state.lastTurn = turnKey;
33378
33423
  if (!judgment.struggling) {
33424
+ writeJevObservation(ctx.caveatHome, {
33425
+ ...base,
33426
+ ftsHits: [],
33427
+ candidates: [],
33428
+ selectedRef: null,
33429
+ noticeText: null,
33430
+ decision: "below_struggle_threshold",
33431
+ delivery: "none"
33432
+ });
33379
33433
  saveState(ctx.caveatHome, sessionId, state);
33380
33434
  return null;
33381
33435
  }
33382
33436
  let candidates = [];
33383
33437
  const query = judgment.terms.join(" ");
33438
+ let ftsHits = [];
33384
33439
  if (query) {
33385
33440
  if (!existsSync14(ctx.paths.dbPath)) throw new Error("jev_knowledge_index_missing");
33386
33441
  const db = openDb({ path: ctx.paths.dbPath });
@@ -33388,26 +33443,41 @@ async function runJevStruggle(ctx, host, payload, deps = {}) {
33388
33443
  const taskText = context.turns.map((turn) => `${turn.user}
33389
33444
  ${turn.assistant}
33390
33445
  ${turn.thinking}`).join("\n");
33391
- candidates = search(db, { query, 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));
33446
+ const hits = search(db, { query, limit: MAX_CANDIDATES });
33447
+ ftsHits = hits.map((hit) => `${hit.source}/${hit.id}`);
33448
+ 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));
33392
33449
  } finally {
33393
33450
  db.close();
33394
33451
  }
33395
33452
  }
33396
- const selected = await rankKnowledge(key, context.turns, candidates, ask);
33453
+ const ranked = await rankKnowledge(key, context.turns, candidates, ask);
33454
+ const selected = ranked.selected;
33397
33455
  const id = selected ? `${selected.hit.source}/${selected.hit.id}` : null;
33398
33456
  const term = query || "_none";
33399
- if (id && (state.deliveredIds.includes(id) || wasHitDelivered(ctx.caveatHome, sessionId, id)) || !id && state.notifiedTerms.includes(term)) {
33457
+ const suppressed = Boolean(id && (state.deliveredIds.includes(id) || wasHitDelivered(ctx.caveatHome, sessionId, id)) || !id && state.notifiedTerms.includes(term));
33458
+ const noticeText = reminder(selected);
33459
+ writeJevObservation(ctx.caveatHome, {
33460
+ ...base,
33461
+ ftsHits,
33462
+ candidates: candidates.map((candidate, index) => ({ ref: `${candidate.hit.source}/${candidate.hit.id}`, score: ranked.scores[index] })),
33463
+ selectedRef: id,
33464
+ noticeText: suppressed ? null : noticeText,
33465
+ decision: id ? "matched" : !query ? "no_terms" : candidates.length === 0 ? "no_candidates" : "below_match_threshold",
33466
+ delivery: suppressed ? "suppressed" : "pending"
33467
+ });
33468
+ if (suppressed) {
33400
33469
  saveState(ctx.caveatHome, sessionId, state);
33401
33470
  return null;
33402
33471
  }
33403
33472
  return {
33404
- text: reminder(selected),
33473
+ text: noticeText,
33405
33474
  ref: id,
33406
33475
  delivered: () => {
33407
33476
  const current = loadState(ctx.caveatHome, sessionId);
33408
33477
  current.lastTurn = turnKey;
33409
33478
  if (id && !current.deliveredIds.includes(id)) current.deliveredIds.push(id);
33410
33479
  if (!id && !current.notifiedTerms.includes(term)) current.notifiedTerms.push(term);
33480
+ updateJevDelivery(ctx.caveatHome, observationId);
33411
33481
  saveState(ctx.caveatHome, sessionId, current);
33412
33482
  if (selected) {
33413
33483
  const db = openDb({ path: ctx.paths.dbPath });
@@ -34527,6 +34597,20 @@ function claudeRegistration(path, nodePath, cliScriptPath) {
34527
34597
  return unverified("config_unreadable");
34528
34598
  }
34529
34599
  }
34600
+ function grokRegistration(path, nodePath, cliScriptPath) {
34601
+ if (!existsSync18(path)) return status(false, "not_registered");
34602
+ try {
34603
+ const config2 = parseToml2(readFileSync12(path, "utf8"));
34604
+ if (!isRecord2(config2) || !isRecord2(config2.mcp_servers)) return status(false, "not_registered");
34605
+ const server = config2.mcp_servers.caveat;
34606
+ if (!isRecord2(server)) return status(false, "not_registered");
34607
+ if (server.enabled === false) return status(false, "disabled");
34608
+ if (server.enabled !== void 0 && server.enabled !== true) return unverified("config_invalid");
34609
+ return status(isCaveatStdioMcpRegistration(server, nodePath, cliScriptPath), "not_registered");
34610
+ } catch {
34611
+ return unverified("config_unreadable");
34612
+ }
34613
+ }
34530
34614
  function claudeHooks(claudeDir, nodePath, cliScriptPath) {
34531
34615
  try {
34532
34616
  const settings = JSON.parse(readFileSync12(join18(claudeDir, "settings.json"), "utf8"));
@@ -34624,7 +34708,7 @@ function sync(own) {
34624
34708
  }
34625
34709
  }
34626
34710
  function factoryDiagnostics(ctx, codexHome = resolveAgentConfigPaths(ctx.userHome).codexHome, requiredConnectors = []) {
34627
- const { claudeDir, claudeMcp, cursorDir } = resolveAgentConfigPaths(ctx.userHome);
34711
+ const { claudeDir, claudeMcp, cursorDir, grokHome } = resolveAgentConfigPaths(ctx.userHome);
34628
34712
  const nodePath = process.execPath;
34629
34713
  const cliScriptPath = process.argv[1] ?? "";
34630
34714
  const db = database(ctx.paths.dbPath);
@@ -34635,13 +34719,15 @@ function factoryDiagnostics(ctx, codexHome = resolveAgentConfigPaths(ctx.userHom
34635
34719
  } catch {
34636
34720
  }
34637
34721
  const codexHook2 = (present) => present === void 0 ? unverified("config_unreadable") : !present ? hook(false) : feature.status === "ready" ? hook(true) : { ...feature };
34638
- const output = { schema: "caveat.native_factory_diagnostics.v1", product: "caveat", version: CAVEAT_VERSION, overall: { status: "unverified" }, database: db, sync: sync(ctx.paths.knowledgeRepo), connectors: { claude: { status: "unverified", mcp: claudeRegistration(claudeMcp, nodePath, cliScriptPath), hooks: claudeHooks(claudeDir, nodePath, cliScriptPath) }, codex: { status: "unverified", hooks: { user_prompt_submit: codexHook2(installedCodexHooks?.userPromptSubmit), post_tool_use: codexHook2(installedCodexHooks?.postToolUse), stop: codexHook2(installedCodexHooks?.stop) } }, cursor: diagnoseCursorHookConnector(cursorDir, nodePath, cliScriptPath) } };
34722
+ const grokMcp = grokRegistration(join18(grokHome, "config.toml"), nodePath, cliScriptPath);
34723
+ const output = { schema: "caveat.native_factory_diagnostics.v1", product: "caveat", version: CAVEAT_VERSION, overall: { status: "unverified" }, database: db, sync: sync(ctx.paths.knowledgeRepo), connectors: { claude: { status: "unverified", mcp: claudeRegistration(claudeMcp, nodePath, cliScriptPath), hooks: claudeHooks(claudeDir, nodePath, cliScriptPath) }, codex: { status: "unverified", hooks: { user_prompt_submit: codexHook2(installedCodexHooks?.userPromptSubmit), post_tool_use: codexHook2(installedCodexHooks?.postToolUse), stop: codexHook2(installedCodexHooks?.stop) } }, cursor: diagnoseCursorHookConnector(cursorDir, nodePath, cliScriptPath), grok: { status: grokMcp.status, mcp: grokMcp } } };
34639
34724
  const aggregate = (all) => all.includes("not_ready") ? "not_ready" : all.includes("unverified") ? "unverified" : "ready";
34640
34725
  const connectorStatus = (connector) => aggregate([...Object.values(connector.hooks), ...connector.mcp ? [connector.mcp] : []].map((v) => v.status));
34641
34726
  output.connectors.claude.status = connectorStatus(output.connectors.claude);
34642
34727
  output.connectors.codex.status = connectorStatus(output.connectors.codex);
34643
34728
  const statuses = [output.database.status, output.sync.status, output.connectors.claude.status, output.connectors.codex.status];
34644
34729
  if (requiredConnectors.includes("cursor")) statuses.push(output.connectors.cursor.compatibility_status);
34730
+ if (requiredConnectors.includes("grok")) statuses.push(output.connectors.grok.status);
34645
34731
  output.overall.status = aggregate(statuses);
34646
34732
  return output;
34647
34733
  }
@@ -34689,6 +34775,15 @@ function jevStatus(ctx) {
34689
34775
  })}
34690
34776
  `);
34691
34777
  }
34778
+ function listJevCases(ctx) {
34779
+ process.stdout.write(`${JSON.stringify({ schema: "caveat.jev_cases.v1", cases: listJevObservations(ctx.caveatHome) })}
34780
+ `);
34781
+ }
34782
+ function reviewJevCase(ctx, id, verdict, note) {
34783
+ const item = reviewJevObservation(ctx.caveatHome, id, verdict, note);
34784
+ process.stdout.write(`${JSON.stringify({ id: item.id, review: item.review })}
34785
+ `);
34786
+ }
34692
34787
 
34693
34788
  // src/index.ts
34694
34789
  var program = new Command();
@@ -34698,7 +34793,7 @@ function collectRepeatable(value, previous) {
34698
34793
  }
34699
34794
  program.name("caveat").description("External spec gotcha knowledge base CLI").version(CAVEAT_VERSION);
34700
34795
  program.command("factory-diagnostics").description("Emit read-only factory diagnostics").requiredOption("--json", "emit the versioned JSON contract").option("--require-connector <name>", "include a connector in overall readiness (repeatable)", collectRepeatable, []).action((opts) => {
34701
- const unsupported = opts.requireConnector.filter((name) => name !== "cursor");
34796
+ const unsupported = opts.requireConnector.filter((name) => name !== "cursor" && name !== "grok");
34702
34797
  if (unsupported.length > 0) throw new Error(`unsupported factory connector: ${unsupported.join(", ")}`);
34703
34798
  const requiredConnectors = [...new Set(opts.requireConnector)];
34704
34799
  const output = factoryDiagnostics(buildContext(stdoutLogger), void 0, requiredConnectors);
@@ -34717,6 +34812,13 @@ jev.command("disable").action(() => {
34717
34812
  jev.command("status").action(() => {
34718
34813
  jevStatus(buildContext(stdoutLogger));
34719
34814
  });
34815
+ jev.command("cases").requiredOption("--json", "emit case records").action(() => {
34816
+ listJevCases(buildContext(stdoutLogger));
34817
+ });
34818
+ 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) => {
34819
+ if (!["correct", "incorrect", "missed", "none", "unclear"].includes(verdict)) throw new Error("jev_review_verdict_invalid");
34820
+ reviewJevCase(buildContext(stdoutLogger), id, verdict, opts.note);
34821
+ });
34720
34822
  var parseLimit = (value) => {
34721
34823
  const parsed = parseCursor(value);
34722
34824
  if (parsed < 1 || parsed > 256) throw Error("invalid_limit");