agent-inspect 2.0.0 → 2.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.
@@ -2,7 +2,7 @@
2
2
  'use strict';
3
3
 
4
4
  var fs = require('fs');
5
- var path10 = require('path');
5
+ var path13 = require('path');
6
6
  var url = require('url');
7
7
  var commander = require('commander');
8
8
  var async_hooks = require('async_hooks');
@@ -16,14 +16,14 @@ var readline = require('readline');
16
16
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
17
17
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
18
18
 
19
- var path10__default = /*#__PURE__*/_interopDefault(path10);
19
+ var path13__default = /*#__PURE__*/_interopDefault(path13);
20
20
  var crypto__default = /*#__PURE__*/_interopDefault(crypto);
21
21
  var os__default = /*#__PURE__*/_interopDefault(os);
22
22
  var process2__default = /*#__PURE__*/_interopDefault(process2);
23
23
  var tty__default = /*#__PURE__*/_interopDefault(tty);
24
24
 
25
25
  // package.json
26
- var version = "2.0.0";
26
+ var version = "2.2.0";
27
27
 
28
28
  // packages/core/src/correlation-metadata.ts
29
29
  var TRACE_CORRELATION_KEYS = [
@@ -755,7 +755,7 @@ function formatDuration(ms) {
755
755
  // packages/core/src/utils.ts
756
756
  var DEFAULT_TRACE_DIR_NAME = ".agent-inspect";
757
757
  var RUNS_DIR_NAME = "runs";
758
- var FALLBACK_TRACE_DIR = path10__default.default.join(
758
+ var FALLBACK_TRACE_DIR = path13__default.default.join(
759
759
  os__default.default.tmpdir(),
760
760
  "agent-inspect",
761
761
  RUNS_DIR_NAME
@@ -790,7 +790,7 @@ function getDefaultTraceDir() {
790
790
  if (typeof home !== "string" || home.trim() === "") {
791
791
  return FALLBACK_TRACE_DIR;
792
792
  }
793
- return path10__default.default.join(home, DEFAULT_TRACE_DIR_NAME, RUNS_DIR_NAME);
793
+ return path13__default.default.join(home, DEFAULT_TRACE_DIR_NAME, RUNS_DIR_NAME);
794
794
  } catch {
795
795
  return FALLBACK_TRACE_DIR;
796
796
  }
@@ -798,11 +798,11 @@ function getDefaultTraceDir() {
798
798
  function getTraceFilePath(runId, traceDir) {
799
799
  const baseDir = traceDir ?? getDefaultTraceDir();
800
800
  let safeId = typeof runId === "string" && runId.trim() !== "" ? runId.trim() : "run_unknown";
801
- safeId = path10__default.default.basename(safeId);
801
+ safeId = path13__default.default.basename(safeId);
802
802
  if (safeId === "" || safeId === "." || safeId === "..") {
803
803
  safeId = "run_unknown";
804
804
  }
805
- return path10__default.default.join(baseDir, `${safeId}.jsonl`);
805
+ return path13__default.default.join(baseDir, `${safeId}.jsonl`);
806
806
  }
807
807
  function formatError(error) {
808
808
  if (error instanceof Error) {
@@ -1564,7 +1564,7 @@ var TraceDirectory = class {
1564
1564
  this.#dir = resolveTraceDir(options);
1565
1565
  }
1566
1566
  getPath(filename) {
1567
- return filename ? path10__default.default.join(this.#dir, filename) : this.#dir;
1567
+ return filename ? path13__default.default.join(this.#dir, filename) : this.#dir;
1568
1568
  }
1569
1569
  async list() {
1570
1570
  try {
@@ -1591,7 +1591,7 @@ function parseIsoToMs2(value) {
1591
1591
  }
1592
1592
  async function extractMetadata(filePath, _quickScan) {
1593
1593
  const stats = await promises.stat(filePath);
1594
- let runIdFromFile = path10__default.default.basename(filePath);
1594
+ let runIdFromFile = path13__default.default.basename(filePath);
1595
1595
  if (runIdFromFile.endsWith(".jsonl")) {
1596
1596
  runIdFromFile = runIdFromFile.slice(0, -".jsonl".length);
1597
1597
  }
@@ -3562,7 +3562,7 @@ function findReaderByFormat(format, readers) {
3562
3562
  }
3563
3563
  async function jsonlFilesInDirectory(dirPath) {
3564
3564
  const entries = await promises.readdir(dirPath, { withFileTypes: true });
3565
- return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => path10__default.default.join(dirPath, entry.name)).sort((a, b) => a.localeCompare(b));
3565
+ return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => path13__default.default.join(dirPath, entry.name)).sort((a, b) => a.localeCompare(b));
3566
3566
  }
3567
3567
  async function resolveInput(input3) {
3568
3568
  const cached = resolvedInputCache.get(input3);
@@ -5305,11 +5305,11 @@ function isRecord9(v) {
5305
5305
  function isNonEmptyStringArray(v) {
5306
5306
  return Array.isArray(v) && v.length > 0 && v.every((x) => typeof x === "string" && x.trim() !== "");
5307
5307
  }
5308
- function validateRedact(redact) {
5309
- if (!Array.isArray(redact)) {
5308
+ function validateRedact(redact2) {
5309
+ if (!Array.isArray(redact2)) {
5310
5310
  throw new Error("Invalid config: redact must be an array");
5311
5311
  }
5312
- for (const r of redact) {
5312
+ for (const r of redact2) {
5313
5313
  if (typeof r === "string") continue;
5314
5314
  if (!isRecord9(r)) {
5315
5315
  throw new Error("Invalid config: redact entries must be strings or objects");
@@ -7688,9 +7688,9 @@ Trace directory: ${traceDir}`);
7688
7688
  if (validation !== void 0 && !validation.ok) {
7689
7689
  process.exitCode = 1;
7690
7690
  }
7691
- const outPath = options.output !== void 0 && options.output.trim() !== "" ? path10__default.default.resolve(options.output.trim()) : void 0;
7691
+ const outPath = options.output !== void 0 && options.output.trim() !== "" ? path13__default.default.resolve(options.output.trim()) : void 0;
7692
7692
  if (outPath !== void 0) {
7693
- await promises.mkdir(path10__default.default.dirname(outPath), { recursive: true });
7693
+ await promises.mkdir(path13__default.default.dirname(outPath), { recursive: true });
7694
7694
  await promises.writeFile(outPath, result.content, "utf-8");
7695
7695
  const vlabel = validation !== void 0 ? validation.ok ? "ok" : "failed" : "skipped";
7696
7696
  console.log(`Wrote ${result.fileExtension} export to ${outPath} (validation: ${vlabel})`);
@@ -7867,13 +7867,13 @@ function pairSteps(left, right) {
7867
7867
  return pairs;
7868
7868
  }
7869
7869
  function compareLeafSteps(L, R, segments, opts, out) {
7870
- const path12 = buildPath(segments);
7870
+ const path15 = buildPath(segments);
7871
7871
  if (L.name !== R.name) {
7872
7872
  out.push({
7873
7873
  kind: "structure",
7874
7874
  severity: "warning",
7875
7875
  message: "Step name differs",
7876
- path: path12,
7876
+ path: path15,
7877
7877
  left: L.name,
7878
7878
  right: R.name
7879
7879
  });
@@ -7883,7 +7883,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
7883
7883
  kind: "step-type",
7884
7884
  severity: "warning",
7885
7885
  message: "Step type differs",
7886
- path: path12,
7886
+ path: path15,
7887
7887
  left: L.type,
7888
7888
  right: R.type
7889
7889
  });
@@ -7893,7 +7893,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
7893
7893
  kind: "step-status",
7894
7894
  severity: "warning",
7895
7895
  message: "Step status differs",
7896
- path: path12,
7896
+ path: path15,
7897
7897
  left: L.status,
7898
7898
  right: R.status
7899
7899
  });
@@ -7905,7 +7905,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
7905
7905
  kind: "error",
7906
7906
  severity: "error",
7907
7907
  message: "Step error message differs",
7908
- path: path12,
7908
+ path: path15,
7909
7909
  left: le || void 0,
7910
7910
  right: re || void 0
7911
7911
  });
@@ -7923,7 +7923,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
7923
7923
  kind: "duration",
7924
7924
  severity: "info",
7925
7925
  message: "Step duration differs",
7926
- path: path12,
7926
+ path: path15,
7927
7927
  left: ld,
7928
7928
  right: rd
7929
7929
  });
@@ -7936,7 +7936,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
7936
7936
  kind: "metadata",
7937
7937
  severity: "info",
7938
7938
  message: "Step metadata differs",
7939
- path: path12,
7939
+ path: path15,
7940
7940
  left: L.metadata,
7941
7941
  right: R.metadata
7942
7942
  });
@@ -7948,7 +7948,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
7948
7948
  kind: "output",
7949
7949
  severity: "info",
7950
7950
  message: "Output preview differs",
7951
- path: path12,
7951
+ path: path15,
7952
7952
  left: L.outputPreview,
7953
7953
  right: R.outputPreview
7954
7954
  });
@@ -8108,11 +8108,11 @@ function diffRuns(left, right, options) {
8108
8108
  }
8109
8109
 
8110
8110
  // packages/core/src/diff/renderer.ts
8111
- function formatPath(path12) {
8112
- if (path12 === void 0 || path12.path.length === 0) {
8111
+ function formatPath(path15) {
8112
+ if (path15 === void 0 || path15.path.length === 0) {
8113
8113
  return "(run)";
8114
8114
  }
8115
- return path12.path.map((s) => s.name).join(" > ");
8115
+ return path15.path.map((s) => s.name).join(" > ");
8116
8116
  }
8117
8117
  function formatValue(v, verbose) {
8118
8118
  if (v === void 0) return "(undefined)";
@@ -8543,9 +8543,9 @@ async function reportCommand(runId, options = {}) {
8543
8543
  redactionProfile,
8544
8544
  correlation: !options.noCorrelation
8545
8545
  });
8546
- const outPath = options.output !== void 0 && options.output.trim() !== "" ? path10__default.default.resolve(options.output.trim()) : void 0;
8546
+ const outPath = options.output !== void 0 && options.output.trim() !== "" ? path13__default.default.resolve(options.output.trim()) : void 0;
8547
8547
  if (outPath !== void 0) {
8548
- await promises.mkdir(path10__default.default.dirname(outPath), { recursive: true });
8548
+ await promises.mkdir(path13__default.default.dirname(outPath), { recursive: true });
8549
8549
  await promises.writeFile(outPath, result.content, "utf-8");
8550
8550
  console.log(`Wrote ${result.fileExtension} report to ${outPath}`);
8551
8551
  }
@@ -8565,6 +8565,383 @@ async function reportCommand(runId, options = {}) {
8565
8565
  console.log(result.content);
8566
8566
  }
8567
8567
  }
8568
+ var DEFAULT_REDACT_KEYS2 = [
8569
+ "authorization",
8570
+ "cookie",
8571
+ "token",
8572
+ "apiKey",
8573
+ "password",
8574
+ "secret",
8575
+ "email"
8576
+ ];
8577
+ var SHARE_PROFILE_EXTRA_KEYS2 = [
8578
+ "userEmail",
8579
+ "customerEmail",
8580
+ "phone",
8581
+ "phoneNumber",
8582
+ "address",
8583
+ "ip",
8584
+ "ipAddress",
8585
+ "sessionId",
8586
+ "requestId",
8587
+ "correlationId",
8588
+ "decisionId",
8589
+ "groupId",
8590
+ "customerId",
8591
+ "userId",
8592
+ "accountId",
8593
+ "tenantId",
8594
+ "orgId",
8595
+ "organizationId",
8596
+ "traceId",
8597
+ "spanId",
8598
+ "parentSpanId"
8599
+ ];
8600
+ var STRICT_PROFILE_EXTRA_KEYS2 = [
8601
+ "prompt",
8602
+ "completion",
8603
+ "input",
8604
+ "output",
8605
+ "inputPreview",
8606
+ "outputPreview",
8607
+ "message",
8608
+ "messages",
8609
+ "transcript",
8610
+ "context",
8611
+ "document",
8612
+ "documents",
8613
+ "chunk",
8614
+ "chunks",
8615
+ "retrieval",
8616
+ "query"
8617
+ ];
8618
+ function isRecord13(value) {
8619
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8620
+ }
8621
+ function toKey2(key) {
8622
+ return key.toLowerCase();
8623
+ }
8624
+ function stableHash2(value) {
8625
+ const hash = crypto__default.default.createHash("sha256").update(value, "utf8").digest("hex");
8626
+ return hash.slice(0, 8);
8627
+ }
8628
+ function stringifyScalar(value) {
8629
+ if (typeof value === "string") return value;
8630
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
8631
+ return String(value);
8632
+ }
8633
+ return void 0;
8634
+ }
8635
+ function patternDetector(options) {
8636
+ return {
8637
+ id: options.id,
8638
+ severity: options.severity ?? "warning",
8639
+ matchKind: "value",
8640
+ detect(input3) {
8641
+ if (typeof input3.value !== "string") return [];
8642
+ options.pattern.lastIndex = 0;
8643
+ return options.pattern.test(input3.value) ? [{ action: "replace", severity: options.severity ?? "warning", matchKind: "value" }] : [];
8644
+ }
8645
+ };
8646
+ }
8647
+ function digitsOnly(value) {
8648
+ return value.replace(/\D/g, "");
8649
+ }
8650
+ function passesLuhn(value) {
8651
+ const digits = digitsOnly(value);
8652
+ if (digits.length < 13 || digits.length > 19) return false;
8653
+ let sum = 0;
8654
+ let double = false;
8655
+ for (let i = digits.length - 1; i >= 0; i -= 1) {
8656
+ let digit = Number(digits[i]);
8657
+ if (double) {
8658
+ digit *= 2;
8659
+ if (digit > 9) digit -= 9;
8660
+ }
8661
+ sum += digit;
8662
+ double = !double;
8663
+ }
8664
+ return sum % 10 === 0;
8665
+ }
8666
+ var credentialDetectors = [
8667
+ patternDetector({
8668
+ id: "value.authorizationHeader",
8669
+ pattern: /^(?:basic|bearer|digest|apikey)\s+[a-z0-9._~+/=-]+$/i,
8670
+ severity: "error"
8671
+ }),
8672
+ patternDetector({
8673
+ id: "value.bearerToken",
8674
+ pattern: /\bbearer\s+[a-z0-9._~+/=-]{12,}\b/i,
8675
+ severity: "error"
8676
+ }),
8677
+ patternDetector({
8678
+ id: "value.cookie",
8679
+ pattern: /\b[a-z0-9_.-]+=[^;\s]+(?:;\s*[a-z0-9_.-]+=[^;\s]+)+/i,
8680
+ severity: "error"
8681
+ }),
8682
+ patternDetector({
8683
+ id: "value.jwt",
8684
+ pattern: /\beyJ[a-zA-Z0-9_-]{8,}\.[a-zA-Z0-9_-]{8,}\.[a-zA-Z0-9_-]{8,}\b/,
8685
+ severity: "error"
8686
+ }),
8687
+ patternDetector({
8688
+ id: "value.providerApiKey",
8689
+ pattern: /\b(?:sk-(?:proj-)?[a-zA-Z0-9_-]{16,}|sk-ant-[a-zA-Z0-9_-]{16,}|AIza[0-9A-Za-z_-]{20,})\b/,
8690
+ severity: "error"
8691
+ }),
8692
+ patternDetector({
8693
+ id: "value.githubToken",
8694
+ pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/,
8695
+ severity: "error"
8696
+ }),
8697
+ patternDetector({
8698
+ id: "value.awsAccessKey",
8699
+ pattern: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/,
8700
+ severity: "error"
8701
+ }),
8702
+ patternDetector({
8703
+ id: "value.privateKey",
8704
+ pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*-----END [A-Z ]*PRIVATE KEY-----/,
8705
+ severity: "error"
8706
+ }),
8707
+ {
8708
+ id: "value.creditCard",
8709
+ severity: "error",
8710
+ matchKind: "value",
8711
+ detect(input3) {
8712
+ if (typeof input3.value !== "string") return [];
8713
+ const candidatePattern = /(?:\d[ -]?){13,19}/g;
8714
+ for (const match of input3.value.matchAll(candidatePattern)) {
8715
+ const candidate = match[0] ?? "";
8716
+ if (passesLuhn(candidate)) {
8717
+ return [{ action: "replace", severity: "error", matchKind: "value" }];
8718
+ }
8719
+ }
8720
+ return [];
8721
+ }
8722
+ }
8723
+ ];
8724
+ var identifierDetectors = [
8725
+ patternDetector({
8726
+ id: "value.email",
8727
+ pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i
8728
+ }),
8729
+ patternDetector({
8730
+ id: "value.phone",
8731
+ pattern: /\b(?:\+\d{1,3}[\s.-]?)?(?:\(?\d{3}\)?[\s.-])\d{3}[\s.-]\d{4}\b/
8732
+ }),
8733
+ patternDetector({
8734
+ id: "value.ipv4",
8735
+ pattern: /\b(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)\b/
8736
+ }),
8737
+ patternDetector({
8738
+ id: "value.ipv6",
8739
+ pattern: /\b(?:[0-9a-f]{1,4}:){2,7}[0-9a-f]{1,4}\b/i
8740
+ })
8741
+ ];
8742
+ function builtInDetectorsForProfile(profile) {
8743
+ if (profile === "local") return credentialDetectors;
8744
+ return [...credentialDetectors, ...identifierDetectors];
8745
+ }
8746
+ function compileRules2(rules, extraKeys) {
8747
+ const out = /* @__PURE__ */ new Map();
8748
+ const set = (rule) => {
8749
+ const key = toKey2(rule.key);
8750
+ out.set(key, { ...rule, key });
8751
+ };
8752
+ for (const key of DEFAULT_REDACT_KEYS2) {
8753
+ set({ key, strategy: "full" });
8754
+ }
8755
+ for (const key of extraKeys ?? []) {
8756
+ if (typeof key === "string" && key.length > 0) {
8757
+ set({ key, strategy: "full" });
8758
+ }
8759
+ }
8760
+ for (const rule of rules ?? []) {
8761
+ if (typeof rule === "string") {
8762
+ set({ key: rule, strategy: "full" });
8763
+ continue;
8764
+ }
8765
+ if (rule.strategy === "full") set({ key: rule.key, strategy: "full" });
8766
+ if (rule.strategy === "hash") set({ key: rule.key, strategy: "hash" });
8767
+ if (rule.strategy === "prefix") {
8768
+ set({
8769
+ key: rule.key,
8770
+ strategy: "prefix",
8771
+ keep: typeof rule.keep === "number" ? rule.keep : 8
8772
+ });
8773
+ }
8774
+ }
8775
+ return [...out.values()];
8776
+ }
8777
+ function actionForRule(rule) {
8778
+ if (rule.strategy === "full") return "replace";
8779
+ return rule.strategy;
8780
+ }
8781
+ function applyRule(rule, value, replacement) {
8782
+ if (rule.strategy === "full") return replacement;
8783
+ const asString = stringifyScalar(value);
8784
+ if (rule.strategy === "prefix") {
8785
+ if (asString === void 0) return replacement;
8786
+ const keep = Math.max(0, Math.floor(rule.keep));
8787
+ return asString.length <= keep ? `${asString}\u2026` : `${asString.slice(0, keep)}\u2026`;
8788
+ }
8789
+ if (rule.strategy === "hash") {
8790
+ if (asString === void 0) return "[HASH:unknown]";
8791
+ return `[HASH:${stableHash2(asString)}]`;
8792
+ }
8793
+ return value;
8794
+ }
8795
+ function childPath(path15, key) {
8796
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
8797
+ return path15 ? `${path15}.${key}` : key;
8798
+ }
8799
+ return `${path15 || "$"}[${JSON.stringify(key)}]`;
8800
+ }
8801
+ function indexPath(path15, index) {
8802
+ return `${path15 || "$"}[${index}]`;
8803
+ }
8804
+ function makeFinding(path15, detector, action, matchKind, severity = "warning", preview) {
8805
+ return preview === void 0 ? { path: path15, detector, action, severity, matchKind } : { path: path15, detector, action, severity, matchKind, preview };
8806
+ }
8807
+ function createRedactionProfile(profile = "local") {
8808
+ switch (profile) {
8809
+ case "local":
8810
+ return { profile: "local", extraKeys: [] };
8811
+ case "share":
8812
+ return {
8813
+ profile: "share",
8814
+ extraKeys: SHARE_PROFILE_EXTRA_KEYS2,
8815
+ maxMetadataValueLengthCap: 500,
8816
+ maxPreviewLengthCap: 200
8817
+ };
8818
+ case "strict":
8819
+ return {
8820
+ profile: "strict",
8821
+ extraKeys: [...SHARE_PROFILE_EXTRA_KEYS2, ...STRICT_PROFILE_EXTRA_KEYS2],
8822
+ maxMetadataValueLengthCap: 200,
8823
+ maxPreviewLengthCap: 80
8824
+ };
8825
+ }
8826
+ }
8827
+ var Redactor2 = class {
8828
+ #rules;
8829
+ #detectors;
8830
+ #profile;
8831
+ #replacement;
8832
+ #maxDepth;
8833
+ #collectFindings;
8834
+ constructor(options) {
8835
+ const resolved = createRedactionProfile(options?.profile ?? "local");
8836
+ this.#profile = resolved.profile;
8837
+ this.#rules = compileRules2(options?.rules, [
8838
+ ...resolved.extraKeys,
8839
+ ...options?.extraKeys ?? []
8840
+ ]);
8841
+ this.#detectors = [
8842
+ ...builtInDetectorsForProfile(this.#profile),
8843
+ ...options?.detectors ?? []
8844
+ ];
8845
+ this.#replacement = options?.replacement ?? "[REDACTED]";
8846
+ this.#maxDepth = options?.maxDepth ?? 32;
8847
+ this.#collectFindings = options?.collectFindings ?? true;
8848
+ }
8849
+ redactValue(key, value) {
8850
+ return this.#redactValue(value, key, key, 0, {
8851
+ findings: [],
8852
+ seen: /* @__PURE__ */ new WeakMap()
8853
+ });
8854
+ }
8855
+ redactRecord(record) {
8856
+ return this.redact(record).value;
8857
+ }
8858
+ redact(value) {
8859
+ const state = {
8860
+ findings: [],
8861
+ seen: /* @__PURE__ */ new WeakMap()
8862
+ };
8863
+ const redacted = this.#redactValue(value, void 0, "$", 0, state);
8864
+ return {
8865
+ value: redacted,
8866
+ findings: state.findings,
8867
+ redacted: state.findings.some((finding) => finding.action !== "keep"),
8868
+ profile: this.#profile
8869
+ };
8870
+ }
8871
+ #recordFinding(state, finding) {
8872
+ if (this.#collectFindings) state.findings.push(finding);
8873
+ }
8874
+ #redactValue(value, key, path15, depth, state) {
8875
+ if (depth > this.#maxDepth) {
8876
+ this.#recordFinding(
8877
+ state,
8878
+ makeFinding(path15, "structure.maxDepth", "truncate", "value", "warning")
8879
+ );
8880
+ return "[Truncated]";
8881
+ }
8882
+ if (key !== void 0) {
8883
+ const rule = this.#rules.find((candidate) => candidate.key === toKey2(key));
8884
+ if (rule) {
8885
+ this.#recordFinding(
8886
+ state,
8887
+ makeFinding(path15, `key.${rule.key}`, actionForRule(rule), "key", "warning")
8888
+ );
8889
+ return applyRule(rule, value, this.#replacement);
8890
+ }
8891
+ }
8892
+ for (const detector of this.#detectors) {
8893
+ const detections = detector.detect({ path: path15, key, value });
8894
+ for (const detection of detections) {
8895
+ const action = detection.action ?? "replace";
8896
+ this.#recordFinding(
8897
+ state,
8898
+ makeFinding(
8899
+ path15,
8900
+ detector.id,
8901
+ action,
8902
+ detection.matchKind ?? detector.matchKind ?? "custom",
8903
+ detection.severity ?? detector.severity ?? "warning",
8904
+ detection.preview
8905
+ )
8906
+ );
8907
+ if (action !== "keep") {
8908
+ return detection.replacement ?? this.#replacement;
8909
+ }
8910
+ }
8911
+ }
8912
+ if (Array.isArray(value)) {
8913
+ if (state.seen.has(value)) return state.seen.get(value);
8914
+ const out = [];
8915
+ state.seen.set(value, out);
8916
+ value.forEach((item, index) => {
8917
+ out[index] = this.#redactValue(item, void 0, indexPath(path15, index), depth + 1, state);
8918
+ });
8919
+ return out;
8920
+ }
8921
+ if (isRecord13(value)) {
8922
+ if (state.seen.has(value)) return state.seen.get(value);
8923
+ const out = {};
8924
+ state.seen.set(value, out);
8925
+ for (const [entryKey, entryValue] of Object.entries(value)) {
8926
+ out[entryKey] = this.#redactValue(
8927
+ entryValue,
8928
+ entryKey,
8929
+ childPath(path15 === "$" ? "" : path15, entryKey),
8930
+ depth + 1,
8931
+ state
8932
+ );
8933
+ }
8934
+ return out;
8935
+ }
8936
+ return value;
8937
+ }
8938
+ };
8939
+ function createRedactor(options) {
8940
+ return new Redactor2(options);
8941
+ }
8942
+ function redact(value, options) {
8943
+ return createRedactor(options).redact(value);
8944
+ }
8568
8945
  async function readStdin(stdin) {
8569
8946
  stdin.setEncoding("utf8");
8570
8947
  let content = "";
@@ -8593,8 +8970,119 @@ async function inputFromTarget(target, options, stdin) {
8593
8970
  return { type: "file", path: runPath };
8594
8971
  }
8595
8972
 
8596
- // packages/cli/src/explain.ts
8973
+ // packages/cli/src/redact.ts
8597
8974
  function parseRedactionProfile3(value) {
8975
+ if (value === void 0 || value === "local" || value === "share" || value === "strict") {
8976
+ return value ?? "share";
8977
+ }
8978
+ throw new Error(`Unsupported --profile "${value}". Use local, share, or strict.`);
8979
+ }
8980
+ function stable(value) {
8981
+ if (Array.isArray(value)) return value.map(stable);
8982
+ if (value === null || typeof value !== "object") return value;
8983
+ const record = value;
8984
+ return Object.fromEntries(
8985
+ Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable(record[key])])
8986
+ );
8987
+ }
8988
+ function isMissingFileError3(error) {
8989
+ return error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT";
8990
+ }
8991
+ async function contentFromTarget(target, options, stdin) {
8992
+ if (target === "-") {
8993
+ return { content: await readStdin(stdin), source: "stdin" };
8994
+ }
8995
+ try {
8996
+ const stats2 = await promises.stat(target);
8997
+ if (stats2.isDirectory()) {
8998
+ throw new Error("redact requires a trace file, JSON file, stdin, or run id.");
8999
+ }
9000
+ return { content: await promises.readFile(target, "utf-8"), source: target };
9001
+ } catch (error) {
9002
+ if (!isMissingFileError3(error)) throw error;
9003
+ }
9004
+ const runPath = getTraceFilePath(target, resolveTraceDir({ dir: options.dir }));
9005
+ const stats = await promises.stat(runPath);
9006
+ if (stats.isDirectory()) {
9007
+ throw new Error("redact requires a trace file, JSON file, stdin, or run id.");
9008
+ }
9009
+ return { content: await promises.readFile(runPath, "utf-8"), source: runPath };
9010
+ }
9011
+ function redactJsonText(content, profile) {
9012
+ const parsed = JSON.parse(content);
9013
+ const result = redact(parsed, { profile });
9014
+ return {
9015
+ content: `${JSON.stringify(result.value, null, 2)}
9016
+ `,
9017
+ findings: result.findings
9018
+ };
9019
+ }
9020
+ function redactJsonlText(content, profile) {
9021
+ const lines = content.split(/\r?\n/);
9022
+ const out = [];
9023
+ const findings = [];
9024
+ for (let index = 0; index < lines.length; index += 1) {
9025
+ const line = lines[index] ?? "";
9026
+ if (line.trim() === "") continue;
9027
+ let parsed;
9028
+ try {
9029
+ parsed = JSON.parse(line);
9030
+ } catch {
9031
+ throw new Error(`Input is not valid JSON or JSONL at line ${index + 1}.`);
9032
+ }
9033
+ const result = redact(parsed, { profile });
9034
+ out.push(JSON.stringify(result.value));
9035
+ findings.push(
9036
+ ...result.findings.map((finding) => ({
9037
+ ...finding,
9038
+ path: `line:${index + 1}:${finding.path}`
9039
+ }))
9040
+ );
9041
+ }
9042
+ return {
9043
+ content: out.length === 0 ? "" : `${out.join("\n")}
9044
+ `,
9045
+ findings
9046
+ };
9047
+ }
9048
+ function redactDocument(content, profile) {
9049
+ try {
9050
+ return redactJsonText(content, profile);
9051
+ } catch {
9052
+ return redactJsonlText(content, profile);
9053
+ }
9054
+ }
9055
+ async function redactCommand(target, options = {}, stdin = process.stdin) {
9056
+ const profile = parseRedactionProfile3(options.profile);
9057
+ const source = await contentFromTarget(target, options, stdin);
9058
+ const redacted = redactDocument(source.content, profile);
9059
+ if (options.output !== void 0) {
9060
+ await promises.writeFile(options.output, redacted.content, "utf-8");
9061
+ }
9062
+ if (options.json) {
9063
+ console.log(
9064
+ JSON.stringify(
9065
+ stable({
9066
+ ok: true,
9067
+ profile,
9068
+ source: source.source,
9069
+ output: options.output,
9070
+ findings: redacted.findings,
9071
+ content: options.output === void 0 ? redacted.content : void 0
9072
+ }),
9073
+ null,
9074
+ 2
9075
+ )
9076
+ );
9077
+ return;
9078
+ }
9079
+ if (options.output === void 0) {
9080
+ process.stdout.write(redacted.content);
9081
+ }
9082
+ }
9083
+
9084
+ // packages/cli/src/explain.ts
9085
+ function parseRedactionProfile4(value) {
8598
9086
  const profile = (value ?? "local").trim().toLowerCase();
8599
9087
  if (profile === "local" || profile === "share" || profile === "strict") {
8600
9088
  return profile;
@@ -8665,7 +9153,7 @@ async function explainCommand(target, options = {}, stdin = process.stdin) {
8665
9153
  }
8666
9154
  let redactionProfile;
8667
9155
  try {
8668
- redactionProfile = parseRedactionProfile3(options.redactionProfile);
9156
+ redactionProfile = parseRedactionProfile4(options.redactionProfile);
8669
9157
  } catch (error) {
8670
9158
  process.exitCode = 1;
8671
9159
  console.error(error instanceof Error ? error.message : String(error));
@@ -8863,7 +9351,7 @@ async function openCommand(input3, options = {}, stdin = process.stdin) {
8863
9351
  }
8864
9352
  }
8865
9353
  }
8866
- function isRecord13(value) {
9354
+ function isRecord14(value) {
8867
9355
  return typeof value === "object" && value !== null && !Array.isArray(value);
8868
9356
  }
8869
9357
  function parseTarget(value) {
@@ -8872,7 +9360,7 @@ function parseTarget(value) {
8872
9360
  throw new Error('Unsupported migration target. Use "--to 1.0".');
8873
9361
  }
8874
9362
  function formatOf(value) {
8875
- if (!isRecord13(value)) return "unknown";
9363
+ if (!isRecord14(value)) return "unknown";
8876
9364
  if (value.schemaVersion === "0.1") return "0.1";
8877
9365
  if (value.schemaVersion === "0.2") return "0.2";
8878
9366
  if (value.schemaVersion === "1.0") return "1.0";
@@ -8885,14 +9373,14 @@ function uniqueSorted(values) {
8885
9373
  return [...new Set(values)].sort();
8886
9374
  }
8887
9375
  function isWithinDirectory(child, parent) {
8888
- const relative = path10__default.default.relative(parent, child);
8889
- return relative === "" || !relative.startsWith("..") && !path10__default.default.isAbsolute(relative);
9376
+ const relative = path13__default.default.relative(parent, child);
9377
+ return relative === "" || !relative.startsWith("..") && !path13__default.default.isAbsolute(relative);
8890
9378
  }
8891
9379
  async function resolveOutputPath(inputPath, output2, force) {
8892
9380
  if (output2 === void 0 || output2.trim() === "") return void 0;
8893
- const inputAbs = path10__default.default.resolve(inputPath);
8894
- const outputAbs = path10__default.default.resolve(output2.trim());
8895
- const inputDir = path10__default.default.dirname(inputAbs);
9381
+ const inputAbs = path13__default.default.resolve(inputPath);
9382
+ const outputAbs = path13__default.default.resolve(output2.trim());
9383
+ const inputDir = path13__default.default.dirname(inputAbs);
8896
9384
  if (!isWithinDirectory(outputAbs, inputDir)) {
8897
9385
  throw new Error("Refusing to write migrated output outside the input directory.");
8898
9386
  }
@@ -9013,7 +9501,7 @@ async function migrateCommand(input3, options = {}) {
9013
9501
  process.exitCode = 1;
9014
9502
  return;
9015
9503
  }
9016
- const inputPath = path10__default.default.resolve(input3.trim());
9504
+ const inputPath = path13__default.default.resolve(input3.trim());
9017
9505
  const dryRun = options.dryRun === true;
9018
9506
  if (!dryRun && (options.output === void 0 || options.output.trim() === "")) {
9019
9507
  console.error("migrate requires --dry-run or --output <path>.");
@@ -9032,7 +9520,7 @@ async function migrateCommand(input3, options = {}) {
9032
9520
  );
9033
9521
  const result = await buildMigration(inputPath, outputPath);
9034
9522
  if (!dryRun && outputPath !== void 0) {
9035
- await promises.mkdir(path10__default.default.dirname(outputPath), { recursive: true });
9523
+ await promises.mkdir(path13__default.default.dirname(outputPath), { recursive: true });
9036
9524
  await promises.writeFile(outputPath, result.content, "utf-8");
9037
9525
  }
9038
9526
  printSummary2(result, dryRun);
@@ -9310,7 +9798,7 @@ function stripPrefix(name, prefixes) {
9310
9798
  }
9311
9799
  return name;
9312
9800
  }
9313
- function eventEvidence(event, path12) {
9801
+ function eventEvidence(event, path15) {
9314
9802
  return {
9315
9803
  runId: event.runId,
9316
9804
  eventId: event.eventId,
@@ -9320,7 +9808,7 @@ function eventEvidence(event, path12) {
9320
9808
  kind: event.kind,
9321
9809
  name: event.name,
9322
9810
  status: event.status,
9323
- ...path12 ? { path: path12 } : {}
9811
+ ...path15 ? { path: path15 } : {}
9324
9812
  };
9325
9813
  }
9326
9814
  function runEvidence(run) {
@@ -9357,7 +9845,7 @@ function finishedEvents(context, kind) {
9357
9845
  (event) => (kind === void 0 || event.kind === kind) && event.status !== "running"
9358
9846
  );
9359
9847
  }
9360
- function isRecord14(value) {
9848
+ function isRecord15(value) {
9361
9849
  return typeof value === "object" && value !== null && !Array.isArray(value);
9362
9850
  }
9363
9851
  function eventMap(events) {
@@ -9383,9 +9871,9 @@ function eventEndMs(event) {
9383
9871
  function normalizedKey(value) {
9384
9872
  return value.toLowerCase().replace(/[^a-z0-9_]/g, "");
9385
9873
  }
9386
- function lastPathSegment(path12) {
9387
- const parts = path12.split(".");
9388
- return parts[parts.length - 1] ?? path12;
9874
+ function lastPathSegment(path15) {
9875
+ const parts = path15.split(".");
9876
+ return parts[parts.length - 1] ?? path15;
9389
9877
  }
9390
9878
  function valueType(value) {
9391
9879
  if (Array.isArray(value)) return "array";
@@ -9399,22 +9887,22 @@ function serializedByteLength(value) {
9399
9887
  return void 0;
9400
9888
  }
9401
9889
  }
9402
- function pushValueEntries(entries, event, value, path12, key, depth = 0) {
9403
- entries.push({ event, path: path12, key, value });
9890
+ function pushValueEntries(entries, event, value, path15, key, depth = 0) {
9891
+ entries.push({ event, path: path15, key, value });
9404
9892
  if (depth >= 8) return;
9405
9893
  if (Array.isArray(value)) {
9406
9894
  for (const [index, item] of value.entries()) {
9407
- pushValueEntries(entries, event, item, `${path12}.${index}`, String(index), depth + 1);
9895
+ pushValueEntries(entries, event, item, `${path15}.${index}`, String(index), depth + 1);
9408
9896
  }
9409
9897
  return;
9410
9898
  }
9411
- if (!isRecord14(value)) return;
9899
+ if (!isRecord15(value)) return;
9412
9900
  for (const nestedKey of Object.keys(value).sort((a, b) => a.localeCompare(b))) {
9413
9901
  pushValueEntries(
9414
9902
  entries,
9415
9903
  event,
9416
9904
  value[nestedKey],
9417
- `${path12}.${nestedKey}`,
9905
+ `${path15}.${nestedKey}`,
9418
9906
  nestedKey,
9419
9907
  depth + 1
9420
9908
  );
@@ -9495,9 +9983,9 @@ function eventDurationMs(event) {
9495
9983
  }
9496
9984
  function treeShape(nodes) {
9497
9985
  const lines = [];
9498
- const visit = (node, path12) => {
9499
- lines.push(`${path12}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
9500
- node.children.forEach((child, index) => visit(child, `${path12}.${index}`));
9986
+ const visit = (node, path15) => {
9987
+ lines.push(`${path15}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
9988
+ node.children.forEach((child, index) => visit(child, `${path15}.${index}`));
9501
9989
  };
9502
9990
  nodes.forEach((node, index) => visit(node, String(index)));
9503
9991
  return lines;
@@ -9546,9 +10034,9 @@ function retrievalShape(context) {
9546
10034
  function guardrailShape(context) {
9547
10035
  return guardrailEvents(context).map((event) => signalName(event, ["guardrailName", "guardrail", "guardrailId"], ["guardrail:"])).sort((a, b) => a.localeCompare(b));
9548
10036
  }
9549
- function firstEvidenceForKind(context, kind, path12) {
10037
+ function firstEvidenceForKind(context, kind, path15) {
9550
10038
  const event = context.events.find((candidate) => candidate.kind === kind);
9551
- return event ? [eventEvidence(event, path12)] : runEvidence(context.selectedRun);
10039
+ return event ? [eventEvidence(event, path15)] : runEvidence(context.selectedRun);
9552
10040
  }
9553
10041
  function baselineDiffFinding(message, evidence, expected, actual) {
9554
10042
  return failFinding("baseline.regression", message, evidence, expected, actual);
@@ -9796,13 +10284,13 @@ function createStructureCycleRule() {
9796
10284
  const seenCycles = /* @__PURE__ */ new Set();
9797
10285
  const findings = [];
9798
10286
  for (const event of [...context.events].sort((a, b) => a.eventId.localeCompare(b.eventId))) {
9799
- const path12 = [];
10287
+ const path15 = [];
9800
10288
  const seenAt = /* @__PURE__ */ new Map();
9801
10289
  let current = event;
9802
10290
  while (current) {
9803
10291
  const existing = seenAt.get(current.eventId);
9804
10292
  if (existing !== void 0) {
9805
- const cycle = path12.slice(existing);
10293
+ const cycle = path15.slice(existing);
9806
10294
  const key = cycle.map((item) => item.eventId).sort().join("\0");
9807
10295
  if (!seenCycles.has(key)) {
9808
10296
  seenCycles.add(key);
@@ -9818,8 +10306,8 @@ function createStructureCycleRule() {
9818
10306
  }
9819
10307
  break;
9820
10308
  }
9821
- seenAt.set(current.eventId, path12.length);
9822
- path12.push(current);
10309
+ seenAt.set(current.eventId, path15.length);
10310
+ path15.push(current);
9823
10311
  current = current.parentId ? byId.get(current.parentId) : void 0;
9824
10312
  }
9825
10313
  }
@@ -10093,7 +10581,7 @@ function createSafetyOversizedAttributeRule(options) {
10093
10581
  )
10094
10582
  );
10095
10583
  }
10096
- if (isRecord14(entry.value) && options.maxObjectKeys !== void 0 && Object.keys(entry.value).length > options.maxObjectKeys) {
10584
+ if (isRecord15(entry.value) && options.maxObjectKeys !== void 0 && Object.keys(entry.value).length > options.maxObjectKeys) {
10097
10585
  findings.push(
10098
10586
  failFinding(
10099
10587
  "safety.oversizedAttribute",
@@ -10344,7 +10832,7 @@ function asConfig(value) {
10344
10832
  }
10345
10833
  async function loadConfig(configPath) {
10346
10834
  if (configPath === void 0) return {};
10347
- const extension = path10__default.default.extname(configPath);
10835
+ const extension = path13__default.default.extname(configPath);
10348
10836
  if (TS_CONFIG_EXTENSIONS.has(extension)) {
10349
10837
  throw new Error(
10350
10838
  "TypeScript check configs require an explicit precompiled JavaScript config or future --config-loader support."
@@ -10353,7 +10841,7 @@ async function loadConfig(configPath) {
10353
10841
  if (!CONFIG_EXTENSIONS.has(extension)) {
10354
10842
  throw new Error("Unsupported check config extension. Use .json, .js, .mjs, or .cjs.");
10355
10843
  }
10356
- const absolute = path10__default.default.resolve(configPath);
10844
+ const absolute = path13__default.default.resolve(configPath);
10357
10845
  if (extension === ".json") {
10358
10846
  const raw = await promises.readFile(absolute, "utf-8");
10359
10847
  return asConfig(JSON.parse(raw));
@@ -10370,12 +10858,12 @@ function normalizeConfig(config) {
10370
10858
  }
10371
10859
  function buildRules(config, options) {
10372
10860
  const diagnostics = [];
10373
- const checks = normalizeConfig(config);
10374
- const run = checks.run ?? {};
10375
- const tool = checks.tool ?? {};
10376
- const llm = checks.llm ?? {};
10377
- const structure = checks.structure ?? {};
10378
- const safety = checks.safety ?? {};
10861
+ const checks2 = normalizeConfig(config);
10862
+ const run = checks2.run ?? {};
10863
+ const tool = checks2.tool ?? {};
10864
+ const llm = checks2.llm ?? {};
10865
+ const structure = checks2.structure ?? {};
10866
+ const safety = checks2.safety ?? {};
10379
10867
  const maxDurationMs = parseNumber(options.maxDurationMs, "--max-duration-ms") ?? run.maxDurationMs;
10380
10868
  const maxTotalTokens = parseNumber(options.maxTotalTokens, "--max-total-tokens") ?? llm.maxTotalTokens;
10381
10869
  const rules = [
@@ -10423,7 +10911,7 @@ function buildRules(config, options) {
10423
10911
  rules.push(createSafetyOversizedAttributeRule(safety));
10424
10912
  }
10425
10913
  const select = [
10426
- ...asStringArray(checks.select) ?? [],
10914
+ ...asStringArray(checks2.select) ?? [],
10427
10915
  ...options.rule ?? []
10428
10916
  ];
10429
10917
  return {
@@ -10453,16 +10941,16 @@ function exitCodeFor(result) {
10453
10941
  }
10454
10942
  return 1;
10455
10943
  }
10456
- function stable(value) {
10457
- if (Array.isArray(value)) return value.map(stable);
10944
+ function stable2(value) {
10945
+ if (Array.isArray(value)) return value.map(stable2);
10458
10946
  if (value === null || typeof value !== "object") return value;
10459
10947
  const record = value;
10460
10948
  return Object.fromEntries(
10461
- Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable(record[key])])
10949
+ Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable2(record[key])])
10462
10950
  );
10463
10951
  }
10464
10952
  function printJson(result) {
10465
- console.log(JSON.stringify(stable(result), null, 2));
10953
+ console.log(JSON.stringify(stable2(result), null, 2));
10466
10954
  }
10467
10955
  function printHuman(result) {
10468
10956
  console.log(`Check status: ${result.status}`);
@@ -10471,12 +10959,12 @@ function printHuman(result) {
10471
10959
  console.log(
10472
10960
  `Summary: ${result.summary.failed} failed, ${result.summary.warnings} warning(s), ${result.summary.errors} error(s)`
10473
10961
  );
10474
- for (const diagnostic3 of result.diagnostics) {
10475
- console.log(`- ${diagnostic3.code}: ${diagnostic3.message}`);
10962
+ for (const diagnostic4 of result.diagnostics) {
10963
+ console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
10476
10964
  }
10477
10965
  for (const finding of result.findings) {
10478
- const path12 = finding.evidence[0]?.path;
10479
- console.log(`- ${finding.ruleId}: ${finding.message}${path12 ? ` (${path12})` : ""}`);
10966
+ const path15 = finding.evidence[0]?.path;
10967
+ console.log(`- ${finding.ruleId}: ${finding.message}${path15 ? ` (${path15})` : ""}`);
10480
10968
  }
10481
10969
  }
10482
10970
  function readErrorResult(error) {
@@ -10530,42 +11018,996 @@ async function checkCommand(target, options = {}, stdin = process.stdin) {
10530
11018
  else printHuman(result);
10531
11019
  }
10532
11020
 
10533
- // packages/cli/src/safety.ts
10534
- var BEST_EFFORT_NOTE = "Best-effort local safety verification only; not a compliance, privacy, security, or regulatory certification.";
10535
- var DEFAULT_MAX_STRING_LENGTH = 16384;
10536
- var DEFAULT_MAX_ARRAY_LENGTH = 1e3;
10537
- var DEFAULT_MAX_OBJECT_KEYS = 200;
10538
- var DEFAULT_MAX_SERIALIZED_BYTES = 128 * 1024;
10539
- function parseLimit3(value, label) {
10540
- if (value === void 0) return void 0;
10541
- const parsed = Number(value);
10542
- if (!Number.isFinite(parsed) || parsed < 0) {
10543
- throw new Error(`${label} must be a non-negative number.`);
11021
+ // packages/eval/src/index.ts
11022
+ function isTraceReadResult(value) {
11023
+ return value !== null && typeof value === "object" && "runs" in value && "events" in value && "format" in value;
11024
+ }
11025
+ function traceInputFrom(value) {
11026
+ return { type: "file", path: value instanceof URL ? value.pathname : value };
11027
+ }
11028
+ async function resolveRead(input3, options) {
11029
+ try {
11030
+ if (typeof input3 === "string") {
11031
+ const read2 = await openTrace(traceInputFrom(input3), {
11032
+ ...options.format !== void 0 && options.format !== "auto" ? { format: options.format } : {}
11033
+ });
11034
+ return { read: read2, runId: options.runId, diagnostics: [] };
11035
+ }
11036
+ if (isTraceReadResult(input3)) {
11037
+ return { read: input3, runId: options.runId, diagnostics: [] };
11038
+ }
11039
+ if (isTraceReadResult(input3.trace)) {
11040
+ return { read: input3.trace, runId: options.runId ?? input3.runId, diagnostics: [] };
11041
+ }
11042
+ const format = options.format ?? input3.format;
11043
+ const read = await openTrace(traceInputFrom(input3.trace), {
11044
+ ...format !== void 0 && format !== "auto" ? { format } : {}
11045
+ });
11046
+ return { read, runId: options.runId ?? input3.runId, diagnostics: [] };
11047
+ } catch (error) {
11048
+ return { diagnostics: [diagnosticFromError(error)] };
10544
11049
  }
10545
- return parsed;
10546
11050
  }
10547
- function stable2(value) {
10548
- if (Array.isArray(value)) return value.map(stable2);
10549
- if (value === null || typeof value !== "object") return value;
10550
- const record = value;
10551
- return Object.fromEntries(
10552
- Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable2(record[key])])
10553
- );
11051
+ function diagnosticFromError(error) {
11052
+ if (error instanceof TraceReadError) {
11053
+ const code = error.code === "unsupported_format" ? "AI_EVAL_UNSUPPORTED_FORMAT" : error.code === "ambiguous_format" ? "AI_EVAL_AMBIGUOUS_FORMAT" : "AI_EVAL_TRACE_UNREADABLE";
11054
+ return { code, severity: "error", message: error.message };
11055
+ }
11056
+ return {
11057
+ code: "AI_EVAL_TRACE_UNREADABLE",
11058
+ severity: "error",
11059
+ message: error instanceof Error ? error.message : String(error)
11060
+ };
10554
11061
  }
10555
- function safetyDiagnostic(code, message, severity = "error") {
10556
- return { code, message, severity };
11062
+ function flatten2(nodes) {
11063
+ return nodes.flatMap((node) => [node, ...flatten2(node.children)]);
10557
11064
  }
10558
- function warningDiagnostics(warnings, unsupportedFields) {
10559
- return [
10560
- ...warnings.map(
10561
- (warning) => safetyDiagnostic(
10562
- warning.code,
10563
- warning.message,
10564
- warning.severity === "error" ? "error" : "warning"
10565
- )
10566
- ),
10567
- ...unsupportedFields.map(
10568
- (field) => safetyDiagnostic(
11065
+ function selectRun3(read, runId) {
11066
+ if (runId !== void 0) {
11067
+ const run = read.runs.find((candidate) => candidate.runId === runId);
11068
+ return run === void 0 ? {
11069
+ diagnostics: [
11070
+ {
11071
+ code: "AI_EVAL_RUN_SELECTION_REQUIRED",
11072
+ severity: "error",
11073
+ message: `Run not found: ${runId}.`
11074
+ }
11075
+ ]
11076
+ } : { run, diagnostics: [] };
11077
+ }
11078
+ if (read.runs.length === 1) {
11079
+ return { run: read.runs[0], diagnostics: [] };
11080
+ }
11081
+ if (read.runs.length === 0) {
11082
+ return {
11083
+ diagnostics: [
11084
+ {
11085
+ code: "AI_EVAL_RUN_SELECTION_REQUIRED",
11086
+ severity: "error",
11087
+ message: "No runs are available for eval."
11088
+ }
11089
+ ]
11090
+ };
11091
+ }
11092
+ return {
11093
+ diagnostics: [
11094
+ {
11095
+ code: "AI_EVAL_RUN_SELECTION_REQUIRED",
11096
+ severity: "error",
11097
+ message: "Multiple runs are available; select a run before eval."
11098
+ }
11099
+ ]
11100
+ };
11101
+ }
11102
+ function errorResult3(format, diagnostics, runId) {
11103
+ const errors = diagnostics.filter((item) => item.severity === "error").length;
11104
+ return {
11105
+ ok: false,
11106
+ status: "error",
11107
+ format,
11108
+ ...runId !== void 0 ? { runId } : {},
11109
+ summary: { passed: 0, failed: 0, warnings: 0, errors },
11110
+ findings: [],
11111
+ diagnostics: [...diagnostics]
11112
+ };
11113
+ }
11114
+ function normalizeFinding2(rule, finding) {
11115
+ return {
11116
+ ...finding,
11117
+ ruleId: finding.ruleId || rule.id,
11118
+ severity: finding.severity ?? rule.severity ?? "error",
11119
+ evidence: [...finding.evidence]
11120
+ };
11121
+ }
11122
+ function compareFindings2(a, b) {
11123
+ const aEvidence = a.evidence[0];
11124
+ const bEvidence = b.evidence[0];
11125
+ return a.ruleId.localeCompare(b.ruleId) || (aEvidence?.runId ?? "").localeCompare(bEvidence?.runId ?? "") || (aEvidence?.eventId ?? "").localeCompare(bEvidence?.eventId ?? "") || (aEvidence?.path ?? "").localeCompare(bEvidence?.path ?? "") || a.message.localeCompare(b.message);
11126
+ }
11127
+ function summarize2(findings, ruleCount) {
11128
+ const failed = findings.filter((item) => item.status === "fail").length;
11129
+ const warnings = findings.filter((item) => item.status === "warning").length;
11130
+ const errors = findings.filter((item) => item.severity === "error").length;
11131
+ return {
11132
+ passed: Math.max(0, ruleCount - failed - warnings),
11133
+ failed,
11134
+ warnings,
11135
+ errors
11136
+ };
11137
+ }
11138
+ function defaultRules() {
11139
+ return [checks.requireSuccess()];
11140
+ }
11141
+ async function evalRun(input3, options = {}) {
11142
+ const resolved = await resolveRead(input3, options);
11143
+ if (resolved.read === void 0) {
11144
+ return errorResult3("unknown", resolved.diagnostics);
11145
+ }
11146
+ const selected = selectRun3(resolved.read, resolved.runId);
11147
+ if (selected.run === void 0) {
11148
+ return errorResult3(resolved.read.format, selected.diagnostics, resolved.runId);
11149
+ }
11150
+ const rules = [...options.checks ?? defaultRules()].sort(
11151
+ (a, b) => a.id.localeCompare(b.id)
11152
+ );
11153
+ const context = {
11154
+ format: resolved.read.format,
11155
+ run: selected.run,
11156
+ nodes: flatten2(selected.run.children),
11157
+ events: resolved.read.events.filter((event) => event.runId === selected.run?.runId)
11158
+ };
11159
+ const diagnostics = [];
11160
+ const findings = [];
11161
+ for (const rule of rules) {
11162
+ try {
11163
+ findings.push(...rule.evaluate(context).map((finding) => normalizeFinding2(rule, finding)));
11164
+ } catch (error) {
11165
+ diagnostics.push({
11166
+ code: "AI_EVAL_RULE_FAILED",
11167
+ severity: "error",
11168
+ ruleId: rule.id,
11169
+ message: `Eval rule ${rule.id} failed: ${error instanceof Error ? error.message : String(error)}`
11170
+ });
11171
+ }
11172
+ }
11173
+ if (diagnostics.length > 0) {
11174
+ return errorResult3(resolved.read.format, diagnostics, selected.run.runId);
11175
+ }
11176
+ const sortedFindings = findings.sort(compareFindings2);
11177
+ const summary = summarize2(sortedFindings, rules.length);
11178
+ const status = summary.failed > 0 ? "fail" : "pass";
11179
+ return {
11180
+ ok: status === "pass",
11181
+ status,
11182
+ format: resolved.read.format,
11183
+ runId: selected.run.runId,
11184
+ summary,
11185
+ findings: sortedFindings,
11186
+ diagnostics: []
11187
+ };
11188
+ }
11189
+ function evidenceForRun(run, path15) {
11190
+ return [{ runId: run.runId, ...path15 !== void 0 ? { path: path15 } : {} }];
11191
+ }
11192
+ function evidenceForEvent(event, path15) {
11193
+ return [
11194
+ {
11195
+ runId: event.runId,
11196
+ eventId: event.eventId,
11197
+ ...event.parentId !== void 0 ? { parentId: event.parentId } : {},
11198
+ kind: event.kind,
11199
+ name: event.name,
11200
+ ...path15 !== void 0 ? { path: path15 } : {}
11201
+ }
11202
+ ];
11203
+ }
11204
+ function fail(ruleId, message, evidence, expected, actual) {
11205
+ return {
11206
+ ruleId,
11207
+ status: "fail",
11208
+ severity: "error",
11209
+ message,
11210
+ ...expected !== void 0 ? { expected } : {},
11211
+ ...actual !== void 0 ? { actual } : {},
11212
+ evidence
11213
+ };
11214
+ }
11215
+ function nodeNames(nodes, kind) {
11216
+ return nodes.filter((node) => node.event.kind === kind).map((node) => node.event.name).sort((a, b) => a.localeCompare(b));
11217
+ }
11218
+ function hasAttribute(node, key) {
11219
+ return node.event.attributes !== void 0 && Object.prototype.hasOwnProperty.call(node.event.attributes, key);
11220
+ }
11221
+ function numericAttribute(node, keys) {
11222
+ const attrs = node.event.attributes;
11223
+ if (attrs === void 0) return void 0;
11224
+ for (const key of keys) {
11225
+ const value = attrs[key];
11226
+ if (typeof value === "number" && Number.isFinite(value)) return value;
11227
+ }
11228
+ return void 0;
11229
+ }
11230
+ function totalTokenCount(events) {
11231
+ return events.reduce((total, event) => {
11232
+ const usage = event.tokenUsage;
11233
+ if (usage?.total !== void 0) return total + usage.total;
11234
+ return total + (usage?.input ?? 0) + (usage?.output ?? 0);
11235
+ }, 0);
11236
+ }
11237
+ function createRule(id, category, evaluate) {
11238
+ return { id, category, severity: "error", evaluate };
11239
+ }
11240
+ var DEFAULT_ANSWER_KEYS = [
11241
+ "answer",
11242
+ "finalAnswer",
11243
+ "final",
11244
+ "response",
11245
+ "result",
11246
+ "output",
11247
+ "outputPreview",
11248
+ "completion",
11249
+ "text"
11250
+ ];
11251
+ var DEFAULT_CONTEXT_KEYS = [
11252
+ "context",
11253
+ "contexts",
11254
+ "document",
11255
+ "documents",
11256
+ "retrieved",
11257
+ "retrieval",
11258
+ "chunks",
11259
+ "chunk",
11260
+ "source",
11261
+ "sources",
11262
+ "sourceText"
11263
+ ];
11264
+ var DEFAULT_CITATION_KEYS = [
11265
+ "citation",
11266
+ "citations",
11267
+ "reference",
11268
+ "references",
11269
+ "sourceId",
11270
+ "sourceIds",
11271
+ "source_id",
11272
+ "source_ids"
11273
+ ];
11274
+ var DEFAULT_SOURCE_ID_KEYS = [
11275
+ ...DEFAULT_CITATION_KEYS,
11276
+ "id",
11277
+ "ids",
11278
+ "documentId",
11279
+ "documentIds",
11280
+ "docId",
11281
+ "docIds"
11282
+ ];
11283
+ var DEFAULT_BANNED_UNSUPPORTED_PHRASES = [
11284
+ "i don't have enough information",
11285
+ "i do not have enough information",
11286
+ "not enough context",
11287
+ "cannot determine from the context",
11288
+ "unable to determine from the provided context"
11289
+ ];
11290
+ var STOP_WORDS = /* @__PURE__ */ new Set([
11291
+ "a",
11292
+ "an",
11293
+ "and",
11294
+ "are",
11295
+ "but",
11296
+ "for",
11297
+ "from",
11298
+ "have",
11299
+ "into",
11300
+ "not",
11301
+ "that",
11302
+ "the",
11303
+ "their",
11304
+ "this",
11305
+ "was",
11306
+ "were",
11307
+ "with",
11308
+ "you",
11309
+ "your"
11310
+ ]);
11311
+ function normalizeKey(key) {
11312
+ return key.toLowerCase().replace(/[^a-z0-9]/g, "");
11313
+ }
11314
+ function keySet(keys) {
11315
+ return new Set(keys.map(normalizeKey));
11316
+ }
11317
+ function valuesAsStrings(value, depth = 0) {
11318
+ if (depth > 5) return [];
11319
+ if (typeof value === "string") {
11320
+ const trimmed = value.trim();
11321
+ return trimmed.length > 0 ? [trimmed] : [];
11322
+ }
11323
+ if (typeof value === "number" || typeof value === "boolean") return [String(value)];
11324
+ if (Array.isArray(value)) {
11325
+ return value.flatMap((item) => valuesAsStrings(item, depth + 1));
11326
+ }
11327
+ if (value !== null && typeof value === "object") {
11328
+ return Object.values(value).flatMap(
11329
+ (item) => valuesAsStrings(item, depth + 1)
11330
+ );
11331
+ }
11332
+ return [];
11333
+ }
11334
+ function collectTextFields(nodes, keys, preferredKinds = []) {
11335
+ const wanted = keySet(keys);
11336
+ const preferred = new Set(preferredKinds);
11337
+ const orderedNodes = [...nodes].sort((a, b) => {
11338
+ const aPreferred = preferred.has(a.event.kind) ? 0 : 1;
11339
+ const bPreferred = preferred.has(b.event.kind) ? 0 : 1;
11340
+ return aPreferred - bPreferred || a.event.eventId.localeCompare(b.event.eventId);
11341
+ });
11342
+ const fields = [];
11343
+ for (const node of orderedNodes) {
11344
+ const attrs = node.event.attributes;
11345
+ if (attrs === void 0) continue;
11346
+ for (const [key, value] of Object.entries(attrs)) {
11347
+ if (!wanted.has(normalizeKey(key))) continue;
11348
+ for (const text of valuesAsStrings(value)) {
11349
+ fields.push({ text, node, path: `attributes.${key}` });
11350
+ }
11351
+ }
11352
+ }
11353
+ return fields;
11354
+ }
11355
+ function tokenize(text) {
11356
+ return [...text.toLowerCase().matchAll(/[a-z0-9][a-z0-9'-]{2,}/g)].map((match) => match[0].replace(/^['-]+|['-]+$/g, "")).filter((token) => token.length > 2 && !STOP_WORDS.has(token));
11357
+ }
11358
+ function firstEvidence(fields, run, path15) {
11359
+ const first = fields[0];
11360
+ return first === void 0 ? evidenceForRun(run, path15) : evidenceForEvent(first.node.event, first.path);
11361
+ }
11362
+ function collectSourceIds(nodes, keys) {
11363
+ const wanted = keySet(keys);
11364
+ const ids = /* @__PURE__ */ new Set();
11365
+ for (const node of nodes) {
11366
+ const attrs = node.event.attributes;
11367
+ if (attrs === void 0) continue;
11368
+ for (const [key, value] of Object.entries(attrs)) {
11369
+ if (!wanted.has(normalizeKey(key))) continue;
11370
+ for (const candidate of valuesAsStrings(value)) {
11371
+ const trimmed = candidate.trim();
11372
+ if (trimmed.length > 0 && trimmed.length <= 128) ids.add(trimmed);
11373
+ }
11374
+ }
11375
+ }
11376
+ return [...ids].sort((a, b) => a.localeCompare(b));
11377
+ }
11378
+ function citationCount(answer, citationFields) {
11379
+ const inline = answer.match(/\[[^\]\n]{1,40}\]|\([A-Za-z][A-Za-z0-9_-]{0,39}\)/g)?.length ?? 0;
11380
+ return inline + citationFields.length;
11381
+ }
11382
+ function quotedSnippets(text, minLength) {
11383
+ const snippets = /* @__PURE__ */ new Set();
11384
+ const pattern = /"([^"\n]+)"|'([^'\n]+)'|“([^”\n]+)”/g;
11385
+ for (const match of text.matchAll(pattern)) {
11386
+ const snippet = (match[1] ?? match[2] ?? match[3] ?? "").trim();
11387
+ if (snippet.length >= minLength) snippets.add(snippet);
11388
+ }
11389
+ return [...snippets].sort((a, b) => a.localeCompare(b));
11390
+ }
11391
+ function wordCount(text) {
11392
+ return tokenize(text).length;
11393
+ }
11394
+ var checks = {
11395
+ requireSuccess() {
11396
+ return createRule(
11397
+ "eval.requireSuccess",
11398
+ "run",
11399
+ (context) => context.run.status === "ok" ? [] : [
11400
+ fail(
11401
+ "eval.requireSuccess",
11402
+ "Run did not complete successfully.",
11403
+ evidenceForRun(context.run, "status"),
11404
+ "ok",
11405
+ context.run.status ?? "unknown"
11406
+ )
11407
+ ]
11408
+ );
11409
+ },
11410
+ requiredTools(required) {
11411
+ const expected = [...required].sort((a, b) => a.localeCompare(b));
11412
+ return createRule("eval.requiredTools", "tool", (context) => {
11413
+ const tools = new Set(nodeNames(context.nodes, "TOOL"));
11414
+ return expected.filter((name) => !tools.has(name)).map(
11415
+ (name) => fail(
11416
+ "eval.requiredTools",
11417
+ `Required tool ${name} did not appear.`,
11418
+ evidenceForRun(context.run, "children"),
11419
+ name,
11420
+ [...tools].sort((a, b) => a.localeCompare(b))
11421
+ )
11422
+ );
11423
+ });
11424
+ },
11425
+ forbiddenTools(forbidden) {
11426
+ const blocked = [...forbidden].sort((a, b) => a.localeCompare(b));
11427
+ return createRule(
11428
+ "eval.forbiddenTools",
11429
+ "tool",
11430
+ (context) => context.nodes.filter((node) => node.event.kind === "TOOL" && blocked.includes(node.event.name)).map(
11431
+ (node) => fail(
11432
+ "eval.forbiddenTools",
11433
+ `Forbidden tool ${node.event.name} appeared.`,
11434
+ evidenceForEvent(node.event, "name"),
11435
+ "tool absent",
11436
+ node.event.name
11437
+ )
11438
+ )
11439
+ );
11440
+ },
11441
+ maxDurationMs(maxDurationMs) {
11442
+ return createRule(
11443
+ "eval.maxDurationMs",
11444
+ "run",
11445
+ (context) => context.run.durationMs !== void 0 && context.run.durationMs > maxDurationMs ? [
11446
+ fail(
11447
+ "eval.maxDurationMs",
11448
+ `Run duration exceeded ${maxDurationMs}ms.`,
11449
+ evidenceForRun(context.run, "durationMs"),
11450
+ { maxDurationMs },
11451
+ context.run.durationMs
11452
+ )
11453
+ ] : []
11454
+ );
11455
+ },
11456
+ maxDepth(maxDepth) {
11457
+ return createRule("eval.maxDepth", "structure", (context) => {
11458
+ const deepest = context.nodes.reduce(
11459
+ (current, node) => current === void 0 || node.depth > current.depth ? node : current,
11460
+ void 0
11461
+ );
11462
+ return deepest !== void 0 && deepest.depth > maxDepth ? [
11463
+ fail(
11464
+ "eval.maxDepth",
11465
+ `Run tree depth exceeded ${maxDepth}.`,
11466
+ evidenceForEvent(deepest.event, "depth"),
11467
+ { maxDepth },
11468
+ deepest.depth
11469
+ )
11470
+ ] : [];
11471
+ });
11472
+ },
11473
+ maxRetries(maxRetries) {
11474
+ return createRule(
11475
+ "eval.maxRetries",
11476
+ "structure",
11477
+ (context) => context.nodes.flatMap((node) => {
11478
+ const retries = numericAttribute(node, ["retryCount", "retries", "attempt"]);
11479
+ return retries !== void 0 && retries > maxRetries ? [
11480
+ fail(
11481
+ "eval.maxRetries",
11482
+ `Retry count exceeded ${maxRetries}.`,
11483
+ evidenceForEvent(node.event, "attributes.retryCount"),
11484
+ { maxRetries },
11485
+ retries
11486
+ )
11487
+ ] : [];
11488
+ })
11489
+ );
11490
+ },
11491
+ maxTotalTokens(maxTotalTokens) {
11492
+ return createRule("eval.maxTotalTokens", "llm", (context) => {
11493
+ const total = totalTokenCount(context.events);
11494
+ return total > maxTotalTokens ? [
11495
+ fail(
11496
+ "eval.maxTotalTokens",
11497
+ `Total token usage exceeded ${maxTotalTokens}.`,
11498
+ evidenceForRun(context.run, "tokenUsage.total"),
11499
+ { maxTotalTokens },
11500
+ total
11501
+ )
11502
+ ] : [];
11503
+ });
11504
+ },
11505
+ noFailedSteps() {
11506
+ return createRule(
11507
+ "eval.noFailedSteps",
11508
+ "run",
11509
+ (context) => context.nodes.filter((node) => node.event.status === "error" || node.event.kind === "ERROR").map(
11510
+ (node) => fail(
11511
+ "eval.noFailedSteps",
11512
+ "Run contains a failed step or error node.",
11513
+ evidenceForEvent(node.event, "status"),
11514
+ "no failed nodes",
11515
+ node.event.status ?? node.event.kind
11516
+ )
11517
+ )
11518
+ );
11519
+ },
11520
+ requiredRetrievalBeforeGeneration() {
11521
+ return createRule("eval.requiredRetrievalBeforeGeneration", "retrieval", (context) => {
11522
+ const firstLlmIndex = context.nodes.findIndex((node) => node.event.kind === "LLM");
11523
+ if (firstLlmIndex === -1) return [];
11524
+ const retrievalIndex = context.nodes.findIndex(
11525
+ (node, index) => index < firstLlmIndex && node.event.kind === "RETRIEVER"
11526
+ );
11527
+ return retrievalIndex === -1 ? [
11528
+ fail(
11529
+ "eval.requiredRetrievalBeforeGeneration",
11530
+ "No retrieval step appeared before the first LLM generation.",
11531
+ evidenceForEvent(context.nodes[firstLlmIndex].event, "kind"),
11532
+ "RETRIEVER before LLM",
11533
+ "LLM before RETRIEVER"
11534
+ )
11535
+ ] : [];
11536
+ });
11537
+ },
11538
+ requiredDecisionMetadata(keys) {
11539
+ const required = [...keys].sort((a, b) => a.localeCompare(b));
11540
+ return createRule("eval.requiredDecisionMetadata", "structure", (context) => {
11541
+ const decisions = context.nodes.filter((node) => node.event.kind === "DECISION");
11542
+ if (decisions.length === 0) {
11543
+ return [
11544
+ fail(
11545
+ "eval.requiredDecisionMetadata",
11546
+ "No decision node is available for required metadata.",
11547
+ evidenceForRun(context.run, "children"),
11548
+ { decisionMetadata: required },
11549
+ "no decision nodes"
11550
+ )
11551
+ ];
11552
+ }
11553
+ return decisions.flatMap(
11554
+ (node) => required.filter((key) => !hasAttribute(node, key)).map(
11555
+ (key) => fail(
11556
+ "eval.requiredDecisionMetadata",
11557
+ `Decision metadata ${key} is missing.`,
11558
+ evidenceForEvent(node.event, `attributes.${key}`),
11559
+ key,
11560
+ "missing"
11561
+ )
11562
+ )
11563
+ );
11564
+ });
11565
+ },
11566
+ contextOverlap(options = {}) {
11567
+ const minOverlap = options.minOverlap ?? 0.1;
11568
+ const minSharedTerms = options.minSharedTerms ?? 1;
11569
+ const answerKeys = options.answerKeys ?? DEFAULT_ANSWER_KEYS;
11570
+ const contextKeys = options.contextKeys ?? DEFAULT_CONTEXT_KEYS;
11571
+ return createRule("eval.contextOverlap", "retrieval", (context) => {
11572
+ const answers = collectTextFields(context.nodes, answerKeys, ["RESULT", "LLM", "AGENT"]);
11573
+ const contexts = collectTextFields(context.nodes, contextKeys, ["RETRIEVER", "TOOL"]);
11574
+ if (answers.length === 0 || contexts.length === 0) {
11575
+ return [
11576
+ fail(
11577
+ "eval.contextOverlap",
11578
+ "Answer and context text are required for overlap evaluation.",
11579
+ firstEvidence(answers.length > 0 ? answers : contexts, context.run, "children"),
11580
+ { answer: "present", context: "present" },
11581
+ { answerFields: answers.length, contextFields: contexts.length }
11582
+ )
11583
+ ];
11584
+ }
11585
+ const answerTerms = new Set(tokenize(answers.map((field) => field.text).join(" ")));
11586
+ const contextTerms = new Set(tokenize(contexts.map((field) => field.text).join(" ")));
11587
+ const sharedTerms = [...answerTerms].filter((term) => contextTerms.has(term)).length;
11588
+ const overlap = answerTerms.size === 0 ? 0 : sharedTerms / answerTerms.size;
11589
+ return sharedTerms < minSharedTerms || overlap < minOverlap ? [
11590
+ fail(
11591
+ "eval.contextOverlap",
11592
+ "Answer text did not sufficiently overlap retrieved context.",
11593
+ firstEvidence(answers, context.run, "attributes.answer"),
11594
+ { minOverlap, minSharedTerms },
11595
+ {
11596
+ answerTerms: answerTerms.size,
11597
+ contextTerms: contextTerms.size,
11598
+ sharedTerms,
11599
+ overlap: Number(overlap.toFixed(4))
11600
+ }
11601
+ )
11602
+ ] : [];
11603
+ });
11604
+ },
11605
+ quoteOverlap(options = {}) {
11606
+ const answerKeys = options.answerKeys ?? DEFAULT_ANSWER_KEYS;
11607
+ const contextKeys = options.contextKeys ?? DEFAULT_CONTEXT_KEYS;
11608
+ const minQuoteLength = options.minQuoteLength ?? 6;
11609
+ const requireQuote = options.requireQuote ?? true;
11610
+ return createRule("eval.quoteOverlap", "retrieval", (context) => {
11611
+ const answers = collectTextFields(context.nodes, answerKeys, ["RESULT", "LLM", "AGENT"]);
11612
+ const contexts = collectTextFields(context.nodes, contextKeys, ["RETRIEVER", "TOOL"]);
11613
+ const answerText = answers.map((field) => field.text).join(" ");
11614
+ const contextText = contexts.map((field) => field.text).join(" ").toLowerCase();
11615
+ const quotes = quotedSnippets(answerText, minQuoteLength);
11616
+ if (quotes.length === 0) {
11617
+ return requireQuote ? [
11618
+ fail(
11619
+ "eval.quoteOverlap",
11620
+ "Answer did not contain a quote for overlap evaluation.",
11621
+ firstEvidence(answers, context.run, "attributes.answer"),
11622
+ { quotedText: "present" },
11623
+ { quoteCount: 0 }
11624
+ )
11625
+ ] : [];
11626
+ }
11627
+ const missing = quotes.filter((quote) => !contextText.includes(quote.toLowerCase()));
11628
+ return missing.length > 0 ? [
11629
+ fail(
11630
+ "eval.quoteOverlap",
11631
+ "Quoted answer text did not appear in retrieved context.",
11632
+ firstEvidence(answers, context.run, "attributes.answer"),
11633
+ { allQuotesInContext: true },
11634
+ { quoteCount: quotes.length, missingQuotes: missing.length }
11635
+ )
11636
+ ] : [];
11637
+ });
11638
+ },
11639
+ citationPresence(options = {}) {
11640
+ const answerKeys = options.answerKeys ?? DEFAULT_ANSWER_KEYS;
11641
+ const citationKeys = options.citationKeys ?? DEFAULT_CITATION_KEYS;
11642
+ return createRule("eval.citationPresence", "retrieval", (context) => {
11643
+ const answers = collectTextFields(context.nodes, answerKeys, ["RESULT", "LLM", "AGENT"]);
11644
+ const citations = collectTextFields(context.nodes, citationKeys);
11645
+ const count = citationCount(answers.map((field) => field.text).join(" "), citations);
11646
+ return count === 0 ? [
11647
+ fail(
11648
+ "eval.citationPresence",
11649
+ "Answer did not include citations or source references.",
11650
+ firstEvidence(answers, context.run, "attributes.answer"),
11651
+ { citationCount: ">= 1" },
11652
+ { citationCount: 0 }
11653
+ )
11654
+ ] : [];
11655
+ });
11656
+ },
11657
+ requiredSourceIds(requiredIds, options = {}) {
11658
+ const expected = [...requiredIds].sort((a, b) => a.localeCompare(b));
11659
+ const sourceIdKeys = options.sourceIdKeys ?? DEFAULT_SOURCE_ID_KEYS;
11660
+ return createRule("eval.requiredSourceIds", "retrieval", (context) => {
11661
+ const available = collectSourceIds(context.nodes, sourceIdKeys);
11662
+ const availableSet = new Set(available);
11663
+ const missing = expected.filter((id) => !availableSet.has(id));
11664
+ return missing.length > 0 ? [
11665
+ fail(
11666
+ "eval.requiredSourceIds",
11667
+ "Required source IDs were not present in trace context or citations.",
11668
+ evidenceForRun(context.run, "children"),
11669
+ { sourceIds: expected },
11670
+ { missingSourceIds: missing, availableSourceIds: available.slice(0, 20) }
11671
+ )
11672
+ ] : [];
11673
+ });
11674
+ },
11675
+ answerLengthBounds(options) {
11676
+ const answerKeys = options.answerKeys ?? DEFAULT_ANSWER_KEYS;
11677
+ return createRule("eval.answerLengthBounds", "llm", (context) => {
11678
+ const answers = collectTextFields(context.nodes, answerKeys, ["RESULT", "LLM", "AGENT"]);
11679
+ const answer = answers.map((field) => field.text).join(" ").trim();
11680
+ const characters = answer.length;
11681
+ const words = wordCount(answer);
11682
+ const tooShort = options.minCharacters !== void 0 && characters < options.minCharacters || options.minWords !== void 0 && words < options.minWords;
11683
+ const tooLong = options.maxCharacters !== void 0 && characters > options.maxCharacters || options.maxWords !== void 0 && words > options.maxWords;
11684
+ return answer.length === 0 || tooShort || tooLong ? [
11685
+ fail(
11686
+ "eval.answerLengthBounds",
11687
+ "Answer length fell outside required bounds.",
11688
+ firstEvidence(answers, context.run, "attributes.answer"),
11689
+ {
11690
+ minCharacters: options.minCharacters,
11691
+ maxCharacters: options.maxCharacters,
11692
+ minWords: options.minWords,
11693
+ maxWords: options.maxWords
11694
+ },
11695
+ { characters, words }
11696
+ )
11697
+ ] : [];
11698
+ });
11699
+ },
11700
+ bannedUnsupportedPhrases(phrases = DEFAULT_BANNED_UNSUPPORTED_PHRASES, options = {}) {
11701
+ const answerKeys = options.answerKeys ?? DEFAULT_ANSWER_KEYS;
11702
+ const banned = [...phrases].map((phrase) => phrase.toLowerCase()).sort();
11703
+ return createRule("eval.bannedUnsupportedPhrases", "safety", (context) => {
11704
+ const answers = collectTextFields(context.nodes, answerKeys, ["RESULT", "LLM", "AGENT"]);
11705
+ const answer = answers.map((field) => field.text).join(" ").toLowerCase();
11706
+ const matches = banned.filter((phrase) => answer.includes(phrase));
11707
+ return matches.length > 0 ? [
11708
+ fail(
11709
+ "eval.bannedUnsupportedPhrases",
11710
+ "Answer contained banned unsupported-answer phrasing.",
11711
+ firstEvidence(answers, context.run, "attributes.answer"),
11712
+ { bannedPhraseCount: banned.length },
11713
+ { matchedPhraseCount: matches.length }
11714
+ )
11715
+ ] : [];
11716
+ });
11717
+ }
11718
+ };
11719
+ function renderEvalMarkdown(result) {
11720
+ const lines = [
11721
+ `# AgentInspect Eval`,
11722
+ "",
11723
+ `Status: ${result.status}`,
11724
+ `Format: ${result.format}`,
11725
+ ...result.runId !== void 0 ? [`Run: ${result.runId}`] : [],
11726
+ `Summary: ${result.summary.passed} passed, ${result.summary.failed} failed, ${result.summary.warnings} warnings, ${result.summary.errors} errors`
11727
+ ];
11728
+ if (result.diagnostics.length > 0) {
11729
+ lines.push("", "## Diagnostics");
11730
+ for (const diagnostic4 of result.diagnostics) {
11731
+ lines.push(`- ${diagnostic4.code}: ${diagnostic4.message}`);
11732
+ }
11733
+ }
11734
+ if (result.findings.length > 0) {
11735
+ lines.push("", "## Findings");
11736
+ for (const finding of result.findings) {
11737
+ const path15 = finding.evidence[0]?.path;
11738
+ lines.push(`- ${finding.ruleId}: ${finding.message}${path15 ? ` (${path15})` : ""}`);
11739
+ }
11740
+ }
11741
+ return `${lines.join("\n")}
11742
+ `;
11743
+ }
11744
+
11745
+ // packages/cli/src/eval.ts
11746
+ var CONFIG_EXTENSIONS2 = /* @__PURE__ */ new Set([".json", ".js", ".mjs", ".cjs"]);
11747
+ var TS_CONFIG_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".mts", ".cts"]);
11748
+ function diagnostic3(code, message, severity = "error") {
11749
+ return { code, message, severity };
11750
+ }
11751
+ function errorResult4(code, message, format = "unknown") {
11752
+ return {
11753
+ ok: false,
11754
+ status: "error",
11755
+ format,
11756
+ summary: { passed: 0, failed: 0, warnings: 0, errors: 1 },
11757
+ findings: [],
11758
+ diagnostics: [diagnostic3(code, message)]
11759
+ };
11760
+ }
11761
+ function parseNumber2(value, label) {
11762
+ if (value === void 0) return void 0;
11763
+ const parsed = Number(value);
11764
+ if (!Number.isFinite(parsed) || parsed < 0) {
11765
+ throw new Error(`${label} must be a non-negative number.`);
11766
+ }
11767
+ return parsed;
11768
+ }
11769
+ function asStringArray2(value, label) {
11770
+ if (value === void 0) return void 0;
11771
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
11772
+ throw new Error(`${label} must be an array of strings.`);
11773
+ }
11774
+ return value;
11775
+ }
11776
+ function asConfig2(value) {
11777
+ if (value === void 0 || value === null) return {};
11778
+ if (typeof value !== "object" || Array.isArray(value)) {
11779
+ throw new Error("Config must export an object.");
11780
+ }
11781
+ return value;
11782
+ }
11783
+ async function loadConfig2(configPath) {
11784
+ if (configPath === void 0) return {};
11785
+ const extension = path13__default.default.extname(configPath);
11786
+ if (TS_CONFIG_EXTENSIONS2.has(extension)) {
11787
+ throw new Error(
11788
+ "TypeScript eval configs require an explicit precompiled JavaScript config or future --config-loader support."
11789
+ );
11790
+ }
11791
+ if (!CONFIG_EXTENSIONS2.has(extension)) {
11792
+ throw new Error("Unsupported eval config extension. Use .json, .js, .mjs, or .cjs.");
11793
+ }
11794
+ const absolute = path13__default.default.resolve(configPath);
11795
+ if (extension === ".json") {
11796
+ const raw = await promises.readFile(absolute, "utf-8");
11797
+ return asConfig2(JSON.parse(raw));
11798
+ }
11799
+ const mod = await import(url.pathToFileURL(absolute).href);
11800
+ return asConfig2("default" in mod ? mod.default : mod);
11801
+ }
11802
+ function normalizeConfig2(config) {
11803
+ if (config.eval === void 0) return {};
11804
+ if (typeof config.eval !== "object" || Array.isArray(config.eval)) {
11805
+ throw new Error("eval config must be an object.");
11806
+ }
11807
+ return config.eval;
11808
+ }
11809
+ function maybeAdd(rules, value, makeRule) {
11810
+ if (value !== void 0) rules.push(makeRule(value));
11811
+ }
11812
+ function optionObject(value) {
11813
+ if (value === void 0 || value === false) return void 0;
11814
+ if (value === true) return {};
11815
+ if (typeof value !== "object" || Array.isArray(value)) {
11816
+ throw new Error("Eval heuristic config must be a boolean or object.");
11817
+ }
11818
+ return value;
11819
+ }
11820
+ function sourceIdsFromConfig(value) {
11821
+ if (value === void 0) return void 0;
11822
+ if (Array.isArray(value)) return { ids: asStringArray2(value, "eval.requiredSourceIds") ?? [] };
11823
+ if (typeof value !== "object" || Array.isArray(value)) {
11824
+ throw new Error("eval.requiredSourceIds must be an array or object.");
11825
+ }
11826
+ const ids = asStringArray2(value.ids, "eval.requiredSourceIds.ids") ?? [];
11827
+ return { ids, options: { sourceIdKeys: value.sourceIdKeys } };
11828
+ }
11829
+ function buildRules2(config, options) {
11830
+ const evalConfig = normalizeConfig2(config);
11831
+ const rules = [];
11832
+ if (evalConfig.requireSuccess || options.requireSuccess) rules.push(checks.requireSuccess());
11833
+ const requiredTools = [
11834
+ ...asStringArray2(evalConfig.requiredTools, "eval.requiredTools") ?? [],
11835
+ ...options.requiredTool ?? []
11836
+ ];
11837
+ if (requiredTools.length > 0) rules.push(checks.requiredTools(requiredTools));
11838
+ const forbiddenTools = [
11839
+ ...asStringArray2(evalConfig.forbiddenTools, "eval.forbiddenTools") ?? [],
11840
+ ...options.forbidTool ?? [],
11841
+ ...options.forbiddenTool ?? []
11842
+ ];
11843
+ if (forbiddenTools.length > 0) rules.push(checks.forbiddenTools(forbiddenTools));
11844
+ const maxDurationMs = parseNumber2(options.maxDurationMs, "--max-duration-ms") ?? evalConfig.maxDurationMs;
11845
+ maybeAdd(rules, maxDurationMs, checks.maxDurationMs);
11846
+ const maxDepth = parseNumber2(options.maxDepth, "--max-depth") ?? evalConfig.maxDepth;
11847
+ maybeAdd(rules, maxDepth, checks.maxDepth);
11848
+ const maxRetries = parseNumber2(options.maxRetries, "--max-retries") ?? evalConfig.maxRetries;
11849
+ maybeAdd(rules, maxRetries, checks.maxRetries);
11850
+ const maxTotalTokens = parseNumber2(options.maxTotalTokens, "--max-total-tokens") ?? evalConfig.maxTotalTokens;
11851
+ maybeAdd(rules, maxTotalTokens, checks.maxTotalTokens);
11852
+ if (evalConfig.requiredRetrievalBeforeGeneration || options.requireRetrievalBeforeGeneration) {
11853
+ rules.push(checks.requiredRetrievalBeforeGeneration());
11854
+ }
11855
+ const requiredDecisionMetadata = [
11856
+ ...asStringArray2(
11857
+ evalConfig.requiredDecisionMetadata,
11858
+ "eval.requiredDecisionMetadata"
11859
+ ) ?? [],
11860
+ ...options.requiredDecisionMetadata ?? []
11861
+ ];
11862
+ if (requiredDecisionMetadata.length > 0) {
11863
+ rules.push(checks.requiredDecisionMetadata(requiredDecisionMetadata));
11864
+ }
11865
+ const contextOverlap = optionObject(evalConfig.contextOverlap);
11866
+ const minOverlap = parseNumber2(options.minContextOverlap, "--min-context-overlap");
11867
+ const minSharedTerms = parseNumber2(options.minSharedTerms, "--min-shared-terms");
11868
+ if (contextOverlap !== void 0 || options.contextOverlap || minOverlap !== void 0 || minSharedTerms !== void 0) {
11869
+ rules.push(checks.contextOverlap({ ...contextOverlap, minOverlap, minSharedTerms }));
11870
+ }
11871
+ const quoteOverlap = optionObject(evalConfig.quoteOverlap);
11872
+ if (quoteOverlap !== void 0 || options.quoteOverlap) {
11873
+ rules.push(checks.quoteOverlap(quoteOverlap));
11874
+ }
11875
+ const citationPresence = optionObject(evalConfig.citationPresence);
11876
+ if (citationPresence !== void 0 || options.citationPresence) {
11877
+ rules.push(checks.citationPresence(citationPresence));
11878
+ }
11879
+ const sourceIds = sourceIdsFromConfig(evalConfig.requiredSourceIds);
11880
+ const requiredSourceIds = [...sourceIds?.ids ?? [], ...options.requiredSourceId ?? []];
11881
+ if (requiredSourceIds.length > 0) {
11882
+ rules.push(checks.requiredSourceIds(requiredSourceIds, sourceIds?.options));
11883
+ }
11884
+ const cliAnswerLength = {
11885
+ minCharacters: parseNumber2(options.minAnswerCharacters, "--min-answer-characters"),
11886
+ maxCharacters: parseNumber2(options.maxAnswerCharacters, "--max-answer-characters"),
11887
+ minWords: parseNumber2(options.minAnswerWords, "--min-answer-words"),
11888
+ maxWords: parseNumber2(options.maxAnswerWords, "--max-answer-words")
11889
+ };
11890
+ const hasCliAnswerLength = Object.values(cliAnswerLength).some((value) => value !== void 0);
11891
+ if (evalConfig.answerLengthBounds !== void 0 || hasCliAnswerLength) {
11892
+ rules.push(checks.answerLengthBounds({ ...evalConfig.answerLengthBounds, ...cliAnswerLength }));
11893
+ }
11894
+ const bannedPhrases = [
11895
+ ...asStringArray2(
11896
+ evalConfig.bannedUnsupportedPhrases,
11897
+ "eval.bannedUnsupportedPhrases"
11898
+ ) ?? [],
11899
+ ...options.bannedPhrase ?? []
11900
+ ];
11901
+ if (bannedPhrases.length > 0) rules.push(checks.bannedUnsupportedPhrases(bannedPhrases));
11902
+ return rules.length > 0 ? rules : void 0;
11903
+ }
11904
+ function exitCodeFor2(result) {
11905
+ if (result.status === "pass") return 0;
11906
+ if (result.status === "fail") return 1;
11907
+ return 2;
11908
+ }
11909
+ function stable3(value) {
11910
+ if (Array.isArray(value)) return value.map(stable3);
11911
+ if (value === null || typeof value !== "object") return value;
11912
+ const record = value;
11913
+ return Object.fromEntries(
11914
+ Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable3(record[key])])
11915
+ );
11916
+ }
11917
+ function printJson2(result) {
11918
+ console.log(JSON.stringify(stable3(result), null, 2));
11919
+ }
11920
+ function printHuman2(result) {
11921
+ console.log(`Eval status: ${result.status}`);
11922
+ console.log(`Format: ${result.format}`);
11923
+ if (result.runId !== void 0) console.log(`Run: ${result.runId}`);
11924
+ console.log(
11925
+ `Summary: ${result.summary.failed} failed, ${result.summary.warnings} warning(s), ${result.summary.errors} error(s)`
11926
+ );
11927
+ for (const diagnostic4 of result.diagnostics) {
11928
+ console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
11929
+ }
11930
+ for (const finding of result.findings) {
11931
+ const path15 = finding.evidence[0]?.path;
11932
+ console.log(`- ${finding.ruleId}: ${finding.message}${path15 ? ` (${path15})` : ""}`);
11933
+ }
11934
+ }
11935
+ function readErrorResult2(error) {
11936
+ if (error instanceof TraceReadError) {
11937
+ const code = error.code === "unsupported_format" ? "AI_EVAL_UNSUPPORTED_FORMAT" : error.code === "ambiguous_format" ? "AI_EVAL_AMBIGUOUS_FORMAT" : "AI_EVAL_TRACE_UNREADABLE";
11938
+ return errorResult4(code, error.message);
11939
+ }
11940
+ return errorResult4(
11941
+ "AI_EVAL_TRACE_UNREADABLE",
11942
+ error instanceof Error ? error.message : String(error)
11943
+ );
11944
+ }
11945
+ async function evalCommand(target, options = {}, stdin = process.stdin) {
11946
+ let result;
11947
+ let phase = "config";
11948
+ try {
11949
+ const config = await loadConfig2(options.config);
11950
+ const rules = buildRules2(config, options);
11951
+ phase = "read";
11952
+ const input3 = await inputFromTarget(target, options, stdin);
11953
+ const read = await openTrace(input3, {
11954
+ ...options.format !== void 0 ? { format: options.format } : {}
11955
+ });
11956
+ result = await evalRun(read, {
11957
+ ...rules !== void 0 ? { checks: rules } : {},
11958
+ ...options.run !== void 0 ? { runId: options.run } : {}
11959
+ });
11960
+ } catch (error) {
11961
+ if (phase === "config") {
11962
+ const message = error instanceof Error ? error.message : String(error);
11963
+ const code = message.startsWith("--") ? "AI_EVAL_INVALID_ARGUMENTS" : error instanceof SyntaxError || message.includes("Unsupported eval config extension") || message.includes("TypeScript eval configs") || message.includes("Config must") || message.includes("eval config") || message.includes("must be an array") ? "AI_EVAL_INVALID_CONFIG" : "AI_EVAL_CONFIG_LOAD_FAILED";
11964
+ result = errorResult4(code, message);
11965
+ } else {
11966
+ result = readErrorResult2(error);
11967
+ }
11968
+ }
11969
+ process.exitCode = exitCodeFor2(result);
11970
+ if (options.json) printJson2(result);
11971
+ else if (options.markdown) console.log(renderEvalMarkdown(result).trimEnd());
11972
+ else printHuman2(result);
11973
+ }
11974
+
11975
+ // packages/cli/src/safety.ts
11976
+ var BEST_EFFORT_NOTE = "Best-effort local safety verification only; not a compliance, privacy, security, or regulatory certification.";
11977
+ var DEFAULT_MAX_STRING_LENGTH = 16384;
11978
+ var DEFAULT_MAX_ARRAY_LENGTH = 1e3;
11979
+ var DEFAULT_MAX_OBJECT_KEYS = 200;
11980
+ var DEFAULT_MAX_SERIALIZED_BYTES = 128 * 1024;
11981
+ function parseLimit3(value, label) {
11982
+ if (value === void 0) return void 0;
11983
+ const parsed = Number(value);
11984
+ if (!Number.isFinite(parsed) || parsed < 0) {
11985
+ throw new Error(`${label} must be a non-negative number.`);
11986
+ }
11987
+ return parsed;
11988
+ }
11989
+ function stable4(value) {
11990
+ if (Array.isArray(value)) return value.map(stable4);
11991
+ if (value === null || typeof value !== "object") return value;
11992
+ const record = value;
11993
+ return Object.fromEntries(
11994
+ Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable4(record[key])])
11995
+ );
11996
+ }
11997
+ function safetyDiagnostic(code, message, severity = "error") {
11998
+ return { code, message, severity };
11999
+ }
12000
+ function warningDiagnostics(warnings, unsupportedFields) {
12001
+ return [
12002
+ ...warnings.map(
12003
+ (warning) => safetyDiagnostic(
12004
+ warning.code,
12005
+ warning.message,
12006
+ warning.severity === "error" ? "error" : "warning"
12007
+ )
12008
+ ),
12009
+ ...unsupportedFields.map(
12010
+ (field) => safetyDiagnostic(
10569
12011
  "unsupported_field",
10570
12012
  `Reader reported unsupported field: ${field}`,
10571
12013
  "warning"
@@ -10607,7 +12049,7 @@ function resultFromParts(parts) {
10607
12049
  note: BEST_EFFORT_NOTE
10608
12050
  };
10609
12051
  }
10610
- function readErrorResult2(command, error) {
12052
+ function readErrorResult3(command, error) {
10611
12053
  if (error instanceof TraceReadError) {
10612
12054
  const code = error.code === "unsupported_format" ? "AI_SAFETY_UNSUPPORTED_FORMAT" : error.code === "ambiguous_format" ? "AI_SAFETY_AMBIGUOUS_FORMAT" : "AI_SAFETY_TRACE_UNREADABLE";
10613
12055
  return resultFromParts({
@@ -10657,27 +12099,76 @@ function buildSafetyRules(options) {
10657
12099
  })
10658
12100
  ];
10659
12101
  }
10660
- function exitCodeFor2(result) {
12102
+ function flattenNodes2(nodes) {
12103
+ return nodes.flatMap((node) => [
12104
+ node,
12105
+ ...flattenNodes2(
12106
+ node.children
12107
+ )
12108
+ ]);
12109
+ }
12110
+ function detectorSeverity(finding) {
12111
+ return finding.severity;
12112
+ }
12113
+ function redactionDetectorFindings(read, runId) {
12114
+ const runs = runId === void 0 ? read.runs : read.runs.filter((run) => run.runId === runId);
12115
+ const out = [];
12116
+ for (const run of runs) {
12117
+ for (const node of flattenNodes2(run.children)) {
12118
+ const attrs = node.event.attributes;
12119
+ if (attrs === void 0) continue;
12120
+ const result = redact(attrs, { profile: "share" });
12121
+ for (const finding of result.findings) {
12122
+ if (finding.action === "keep") continue;
12123
+ out.push({
12124
+ ruleId: "safety.redactDetector",
12125
+ severity: detectorSeverity(finding),
12126
+ status: finding.severity === "error" ? "fail" : "warning",
12127
+ message: `Redaction detector ${finding.detector} matched ${finding.matchKind} at ${finding.path}.`,
12128
+ expected: "redacted trace content",
12129
+ actual: finding.detector,
12130
+ evidence: [
12131
+ {
12132
+ runId: node.event.runId,
12133
+ eventId: node.event.eventId,
12134
+ ...node.event.parentId !== void 0 ? { parentId: node.event.parentId } : {},
12135
+ kind: node.event.kind,
12136
+ name: node.event.name,
12137
+ ...node.event.status !== void 0 ? { status: node.event.status } : {},
12138
+ path: `attributes.${finding.path.replace(/^\$\.?/, "")}`
12139
+ }
12140
+ ]
12141
+ });
12142
+ }
12143
+ }
12144
+ }
12145
+ return out.sort((a, b) => {
12146
+ const aEvidence = a.evidence[0];
12147
+ const bEvidence = b.evidence[0];
12148
+ return (aEvidence?.runId ?? "").localeCompare(bEvidence?.runId ?? "") || (aEvidence?.eventId ?? "").localeCompare(bEvidence?.eventId ?? "") || (aEvidence?.path ?? "").localeCompare(bEvidence?.path ?? "") || a.message.localeCompare(b.message);
12149
+ });
12150
+ }
12151
+ function exitCodeFor3(result) {
10661
12152
  if (result.status === "SAFE" || result.status === "SAFE WITH WARNINGS") return 0;
10662
12153
  if (result.status === "UNSAFE") return 1;
10663
12154
  return 2;
10664
12155
  }
10665
- function printJson2(result) {
10666
- console.log(JSON.stringify(stable2(result), null, 2));
12156
+ function printJson3(result) {
12157
+ console.log(JSON.stringify(stable4(result), null, 2));
10667
12158
  }
10668
- function printHuman2(result) {
12159
+ function printHuman3(result) {
10669
12160
  console.log(`Safety status: ${result.status}`);
10670
12161
  console.log(`Format: ${result.format}`);
10671
12162
  if (result.runId !== void 0) console.log(`Run: ${result.runId}`);
10672
12163
  console.log(
10673
12164
  `Summary: ${result.summary.findings} finding(s), ${result.summary.warnings} warning(s), ${result.summary.errors} error(s)`
10674
12165
  );
10675
- for (const diagnostic3 of result.diagnostics) {
10676
- console.log(`- ${diagnostic3.code}: ${diagnostic3.message}`);
12166
+ for (const diagnostic4 of result.diagnostics) {
12167
+ console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
10677
12168
  }
10678
12169
  for (const finding of result.findings) {
10679
- const path12 = finding.evidence[0]?.path;
10680
- console.log(`- ${finding.ruleId}: ${finding.message}${path12 ? ` (${path12})` : ""}`);
12170
+ const path15 = finding.evidence[0]?.path;
12171
+ console.log(`- ${finding.ruleId}: ${finding.message}${path15 ? ` (${path15})` : ""}`);
10681
12172
  }
10682
12173
  console.log(`Note: ${result.note}`);
10683
12174
  }
@@ -10696,11 +12187,12 @@ async function safetyCommand(command, target, options, stdin) {
10696
12187
  ...options.run !== void 0 ? { runId: options.run } : {}
10697
12188
  }
10698
12189
  );
12190
+ const detectorFindings = checkResult.diagnostics.length === 0 ? redactionDetectorFindings(read, checkResult.runId) : [];
10699
12191
  result = resultFromParts({
10700
12192
  command,
10701
12193
  format: checkResult.format,
10702
12194
  runId: checkResult.runId,
10703
- findings: checkResult.findings,
12195
+ findings: [...checkResult.findings, ...detectorFindings],
10704
12196
  diagnostics: [
10705
12197
  ...checkResult.diagnostics.map(diagnosticFromCheck),
10706
12198
  ...warningDiagnostics(read.warnings, read.unsupportedFields)
@@ -10710,11 +12202,11 @@ async function safetyCommand(command, target, options, stdin) {
10710
12202
  });
10711
12203
  } catch (error) {
10712
12204
  const message = error instanceof Error ? error.message : String(error);
10713
- result = message.startsWith("--") ? invalidArgumentResult(command, error) : readErrorResult2(command, error);
12205
+ result = message.startsWith("--") ? invalidArgumentResult(command, error) : readErrorResult3(command, error);
10714
12206
  }
10715
- process.exitCode = exitCodeFor2(result);
10716
- if (options.json) printJson2(result);
10717
- else printHuman2(result);
12207
+ process.exitCode = exitCodeFor3(result);
12208
+ if (options.json) printJson3(result);
12209
+ else printHuman3(result);
10718
12210
  }
10719
12211
  function scanCommand(target, options = {}, stdin = process.stdin) {
10720
12212
  return safetyCommand("scan", target, options, stdin);
@@ -10734,16 +12226,16 @@ var SAFETY_RULES = [
10734
12226
  maxSerializedBytes: 128 * 1024
10735
12227
  })
10736
12228
  ];
10737
- function stable3(value) {
10738
- if (Array.isArray(value)) return value.map(stable3);
12229
+ function stable5(value) {
12230
+ if (Array.isArray(value)) return value.map(stable5);
10739
12231
  if (value === null || typeof value !== "object") return value;
10740
12232
  const record = value;
10741
12233
  return Object.fromEntries(
10742
- Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable3(record[key])])
12234
+ Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable5(record[key])])
10743
12235
  );
10744
12236
  }
10745
12237
  function writeJson3(value) {
10746
- return `${JSON.stringify(stable3(value), null, 2)}
12238
+ return `${JSON.stringify(stable5(value), null, 2)}
10747
12239
  `;
10748
12240
  }
10749
12241
  function escapeHtml2(value) {
@@ -10760,7 +12252,7 @@ function increment(record, key) {
10760
12252
  const label = key && key.trim() !== "" ? key : "unknown";
10761
12253
  record[label] = (record[label] ?? 0) + 1;
10762
12254
  }
10763
- function selectRun3(read, runId) {
12255
+ function selectRun4(read, runId) {
10764
12256
  if (runId !== void 0) {
10765
12257
  return read.runs.find((run) => run.runId === runId);
10766
12258
  }
@@ -10795,11 +12287,11 @@ function renderCheckSection(result) {
10795
12287
  `Diagnostics: ${result.diagnostics.length}`
10796
12288
  ];
10797
12289
  for (const finding of result.findings.slice(0, 10)) {
10798
- const path12 = finding.evidence[0]?.path ?? "(run)";
10799
- lines.push(`- ${finding.ruleId}: ${finding.message} (${path12})`);
12290
+ const path15 = finding.evidence[0]?.path ?? "(run)";
12291
+ lines.push(`- ${finding.ruleId}: ${finding.message} (${path15})`);
10800
12292
  }
10801
- for (const diagnostic3 of result.diagnostics.slice(0, 10)) {
10802
- lines.push(`- ${diagnostic3.code}: ${diagnostic3.message}`);
12293
+ for (const diagnostic4 of result.diagnostics.slice(0, 10)) {
12294
+ lines.push(`- ${diagnostic4.code}: ${diagnostic4.message}`);
10803
12295
  }
10804
12296
  return lines.join("\n");
10805
12297
  }
@@ -10874,8 +12366,8 @@ function renderHtml(trace, check, diff) {
10874
12366
  `;
10875
12367
  }
10876
12368
  async function writeArtifact(outputDir, relativePath, content, files) {
10877
- const outPath = path10__default.default.join(outputDir, relativePath);
10878
- await promises.mkdir(path10__default.default.dirname(outPath), { recursive: true });
12369
+ const outPath = path13__default.default.join(outputDir, relativePath);
12370
+ await promises.mkdir(path13__default.default.dirname(outPath), { recursive: true });
10879
12371
  await promises.writeFile(outPath, content, "utf-8");
10880
12372
  files.push(relativePath);
10881
12373
  }
@@ -10891,7 +12383,7 @@ function manifestStatus(check, diff) {
10891
12383
  return "ok";
10892
12384
  }
10893
12385
  async function artifactsCommand(target, options = {}, stdin = process.stdin) {
10894
- const outputDir = options.outputDir !== void 0 && options.outputDir.trim() !== "" ? path10__default.default.resolve(options.outputDir.trim()) : "";
12386
+ const outputDir = options.outputDir !== void 0 && options.outputDir.trim() !== "" ? path13__default.default.resolve(options.outputDir.trim()) : "";
10895
12387
  if (outputDir === "") {
10896
12388
  console.error("--output-dir is required.");
10897
12389
  process.exitCode = 1;
@@ -10908,7 +12400,7 @@ async function artifactsCommand(target, options = {}, stdin = process.stdin) {
10908
12400
  process.exitCode = 1;
10909
12401
  return;
10910
12402
  }
10911
- const selectedRun = selectRun3(read, options.run);
12403
+ const selectedRun = selectRun4(read, options.run);
10912
12404
  const check = runTraceChecks(
10913
12405
  { read },
10914
12406
  {
@@ -10957,8 +12449,8 @@ async function artifactsCommand(target, options = {}, stdin = process.stdin) {
10957
12449
  await writeArtifact(outputDir, "report.html", renderHtml(trace, check, diff), files);
10958
12450
  const summaryTarget = options.githubSummary ?? process.env.GITHUB_STEP_SUMMARY;
10959
12451
  if (summaryTarget !== void 0 && summaryTarget.trim() !== "") {
10960
- await promises.mkdir(path10__default.default.dirname(path10__default.default.resolve(summaryTarget)), { recursive: true });
10961
- await promises.appendFile(path10__default.default.resolve(summaryTarget), `
12452
+ await promises.mkdir(path13__default.default.dirname(path13__default.default.resolve(summaryTarget)), { recursive: true });
12453
+ await promises.appendFile(path13__default.default.resolve(summaryTarget), `
10962
12454
  ${renderMarkdown(trace, check, diff)}`, "utf-8");
10963
12455
  }
10964
12456
  const manifestFiles = [...files, "manifest.json"].sort((a, b) => a.localeCompare(b));
@@ -10977,10 +12469,10 @@ ${renderMarkdown(trace, check, diff)}`, "utf-8");
10977
12469
  findings: diff?.findings.length ?? 0,
10978
12470
  diagnostics: diff?.diagnostics.length ?? 0
10979
12471
  },
10980
- ...summaryTarget !== void 0 && summaryTarget.trim() !== "" ? { githubSummary: path10__default.default.resolve(summaryTarget) } : {},
12472
+ ...summaryTarget !== void 0 && summaryTarget.trim() !== "" ? { githubSummary: path13__default.default.resolve(summaryTarget) } : {},
10981
12473
  note: NOTE
10982
12474
  };
10983
- await promises.writeFile(path10__default.default.join(outputDir, "manifest.json"), writeJson3(manifest), "utf-8");
12475
+ await promises.writeFile(path13__default.default.join(outputDir, "manifest.json"), writeJson3(manifest), "utf-8");
10984
12476
  if (options.json === true) {
10985
12477
  console.log(writeJson3(manifest).trimEnd());
10986
12478
  } else {
@@ -10991,6 +12483,371 @@ ${renderMarkdown(trace, check, diff)}`, "utf-8");
10991
12483
  }
10992
12484
  }
10993
12485
  }
12486
+ function validateReporterArtifactPath(options) {
12487
+ const outputDir = path13__default.default.resolve(options.outputDir);
12488
+ const diagnostics = [];
12489
+ const rawPath = options.relativePath;
12490
+ if (rawPath.length === 0) {
12491
+ diagnostics.push({
12492
+ code: "artifact_path_empty",
12493
+ severity: "error",
12494
+ message: "Reporter artifact path must not be empty."
12495
+ });
12496
+ return { ok: false, outputDir, diagnostics };
12497
+ }
12498
+ if (rawPath.includes("\0")) {
12499
+ diagnostics.push({
12500
+ code: "invalid_artifact_path",
12501
+ severity: "error",
12502
+ message: "Reporter artifact path must not contain null bytes.",
12503
+ target: rawPath
12504
+ });
12505
+ return { ok: false, outputDir, diagnostics };
12506
+ }
12507
+ if (path13__default.default.isAbsolute(rawPath) || path13__default.default.win32.isAbsolute(rawPath)) {
12508
+ diagnostics.push({
12509
+ code: "artifact_path_absolute",
12510
+ severity: "error",
12511
+ message: "Reporter artifact path must be relative.",
12512
+ target: rawPath
12513
+ });
12514
+ return { ok: false, outputDir, diagnostics };
12515
+ }
12516
+ const normalized = path13__default.default.posix.normalize(rawPath.replace(/\\/g, "/"));
12517
+ const segments = normalized.split("/");
12518
+ if (normalized === "." || normalized.startsWith("../") || segments.some((segment) => segment === "..")) {
12519
+ diagnostics.push({
12520
+ code: "artifact_path_escape",
12521
+ severity: "error",
12522
+ message: "Reporter artifact path must stay under the output directory.",
12523
+ target: rawPath
12524
+ });
12525
+ return { ok: false, outputDir, diagnostics };
12526
+ }
12527
+ const absolutePath = path13__default.default.resolve(outputDir, normalized);
12528
+ const relFromOutput = path13__default.default.relative(outputDir, absolutePath);
12529
+ if (relFromOutput.length === 0 || relFromOutput.startsWith("..") || path13__default.default.isAbsolute(relFromOutput)) {
12530
+ diagnostics.push({
12531
+ code: "artifact_path_escape",
12532
+ severity: "error",
12533
+ message: "Reporter artifact path resolved outside the output directory.",
12534
+ target: rawPath
12535
+ });
12536
+ return { ok: false, outputDir, diagnostics };
12537
+ }
12538
+ return {
12539
+ ok: true,
12540
+ outputDir,
12541
+ relativePath: normalized,
12542
+ absolutePath,
12543
+ diagnostics
12544
+ };
12545
+ }
12546
+
12547
+ // packages/cli/src/ci-summary.ts
12548
+ var NOTE2 = "Generated locally by AgentInspect from reporter artifact manifests. Trace contents are not embedded.";
12549
+ var MAX_TEXT = 180;
12550
+ var FRAMEWORKS = /* @__PURE__ */ new Set(["vitest", "jest", "manual"]);
12551
+ var STATUSES = /* @__PURE__ */ new Set(["passed", "failed", "skipped", "todo"]);
12552
+ var ARTIFACT_KINDS = /* @__PURE__ */ new Set(["trace", "report", "eval", "redaction", "summary"]);
12553
+ var ARTIFACT_FORMATS = /* @__PURE__ */ new Set(["json", "jsonl", "md", "html"]);
12554
+ var REDACTION_PROFILES = /* @__PURE__ */ new Set(["local", "share", "strict"]);
12555
+ function stable6(value) {
12556
+ if (Array.isArray(value)) return value.map(stable6);
12557
+ if (value === null || typeof value !== "object") return value;
12558
+ const record = value;
12559
+ return Object.fromEntries(
12560
+ Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable6(record[key])])
12561
+ );
12562
+ }
12563
+ function writeJson4(value) {
12564
+ return `${JSON.stringify(stable6(value), null, 2)}
12565
+ `;
12566
+ }
12567
+ function isObject(value) {
12568
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12569
+ }
12570
+ function readString(value, label) {
12571
+ if (typeof value !== "string" || value.trim() === "") {
12572
+ throw new Error(`${label} must be a non-empty string.`);
12573
+ }
12574
+ return safeText(value);
12575
+ }
12576
+ function readOptionalString(value) {
12577
+ return typeof value === "string" && value.trim() !== "" ? safeText(value) : void 0;
12578
+ }
12579
+ function readFramework(value) {
12580
+ const framework = readString(value, "manifest.framework");
12581
+ if (!FRAMEWORKS.has(framework)) {
12582
+ throw new Error(`Unsupported reporter framework: ${framework}.`);
12583
+ }
12584
+ return framework;
12585
+ }
12586
+ function readStatus(value) {
12587
+ const status = readString(value, "result.status");
12588
+ if (!STATUSES.has(status)) {
12589
+ throw new Error(`Unsupported reporter test status: ${status}.`);
12590
+ }
12591
+ return status;
12592
+ }
12593
+ function readArtifact(value, index) {
12594
+ if (!isObject(value)) throw new Error(`manifest.artifacts[${index}] must be an object.`);
12595
+ const kind = readString(value.kind, `manifest.artifacts[${index}].kind`);
12596
+ const format = readString(value.format, `manifest.artifacts[${index}].format`);
12597
+ const redactionProfile = readString(
12598
+ value.redactionProfile,
12599
+ `manifest.artifacts[${index}].redactionProfile`
12600
+ );
12601
+ if (!ARTIFACT_KINDS.has(kind)) throw new Error(`Unsupported artifact kind: ${kind}.`);
12602
+ if (!ARTIFACT_FORMATS.has(format)) throw new Error(`Unsupported artifact format: ${format}.`);
12603
+ if (!REDACTION_PROFILES.has(redactionProfile)) {
12604
+ throw new Error(`Unsupported artifact redaction profile: ${redactionProfile}.`);
12605
+ }
12606
+ const artifactPath = readString(value.path, `manifest.artifacts[${index}].path`);
12607
+ const pathCheck = validateReporterArtifactPath({
12608
+ outputDir: process.cwd(),
12609
+ relativePath: artifactPath
12610
+ });
12611
+ if (!pathCheck.ok || pathCheck.relativePath === void 0) {
12612
+ throw new Error(`Unsafe reporter artifact path: ${artifactPath}.`);
12613
+ }
12614
+ return {
12615
+ kind,
12616
+ path: pathCheck.relativePath,
12617
+ format,
12618
+ redactionProfile
12619
+ };
12620
+ }
12621
+ function readArtifacts(value, label) {
12622
+ if (value === void 0) return [];
12623
+ if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
12624
+ return value.map((item, index) => readArtifact(item, index));
12625
+ }
12626
+ function readDiagnosticsCount(value, label) {
12627
+ if (value === void 0) return 0;
12628
+ if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
12629
+ return value.length;
12630
+ }
12631
+ function readResult(value, index) {
12632
+ if (!isObject(value)) throw new Error(`manifest.results[${index}] must be an object.`);
12633
+ const file = readOptionalString(value.file);
12634
+ const tracePath = readOptionalString(value.tracePath);
12635
+ return {
12636
+ testId: readString(value.testId, `manifest.results[${index}].testId`),
12637
+ name: readString(value.name, `manifest.results[${index}].name`),
12638
+ ...file === void 0 ? {} : { file },
12639
+ status: readStatus(value.status),
12640
+ ...tracePath === void 0 ? {} : { tracePath },
12641
+ artifacts: readArtifacts(value.artifacts, `manifest.results[${index}].artifacts`),
12642
+ diagnostics: readDiagnosticsCount(
12643
+ value.diagnostics,
12644
+ `manifest.results[${index}].diagnostics`
12645
+ )
12646
+ };
12647
+ }
12648
+ function readManifestDocument(value) {
12649
+ if (!isObject(value)) throw new Error("Reporter manifest file must contain a JSON object.");
12650
+ const candidate = isObject(value.manifest) ? value.manifest : value;
12651
+ if (!isObject(candidate)) throw new Error("Reporter manifest must be a JSON object.");
12652
+ const schemaVersion = readString(candidate.schemaVersion, "manifest.schemaVersion");
12653
+ if (schemaVersion !== "0.1") {
12654
+ throw new Error(`Unsupported reporter manifest schemaVersion: ${schemaVersion}.`);
12655
+ }
12656
+ if (!Array.isArray(candidate.results)) {
12657
+ throw new Error("manifest.results must be an array.");
12658
+ }
12659
+ const artifacts = readArtifacts(candidate.artifacts, "manifest.artifacts");
12660
+ const results = candidate.results.map((item, index) => readResult(item, index));
12661
+ const diagnostics = readDiagnosticsCount(candidate.diagnostics, "manifest.diagnostics");
12662
+ const manifest = {
12663
+ framework: readFramework(candidate.framework),
12664
+ generatedAt: readString(candidate.generatedAt, "manifest.generatedAt"),
12665
+ results,
12666
+ artifacts
12667
+ };
12668
+ return {
12669
+ packageName: readOptionalString(value.package),
12670
+ manifest,
12671
+ diagnostics
12672
+ };
12673
+ }
12674
+ function cwdRelative(filePath) {
12675
+ const relative = path13__default.default.relative(process.cwd(), path13__default.default.resolve(filePath)).replace(/\\/g, "/");
12676
+ if (relative === "" || relative.startsWith("../") || path13__default.default.isAbsolute(relative)) {
12677
+ return path13__default.default.basename(filePath);
12678
+ }
12679
+ return relative;
12680
+ }
12681
+ async function readReporterManifest(filePath) {
12682
+ const absolute = path13__default.default.resolve(filePath);
12683
+ const raw = await promises.readFile(absolute, "utf-8");
12684
+ const document = readManifestDocument(JSON.parse(raw));
12685
+ const manifest = document.manifest;
12686
+ const results = manifest.results.map((result) => ({
12687
+ testId: safeText(result.testId),
12688
+ name: safeText(result.name),
12689
+ ...result.file === void 0 ? {} : { file: safeText(path13__default.default.basename(result.file)) },
12690
+ status: result.status,
12691
+ ...result.tracePath === void 0 ? {} : { tracePath: safeText(path13__default.default.basename(result.tracePath)) },
12692
+ artifacts: result.artifacts,
12693
+ diagnostics: result.diagnostics
12694
+ }));
12695
+ return {
12696
+ ...document.packageName === void 0 ? {} : { packageName: document.packageName },
12697
+ manifestFile: cwdRelative(absolute),
12698
+ framework: manifest.framework,
12699
+ generatedAt: manifest.generatedAt,
12700
+ results,
12701
+ artifacts: manifest.artifacts,
12702
+ diagnostics: document.diagnostics
12703
+ };
12704
+ }
12705
+ function summarize3(manifests) {
12706
+ const summary = {
12707
+ manifests: manifests.length,
12708
+ tests: 0,
12709
+ failed: 0,
12710
+ passed: 0,
12711
+ skipped: 0,
12712
+ todo: 0,
12713
+ artifacts: 0,
12714
+ diagnostics: 0
12715
+ };
12716
+ for (const manifest of manifests) {
12717
+ summary.artifacts += manifest.artifacts.length;
12718
+ summary.diagnostics += manifest.diagnostics;
12719
+ for (const result of manifest.results) {
12720
+ summary.tests += 1;
12721
+ if (result.status === "failed") summary.failed += 1;
12722
+ else if (result.status === "passed") summary.passed += 1;
12723
+ else if (result.status === "skipped") summary.skipped += 1;
12724
+ else summary.todo += 1;
12725
+ summary.artifacts += result.artifacts.length;
12726
+ summary.diagnostics += result.diagnostics;
12727
+ }
12728
+ }
12729
+ return {
12730
+ status: summary.failed > 0 ? "failed" : summary.diagnostics > 0 ? "warning" : "ok",
12731
+ manifests,
12732
+ summary,
12733
+ note: NOTE2
12734
+ };
12735
+ }
12736
+ function markdownCell(value) {
12737
+ return safeText(String(value ?? "unknown")).replaceAll("|", "\\|").replace(/\r?\n/g, " ");
12738
+ }
12739
+ function renderMarkdown2(result) {
12740
+ const lines = [
12741
+ "# AgentInspect CI Summary",
12742
+ "",
12743
+ NOTE2,
12744
+ "",
12745
+ "| Field | Value |",
12746
+ "| --- | --- |",
12747
+ `| Status | ${result.status} |`,
12748
+ `| Manifests | ${result.summary.manifests} |`,
12749
+ `| Tests | ${result.summary.tests} |`,
12750
+ `| Failed | ${result.summary.failed} |`,
12751
+ `| Passed | ${result.summary.passed} |`,
12752
+ `| Skipped | ${result.summary.skipped} |`,
12753
+ `| Todo | ${result.summary.todo} |`,
12754
+ `| Artifacts | ${result.summary.artifacts} |`,
12755
+ `| Diagnostics | ${result.summary.diagnostics} |`,
12756
+ "",
12757
+ "## Tests",
12758
+ "",
12759
+ "| Framework | Status | Test | File | Trace | Artifacts |",
12760
+ "| --- | --- | --- | --- | --- | --- |"
12761
+ ];
12762
+ const rows = result.manifests.flatMap(
12763
+ (manifest) => manifest.results.map((test) => ({
12764
+ framework: manifest.framework,
12765
+ test
12766
+ }))
12767
+ );
12768
+ if (rows.length === 0) {
12769
+ lines.push("| unknown | unknown | No tests found | unknown | unknown | 0 |");
12770
+ } else {
12771
+ for (const row of rows) {
12772
+ lines.push(
12773
+ `| ${markdownCell(row.framework)} | ${markdownCell(row.test.status)} | ${markdownCell(row.test.name)} | ${markdownCell(row.test.file)} | ${markdownCell(row.test.tracePath)} | ${row.test.artifacts.length} |`
12774
+ );
12775
+ }
12776
+ }
12777
+ lines.push("", "## Manifests", "", "| Framework | File | Generated | Artifacts |", "| --- | --- | --- | --- |");
12778
+ for (const manifest of result.manifests) {
12779
+ lines.push(
12780
+ `| ${markdownCell(manifest.framework)} | ${markdownCell(manifest.manifestFile)} | ${markdownCell(manifest.generatedAt)} | ${manifest.artifacts.length} |`
12781
+ );
12782
+ }
12783
+ lines.push("", "## Artifacts", "", "| Framework | Kind | Path | Format | Profile |", "| --- | --- | --- | --- | --- |");
12784
+ const artifacts = result.manifests.flatMap(
12785
+ (manifest) => manifest.artifacts.map((artifact) => ({ framework: manifest.framework, artifact }))
12786
+ );
12787
+ if (artifacts.length === 0) {
12788
+ lines.push("| unknown | unknown | No artifacts found | unknown | unknown |");
12789
+ } else {
12790
+ for (const row of artifacts) {
12791
+ lines.push(
12792
+ `| ${markdownCell(row.framework)} | ${markdownCell(row.artifact.kind)} | ${markdownCell(row.artifact.path)} | ${markdownCell(row.artifact.format)} | ${markdownCell(row.artifact.redactionProfile)} |`
12793
+ );
12794
+ }
12795
+ }
12796
+ lines.push("");
12797
+ return `${lines.join("\n")}
12798
+ `;
12799
+ }
12800
+ function safeText(value) {
12801
+ const compact = value.replace(/\s+/g, " ").trim();
12802
+ const redacted = compact.replace(/\bsk-[A-Za-z0-9_-]{8,}\b/g, "[REDACTED]").replace(
12803
+ /\b(?:api[_-]?key|authorization|token|secret|password)\s*[:=]\s*[^,\s]+/gi,
12804
+ "$1=[REDACTED]"
12805
+ );
12806
+ if (redacted.length <= MAX_TEXT) return redacted;
12807
+ return `${redacted.slice(0, MAX_TEXT - 12)}...[truncated]`;
12808
+ }
12809
+ async function ciSummaryCommand(manifestPaths, options = {}) {
12810
+ if (manifestPaths.length === 0) {
12811
+ console.error("At least one reporter manifest path is required.");
12812
+ process.exitCode = 1;
12813
+ return;
12814
+ }
12815
+ let result;
12816
+ try {
12817
+ const manifests = [];
12818
+ for (const manifestPath of manifestPaths) {
12819
+ manifests.push(await readReporterManifest(manifestPath));
12820
+ }
12821
+ manifests.sort((a, b) => a.manifestFile.localeCompare(b.manifestFile));
12822
+ result = summarize3(manifests);
12823
+ } catch (error) {
12824
+ const message = error instanceof Error ? error.message : String(error);
12825
+ console.error(`[AgentInspect] ci-summary failed: ${message}`);
12826
+ process.exitCode = 1;
12827
+ return;
12828
+ }
12829
+ const markdown = renderMarkdown2(result);
12830
+ const outputPath = options.output !== void 0 && options.output.trim() !== "" ? path13__default.default.resolve(options.output.trim()) : void 0;
12831
+ if (outputPath !== void 0) {
12832
+ await promises.mkdir(path13__default.default.dirname(outputPath), { recursive: true });
12833
+ await promises.writeFile(outputPath, markdown, "utf-8");
12834
+ }
12835
+ const summaryTarget = options.githubSummary ?? process.env.GITHUB_STEP_SUMMARY;
12836
+ if (summaryTarget !== void 0 && summaryTarget.trim() !== "") {
12837
+ const summaryPath = path13__default.default.resolve(summaryTarget);
12838
+ await promises.mkdir(path13__default.default.dirname(summaryPath), { recursive: true });
12839
+ await promises.appendFile(summaryPath, `
12840
+ ${markdown}`, "utf-8");
12841
+ }
12842
+ if (options.json === true) {
12843
+ console.log(writeJson4(result).trimEnd());
12844
+ } else if (outputPath !== void 0) {
12845
+ console.log(`Wrote AgentInspect CI summary to ${outputPath}`);
12846
+ console.log(`Status: ${result.status}`);
12847
+ } else {
12848
+ console.log(markdown.trimEnd());
12849
+ }
12850
+ }
10994
12851
 
10995
12852
  // packages/cli/src/index.ts
10996
12853
  function runCommand(action) {
@@ -11119,6 +12976,36 @@ function createCliProgram() {
11119
12976
  ]).option("--max-total-tokens <number>", "add llm.usage with a max total-token budget").action((target, opts) => {
11120
12977
  runCommand(() => checkCommand(target, opts));
11121
12978
  });
12979
+ program.command("eval").description("Run deterministic local evals against a trace").argument("<trace-path-or-run-id>", "trace file, directory, stdin -, or run id").option("--dir <path>", "trace directory for run-id lookup").addOption(
12980
+ new commander.Option("--format <format>", "trace input format").choices([
12981
+ "agent-inspect-jsonl",
12982
+ "openinference-json",
12983
+ "otlp-json"
12984
+ ])
12985
+ ).option("--run <run-id>", "select a run when the trace contains multiple runs").option("--config <path>", "path to eval config (.json, .js, .mjs, .cjs)").option("--json", "print deterministic JSON eval result").option("--markdown", "print deterministic Markdown eval summary").option("--require-success", "require the selected run to complete successfully").option("--required-tool <name>", "require a tool name (repeatable)", (value, previous = []) => [
12986
+ ...previous,
12987
+ value
12988
+ ]).option("--forbid-tool <name>", "forbid a tool name (repeatable)", (value, previous = []) => [
12989
+ ...previous,
12990
+ value
12991
+ ]).option("--forbidden-tool <name>", "alias for --forbid-tool (repeatable)", (value, previous = []) => [
12992
+ ...previous,
12993
+ value
12994
+ ]).option("--max-duration-ms <number>", "require run duration at or below this value").option("--max-depth <number>", "require tree depth at or below this value").option("--max-retries <number>", "require retry counts at or below this value").option("--max-total-tokens <number>", "require total LLM tokens at or below this value").option(
12995
+ "--require-retrieval-before-generation",
12996
+ "require a retrieval step before the first LLM generation"
12997
+ ).option("--required-decision-metadata <key>", "require decision metadata (repeatable)", (value, previous = []) => [
12998
+ ...previous,
12999
+ value
13000
+ ]).option("--context-overlap", "require answer/context token overlap").option("--min-context-overlap <number>", "minimum answer/context overlap ratio").option("--min-shared-terms <number>", "minimum shared answer/context terms").option("--quote-overlap", "require quoted answer text to appear in context").option("--citation-presence", "require a citation or source reference").option("--required-source-id <id>", "require a source id in context or citations (repeatable)", (value, previous = []) => [
13001
+ ...previous,
13002
+ value
13003
+ ]).option("--min-answer-characters <number>", "minimum answer character count").option("--max-answer-characters <number>", "maximum answer character count").option("--min-answer-words <number>", "minimum answer word count").option("--max-answer-words <number>", "maximum answer word count").option("--banned-phrase <text>", "ban unsupported-answer phrasing (repeatable)", (value, previous = []) => [
13004
+ ...previous,
13005
+ value
13006
+ ]).action((target, opts) => {
13007
+ runCommand(() => evalCommand(target, opts));
13008
+ });
11122
13009
  program.command("scan").description("Best-effort local safety scan for trace capture risks").argument("<trace-path-or-run-id>", "trace file, directory, stdin -, or run id").option("--dir <path>", "trace directory for run-id lookup").addOption(
11123
13010
  new commander.Option("--format <format>", "trace input format").choices([
11124
13011
  "agent-inspect-jsonl",
@@ -11146,6 +13033,9 @@ function createCliProgram() {
11146
13033
  ).option("--run <run-id>", "select a run when the trace contains multiple runs").option("--baseline <trace-path-or-run-id>", "optional baseline trace for diff artifacts").option("--baseline-run <run-id>", "select a run from the baseline trace").option("--github-summary <path>", "append a safe summary to this file, e.g. GITHUB_STEP_SUMMARY").option("--json", "print deterministic JSON manifest").action((target, opts) => {
11147
13034
  runCommand(() => artifactsCommand(target, opts));
11148
13035
  });
13036
+ program.command("ci-summary").description("Summarize local reporter artifact manifests for CI").argument("<manifest...>", "reporter artifact manifest JSON files").option("-o, --output <path>", "write Markdown summary to a local file").option("--github-summary <path>", "append Markdown summary to this local file, e.g. GITHUB_STEP_SUMMARY").option("--json", "print deterministic JSON summary").action((manifest, opts) => {
13037
+ runCommand(() => ciSummaryCommand(manifest, opts));
13038
+ });
11149
13039
  program.command("diff").description("Compare two local AgentInspect JSONL traces (read-only)").argument("<left-run-id>", "first run id").argument("<right-run-id>", "second run id").option("--dir <path>", "trace directory").option("--json", "print diff result as JSON").option("--ignore-duration", "omit duration comparisons").option(
11150
13040
  "--duration-threshold <duration>",
11151
13041
  "ignore duration deltas at or below this (e.g. 500ms, 2s, 1m)"
@@ -11206,6 +13096,14 @@ function createCliProgram() {
11206
13096
  ).action((runId, opts) => {
11207
13097
  runCommand(() => reportCommand(runId, opts));
11208
13098
  });
13099
+ program.command("redact").description("Redact a local JSON or JSONL trace/file").argument("<trace-or-file>", "trace file, JSON file, stdin -, or run id").option("--dir <path>", "trace directory for run-id lookup").addOption(
13100
+ new commander.Option(
13101
+ "--profile <profile>",
13102
+ "redaction profile: local, share, strict (default: share)"
13103
+ ).choices(["local", "share", "strict"])
13104
+ ).option("-o, --output <path>", "write redacted content to a file").option("--json", "print deterministic JSON wrapper with findings").action((target, opts) => {
13105
+ runCommand(() => redactCommand(target, opts));
13106
+ });
11209
13107
  program.command("explain").description("Explain a local trace with deterministic facts (no provider calls)").argument("<trace-path-or-run-id>", "trace file, directory, stdin -, or run id").option("--dir <path>", "trace directory for run-id lookup").addOption(
11210
13108
  new commander.Option("--format <format>", "trace input format").choices([
11211
13109
  "agent-inspect-jsonl",
@@ -11230,9 +13128,9 @@ function isPrimaryModule() {
11230
13128
  if (!entry) return false;
11231
13129
  const selfPath = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
11232
13130
  try {
11233
- return fs.realpathSync(path10__default.default.resolve(entry)) === fs.realpathSync(path10__default.default.resolve(selfPath));
13131
+ return fs.realpathSync(path13__default.default.resolve(entry)) === fs.realpathSync(path13__default.default.resolve(selfPath));
11234
13132
  } catch {
11235
- return path10__default.default.resolve(entry) === path10__default.default.resolve(selfPath);
13133
+ return path13__default.default.resolve(entry) === path13__default.default.resolve(selfPath);
11236
13134
  }
11237
13135
  }
11238
13136
  if (isPrimaryModule()) {