@twarc_net/groundtruth 0.1.0 → 0.2.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
@@ -75,21 +75,34 @@ function extractClaims(summary) {
75
75
  seen.add(key);
76
76
  claims.push({ kind, target, polarity, source: source.trim() });
77
77
  };
78
- for (const clause of splitClauses(summary)) {
78
+ for (const clause of splitClauses(stripCodeFences(summary))) {
79
79
  if (isIntent(clause)) continue;
80
80
  const polarity = detectPolarity(clause);
81
81
  const hasVerb = ADD_VERBS.test(clause) || REMOVE_VERBS.test(clause) || MODIFY_VERBS.test(clause);
82
82
  let concrete = false;
83
+ const renamed = matchRename(clause);
84
+ if (renamed) {
85
+ add("symbol", renamed.from, "remove", clause);
86
+ add("symbol", renamed.to, "add", clause);
87
+ concrete = true;
88
+ }
83
89
  for (const tok of backtickTokens(clause)) {
84
- const kind = classifyToken(tok);
85
- if (kind === "file") {
90
+ if (looksLikePath(tok)) {
86
91
  add("file", tok, polarity, clause);
87
92
  concrete = true;
88
- } else if (kind === "symbol") {
89
- add("symbol", stripCall(tok), polarity, clause);
90
- concrete = true;
91
- } else if (kind === "command") {
92
- add("command", tok, polarity, clause);
93
+ continue;
94
+ }
95
+ if (tok.includes(" ")) {
96
+ const first = (tok.split(/\s+/)[0] ?? "").toLowerCase();
97
+ if (COMMAND_WORDS.has(first)) {
98
+ add("command", tok, polarity, clause);
99
+ concrete = true;
100
+ }
101
+ continue;
102
+ }
103
+ const sym = symbolName(tok);
104
+ if (sym) {
105
+ add("symbol", sym, polarity, clause);
93
106
  concrete = true;
94
107
  }
95
108
  }
@@ -248,22 +261,31 @@ var SPECIAL_FILES = /* @__PURE__ */ new Set([
248
261
  ".dockerignore",
249
262
  ".editorconfig"
250
263
  ]);
251
- function classifyToken(tok) {
252
- if (looksLikePath(tok)) return "file";
253
- if (tok.includes(" ")) {
254
- const first = tok.split(/\s+/)[0] ?? "";
255
- return COMMAND_WORDS.has(first.toLowerCase()) ? "command" : null;
256
- }
264
+ function symbolName(tok) {
265
+ const jsx = /^<\/?\s*([A-Za-z][\w.]*)\b[^>]*>$/.exec(tok);
266
+ if (jsx?.[1]) return jsx[1];
257
267
  const id = stripCall(tok);
258
- if (/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(id)) return "symbol";
259
- return null;
268
+ return /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(id) ? id : null;
269
+ }
270
+ function matchRename(clause) {
271
+ if (!/\brenam(?:e|ed|es|ing)\b/i.test(clause)) return null;
272
+ const m = /`([^`]+)`\s*(?:to|into|->|→|=>)\s*`([^`]+)`/i.exec(clause);
273
+ if (!m?.[1] || !m[2]) return null;
274
+ const from = symbolName(m[1]);
275
+ const to = symbolName(m[2]);
276
+ return from && to ? { from, to } : null;
277
+ }
278
+ function stripCodeFences(text) {
279
+ return text.replace(/```[\s\S]*?```/g, " ").replace(/~~~[\s\S]*?~~~/g, " ");
260
280
  }
261
281
  function looksLikePath(tok) {
262
282
  if (tok.includes(" ")) return false;
283
+ if (/[<>]/.test(tok)) return false;
263
284
  if (SPECIAL_FILES.has(tok)) return true;
264
285
  if (/^https?:\/\//i.test(tok)) return false;
265
- if (tok.includes("/")) return true;
266
286
  const ext = extOf(tok);
287
+ if (tok.startsWith("/")) return ext !== null && CODE_EXTENSIONS.has(ext);
288
+ if (tok.includes("/")) return true;
267
289
  return ext !== null && CODE_EXTENSIONS.has(ext);
268
290
  }
269
291
  function extOf(tok) {
@@ -316,10 +338,97 @@ function escapeRe(s) {
316
338
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
317
339
  }
318
340
 
341
+ // src/config.ts
342
+ import { existsSync, readFileSync as readFileSync2 } from "fs";
343
+ import { join } from "path";
344
+ var RC_FILE = ".groundtruthrc.json";
345
+ var VALID_FAIL_LEVELS = /* @__PURE__ */ new Set(["unsupported", "unverifiable"]);
346
+ var VALID_KINDS = /* @__PURE__ */ new Set([
347
+ "file",
348
+ "symbol",
349
+ "test",
350
+ "dependency",
351
+ "command",
352
+ "action"
353
+ ]);
354
+ var VALID_OUTPUTS = /* @__PURE__ */ new Set(["terminal", "json", "markdown"]);
355
+ function loadConfig(cwd) {
356
+ const pkg = readJson(join(cwd, "package.json"));
357
+ const fromPkg = pkg && isRecord2(pkg.groundtruth) ? pkg.groundtruth : void 0;
358
+ const fromRc = readJson(join(cwd, RC_FILE));
359
+ return { ...sanitize(fromPkg), ...sanitize(fromRc) };
360
+ }
361
+ function applyConfig(claims, config) {
362
+ const ignoreKinds = new Set(config.ignoreKinds ?? []);
363
+ const matchers = (config.ignore ?? []).map(toMatcher);
364
+ return claims.filter((claim) => {
365
+ if (ignoreKinds.has(claim.kind)) return false;
366
+ return !matchers.some((match) => match(claim.target));
367
+ });
368
+ }
369
+ function toMatcher(pattern) {
370
+ const p = pattern.toLowerCase();
371
+ if (p.includes("*")) {
372
+ const re = new RegExp(`^${p.split("*").map(escapeRe2).join(".*")}$`);
373
+ return (value) => re.test(value.toLowerCase());
374
+ }
375
+ return (value) => value.toLowerCase().includes(p);
376
+ }
377
+ function failingCount(report, config) {
378
+ const levels = config.failOn ?? ["unsupported"];
379
+ let n = 0;
380
+ if (levels.includes("unsupported")) n += report.summary.unsupported;
381
+ if (levels.includes("unverifiable")) n += report.summary.unverifiable;
382
+ return n;
383
+ }
384
+ function sanitize(input) {
385
+ if (!isRecord2(input)) return {};
386
+ const out = {};
387
+ if (typeof input.strict === "boolean") out.strict = input.strict;
388
+ if (typeof input.shadow === "boolean") out.shadow = input.shadow;
389
+ if (isStringArray(input.failOn)) {
390
+ out.failOn = input.failOn.filter((l) => VALID_FAIL_LEVELS.has(l));
391
+ }
392
+ if (isStringArray(input.ignore)) out.ignore = input.ignore;
393
+ if (isStringArray(input.ignoreKinds)) {
394
+ out.ignoreKinds = input.ignoreKinds.filter((k) => VALID_KINDS.has(k));
395
+ }
396
+ if (typeof input.output === "string" && VALID_OUTPUTS.has(input.output)) {
397
+ out.output = input.output;
398
+ }
399
+ return out;
400
+ }
401
+ function readJson(path) {
402
+ if (!existsSync(path)) return void 0;
403
+ try {
404
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
405
+ return isRecord2(parsed) ? parsed : void 0;
406
+ } catch {
407
+ return void 0;
408
+ }
409
+ }
410
+ function isRecord2(v) {
411
+ return typeof v === "object" && v !== null && !Array.isArray(v);
412
+ }
413
+ function isStringArray(v) {
414
+ return Array.isArray(v) && v.every((item) => typeof item === "string");
415
+ }
416
+ function escapeRe2(s) {
417
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
418
+ }
419
+
319
420
  // src/git.ts
320
421
  import { execFileSync } from "child_process";
321
- function collectGitEvidence(cwd) {
422
+ function collectGitEvidence(cwd, base2) {
322
423
  const ev = emptyEvidence();
424
+ if (base2) {
425
+ const range = `${base2}...HEAD`;
426
+ const diff2 = git(["diff", range, "--no-color", "--unified=0"], cwd);
427
+ if (diff2 !== null) parseDiff(diff2, ev);
428
+ const names = git(["diff", "--name-status", range], cwd);
429
+ if (names !== null) parseNameStatus(names, ev);
430
+ return ev;
431
+ }
323
432
  const diff = git(["diff", "HEAD", "--no-color", "--unified=0"], cwd);
324
433
  if (diff !== null) parseDiff(diff, ev);
325
434
  const status = git(["status", "--porcelain"], cwd);
@@ -357,6 +466,17 @@ function stripDiffPath(raw) {
357
466
  if (t === "/dev/null") return "";
358
467
  return t.replace(/^[ab]\//, "");
359
468
  }
469
+ function parseNameStatus(out, ev) {
470
+ for (const line of out.split("\n")) {
471
+ if (!line.trim()) continue;
472
+ const parts = line.split(" ");
473
+ const code = parts[0] ?? "";
474
+ const path = (parts.length > 2 ? parts[parts.length - 1] : parts[1]) ?? "";
475
+ if (!path) continue;
476
+ pushUnique(ev.touchedFiles, path);
477
+ if (code.startsWith("A")) pushUnique(ev.createdFiles, path);
478
+ }
479
+ }
360
480
  function parseStatus(status, ev) {
361
481
  for (const line of status.split("\n")) {
362
482
  if (!line.trim()) continue;
@@ -375,10 +495,10 @@ function pushUnique(arr, value) {
375
495
  }
376
496
 
377
497
  // src/evidence.ts
378
- function buildEvidence(toolUses, cwd) {
498
+ function buildEvidence(toolUses, cwd, base2) {
379
499
  const ev = emptyEvidence();
380
500
  collectToolEvidence(toolUses, ev);
381
- if (cwd) mergeEvidence(ev, collectGitEvidence(cwd));
501
+ if (cwd) mergeEvidence(ev, collectGitEvidence(cwd, base2));
382
502
  return ev;
383
503
  }
384
504
  function emptyEvidence() {
@@ -410,7 +530,7 @@ ${str(input.old_string)}`;
410
530
  if (file) addFile(ev, file, false);
411
531
  const edits = Array.isArray(input.edits) ? input.edits : [];
412
532
  for (const edit of edits) {
413
- if (!isRecord2(edit)) continue;
533
+ if (!isRecord3(edit)) continue;
414
534
  ev.addedText += `
415
535
  ${str(edit.new_string)}`;
416
536
  ev.removedText += `
@@ -458,7 +578,7 @@ function pushUnique2(arr, value) {
458
578
  function str(v) {
459
579
  return typeof v === "string" ? v : "";
460
580
  }
461
- function isRecord2(v) {
581
+ function isRecord3(v) {
462
582
  return typeof v === "object" && v !== null && !Array.isArray(v);
463
583
  }
464
584
 
@@ -556,8 +676,20 @@ function fileMatches(claimPath, actual) {
556
676
  if (b.endsWith(`/${a}`) || a.endsWith(`/${b}`)) return true;
557
677
  if (!a.includes("/") && base(b) === a) return true;
558
678
  if (!b.includes("/") && base(a) === b) return true;
679
+ if (!hasExt(a)) {
680
+ const bNoExt = stripExt(b);
681
+ if (a === bNoExt) return true;
682
+ if (bNoExt.endsWith(`/${a}`)) return true;
683
+ if (!a.includes("/") && base(bNoExt) === a) return true;
684
+ }
559
685
  return false;
560
686
  }
687
+ function hasExt(path) {
688
+ return /\.[A-Za-z0-9]+$/.test(base(path));
689
+ }
690
+ function stripExt(path) {
691
+ return path.replace(/\.[A-Za-z0-9]+$/, "");
692
+ }
561
693
  function identifierPresent(haystack, id) {
562
694
  if (!haystack) return false;
563
695
  const esc = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -807,23 +939,492 @@ function truncate(s, max) {
807
939
  // src/pipeline.ts
808
940
  function runPipeline(input) {
809
941
  const turn = input.turn ?? (input.transcriptPath ? parseTranscriptFile(input.transcriptPath) : { summary: "", toolUses: [] });
810
- const evidence = buildEvidence(turn.toolUses, input.cwd);
811
- const claims = extractClaims(turn.summary);
942
+ const config = input.config ?? (input.cwd ? loadConfig(input.cwd) : {});
943
+ const evidence = buildEvidence(turn.toolUses, input.cwd, input.base);
944
+ const claims = applyConfig(extractClaims(turn.summary), config);
812
945
  const verdicts = verifyClaims(claims, evidence);
813
946
  return buildReport(verdicts);
814
947
  }
948
+
949
+ // src/adapters/index.ts
950
+ import { createHash } from "crypto";
951
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
952
+ import { homedir as homedir2 } from "os";
953
+ import { join as join3 } from "path";
954
+
955
+ // src/locate.ts
956
+ import { existsSync as existsSync2, readdirSync, statSync } from "fs";
957
+ import { homedir } from "os";
958
+ import { join as join2, resolve } from "path";
959
+ function findLatestTranscript(cwd) {
960
+ const dir = projectDir(cwd);
961
+ if (!existsSync2(dir)) return null;
962
+ let newest = null;
963
+ for (const name of readdirSync(dir)) {
964
+ if (!name.endsWith(".jsonl")) continue;
965
+ const path = join2(dir, name);
966
+ try {
967
+ const mtime = statSync(path).mtimeMs;
968
+ if (!newest || mtime > newest.mtime) newest = { path, mtime };
969
+ } catch {
970
+ }
971
+ }
972
+ return newest?.path ?? null;
973
+ }
974
+ function projectDir(cwd) {
975
+ const encoded = resolve(cwd).replace(/[^A-Za-z0-9]/g, "-");
976
+ return join2(homedir(), ".claude", "projects", encoded);
977
+ }
978
+
979
+ // src/adapters/turn.ts
980
+ function assembleTurn(events) {
981
+ let start = -1;
982
+ for (let i = events.length - 1; i >= 0; i--) {
983
+ if (events[i]?.role === "user") {
984
+ start = i;
985
+ break;
986
+ }
987
+ }
988
+ let summary = "";
989
+ const toolUses = [];
990
+ for (const e of events.slice(start + 1)) {
991
+ if (e.role === "assistant" && e.text && e.text.trim()) summary = e.text.trim();
992
+ if (e.tool) toolUses.push(e.tool);
993
+ }
994
+ return { summary, toolUses };
995
+ }
996
+ function parseJsonlLines(raw) {
997
+ const out = [];
998
+ for (const line of raw.split("\n")) {
999
+ const trimmed = line.trim();
1000
+ if (!trimmed) continue;
1001
+ try {
1002
+ out.push(JSON.parse(trimmed));
1003
+ } catch {
1004
+ }
1005
+ }
1006
+ return out;
1007
+ }
1008
+ function parseApplyPatch(patch) {
1009
+ const tools = [];
1010
+ let file = null;
1011
+ let op = null;
1012
+ let added = [];
1013
+ let removed = [];
1014
+ const flush = () => {
1015
+ if (!file || !op) return;
1016
+ if (op === "add") {
1017
+ tools.push({ name: "Write", input: { file_path: file, content: added.join("\n") } });
1018
+ } else {
1019
+ tools.push({
1020
+ name: "Edit",
1021
+ input: { file_path: file, new_string: added.join("\n"), old_string: removed.join("\n") }
1022
+ });
1023
+ }
1024
+ added = [];
1025
+ removed = [];
1026
+ };
1027
+ for (const line of patch.split("\n")) {
1028
+ const add = /^\*\*\* Add File: (.+)$/.exec(line);
1029
+ const upd = /^\*\*\* Update File: (.+)$/.exec(line);
1030
+ if (add?.[1]) {
1031
+ flush();
1032
+ file = add[1].trim();
1033
+ op = "add";
1034
+ } else if (upd?.[1]) {
1035
+ flush();
1036
+ file = upd[1].trim();
1037
+ op = "update";
1038
+ } else if (/^\*\*\* (End Patch|Begin Patch|Delete File:)/.test(line)) {
1039
+ flush();
1040
+ if (line.startsWith("*** End Patch")) {
1041
+ file = null;
1042
+ op = null;
1043
+ }
1044
+ } else if (line.startsWith("+") && !line.startsWith("+++")) {
1045
+ added.push(line.slice(1));
1046
+ } else if (line.startsWith("-") && !line.startsWith("---")) {
1047
+ removed.push(line.slice(1));
1048
+ }
1049
+ }
1050
+ flush();
1051
+ return tools;
1052
+ }
1053
+ function isRecord4(v) {
1054
+ return typeof v === "object" && v !== null && !Array.isArray(v);
1055
+ }
1056
+ function str2(v) {
1057
+ return typeof v === "string" ? v : "";
1058
+ }
1059
+
1060
+ // src/adapters/codex.ts
1061
+ function parseCodex(raw) {
1062
+ const events = [];
1063
+ for (const line of parseJsonlLines(raw)) {
1064
+ if (!isRecord4(line) || line.type !== "response_item" || !isRecord4(line.payload)) continue;
1065
+ const p = line.payload;
1066
+ switch (p.type) {
1067
+ case "message": {
1068
+ events.push({ role: p.role === "user" ? "user" : "assistant", text: textOf(p.content) });
1069
+ break;
1070
+ }
1071
+ case "function_call": {
1072
+ for (const tool of fromFunctionCall(str2(p.name), str2(p.arguments))) {
1073
+ events.push({ role: "assistant", tool });
1074
+ }
1075
+ break;
1076
+ }
1077
+ case "custom_tool_call": {
1078
+ if (str2(p.name) === "apply_patch") {
1079
+ for (const tool of parseApplyPatch(str2(p.input)))
1080
+ events.push({ role: "assistant", tool });
1081
+ }
1082
+ break;
1083
+ }
1084
+ case "local_shell_call": {
1085
+ const cmd = isRecord4(p.action) ? commandToString(p.action.command) : "";
1086
+ if (cmd)
1087
+ events.push({ role: "assistant", tool: { name: "Bash", input: { command: cmd } } });
1088
+ break;
1089
+ }
1090
+ default:
1091
+ break;
1092
+ }
1093
+ }
1094
+ return assembleTurn(events);
1095
+ }
1096
+ function textOf(content) {
1097
+ if (typeof content === "string") return content;
1098
+ if (!Array.isArray(content)) return "";
1099
+ return content.filter(isRecord4).map((b) => typeof b.text === "string" ? b.text : "").join("\n").trim();
1100
+ }
1101
+ function fromFunctionCall(name, argsJson) {
1102
+ if (name === "apply_patch") {
1103
+ const parsed = safeJson(argsJson);
1104
+ const patch = isRecord4(parsed) && typeof parsed.input === "string" ? parsed.input : argsJson;
1105
+ return parseApplyPatch(patch);
1106
+ }
1107
+ if (name === "shell" || name === "shell_command" || name === "bash") {
1108
+ const parsed = safeJson(argsJson);
1109
+ const cmd = isRecord4(parsed) ? commandToString(parsed.command) : "";
1110
+ if (!cmd) return [];
1111
+ if (cmd.includes("apply_patch") && cmd.includes("*** Begin Patch")) {
1112
+ const m = /\*\*\* Begin Patch[\s\S]*?\*\*\* End Patch/.exec(cmd);
1113
+ if (m) return parseApplyPatch(m[0]);
1114
+ }
1115
+ return [{ name: "Bash", input: { command: cmd } }];
1116
+ }
1117
+ return [];
1118
+ }
1119
+ function commandToString(command) {
1120
+ if (typeof command === "string") return command;
1121
+ if (Array.isArray(command)) return command.filter((c2) => typeof c2 === "string").join(" ");
1122
+ return "";
1123
+ }
1124
+ function safeJson(s) {
1125
+ try {
1126
+ return JSON.parse(s);
1127
+ } catch {
1128
+ return null;
1129
+ }
1130
+ }
1131
+
1132
+ // src/adapters/cursor.ts
1133
+ function parseCursor(raw) {
1134
+ const events = [];
1135
+ const seen = /* @__PURE__ */ new Set();
1136
+ let resultText = "";
1137
+ for (const line of parseJsonlLines(raw)) {
1138
+ if (!isRecord4(line)) continue;
1139
+ switch (line.type) {
1140
+ case "user":
1141
+ events.push({ role: "user", text: messageText(line.message) });
1142
+ break;
1143
+ case "assistant":
1144
+ events.push({ role: "assistant", text: messageText(line.message) });
1145
+ break;
1146
+ case "tool_call": {
1147
+ const id = str2(line.call_id);
1148
+ if (id && seen.has(id)) break;
1149
+ if (id) seen.add(id);
1150
+ const tool = fromToolCall(line.tool_call);
1151
+ if (tool) events.push({ role: "assistant", tool });
1152
+ break;
1153
+ }
1154
+ case "result":
1155
+ resultText = str2(line.result);
1156
+ break;
1157
+ default:
1158
+ break;
1159
+ }
1160
+ }
1161
+ if (resultText.trim()) events.push({ role: "assistant", text: resultText });
1162
+ return assembleTurn(events);
1163
+ }
1164
+ function messageText(message) {
1165
+ if (!isRecord4(message)) return "";
1166
+ const content = message.content;
1167
+ if (typeof content === "string") return content;
1168
+ if (!Array.isArray(content)) return "";
1169
+ return content.filter(isRecord4).map((b) => typeof b.text === "string" ? b.text : "").join("\n").trim();
1170
+ }
1171
+ function fromToolCall(tc) {
1172
+ if (!isRecord4(tc)) return null;
1173
+ if (isRecord4(tc.writeToolCall)) {
1174
+ const a = argsOf(tc.writeToolCall);
1175
+ return { name: "Write", input: { file_path: str2(a.path), content: str2(a.fileText) } };
1176
+ }
1177
+ if (isRecord4(tc.editToolCall)) {
1178
+ const a = argsOf(tc.editToolCall);
1179
+ return {
1180
+ name: "Edit",
1181
+ input: {
1182
+ file_path: str2(a.path),
1183
+ new_string: str2(a.fileText ?? a.newString),
1184
+ old_string: str2(a.oldString)
1185
+ }
1186
+ };
1187
+ }
1188
+ if (isRecord4(tc.shellToolCall)) {
1189
+ const a = argsOf(tc.shellToolCall);
1190
+ return { name: "Bash", input: { command: str2(a.command) } };
1191
+ }
1192
+ return null;
1193
+ }
1194
+ function argsOf(x) {
1195
+ return isRecord4(x.args) ? x.args : {};
1196
+ }
1197
+
1198
+ // src/adapters/gemini.ts
1199
+ function parseGemini(raw) {
1200
+ const events = [];
1201
+ for (const rec of records(raw)) {
1202
+ if (!isRecord4(rec)) continue;
1203
+ if (rec.type === "user") {
1204
+ events.push({ role: "user", text: textOf2(rec.content) });
1205
+ } else if (rec.type === "gemini") {
1206
+ events.push({ role: "assistant", text: textOf2(rec.content) });
1207
+ const calls = Array.isArray(rec.toolCalls) ? rec.toolCalls : [];
1208
+ for (const call of calls) {
1209
+ const tool = fromToolCall2(call);
1210
+ if (tool) events.push({ role: "assistant", tool });
1211
+ }
1212
+ }
1213
+ }
1214
+ return assembleTurn(events);
1215
+ }
1216
+ function records(raw) {
1217
+ const trimmed = raw.trim();
1218
+ if (trimmed.startsWith("{")) {
1219
+ try {
1220
+ const obj = JSON.parse(trimmed);
1221
+ if (isRecord4(obj) && Array.isArray(obj.messages)) return obj.messages;
1222
+ } catch {
1223
+ }
1224
+ }
1225
+ return parseJsonlLines(raw);
1226
+ }
1227
+ function textOf2(content) {
1228
+ if (typeof content === "string") return content;
1229
+ if (!Array.isArray(content)) return "";
1230
+ return content.filter(isRecord4).map((b) => typeof b.text === "string" ? b.text : "").join("\n").trim();
1231
+ }
1232
+ function fromToolCall2(call) {
1233
+ if (!isRecord4(call)) return null;
1234
+ const name = str2(call.name);
1235
+ const args = isRecord4(call.args) ? call.args : {};
1236
+ if (name === "write_file") {
1237
+ return { name: "Write", input: { file_path: str2(args.file_path), content: str2(args.content) } };
1238
+ }
1239
+ if (name === "replace") {
1240
+ return {
1241
+ name: "Edit",
1242
+ input: {
1243
+ file_path: str2(args.file_path),
1244
+ new_string: str2(args.new_string),
1245
+ old_string: str2(args.old_string)
1246
+ }
1247
+ };
1248
+ }
1249
+ if (name === "run_shell_command") {
1250
+ return { name: "Bash", input: { command: str2(args.command) } };
1251
+ }
1252
+ return null;
1253
+ }
1254
+
1255
+ // src/adapters/index.ts
1256
+ var claude = {
1257
+ name: "claude",
1258
+ locate: (cwd) => findLatestTranscript(cwd),
1259
+ parse: (path) => parseTranscriptFile(path)
1260
+ };
1261
+ var codex = {
1262
+ name: "codex",
1263
+ locate() {
1264
+ const home = process.env.CODEX_HOME ?? join3(homedir2(), ".codex");
1265
+ return newestMatch(
1266
+ join3(home, "sessions"),
1267
+ (n) => n.startsWith("rollout-") && n.endsWith(".jsonl")
1268
+ );
1269
+ },
1270
+ parse: (path) => parseCodex(readFileSync3(path, "utf8"))
1271
+ };
1272
+ var gemini = {
1273
+ name: "gemini",
1274
+ locate(cwd) {
1275
+ const home = process.env.GEMINI_DIR ?? join3(homedir2(), ".gemini");
1276
+ const hash = createHash("sha256").update(cwd).digest("hex");
1277
+ const scoped = newestMatch(
1278
+ join3(home, "tmp", hash, "chats"),
1279
+ (n) => n.endsWith(".jsonl") || n.endsWith(".json")
1280
+ );
1281
+ if (scoped) return scoped;
1282
+ return newestMatch(join3(home, "tmp"), (n) => n.startsWith("session") && /\.jsonl?$/.test(n));
1283
+ },
1284
+ parse: (path) => parseGemini(readFileSync3(path, "utf8"))
1285
+ };
1286
+ var cursor = {
1287
+ name: "cursor",
1288
+ locate() {
1289
+ return newestMatch(join3(homedir2(), ".cursor", "projects"), (n) => n.endsWith(".jsonl"));
1290
+ },
1291
+ parse: (path) => parseCursor(readFileSync3(path, "utf8"))
1292
+ };
1293
+ var ADAPTERS = { claude, codex, gemini, cursor };
1294
+ var AGENT_NAMES = Object.keys(ADAPTERS);
1295
+ function getAdapter(name) {
1296
+ return ADAPTERS[name] ?? null;
1297
+ }
1298
+ function autoDetect(cwd) {
1299
+ let best = null;
1300
+ for (const adapter of Object.values(ADAPTERS)) {
1301
+ const path = adapter.locate(cwd);
1302
+ if (!path) continue;
1303
+ try {
1304
+ const mtime = statSync2(path).mtimeMs;
1305
+ if (!best || mtime > best.mtime) best = { adapter, path, mtime };
1306
+ } catch {
1307
+ }
1308
+ }
1309
+ return best ? { adapter: best.adapter, path: best.path } : null;
1310
+ }
1311
+ function newestMatch(dir, test, depth = 6) {
1312
+ const found = walk(dir, test, depth);
1313
+ return found?.path ?? null;
1314
+ }
1315
+ function walk(dir, test, depth) {
1316
+ if (depth < 0 || !existsSync3(dir)) return null;
1317
+ let entries;
1318
+ try {
1319
+ entries = readdirSync2(dir, { withFileTypes: true });
1320
+ } catch {
1321
+ return null;
1322
+ }
1323
+ let best = null;
1324
+ for (const entry of entries) {
1325
+ const full = join3(dir, entry.name);
1326
+ try {
1327
+ if (entry.isDirectory()) {
1328
+ const sub = walk(full, test, depth - 1);
1329
+ if (sub && (!best || sub.mtime > best.mtime)) best = sub;
1330
+ } else if (entry.isFile() && test(entry.name)) {
1331
+ const mtime = statSync2(full).mtimeMs;
1332
+ if (!best || mtime > best.mtime) best = { path: full, mtime };
1333
+ }
1334
+ } catch {
1335
+ }
1336
+ }
1337
+ return best;
1338
+ }
1339
+
1340
+ // src/ledger.ts
1341
+ import { appendFileSync, existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4 } from "fs";
1342
+ import { homedir as homedir3 } from "os";
1343
+ import { dirname, join as join4 } from "path";
1344
+ function ledgerPath() {
1345
+ return process.env.GROUNDTRUTH_LEDGER ?? join4(homedir3(), ".groundtruth", "ledger.jsonl");
1346
+ }
1347
+ function recordRun(report, cwd, session) {
1348
+ if (report.summary.total === 0) return;
1349
+ const entry = {
1350
+ t: (/* @__PURE__ */ new Date()).toISOString(),
1351
+ cwd,
1352
+ v: report.summary.verified,
1353
+ u: report.summary.unsupported,
1354
+ r: report.summary.unverifiable
1355
+ };
1356
+ if (session) entry.session = session;
1357
+ try {
1358
+ const path = ledgerPath();
1359
+ mkdirSync(dirname(path), { recursive: true });
1360
+ appendFileSync(path, `${JSON.stringify(entry)}
1361
+ `, "utf8");
1362
+ } catch {
1363
+ }
1364
+ }
1365
+ function readLedger() {
1366
+ const path = ledgerPath();
1367
+ if (!existsSync4(path)) return [];
1368
+ const out = [];
1369
+ try {
1370
+ for (const line of readFileSync4(path, "utf8").split("\n")) {
1371
+ const trimmed = line.trim();
1372
+ if (!trimmed) continue;
1373
+ try {
1374
+ const parsed = JSON.parse(trimmed);
1375
+ if (isEntry(parsed)) out.push(parsed);
1376
+ } catch {
1377
+ }
1378
+ }
1379
+ } catch {
1380
+ }
1381
+ return out;
1382
+ }
1383
+ function summarize(entries, opts = {}) {
1384
+ const cutoff = opts.sinceDays !== void 0 ? Date.now() - opts.sinceDays * 864e5 : 0;
1385
+ const sum = { runs: 0, verified: 0, unsupported: 0, unverifiable: 0 };
1386
+ for (const e of entries) {
1387
+ if (opts.cwd && e.cwd !== opts.cwd) continue;
1388
+ if (opts.session && e.session !== opts.session) continue;
1389
+ if (cutoff && Date.parse(e.t) < cutoff) continue;
1390
+ sum.runs += 1;
1391
+ sum.verified += e.v;
1392
+ sum.unsupported += e.u;
1393
+ sum.unverifiable += e.r;
1394
+ }
1395
+ return sum;
1396
+ }
1397
+ function isEntry(v) {
1398
+ if (typeof v !== "object" || v === null) return false;
1399
+ const e = v;
1400
+ return typeof e.t === "string" && typeof e.cwd === "string" && typeof e.v === "number" && typeof e.u === "number" && typeof e.r === "number";
1401
+ }
815
1402
  export {
1403
+ ADAPTERS,
1404
+ AGENT_NAMES,
1405
+ applyConfig,
1406
+ autoDetect,
816
1407
  buildEvidence,
817
1408
  buildReport,
818
1409
  collectGitEvidence,
819
1410
  emptyEvidence,
820
1411
  extractClaims,
1412
+ failingCount,
1413
+ getAdapter,
1414
+ ledgerPath,
1415
+ loadConfig,
821
1416
  mergeEvidence,
1417
+ parseCodex,
1418
+ parseCursor,
1419
+ parseGemini,
822
1420
  parseTranscript,
823
1421
  parseTranscriptFile,
1422
+ readLedger,
1423
+ recordRun,
824
1424
  renderJson,
825
1425
  renderMarkdown,
826
1426
  renderTerminal,
827
1427
  runPipeline,
1428
+ summarize,
828
1429
  verifyClaims
829
1430
  };