agent-inspect 1.7.0 → 1.9.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/README.md +145 -21
  3. package/docs/ADAPTER-CONFORMANCE.md +7 -3
  4. package/docs/ADAPTERS.md +155 -5
  5. package/docs/API.md +214 -26
  6. package/docs/CLI.md +189 -7
  7. package/docs/GETTING-STARTED.md +71 -16
  8. package/docs/KNOWN-ISSUES.md +7 -1
  9. package/docs/LIMITATIONS.md +7 -1
  10. package/docs/LOG-TO-TREE-QUICKSTART.md +1 -2
  11. package/docs/MIGRATION.md +67 -0
  12. package/docs/SCHEMA.md +1 -0
  13. package/package.json +12 -2
  14. package/packages/cli/dist/index.cjs +2454 -140
  15. package/packages/cli/dist/index.cjs.map +1 -1
  16. package/packages/cli/dist/index.mjs +2454 -140
  17. package/packages/cli/dist/index.mjs.map +1 -1
  18. package/packages/core/dist/advanced.d.cts +4 -4
  19. package/packages/core/dist/advanced.d.ts +4 -4
  20. package/packages/core/dist/checks.cjs +1535 -0
  21. package/packages/core/dist/checks.cjs.map +1 -0
  22. package/packages/core/dist/checks.d.cts +585 -0
  23. package/packages/core/dist/checks.d.ts +585 -0
  24. package/packages/core/dist/checks.mjs +1512 -0
  25. package/packages/core/dist/checks.mjs.map +1 -0
  26. package/packages/core/dist/diff.d.cts +3 -3
  27. package/packages/core/dist/diff.d.ts +3 -3
  28. package/packages/core/dist/exporters.d.cts +3 -3
  29. package/packages/core/dist/exporters.d.ts +3 -3
  30. package/packages/core/dist/index.cjs +146 -0
  31. package/packages/core/dist/index.cjs.map +1 -1
  32. package/packages/core/dist/index.d.cts +44 -7
  33. package/packages/core/dist/index.d.ts +44 -7
  34. package/packages/core/dist/index.mjs +148 -1
  35. package/packages/core/dist/index.mjs.map +1 -1
  36. package/packages/core/dist/{inspect-event-Des4JDHo.d.cts → inspect-event-CevRYp58.d.cts} +1 -1
  37. package/packages/core/dist/{inspect-event-Des4JDHo.d.ts → inspect-event-CevRYp58.d.ts} +1 -1
  38. package/packages/core/dist/{log-config-C1GcJPIM.d.ts → log-config-BPHS4Sds.d.ts} +1 -1
  39. package/packages/core/dist/{log-config-BnH8Ykcb.d.cts → log-config-DanPV3P9.d.cts} +1 -1
  40. package/packages/core/dist/logs.d.cts +3 -3
  41. package/packages/core/dist/logs.d.ts +3 -3
  42. package/packages/core/dist/{persisted-inspect-event-DiFto0K2.d.ts → persisted-inspect-event-Cw7TeYGr.d.ts} +1 -1
  43. package/packages/core/dist/{persisted-inspect-event-0kaRADsp.d.cts → persisted-inspect-event-DHPfzUd8.d.cts} +1 -1
  44. package/packages/core/dist/persisted.d.cts +5 -5
  45. package/packages/core/dist/persisted.d.ts +5 -5
  46. package/packages/core/dist/readers.d.cts +2 -2
  47. package/packages/core/dist/readers.d.ts +2 -2
  48. package/packages/core/dist/{types-tSix7tfv.d.ts → types-Ap9uMdx_.d.ts} +1 -1
  49. package/packages/core/dist/{types-DB8jB6Jg.d.cts → types-B2-BU5CS.d.cts} +1 -1
  50. package/packages/core/dist/writers.d.cts +2 -2
  51. package/packages/core/dist/writers.d.ts +2 -2
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { realpathSync, createReadStream } from 'fs';
3
- import path from 'path';
4
- import { fileURLToPath } from 'url';
3
+ import path10 from 'path';
4
+ import { fileURLToPath, pathToFileURL } from 'url';
5
5
  import { Command, Option } from 'commander';
6
6
  import { unlink, stat, mkdir, writeFile, appendFile, readdir, readFile, access, open } from 'fs/promises';
7
7
  import crypto, { webcrypto } from 'crypto';
@@ -12,7 +12,7 @@ import process2, { stdin, stdout } from 'process';
12
12
  import tty from 'tty';
13
13
 
14
14
  // package.json
15
- var version = "1.7.0";
15
+ var version = "1.9.0";
16
16
 
17
17
  // packages/core/src/types.ts
18
18
  var STEP_TYPES = [
@@ -1894,7 +1894,7 @@ function formatDuration(ms) {
1894
1894
  // packages/core/src/utils.ts
1895
1895
  var DEFAULT_TRACE_DIR_NAME = ".agent-inspect";
1896
1896
  var RUNS_DIR_NAME = "runs";
1897
- var FALLBACK_TRACE_DIR = path.join(
1897
+ var FALLBACK_TRACE_DIR = path10.join(
1898
1898
  os.tmpdir(),
1899
1899
  "agent-inspect",
1900
1900
  RUNS_DIR_NAME
@@ -1932,7 +1932,7 @@ function getDefaultTraceDir() {
1932
1932
  if (typeof home !== "string" || home.trim() === "") {
1933
1933
  return FALLBACK_TRACE_DIR;
1934
1934
  }
1935
- return path.join(home, DEFAULT_TRACE_DIR_NAME, RUNS_DIR_NAME);
1935
+ return path10.join(home, DEFAULT_TRACE_DIR_NAME, RUNS_DIR_NAME);
1936
1936
  } catch {
1937
1937
  return FALLBACK_TRACE_DIR;
1938
1938
  }
@@ -1940,20 +1940,20 @@ function getDefaultTraceDir() {
1940
1940
  function getTraceFilePath(runId, traceDir) {
1941
1941
  const baseDir = traceDir ?? getDefaultTraceDir();
1942
1942
  let safeId = typeof runId === "string" && runId.trim() !== "" ? runId.trim() : "run_unknown";
1943
- safeId = path.basename(safeId);
1943
+ safeId = path10.basename(safeId);
1944
1944
  if (safeId === "" || safeId === "." || safeId === "..") {
1945
1945
  safeId = "run_unknown";
1946
1946
  }
1947
- return path.join(baseDir, `${safeId}.jsonl`);
1947
+ return path10.join(baseDir, `${safeId}.jsonl`);
1948
1948
  }
1949
1949
  async function ensureTraceDir(traceDir) {
1950
- const primary = path.resolve(traceDir);
1950
+ const primary = path10.resolve(traceDir);
1951
1951
  try {
1952
1952
  await mkdir(primary, { recursive: true });
1953
1953
  return primary;
1954
1954
  } catch {
1955
1955
  warn(`Failed to create trace directory: ${primary}`);
1956
- const fallback = path.resolve(FALLBACK_TRACE_DIR);
1956
+ const fallback = path10.resolve(FALLBACK_TRACE_DIR);
1957
1957
  try {
1958
1958
  await mkdir(fallback, { recursive: true });
1959
1959
  return fallback;
@@ -2627,7 +2627,7 @@ var TraceDirectory = class {
2627
2627
  this.#dir = resolveTraceDir(options);
2628
2628
  }
2629
2629
  getPath(filename) {
2630
- return filename ? path.join(this.#dir, filename) : this.#dir;
2630
+ return filename ? path10.join(this.#dir, filename) : this.#dir;
2631
2631
  }
2632
2632
  async list() {
2633
2633
  try {
@@ -2654,7 +2654,7 @@ function parseIsoToMs3(value) {
2654
2654
  }
2655
2655
  async function extractMetadata(filePath, _quickScan) {
2656
2656
  const stats = await stat(filePath);
2657
- let runIdFromFile = path.basename(filePath);
2657
+ let runIdFromFile = path10.basename(filePath);
2658
2658
  if (runIdFromFile.endsWith(".jsonl")) {
2659
2659
  runIdFromFile = runIdFromFile.slice(0, -".jsonl".length);
2660
2660
  }
@@ -4058,6 +4058,151 @@ ${attrsSection}
4058
4058
  };
4059
4059
  }
4060
4060
 
4061
+ // packages/core/src/explain.ts
4062
+ function flatten(nodes, out = []) {
4063
+ for (const node of nodes) {
4064
+ out.push({ node, index: out.length + 1 });
4065
+ flatten(node.children, out);
4066
+ }
4067
+ return out;
4068
+ }
4069
+ function redactValue(redactor, key, value) {
4070
+ return redactor.redactValue(key, value);
4071
+ }
4072
+ function fact(id, label, value, redactor) {
4073
+ return {
4074
+ id,
4075
+ label,
4076
+ value: redactValue(redactor, id.split(".").at(-1) ?? id, value),
4077
+ source: "trace",
4078
+ confidence: "observed"
4079
+ };
4080
+ }
4081
+ function topKinds(run) {
4082
+ return Object.entries(run.metadata.kinds).filter(([, count]) => count > 0).sort((a, b) => {
4083
+ if (b[1] !== a[1]) return b[1] - a[1];
4084
+ return a[0].localeCompare(b[0]);
4085
+ }).slice(0, 5).map(([kind, count]) => `${kind}:${count}`);
4086
+ }
4087
+ function countErrorNodes(nodes) {
4088
+ return nodes.filter((entry) => entry.node.event.status === "error").length;
4089
+ }
4090
+ function slowestNode(nodes) {
4091
+ return nodes.filter((entry) => entry.node.event.durationMs !== void 0).sort((a, b) => {
4092
+ const delta = (b.node.event.durationMs ?? 0) - (a.node.event.durationMs ?? 0);
4093
+ return delta !== 0 ? delta : a.index - b.index;
4094
+ })[0];
4095
+ }
4096
+ function attributeFacts(nodes, redactor) {
4097
+ const facts = [];
4098
+ for (const entry of nodes) {
4099
+ const attrs = entry.node.event.attributes;
4100
+ if (attrs === void 0) continue;
4101
+ for (const key of Object.keys(attrs).sort()) {
4102
+ facts.push({
4103
+ id: `node.${entry.index}.attributes.${key}`,
4104
+ label: `${entry.node.event.name} attribute ${key}`,
4105
+ value: redactValue(redactor, key, attrs[key]),
4106
+ source: "trace",
4107
+ confidence: "observed"
4108
+ });
4109
+ if (facts.length >= 8) return facts;
4110
+ }
4111
+ }
4112
+ return facts;
4113
+ }
4114
+ function buildFacts(run, redactor) {
4115
+ const nodes = flatten(run.children);
4116
+ const facts = [
4117
+ fact("run.id", "Run id", run.runId, redactor),
4118
+ fact("run.name", "Run name", run.name ?? run.runId, redactor),
4119
+ fact("run.status", "Run status", run.status ?? "unknown", redactor),
4120
+ fact("run.totalEvents", "Total events", run.metadata.totalEvents, redactor),
4121
+ fact("run.stepCount", "Top-level step count", run.children.length, redactor),
4122
+ fact("run.nodeCount", "Total node count", nodes.length, redactor),
4123
+ fact("run.errorNodeCount", "Error node count", countErrorNodes(nodes), redactor),
4124
+ fact("run.kinds", "Observed kind mix", topKinds(run), redactor)
4125
+ ];
4126
+ if (run.durationMs !== void 0) {
4127
+ facts.push(fact("run.durationMs", "Run duration milliseconds", run.durationMs, redactor));
4128
+ }
4129
+ const slowest = slowestNode(nodes);
4130
+ if (slowest !== void 0) {
4131
+ facts.push(
4132
+ fact("run.slowestNode", "Slowest observed node", {
4133
+ name: slowest.node.event.name,
4134
+ kind: slowest.node.event.kind,
4135
+ durationMs: slowest.node.event.durationMs
4136
+ }, redactor)
4137
+ );
4138
+ }
4139
+ facts.push(...attributeFacts(nodes, redactor));
4140
+ return facts;
4141
+ }
4142
+ function buildInferences(run, facts) {
4143
+ const inferences = [];
4144
+ const errorFact = facts.find((item) => item.id === "run.errorNodeCount");
4145
+ const kindFact = facts.find((item) => item.id === "run.kinds");
4146
+ const durationFact = facts.find((item) => item.id === "run.durationMs");
4147
+ const errorNodeCount = typeof errorFact?.value === "number" ? errorFact.value : 0;
4148
+ if (run.status === "error" || errorNodeCount > 0) {
4149
+ inferences.push({
4150
+ id: "outcome.error",
4151
+ label: "Outcome",
4152
+ text: "The run recorded an error status or at least one error node.",
4153
+ evidence: ["run.status", "run.errorNodeCount"],
4154
+ confidence: "deterministic"
4155
+ });
4156
+ } else if (run.status === "ok") {
4157
+ inferences.push({
4158
+ id: "outcome.success",
4159
+ label: "Outcome",
4160
+ text: "The run completed without observed error nodes.",
4161
+ evidence: ["run.status", "run.errorNodeCount"],
4162
+ confidence: "deterministic"
4163
+ });
4164
+ }
4165
+ if (kindFact !== void 0) {
4166
+ inferences.push({
4167
+ id: "shape.kind-mix",
4168
+ label: "Trace shape",
4169
+ text: "The explanation is based on the observed event kind mix, not generated content.",
4170
+ evidence: [kindFact.id],
4171
+ confidence: "deterministic"
4172
+ });
4173
+ }
4174
+ if (durationFact !== void 0) {
4175
+ inferences.push({
4176
+ id: "timing.duration",
4177
+ label: "Timing",
4178
+ text: "Timing claims are limited to persisted duration fields in the trace.",
4179
+ evidence: [durationFact.id],
4180
+ confidence: "deterministic"
4181
+ });
4182
+ }
4183
+ return inferences;
4184
+ }
4185
+ function buildLocalExplanation(run, options = {}) {
4186
+ const redactionProfile = options.redactionProfile ?? "local";
4187
+ const resolved = resolveRedactionProfile(redactionProfile);
4188
+ const redactor = new Redactor({ extraKeys: resolved.extraKeys });
4189
+ const mode = options.mode ?? "local";
4190
+ const facts = buildFacts(run, redactor);
4191
+ return {
4192
+ mode,
4193
+ runId: String(redactValue(redactor, "runId", run.runId)),
4194
+ ...run.name !== void 0 ? { name: String(redactValue(redactor, "name", run.name)) } : {},
4195
+ ...run.status !== void 0 ? { status: run.status } : {},
4196
+ redactionProfile,
4197
+ facts,
4198
+ inferences: mode === "dry-run" ? [] : buildInferences(run, facts),
4199
+ notes: [
4200
+ "Generated locally without provider or network calls.",
4201
+ "Facts are observed from normalized trace data; inferences are deterministic labels."
4202
+ ]
4203
+ };
4204
+ }
4205
+
4061
4206
  // packages/core/src/stats.ts
4062
4207
  function percentile(sorted, p) {
4063
4208
  if (sorted.length === 0) return void 0;
@@ -4409,8 +4554,8 @@ function matchStepLevel(m, events, opts) {
4409
4554
  fields.push("step.name");
4410
4555
  }
4411
4556
  if (opts.toolQuery) {
4412
- const toolName = typeof s.metadata?.toolName === "string" ? s.metadata.toolName : s.name;
4413
- if (!nameMatches(toolName, opts.toolQuery)) continue;
4557
+ const toolName2 = typeof s.metadata?.toolName === "string" ? s.metadata.toolName : s.name;
4558
+ if (!nameMatches(toolName2, opts.toolQuery)) continue;
4414
4559
  fields.push("step.tool");
4415
4560
  }
4416
4561
  if (opts.durationFilter) {
@@ -4615,7 +4760,7 @@ function findReaderByFormat(format, readers) {
4615
4760
  }
4616
4761
  async function jsonlFilesInDirectory(dirPath) {
4617
4762
  const entries = await readdir(dirPath, { withFileTypes: true });
4618
- return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => path.join(dirPath, entry.name)).sort((a, b) => a.localeCompare(b));
4763
+ return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => path10.join(dirPath, entry.name)).sort((a, b) => a.localeCompare(b));
4619
4764
  }
4620
4765
  async function resolveInput(input3) {
4621
4766
  const cached = resolvedInputCache.get(input3);
@@ -6195,13 +6340,13 @@ function pairSteps(left, right) {
6195
6340
  return pairs;
6196
6341
  }
6197
6342
  function compareLeafSteps(L, R, segments, opts, out) {
6198
- const path10 = buildPath(segments);
6343
+ const path12 = buildPath(segments);
6199
6344
  if (L.name !== R.name) {
6200
6345
  out.push({
6201
6346
  kind: "structure",
6202
6347
  severity: "warning",
6203
6348
  message: "Step name differs",
6204
- path: path10,
6349
+ path: path12,
6205
6350
  left: L.name,
6206
6351
  right: R.name
6207
6352
  });
@@ -6211,7 +6356,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
6211
6356
  kind: "step-type",
6212
6357
  severity: "warning",
6213
6358
  message: "Step type differs",
6214
- path: path10,
6359
+ path: path12,
6215
6360
  left: L.type,
6216
6361
  right: R.type
6217
6362
  });
@@ -6221,7 +6366,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
6221
6366
  kind: "step-status",
6222
6367
  severity: "warning",
6223
6368
  message: "Step status differs",
6224
- path: path10,
6369
+ path: path12,
6225
6370
  left: L.status,
6226
6371
  right: R.status
6227
6372
  });
@@ -6233,7 +6378,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
6233
6378
  kind: "error",
6234
6379
  severity: "error",
6235
6380
  message: "Step error message differs",
6236
- path: path10,
6381
+ path: path12,
6237
6382
  left: le || void 0,
6238
6383
  right: re || void 0
6239
6384
  });
@@ -6251,7 +6396,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
6251
6396
  kind: "duration",
6252
6397
  severity: "info",
6253
6398
  message: "Step duration differs",
6254
- path: path10,
6399
+ path: path12,
6255
6400
  left: ld,
6256
6401
  right: rd
6257
6402
  });
@@ -6264,7 +6409,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
6264
6409
  kind: "metadata",
6265
6410
  severity: "info",
6266
6411
  message: "Step metadata differs",
6267
- path: path10,
6412
+ path: path12,
6268
6413
  left: L.metadata,
6269
6414
  right: R.metadata
6270
6415
  });
@@ -6276,7 +6421,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
6276
6421
  kind: "output",
6277
6422
  severity: "info",
6278
6423
  message: "Output preview differs",
6279
- path: path10,
6424
+ path: path12,
6280
6425
  left: L.outputPreview,
6281
6426
  right: R.outputPreview
6282
6427
  });
@@ -6926,11 +7071,11 @@ createChalk({ level: stderrColor ? stderrColor.level : 0 });
6926
7071
  var source_default = chalk;
6927
7072
 
6928
7073
  // packages/core/src/diff/renderer.ts
6929
- function formatPath(path10) {
6930
- if (path10 === void 0 || path10.path.length === 0) {
7074
+ function formatPath(path12) {
7075
+ if (path12 === void 0 || path12.path.length === 0) {
6931
7076
  return "(run)";
6932
7077
  }
6933
- return path10.path.map((s) => s.name).join(" > ");
7078
+ return path12.path.map((s) => s.name).join(" > ");
6934
7079
  }
6935
7080
  function formatValue(v, verbose) {
6936
7081
  if (v === void 0) return "(undefined)";
@@ -7211,8 +7356,8 @@ async function stepLlm(model, fn) {
7211
7356
  metadata: { model: modelName }
7212
7357
  });
7213
7358
  }
7214
- async function stepTool(toolName, fn) {
7215
- const normalized = typeof toolName === "string" && toolName.trim() !== "" ? toolName.trim() : "unknown-tool";
7359
+ async function stepTool(toolName2, fn) {
7360
+ const normalized = typeof toolName2 === "string" && toolName2.trim() !== "" ? toolName2.trim() : "unknown-tool";
7216
7361
  return stepImpl(`tool:${normalized}`, fn, {
7217
7362
  type: "tool",
7218
7363
  metadata: { toolName: normalized }
@@ -8523,9 +8668,9 @@ Trace directory: ${traceDir}`);
8523
8668
  if (validation !== void 0 && !validation.ok) {
8524
8669
  process.exitCode = 1;
8525
8670
  }
8526
- const outPath = options.output !== void 0 && options.output.trim() !== "" ? path.resolve(options.output.trim()) : void 0;
8671
+ const outPath = options.output !== void 0 && options.output.trim() !== "" ? path10.resolve(options.output.trim()) : void 0;
8527
8672
  if (outPath !== void 0) {
8528
- await mkdir(path.dirname(outPath), { recursive: true });
8673
+ await mkdir(path10.dirname(outPath), { recursive: true });
8529
8674
  await writeFile(outPath, result.content, "utf-8");
8530
8675
  const vlabel = validation !== void 0 ? validation.ok ? "ok" : "failed" : "skipped";
8531
8676
  console.log(`Wrote ${result.fileExtension} export to ${outPath} (validation: ${vlabel})`);
@@ -8917,9 +9062,9 @@ async function reportCommand(runId, options = {}) {
8917
9062
  redactionProfile,
8918
9063
  correlation: !options.noCorrelation
8919
9064
  });
8920
- const outPath = options.output !== void 0 && options.output.trim() !== "" ? path.resolve(options.output.trim()) : void 0;
9065
+ const outPath = options.output !== void 0 && options.output.trim() !== "" ? path10.resolve(options.output.trim()) : void 0;
8921
9066
  if (outPath !== void 0) {
8922
- await mkdir(path.dirname(outPath), { recursive: true });
9067
+ await mkdir(path10.dirname(outPath), { recursive: true });
8923
9068
  await writeFile(outPath, result.content, "utf-8");
8924
9069
  console.log(`Wrote ${result.fileExtension} report to ${outPath}`);
8925
9070
  }
@@ -8947,9 +9092,164 @@ async function readStdin(stdin) {
8947
9092
  }
8948
9093
  return content;
8949
9094
  }
9095
+ function isMissingFileError2(error) {
9096
+ return error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT";
9097
+ }
9098
+ async function inputFromTarget(target, options, stdin) {
9099
+ if (target === "-") {
9100
+ return { type: "string", content: await readStdin(stdin) };
9101
+ }
9102
+ try {
9103
+ const stats2 = await stat(target);
9104
+ if (stats2.isDirectory()) return { type: "directory", path: target };
9105
+ return { type: "file", path: target };
9106
+ } catch (error) {
9107
+ if (!isMissingFileError2(error)) throw error;
9108
+ }
9109
+ const runPath = getTraceFilePath(target, resolveTraceDir({ dir: options.dir }));
9110
+ const stats = await stat(runPath);
9111
+ if (stats.isDirectory()) return { type: "directory", path: runPath };
9112
+ return { type: "file", path: runPath };
9113
+ }
9114
+
9115
+ // packages/cli/src/explain.ts
9116
+ function parseRedactionProfile3(value) {
9117
+ const profile = (value ?? "local").trim().toLowerCase();
9118
+ if (profile === "local" || profile === "share" || profile === "strict") {
9119
+ return profile;
9120
+ }
9121
+ throw new Error(
9122
+ `Unsupported --redaction-profile "${value ?? ""}". Use local, share, or strict.`
9123
+ );
9124
+ }
9125
+ function selectRun(result, runId) {
9126
+ if (runId !== void 0) {
9127
+ return result.runs.find((run) => run.runId === runId);
9128
+ }
9129
+ return result.runs.length === 1 ? result.runs[0] : void 0;
9130
+ }
9131
+ function printMultipleRuns(result) {
9132
+ console.error(
9133
+ `Trace contains ${result.runs.length} runs. Re-run with --run <run-id>.`
9134
+ );
9135
+ for (const run of result.runs) {
9136
+ console.error(`- ${run.runId}${run.name !== void 0 ? ` name=${run.name}` : ""}`);
9137
+ }
9138
+ }
9139
+ function renderHuman(result) {
9140
+ const lines = [
9141
+ `Explain: ${result.name ?? result.runId}`,
9142
+ `Mode: ${result.mode}`,
9143
+ `Status: ${result.status ?? "unknown"}`,
9144
+ `Redaction: ${result.redactionProfile}`,
9145
+ "",
9146
+ "Facts:"
9147
+ ];
9148
+ for (const fact2 of result.facts) {
9149
+ lines.push(`- ${fact2.id}: ${JSON.stringify(fact2.value)}`);
9150
+ }
9151
+ lines.push("", "Inferences:");
9152
+ if (result.inferences.length === 0) {
9153
+ lines.push("- none");
9154
+ } else {
9155
+ for (const inference of result.inferences) {
9156
+ lines.push(`- ${inference.label}: ${inference.text}`);
9157
+ }
9158
+ }
9159
+ lines.push("", "Notes:");
9160
+ for (const note of result.notes) {
9161
+ lines.push(`- ${note}`);
9162
+ }
9163
+ return lines.join("\n");
9164
+ }
9165
+ function writeJson(result) {
9166
+ console.log(JSON.stringify(result, null, 2));
9167
+ }
9168
+ function rejectProvider(provider, json) {
9169
+ process.exitCode = 1;
9170
+ const message = `Provider explain is not implemented in this build: ${provider}. Use --dry-run to inspect the redacted local payload.`;
9171
+ if (json) {
9172
+ writeJson({
9173
+ ok: false,
9174
+ error: { code: "PROVIDER_NOT_IMPLEMENTED", message }
9175
+ });
9176
+ return;
9177
+ }
9178
+ console.error(message);
9179
+ }
9180
+ async function explainCommand(target, options = {}, stdin = process.stdin) {
9181
+ if (options.provider !== void 0) {
9182
+ rejectProvider(options.provider, options.json);
9183
+ return;
9184
+ }
9185
+ let redactionProfile;
9186
+ try {
9187
+ redactionProfile = parseRedactionProfile3(options.redactionProfile);
9188
+ } catch (error) {
9189
+ process.exitCode = 1;
9190
+ console.error(error instanceof Error ? error.message : String(error));
9191
+ return;
9192
+ }
9193
+ try {
9194
+ const input3 = await inputFromTarget(target, options, stdin);
9195
+ const read = await openTrace(input3, {
9196
+ ...options.format !== void 0 ? { format: options.format } : {}
9197
+ });
9198
+ const selected = selectRun(read, options.run);
9199
+ if (selected === void 0) {
9200
+ process.exitCode = 1;
9201
+ const message = options.run !== void 0 ? `Run not found: ${options.run}` : `Trace contains ${read.runs.length} runs. Specify --run <run-id>.`;
9202
+ if (options.json) {
9203
+ writeJson({ ok: false, error: { message }, runs: read.runs });
9204
+ } else if (options.run !== void 0) {
9205
+ console.error(message);
9206
+ } else {
9207
+ printMultipleRuns(read);
9208
+ }
9209
+ return;
9210
+ }
9211
+ const mode = options.dryRun ? "dry-run" : "local";
9212
+ const explanation = buildLocalExplanation(selected, {
9213
+ mode,
9214
+ redactionProfile
9215
+ });
9216
+ if (options.json) {
9217
+ writeJson({
9218
+ ok: true,
9219
+ format: read.format,
9220
+ sourceFiles: read.sourceFiles,
9221
+ warnings: read.warnings,
9222
+ unsupportedFields: read.unsupportedFields,
9223
+ explanation
9224
+ });
9225
+ return;
9226
+ }
9227
+ console.log(renderHuman(explanation));
9228
+ } catch (error) {
9229
+ process.exitCode = 1;
9230
+ const message = error instanceof Error ? error.message : String(error);
9231
+ const code = error instanceof TraceReadError ? error.code : void 0;
9232
+ if (options.json) {
9233
+ writeJson({
9234
+ ok: false,
9235
+ error: { ...code !== void 0 ? { code } : {}, message }
9236
+ });
9237
+ return;
9238
+ }
9239
+ console.error(message);
9240
+ }
9241
+ }
9242
+ async function readStdin2(stdin) {
9243
+ stdin.setEncoding("utf8");
9244
+ let content = "";
9245
+ for await (const chunk of stdin) {
9246
+ content += typeof chunk === "string" ? chunk : String(chunk);
9247
+ }
9248
+ return content;
9249
+ }
8950
9250
  async function inputFromPathOrStdin(input3, stdin) {
8951
9251
  if (input3 === void 0 || input3 === "-") {
8952
- return { type: "string", content: await readStdin(stdin) };
9252
+ return { type: "string", content: await readStdin2(stdin) };
8953
9253
  }
8954
9254
  const stats = await stat(input3);
8955
9255
  if (stats.isDirectory()) return { type: "directory", path: input3 };
@@ -9000,13 +9300,13 @@ function printRun(result, run) {
9000
9300
  printNode(node, 0);
9001
9301
  }
9002
9302
  }
9003
- function selectRun(result, runId) {
9303
+ function selectRun2(result, runId) {
9004
9304
  if (runId !== void 0) {
9005
9305
  return result.runs.find((run) => run.runId === runId);
9006
9306
  }
9007
9307
  return result.runs.length === 1 ? result.runs[0] : void 0;
9008
9308
  }
9009
- function printMultipleRuns(result) {
9309
+ function printMultipleRuns2(result) {
9010
9310
  console.error(
9011
9311
  `Trace contains ${result.runs.length} runs. Re-run with --run <run-id>.`
9012
9312
  );
@@ -9020,7 +9320,7 @@ function printMultipleRuns(result) {
9020
9320
  console.error(`- ${bits.join(" ")}`);
9021
9321
  }
9022
9322
  }
9023
- function writeJson(output2) {
9323
+ function writeJson2(output2) {
9024
9324
  console.log(JSON.stringify(output2, null, 2));
9025
9325
  }
9026
9326
  async function openCommand(input3, options = {}, stdin = process.stdin) {
@@ -9029,12 +9329,12 @@ async function openCommand(input3, options = {}, stdin = process.stdin) {
9029
9329
  const result = await openTrace(traceInput, {
9030
9330
  ...options.format !== void 0 ? { format: options.format } : {}
9031
9331
  });
9032
- const selected = selectRun(result, options.run);
9332
+ const selected = selectRun2(result, options.run);
9033
9333
  if (selected === void 0) {
9034
9334
  process.exitCode = 1;
9035
9335
  const message = options.run !== void 0 ? `Run not found: ${options.run}` : `Trace contains ${result.runs.length} runs. Specify --run <run-id>.`;
9036
9336
  if (options.json) {
9037
- writeJson({
9337
+ writeJson2({
9038
9338
  format: result.format,
9039
9339
  sourceFiles: result.sourceFiles,
9040
9340
  warnings: result.warnings,
@@ -9045,12 +9345,12 @@ async function openCommand(input3, options = {}, stdin = process.stdin) {
9045
9345
  } else if (options.run !== void 0) {
9046
9346
  console.error(message);
9047
9347
  } else {
9048
- printMultipleRuns(result);
9348
+ printMultipleRuns2(result);
9049
9349
  }
9050
9350
  return;
9051
9351
  }
9052
9352
  if (options.json) {
9053
- writeJson({
9353
+ writeJson2({
9054
9354
  format: result.format,
9055
9355
  sourceFiles: result.sourceFiles,
9056
9356
  warnings: result.warnings,
@@ -9070,7 +9370,7 @@ async function openCommand(input3, options = {}, stdin = process.stdin) {
9070
9370
  const code = error instanceof TraceReadError ? error.code : void 0;
9071
9371
  const warnings = error instanceof TraceReadError ? error.warnings : [];
9072
9372
  if (options.json) {
9073
- writeJson({
9373
+ writeJson2({
9074
9374
  warnings,
9075
9375
  error: { ...code !== void 0 ? { code } : {}, message }
9076
9376
  });
@@ -9083,108 +9383,2105 @@ async function openCommand(input3, options = {}, stdin = process.stdin) {
9083
9383
  }
9084
9384
  }
9085
9385
 
9086
- // packages/cli/src/index.ts
9087
- function runCommand(action) {
9088
- void action().catch((error) => {
9089
- const msg = error instanceof Error ? error.message : String(error);
9090
- console.error(`[AgentInspect] ${msg}`);
9091
- process.exitCode = 1;
9092
- });
9386
+ // packages/core/src/checks/index.ts
9387
+ var SEVERITY_RANK = {
9388
+ error: 0,
9389
+ warning: 1,
9390
+ info: 2
9391
+ };
9392
+ var STATUS_RANK = {
9393
+ fail: 0,
9394
+ warning: 1,
9395
+ pass: 2
9396
+ };
9397
+ var CONFIDENCE_RANK = {
9398
+ unknown: 0,
9399
+ heuristic: 1,
9400
+ correlated: 2,
9401
+ explicit: 3
9402
+ };
9403
+ var DEFAULT_SENSITIVE_KEYS = [
9404
+ "authorization",
9405
+ "cookie",
9406
+ "token",
9407
+ "apikey",
9408
+ "api_key",
9409
+ "password",
9410
+ "secret",
9411
+ "email"
9412
+ ];
9413
+ var DEFAULT_RAW_CONTENT_KEYS = [
9414
+ "body",
9415
+ "headers",
9416
+ "input",
9417
+ "messages",
9418
+ "output",
9419
+ "payload",
9420
+ "prompt",
9421
+ "requestbody",
9422
+ "request_body",
9423
+ "responsebody",
9424
+ "response_body",
9425
+ "rawprompt",
9426
+ "raw_prompt",
9427
+ "rawoutput",
9428
+ "raw_output",
9429
+ "toolinput",
9430
+ "tool_input",
9431
+ "tooloutput",
9432
+ "tool_output"
9433
+ ];
9434
+ var DEFAULT_SECRET_PATTERNS = [
9435
+ { id: "bearer-token", pattern: /Bearer\s+[A-Za-z0-9._~+/-]{12,}=*/ },
9436
+ { id: "openai-key", pattern: /sk-[A-Za-z0-9_-]{16,}/ },
9437
+ { id: "aws-access-key", pattern: /AKIA[0-9A-Z]{16}/ },
9438
+ { id: "github-token", pattern: /gh[opsu]_[A-Za-z0-9_]{20,}/ },
9439
+ { id: "key-value-secret", pattern: /(api[_-]?key|token|password|secret)=\S{8,}/i }
9440
+ ];
9441
+ function compareStrings(a, b) {
9442
+ return (a ?? "").localeCompare(b ?? "");
9093
9443
  }
9094
- function createCliProgram() {
9095
- const program = new Command("agent-inspect").description("Local-first execution-tree debugger for AI agents").version(version);
9096
- program.command("list").description("List recent AgentInspect runs").option("--dir <path>", "trace directory").option("--limit <number>", "max runs to show (default 20, max 100)").addOption(
9097
- new Option("--status <status>", "filter by run status").choices([
9098
- "running",
9099
- "success",
9100
- "error",
9101
- "unknown"
9102
- ])
9103
- ).option("--name <query>", "filter by run name or id (substring match)").option(
9104
- "--since <duration>",
9105
- "only include runs since a duration (e.g. 30s, 5m, 2h, 7d)"
9106
- ).option("--json", "print runs as JSON").action(
9107
- (opts) => {
9108
- runCommand(() => list(opts));
9444
+ function diagnostic(code, message, ruleId) {
9445
+ return {
9446
+ code,
9447
+ message,
9448
+ severity: "error",
9449
+ ...ruleId ? { ruleId } : {}
9450
+ };
9451
+ }
9452
+ function emptySummary() {
9453
+ return {
9454
+ passed: 0,
9455
+ failed: 0,
9456
+ warnings: 0,
9457
+ errors: 0
9458
+ };
9459
+ }
9460
+ function errorResult(input3, diagnostics, selectedRun) {
9461
+ return {
9462
+ ok: false,
9463
+ status: "error",
9464
+ format: input3.read.format,
9465
+ ...selectedRun ? { runId: selectedRun.runId } : {},
9466
+ summary: {
9467
+ ...emptySummary(),
9468
+ errors: diagnostics.filter((item) => item.severity === "error").length
9469
+ },
9470
+ findings: [],
9471
+ diagnostics: [...diagnostics]
9472
+ };
9473
+ }
9474
+ function flattenNodes(nodes) {
9475
+ return nodes.flatMap((node) => [node, ...flattenNodes(node.children)]);
9476
+ }
9477
+ function buildFacts2(input3, selectedRun) {
9478
+ const scopedRuns = selectedRun ? [selectedRun] : input3.read.runs;
9479
+ const scopedRunIds = new Set(scopedRuns.map((run) => run.runId));
9480
+ const scopedEvents = selectedRun === void 0 ? input3.read.events : input3.read.events.filter((event) => scopedRunIds.has(event.runId));
9481
+ const nodes = flattenNodes(scopedRuns.flatMap((run) => run.children));
9482
+ const nodesByEventId = /* @__PURE__ */ new Map();
9483
+ const childrenByParentId = /* @__PURE__ */ new Map();
9484
+ for (const node of nodes) {
9485
+ nodesByEventId.set(node.event.eventId, node);
9486
+ const parentId = node.event.parentId;
9487
+ if (parentId) {
9488
+ const children = childrenByParentId.get(parentId) ?? [];
9489
+ children.push(node);
9490
+ childrenByParentId.set(parentId, children);
9109
9491
  }
9110
- );
9111
- program.command("view").description("View a single run trace").argument("<run-id>", "run id (e.g. from list output)").option("--dir <path>", "trace directory").option("--summary", "print a run summary (counts, duration, max depth)").option("--metadata", "print trace metadata (file path/size, timestamps)").option("--errors-only", "show only error events / failed steps").option("--verbose", "show extra detail (types, metadata, error stacks)").option("--json", "print raw trace events as JSON").option(
9112
- "--tui",
9113
- "open optional interactive TUI viewer (requires @agent-inspect/tui)"
9114
- ).action(
9115
- (runId, opts) => {
9116
- runCommand(() => view(runId, opts));
9492
+ }
9493
+ return {
9494
+ format: input3.read.format,
9495
+ runs: Object.freeze([...input3.read.runs]),
9496
+ events: Object.freeze([...scopedEvents]),
9497
+ readerWarnings: Object.freeze([...input3.read.warnings]),
9498
+ unsupportedFields: Object.freeze([...input3.read.unsupportedFields]),
9499
+ sourceFiles: Object.freeze([...input3.read.sourceFiles]),
9500
+ nodesByEventId,
9501
+ childrenByParentId,
9502
+ rootNodes: Object.freeze(scopedRuns.flatMap((run) => run.children))
9503
+ };
9504
+ }
9505
+ function resolveSelectedRun(input3, runId) {
9506
+ if (input3.selectedRun) {
9507
+ if (runId && input3.selectedRun.runId !== runId) {
9508
+ return {
9509
+ diagnostics: [
9510
+ diagnostic(
9511
+ "AI_CHECK_INVALID_ARGUMENTS",
9512
+ `Selected run ${input3.selectedRun.runId} does not match requested run ${runId}.`
9513
+ )
9514
+ ]
9515
+ };
9117
9516
  }
9118
- );
9119
- program.command("clean").description("Safely delete old AgentInspect run traces").option("--dir <path>", "trace directory").option(
9120
- "--older-than <duration>",
9121
- "delete runs older than a duration (e.g. 30s, 5m, 2h, 7d)"
9122
- ).option("--keep <count>", "keep N most recent runs (delete the rest)").option("--dry-run", "print what would be deleted (no changes)").option("--yes", "skip confirmation prompt").action(
9123
- (opts) => {
9124
- runCommand(() => clean(opts));
9517
+ return { run: input3.selectedRun, diagnostics: [] };
9518
+ }
9519
+ if (runId) {
9520
+ const run = input3.read.runs.find((candidate) => candidate.runId === runId);
9521
+ if (!run) {
9522
+ return {
9523
+ diagnostics: [
9524
+ diagnostic("AI_CHECK_RUN_SELECTION_REQUIRED", `Run not found: ${runId}.`)
9525
+ ]
9526
+ };
9125
9527
  }
9126
- );
9127
- program.command("logs").description("Parse structured logs into execution trees").argument("<file>", "path to log file").addOption(
9128
- new Option("--format <format>", "log format").choices([
9129
- "auto",
9130
- "json",
9131
- "log4js"
9132
- ])
9133
- ).option("--config <path>", "path to log ingest config (JSON)").option(
9134
- "--run-id-key <keys>",
9135
- "override run id keys (comma-separated, e.g. decisionId,requestId,jobId)"
9136
- ).option("--event-key <key>", "override event key").option("--timestamp-key <key>", "override timestamp key").option("--message-key <key>", "override message key").option("--level-key <key>", "override level key").option("--parent-id-key <key>", "override parent id key").option("--duration-key <key>", "override duration key").option("--status-key <key>", "override status key").option("--json", "print result as JSON").option("--summary", "include summary section in human output").addOption(
9137
- new Option("--warnings <mode>", "warning output mode").choices([
9138
- "summary",
9139
- "all",
9140
- "none"
9141
- ])
9142
- ).option("--verbose", "show more detail (reserved for future)").option("--no-color", "disable color output").action((file, opts) => {
9143
- runCommand(() => logs(file, opts));
9144
- });
9145
- program.command("tail").description("Live tail structured logs into execution trees").option("--file <path>", "tail a log file (default: read from stdin)").addOption(
9146
- new Option("--format <format>", "log format").choices([
9147
- "auto",
9148
- "json",
9149
- "log4js"
9150
- ])
9151
- ).option("--config <path>", "path to log ingest config (JSON)").option(
9152
- "--run-id-key <keys>",
9153
- "override run id keys (comma-separated, e.g. decisionId,requestId,jobId)"
9154
- ).option("--event-key <key>", "override event key").option("--timestamp-key <key>", "override timestamp key").option("--message-key <key>", "override message key").option("--level-key <key>", "override level key").option("--parent-id-key <key>", "override parent id key").option("--duration-key <key>", "override duration key").option("--status-key <key>", "override status key").addOption(
9155
- new Option("--warnings <mode>", "warning output mode").choices([
9156
- "summary",
9157
- "all",
9158
- "none"
9159
- ])
9160
- ).option("--refresh <ms>", "minimum time between renders (ms)").option("--once", "read once and exit (for --file)").option("--json", "print newline-delimited JSON updates").option("--no-clear", "do not clear screen between renders").option("--verbose", "show more detail (reserved for future)").option("--no-color", "disable color output").action((opts) => {
9161
- runCommand(() => tail(opts));
9162
- });
9163
- program.command("export").description("Export a manual trace run (Markdown, HTML, OpenInference-compatible JSON, OTLP JSON)").argument("<run-id>", "run id (e.g. from list output)").option("--dir <path>", "trace directory").addOption(
9164
- new Option("--format <format>", "export format (default: markdown)").choices([
9165
- "markdown",
9166
- "html",
9167
- "openinference",
9168
- "otlp-json"
9169
- ])
9170
- ).option("-o, --output <path>", "write export to file (creates parent dirs)").option("--json", "emit JSON wrapper about the export (includes content when writing to stdout)").option("--validate", "validate exported payload shape after generation").option("--include-attributes", "include bounded attributes (review before sharing)").option("--no-metadata", "omit summary / metadata sections").option("--no-errors", "omit error sections").addOption(
9171
- new Option(
9172
- "--redaction-profile <profile>",
9173
- "redaction profile for exported copies: local, share, strict (default: local)"
9174
- ).choices(["local", "share", "strict"])
9175
- ).action((runId, opts) => {
9176
- runCommand(() => exportCommand(runId, opts));
9177
- });
9178
- program.command("open").description("Open any supported local trace through the reader pipeline").argument("[input]", "trace file, directory, or - for stdin").addOption(
9179
- new Option("--format <format>", "trace input format").choices([
9180
- "agent-inspect-jsonl",
9181
- "openinference-json",
9182
- "otlp-json"
9183
- ])
9184
- ).option("--json", "print result as JSON").option("--diagnostics", "print reader warnings and unsupported fields").option("--run <run-id>", "select a run when the trace contains multiple runs").action((input3, opts) => {
9185
- runCommand(() => openCommand(input3, opts));
9186
- });
9187
- 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(
9528
+ return { run, diagnostics: [] };
9529
+ }
9530
+ if (input3.read.runs.length === 1) {
9531
+ return { run: input3.read.runs[0], diagnostics: [] };
9532
+ }
9533
+ if (input3.read.runs.length === 0) {
9534
+ return {
9535
+ diagnostics: [
9536
+ diagnostic("AI_CHECK_RUN_SELECTION_REQUIRED", "No runs are available for checks.")
9537
+ ]
9538
+ };
9539
+ }
9540
+ return {
9541
+ diagnostics: [
9542
+ diagnostic(
9543
+ "AI_CHECK_RUN_SELECTION_REQUIRED",
9544
+ "Multiple runs are available; select a run before executing checks."
9545
+ )
9546
+ ]
9547
+ };
9548
+ }
9549
+ function selectRules(rules, selectedIds) {
9550
+ const diagnostics = [];
9551
+ const byId = /* @__PURE__ */ new Map();
9552
+ for (const rule of rules) {
9553
+ if (byId.has(rule.id)) {
9554
+ diagnostics.push(
9555
+ diagnostic("AI_CHECK_INVALID_CONFIG", `Duplicate trace check rule id: ${rule.id}.`, rule.id)
9556
+ );
9557
+ continue;
9558
+ }
9559
+ byId.set(rule.id, rule);
9560
+ }
9561
+ if (selectedIds && selectedIds.length > 0) {
9562
+ const selected = new Set(selectedIds);
9563
+ for (const id of selected) {
9564
+ if (!byId.has(id)) {
9565
+ diagnostics.push(
9566
+ diagnostic("AI_CHECK_INVALID_CONFIG", `Unknown trace check rule id: ${id}.`, id)
9567
+ );
9568
+ }
9569
+ }
9570
+ return {
9571
+ rules: [...byId.values()].filter((rule) => selected.has(rule.id)).sort(compareRules),
9572
+ diagnostics
9573
+ };
9574
+ }
9575
+ return { rules: [...byId.values()].sort(compareRules), diagnostics };
9576
+ }
9577
+ function compareRules(a, b) {
9578
+ return a.id.localeCompare(b.id);
9579
+ }
9580
+ function eventTimestamp(finding, eventById) {
9581
+ const eventId = finding.evidence[0]?.eventId;
9582
+ return eventId ? eventById.get(eventId)?.timestamp ?? "" : "";
9583
+ }
9584
+ function compareFindings(eventById) {
9585
+ return (a, b) => {
9586
+ if (SEVERITY_RANK[a.severity] !== SEVERITY_RANK[b.severity]) {
9587
+ return SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity];
9588
+ }
9589
+ const byRule = a.ruleId.localeCompare(b.ruleId);
9590
+ if (byRule !== 0) return byRule;
9591
+ if (STATUS_RANK[a.status] !== STATUS_RANK[b.status]) {
9592
+ return STATUS_RANK[a.status] - STATUS_RANK[b.status];
9593
+ }
9594
+ const byRun = compareStrings(a.evidence[0]?.runId, b.evidence[0]?.runId);
9595
+ if (byRun !== 0) return byRun;
9596
+ const byTime = eventTimestamp(a, eventById).localeCompare(eventTimestamp(b, eventById));
9597
+ if (byTime !== 0) return byTime;
9598
+ const byEvent = compareStrings(a.evidence[0]?.eventId, b.evidence[0]?.eventId);
9599
+ if (byEvent !== 0) return byEvent;
9600
+ return compareStrings(a.evidence[0]?.path, b.evidence[0]?.path);
9601
+ };
9602
+ }
9603
+ function normalizeFinding(rule, finding) {
9604
+ return {
9605
+ ruleId: finding.ruleId || rule.id,
9606
+ severity: finding.severity ?? rule.defaultSeverity,
9607
+ status: finding.status,
9608
+ message: finding.message,
9609
+ ...finding.expected !== void 0 ? { expected: finding.expected } : {},
9610
+ ...finding.actual !== void 0 ? { actual: finding.actual } : {},
9611
+ evidence: [...finding.evidence ?? []]
9612
+ };
9613
+ }
9614
+ function summarize(findings, diagnostics) {
9615
+ return {
9616
+ passed: findings.filter((finding) => finding.status === "pass").length,
9617
+ failed: findings.filter(
9618
+ (finding) => finding.status === "fail" && finding.severity === "error"
9619
+ ).length,
9620
+ warnings: findings.filter(
9621
+ (finding) => finding.status === "warning" || finding.severity === "warning"
9622
+ ).length,
9623
+ errors: diagnostics.filter((item) => item.severity === "error").length
9624
+ };
9625
+ }
9626
+ function stringAttr2(event, keys) {
9627
+ for (const key of keys) {
9628
+ const value = event.attributes?.[key];
9629
+ if (typeof value === "string" && value.trim() !== "") return value;
9630
+ }
9631
+ return void 0;
9632
+ }
9633
+ function numericAttr(event, keys) {
9634
+ for (const key of keys) {
9635
+ const value = event.attributes?.[key];
9636
+ if (typeof value === "number" && Number.isFinite(value)) return value;
9637
+ }
9638
+ return void 0;
9639
+ }
9640
+ function booleanAttr(event, keys) {
9641
+ for (const key of keys) {
9642
+ const value = event.attributes?.[key];
9643
+ if (typeof value === "boolean") return value;
9644
+ }
9645
+ return void 0;
9646
+ }
9647
+ function stripPrefix(name, prefixes) {
9648
+ for (const prefix of prefixes) {
9649
+ if (name.startsWith(prefix)) return name.slice(prefix.length);
9650
+ }
9651
+ return name;
9652
+ }
9653
+ function eventEvidence(event, path12) {
9654
+ return {
9655
+ runId: event.runId,
9656
+ eventId: event.eventId,
9657
+ parentId: event.parentId,
9658
+ traceId: event.trace?.traceId,
9659
+ spanId: event.trace?.spanId,
9660
+ kind: event.kind,
9661
+ name: event.name,
9662
+ status: event.status,
9663
+ ...path12 ? { path: path12 } : {}
9664
+ };
9665
+ }
9666
+ function runEvidence(run) {
9667
+ return run ? [{ runId: run.runId, name: run.name, status: run.status }] : [];
9668
+ }
9669
+ function failFinding(ruleId, message, evidence, expected, actual) {
9670
+ return {
9671
+ ruleId,
9672
+ severity: "error",
9673
+ status: "fail",
9674
+ message,
9675
+ ...expected !== void 0 ? { expected } : {},
9676
+ ...actual !== void 0 ? { actual } : {},
9677
+ evidence: [...evidence]
9678
+ };
9679
+ }
9680
+ function toolName(event) {
9681
+ return stringAttr2(event, ["toolName", "tool"]) ?? stripPrefix(event.name, ["tool:", "function:", "mcp-tools:"]);
9682
+ }
9683
+ function llmModel(event) {
9684
+ return stringAttr2(event, ["model", "modelId", "responseModelId", "modelName", "model_name"]) ?? stripPrefix(event.name, ["llm:", "generation:", "transcription:", "speech:"]);
9685
+ }
9686
+ function llmProvider(event) {
9687
+ return stringAttr2(event, ["provider", "providerName", "provider_name"]);
9688
+ }
9689
+ function llmFinishReason(event) {
9690
+ return stringAttr2(event, ["finishReason", "rawFinishReason", "finish_reason"]);
9691
+ }
9692
+ function retryCount(event) {
9693
+ return numericAttr(event, ["retryCount", "retryAttempt", "retry_attempt", "attempt"]);
9694
+ }
9695
+ function finishedEvents(context, kind) {
9696
+ return context.events.filter(
9697
+ (event) => (kind === void 0 || event.kind === kind) && event.status !== "running"
9698
+ );
9699
+ }
9700
+ function isRecord14(value) {
9701
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9702
+ }
9703
+ function eventMap(events) {
9704
+ return new Map(events.map((event) => [event.eventId, event]));
9705
+ }
9706
+ function parseEventTime(value) {
9707
+ if (!value) return void 0;
9708
+ const parsed = Date.parse(value);
9709
+ return Number.isFinite(parsed) ? parsed : void 0;
9710
+ }
9711
+ function eventStartMs(event) {
9712
+ return parseEventTime(event.startedAt) ?? parseEventTime(event.timestamp);
9713
+ }
9714
+ function eventEndMs(event) {
9715
+ const endedAt = parseEventTime(event.endedAt);
9716
+ if (endedAt !== void 0) return endedAt;
9717
+ const startedAt = eventStartMs(event);
9718
+ if (startedAt !== void 0 && event.durationMs !== void 0 && Number.isFinite(event.durationMs)) {
9719
+ return startedAt + event.durationMs;
9720
+ }
9721
+ return void 0;
9722
+ }
9723
+ function normalizedKey(value) {
9724
+ return value.toLowerCase().replace(/[^a-z0-9_]/g, "");
9725
+ }
9726
+ function lastPathSegment(path12) {
9727
+ const parts = path12.split(".");
9728
+ return parts[parts.length - 1] ?? path12;
9729
+ }
9730
+ function valueType(value) {
9731
+ if (Array.isArray(value)) return "array";
9732
+ if (value === null) return "null";
9733
+ return typeof value;
9734
+ }
9735
+ function serializedByteLength(value) {
9736
+ try {
9737
+ return Buffer.byteLength(JSON.stringify(value), "utf-8");
9738
+ } catch {
9739
+ return void 0;
9740
+ }
9741
+ }
9742
+ function pushValueEntries(entries, event, value, path12, key, depth = 0) {
9743
+ entries.push({ event, path: path12, key, value });
9744
+ if (depth >= 8) return;
9745
+ if (Array.isArray(value)) {
9746
+ for (const [index, item] of value.entries()) {
9747
+ pushValueEntries(entries, event, item, `${path12}.${index}`, String(index), depth + 1);
9748
+ }
9749
+ return;
9750
+ }
9751
+ if (!isRecord14(value)) return;
9752
+ for (const nestedKey of Object.keys(value).sort((a, b) => a.localeCompare(b))) {
9753
+ pushValueEntries(
9754
+ entries,
9755
+ event,
9756
+ value[nestedKey],
9757
+ `${path12}.${nestedKey}`,
9758
+ nestedKey,
9759
+ depth + 1
9760
+ );
9761
+ }
9762
+ }
9763
+ function eventValueEntries(event, options = {}) {
9764
+ const entries = [];
9765
+ if (event.attributes !== void 0) {
9766
+ pushValueEntries(entries, event, event.attributes, "attributes", "attributes");
9767
+ }
9768
+ if (options.includeSummaries) {
9769
+ if (event.inputSummary !== void 0) {
9770
+ pushValueEntries(entries, event, event.inputSummary, "inputSummary", "inputSummary");
9771
+ }
9772
+ if (event.outputSummary !== void 0) {
9773
+ pushValueEntries(entries, event, event.outputSummary, "outputSummary", "outputSummary");
9774
+ }
9775
+ }
9776
+ if (options.includeError && event.error !== void 0) {
9777
+ pushValueEntries(entries, event, event.error, "error", "error");
9778
+ }
9779
+ return entries;
9780
+ }
9781
+ function limitFindings(findings, maxFindings) {
9782
+ if (maxFindings === void 0 || findings.length <= maxFindings) return findings;
9783
+ return findings.slice(0, Math.max(0, maxFindings));
9784
+ }
9785
+ function hasRedactionMarker(value, markers) {
9786
+ return markers.some((marker) => value.includes(marker)) || /^\[HASH:[A-Za-z0-9_-]+\]$/.test(value);
9787
+ }
9788
+ function isSensitiveKey(key, sensitiveKeys) {
9789
+ if (!key) return false;
9790
+ const normalized = normalizedKey(key);
9791
+ return sensitiveKeys.some((sensitive) => normalized.includes(normalizedKey(sensitive)));
9792
+ }
9793
+ function isRawContentKey(key, forbiddenKeys) {
9794
+ if (!key) return false;
9795
+ const normalized = normalizedKey(key);
9796
+ return forbiddenKeys.some((forbidden) => normalized === normalizedKey(forbidden));
9797
+ }
9798
+ function parentMarkedUnresolved(event) {
9799
+ if (booleanAttr(event, [
9800
+ "parentUnresolved",
9801
+ "unresolvedParent",
9802
+ "relationshipUnresolved",
9803
+ "unresolvedRelationship"
9804
+ ]) === true) {
9805
+ return true;
9806
+ }
9807
+ const resolution = stringAttr2(event, [
9808
+ "parentResolution",
9809
+ "relationshipResolution",
9810
+ "relationshipStatus"
9811
+ ]);
9812
+ return resolution === "unresolved" || resolution === "missing-parent";
9813
+ }
9814
+ function signalName(event, attributeKeys, prefixes) {
9815
+ return stringAttr2(event, attributeKeys) ?? stripPrefix(event.name, prefixes);
9816
+ }
9817
+ function guardrailEvents(context) {
9818
+ return finishedEvents2().filter((event) => {
9819
+ const name = event.name.toLowerCase();
9820
+ if (name.startsWith("guardrail:") || name.includes(".guardrail.")) return true;
9821
+ return stringAttr2(event, ["guardrailName", "guardrail", "guardrailId"]) !== void 0;
9822
+ });
9823
+ function finishedEvents2() {
9824
+ return context.events.filter((event) => event.status !== "running");
9825
+ }
9826
+ }
9827
+ function retryValue(event) {
9828
+ return retryCount(event) ?? 0;
9829
+ }
9830
+ function eventDurationMs(event) {
9831
+ if (event.durationMs !== void 0) return event.durationMs;
9832
+ const start = eventStartMs(event);
9833
+ const end = eventEndMs(event);
9834
+ return start !== void 0 && end !== void 0 && end >= start ? end - start : void 0;
9835
+ }
9836
+ function treeShape(nodes) {
9837
+ const lines = [];
9838
+ const visit = (node, path12) => {
9839
+ lines.push(`${path12}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
9840
+ node.children.forEach((child, index) => visit(child, `${path12}.${index}`));
9841
+ };
9842
+ nodes.forEach((node, index) => visit(node, String(index)));
9843
+ return lines;
9844
+ }
9845
+ function statusShape(context) {
9846
+ return context.events.map((event) => `${event.kind}:${event.name}:${event.status ?? "unknown"}`).sort((a, b) => a.localeCompare(b));
9847
+ }
9848
+ function toolShape(context) {
9849
+ return finishedEvents(context, "TOOL").map(
9850
+ (event) => [
9851
+ toolName(event),
9852
+ event.status ?? "unknown",
9853
+ retryValue(event),
9854
+ eventDurationMs(event) ?? "unknown"
9855
+ ].join(":")
9856
+ );
9857
+ }
9858
+ function llmShape(context) {
9859
+ return finishedEvents(context, "LLM").map(
9860
+ (event) => [
9861
+ llmProvider(event) ?? "unknown",
9862
+ llmModel(event) ?? "unknown",
9863
+ llmFinishReason(event) ?? "unknown",
9864
+ event.tokenUsage?.input ?? 0,
9865
+ event.tokenUsage?.output ?? 0,
9866
+ event.tokenUsage?.total ?? 0,
9867
+ event.tokenUsage?.cached ?? 0
9868
+ ].join(":")
9869
+ );
9870
+ }
9871
+ function errorShape(context) {
9872
+ return context.events.filter((event) => event.status === "error" || event.error !== void 0).map(
9873
+ (event) => [
9874
+ event.kind,
9875
+ event.name,
9876
+ event.error?.name ?? "Error",
9877
+ event.error?.code ?? "unknown"
9878
+ ].join(":")
9879
+ ).sort((a, b) => a.localeCompare(b));
9880
+ }
9881
+ function retrievalShape(context) {
9882
+ return finishedEvents(context, "RETRIEVER").map(
9883
+ (event) => signalName(event, ["retrievalName", "retrieverName", "retriever"], ["retriever:", "retrieval:"])
9884
+ ).sort((a, b) => a.localeCompare(b));
9885
+ }
9886
+ function guardrailShape(context) {
9887
+ return guardrailEvents(context).map((event) => signalName(event, ["guardrailName", "guardrail", "guardrailId"], ["guardrail:"])).sort((a, b) => a.localeCompare(b));
9888
+ }
9889
+ function firstEvidenceForKind(context, kind, path12) {
9890
+ const event = context.events.find((candidate) => candidate.kind === kind);
9891
+ return event ? [eventEvidence(event, path12)] : runEvidence(context.selectedRun);
9892
+ }
9893
+ function baselineDiffFinding(message, evidence, expected, actual) {
9894
+ return failFinding("baseline.regression", message, evidence, expected, actual);
9895
+ }
9896
+ function createRunStatusRule(options = {}) {
9897
+ const expected = options.expected ?? "ok";
9898
+ const allowIncomplete = options.allowIncomplete === true;
9899
+ return {
9900
+ id: "run.status",
9901
+ category: "run",
9902
+ defaultSeverity: "error",
9903
+ evaluate(context) {
9904
+ const findings = [];
9905
+ const actual = context.selectedRun?.status ?? "unknown";
9906
+ if (actual !== expected) {
9907
+ findings.push(
9908
+ failFinding(
9909
+ "run.status",
9910
+ `Run status ${actual} did not match expected ${expected}.`,
9911
+ runEvidence(context.selectedRun),
9912
+ expected,
9913
+ actual
9914
+ )
9915
+ );
9916
+ }
9917
+ if (!allowIncomplete) {
9918
+ const running = context.events.filter((event) => event.status === "running");
9919
+ if (running.length > 0) {
9920
+ findings.push(
9921
+ failFinding(
9922
+ "run.status",
9923
+ "Run contains incomplete running events.",
9924
+ running.map((event) => eventEvidence(event)),
9925
+ "no running events",
9926
+ running.length
9927
+ )
9928
+ );
9929
+ }
9930
+ }
9931
+ return findings;
9932
+ }
9933
+ };
9934
+ }
9935
+ function createRunDurationRule(options) {
9936
+ return {
9937
+ id: "run.duration",
9938
+ category: "run",
9939
+ defaultSeverity: "error",
9940
+ evaluate(context) {
9941
+ const actual = context.selectedRun?.durationMs;
9942
+ if (actual === void 0 || actual <= options.maxDurationMs) return [];
9943
+ return [
9944
+ failFinding(
9945
+ "run.duration",
9946
+ `Run duration ${actual}ms exceeded ${options.maxDurationMs}ms.`,
9947
+ runEvidence(context.selectedRun),
9948
+ { maxDurationMs: options.maxDurationMs },
9949
+ actual
9950
+ )
9951
+ ];
9952
+ }
9953
+ };
9954
+ }
9955
+ function createRunDepthRule(options) {
9956
+ return {
9957
+ id: "run.depth",
9958
+ category: "run",
9959
+ defaultSeverity: "error",
9960
+ evaluate(context) {
9961
+ const nodes = [...context.nodesByEventId.values()];
9962
+ const maxDepth = nodes.reduce((max, node) => Math.max(max, node.depth), 0);
9963
+ if (maxDepth <= options.maxDepth) return [];
9964
+ const deepest = nodes.filter((node) => node.depth === maxDepth);
9965
+ return [
9966
+ failFinding(
9967
+ "run.depth",
9968
+ `Run depth ${maxDepth} exceeded ${options.maxDepth}.`,
9969
+ deepest.map((node) => ({
9970
+ runId: node.event.runId,
9971
+ eventId: node.event.eventId,
9972
+ parentId: node.event.parentId,
9973
+ kind: node.event.kind,
9974
+ name: node.event.name,
9975
+ status: node.event.status
9976
+ })),
9977
+ { maxDepth: options.maxDepth },
9978
+ maxDepth
9979
+ )
9980
+ ];
9981
+ }
9982
+ };
9983
+ }
9984
+ function createToolUsageRule(options) {
9985
+ return {
9986
+ id: "tool.usage",
9987
+ category: "tool",
9988
+ defaultSeverity: "error",
9989
+ evaluate(context) {
9990
+ const tools = finishedEvents(context, "TOOL");
9991
+ const names = tools.map(toolName);
9992
+ const nameSet = new Set(names);
9993
+ const findings = [];
9994
+ for (const required of options.required ?? []) {
9995
+ if (!nameSet.has(required)) {
9996
+ findings.push(
9997
+ failFinding("tool.usage", `Required tool ${required} did not appear.`, runEvidence(context.selectedRun), required, names)
9998
+ );
9999
+ }
10000
+ }
10001
+ const forbidden = new Set(options.forbidden ?? []);
10002
+ const allowed = options.allowed ? new Set(options.allowed) : void 0;
10003
+ for (const event of tools) {
10004
+ const name = toolName(event);
10005
+ if (forbidden.has(name)) {
10006
+ findings.push(
10007
+ failFinding("tool.usage", `Forbidden tool ${name} appeared.`, [eventEvidence(event)], "tool absent", name)
10008
+ );
10009
+ }
10010
+ if (allowed && !allowed.has(name)) {
10011
+ findings.push(
10012
+ failFinding("tool.usage", `Tool ${name} is not in the allowed tool set.`, [eventEvidence(event)], [...allowed].sort(), name)
10013
+ );
10014
+ }
10015
+ }
10016
+ if (options.minCount !== void 0 && tools.length < options.minCount) {
10017
+ findings.push(
10018
+ failFinding("tool.usage", `Tool count ${tools.length} was below minimum ${options.minCount}.`, runEvidence(context.selectedRun), { minCount: options.minCount }, tools.length)
10019
+ );
10020
+ }
10021
+ if (options.maxCount !== void 0 && tools.length > options.maxCount) {
10022
+ findings.push(
10023
+ failFinding("tool.usage", `Tool count ${tools.length} exceeded maximum ${options.maxCount}.`, tools.map((event) => eventEvidence(event)), { maxCount: options.maxCount }, tools.length)
10024
+ );
10025
+ }
10026
+ return findings;
10027
+ }
10028
+ };
10029
+ }
10030
+ function createLlmUsageRule(options) {
10031
+ return {
10032
+ id: "llm.usage",
10033
+ category: "llm",
10034
+ defaultSeverity: "error",
10035
+ evaluate(context) {
10036
+ const llms = finishedEvents(context, "LLM");
10037
+ const findings = [];
10038
+ const allowedModels = options.allowedModels ? new Set(options.allowedModels) : void 0;
10039
+ const allowedProviders = options.allowedProviders ? new Set(options.allowedProviders) : void 0;
10040
+ const finishReasons = options.finishReasons ? new Set(options.finishReasons) : void 0;
10041
+ if (options.maxCalls !== void 0 && llms.length > options.maxCalls) {
10042
+ findings.push(
10043
+ failFinding(
10044
+ "llm.usage",
10045
+ `LLM call count ${llms.length} exceeded ${options.maxCalls}.`,
10046
+ llms.map((event) => eventEvidence(event)),
10047
+ { maxCalls: options.maxCalls },
10048
+ llms.length
10049
+ )
10050
+ );
10051
+ }
10052
+ for (const event of llms) {
10053
+ const model = llmModel(event);
10054
+ const provider = llmProvider(event);
10055
+ const finishReason = llmFinishReason(event);
10056
+ if (allowedModels && (!model || !allowedModels.has(model))) {
10057
+ findings.push(
10058
+ failFinding("llm.usage", `LLM model ${model ?? "unknown"} is not allowed.`, [eventEvidence(event, "attributes.model")], [...allowedModels].sort(), model ?? "unknown")
10059
+ );
10060
+ }
10061
+ if (allowedProviders && (!provider || !allowedProviders.has(provider))) {
10062
+ findings.push(
10063
+ failFinding("llm.usage", `LLM provider ${provider ?? "unknown"} is not allowed.`, [eventEvidence(event, "attributes.provider")], [...allowedProviders].sort(), provider ?? "unknown")
10064
+ );
10065
+ }
10066
+ if (finishReasons && (!finishReason || !finishReasons.has(finishReason))) {
10067
+ findings.push(
10068
+ failFinding("llm.usage", `LLM finish reason ${finishReason ?? "unknown"} is not allowed.`, [eventEvidence(event, "attributes.finishReason")], [...finishReasons].sort(), finishReason ?? "unknown")
10069
+ );
10070
+ }
10071
+ }
10072
+ const tokenTotals = llms.reduce(
10073
+ (totals, event) => ({
10074
+ input: totals.input + (event.tokenUsage?.input ?? 0),
10075
+ output: totals.output + (event.tokenUsage?.output ?? 0),
10076
+ total: totals.total + (event.tokenUsage?.total ?? 0),
10077
+ cached: totals.cached + (event.tokenUsage?.cached ?? 0)
10078
+ }),
10079
+ { input: 0, output: 0, total: 0, cached: 0 }
10080
+ );
10081
+ const tokenLimits = [
10082
+ ["input", options.maxInputTokens],
10083
+ ["output", options.maxOutputTokens],
10084
+ ["total", options.maxTotalTokens],
10085
+ ["cached", options.maxCachedTokens]
10086
+ ];
10087
+ for (const [key, limit] of tokenLimits) {
10088
+ if (limit !== void 0 && tokenTotals[key] > limit) {
10089
+ findings.push(
10090
+ failFinding(
10091
+ "llm.usage",
10092
+ `LLM ${key} token count ${tokenTotals[key]} exceeded ${limit}.`,
10093
+ llms.map((event) => eventEvidence(event, `tokenUsage.${key}`)),
10094
+ { [`max${key[0].toUpperCase()}${key.slice(1)}Tokens`]: limit },
10095
+ tokenTotals[key]
10096
+ )
10097
+ );
10098
+ }
10099
+ }
10100
+ return findings;
10101
+ }
10102
+ };
10103
+ }
10104
+ function createStructureOrphanRule(options = {}) {
10105
+ const allowMarkedUnresolved = options.allowMarkedUnresolved ?? true;
10106
+ return {
10107
+ id: "structure.orphan",
10108
+ category: "structure",
10109
+ defaultSeverity: "error",
10110
+ evaluate(context) {
10111
+ const byId = eventMap(context.events);
10112
+ const orphans = context.events.filter((event) => {
10113
+ if (!event.parentId || byId.has(event.parentId)) return false;
10114
+ return !(allowMarkedUnresolved && parentMarkedUnresolved(event));
10115
+ });
10116
+ if (orphans.length === 0) return [];
10117
+ return [
10118
+ failFinding(
10119
+ "structure.orphan",
10120
+ "Trace contains events whose parentId is not present in the selected run.",
10121
+ orphans.map((event) => eventEvidence(event, "parentId")),
10122
+ "parentId resolves to an event in the selected run",
10123
+ orphans.length
10124
+ )
10125
+ ];
10126
+ }
10127
+ };
10128
+ }
10129
+ function createStructureCycleRule() {
10130
+ return {
10131
+ id: "structure.cycle",
10132
+ category: "structure",
10133
+ defaultSeverity: "error",
10134
+ evaluate(context) {
10135
+ const byId = eventMap(context.events);
10136
+ const seenCycles = /* @__PURE__ */ new Set();
10137
+ const findings = [];
10138
+ for (const event of [...context.events].sort((a, b) => a.eventId.localeCompare(b.eventId))) {
10139
+ const path12 = [];
10140
+ const seenAt = /* @__PURE__ */ new Map();
10141
+ let current = event;
10142
+ while (current) {
10143
+ const existing = seenAt.get(current.eventId);
10144
+ if (existing !== void 0) {
10145
+ const cycle = path12.slice(existing);
10146
+ const key = cycle.map((item) => item.eventId).sort().join("\0");
10147
+ if (!seenCycles.has(key)) {
10148
+ seenCycles.add(key);
10149
+ findings.push(
10150
+ failFinding(
10151
+ "structure.cycle",
10152
+ "Trace contains a parentId cycle.",
10153
+ cycle.map((item) => eventEvidence(item, "parentId")),
10154
+ "acyclic parentId graph",
10155
+ cycle.map((item) => item.eventId).sort()
10156
+ )
10157
+ );
10158
+ }
10159
+ break;
10160
+ }
10161
+ seenAt.set(current.eventId, path12.length);
10162
+ path12.push(current);
10163
+ current = current.parentId ? byId.get(current.parentId) : void 0;
10164
+ }
10165
+ }
10166
+ return findings;
10167
+ }
10168
+ };
10169
+ }
10170
+ function createStructureRelationshipRule(options = {}) {
10171
+ return {
10172
+ id: "structure.relationship",
10173
+ category: "structure",
10174
+ defaultSeverity: "error",
10175
+ evaluate(context) {
10176
+ const byId = eventMap(context.events);
10177
+ const findings = [];
10178
+ const minConfidence = options.minConfidence;
10179
+ for (const event of context.events) {
10180
+ if (minConfidence && CONFIDENCE_RANK[event.confidence] < CONFIDENCE_RANK[minConfidence]) {
10181
+ findings.push(
10182
+ failFinding(
10183
+ "structure.relationship",
10184
+ `Event confidence ${event.confidence} is below ${minConfidence}.`,
10185
+ [eventEvidence(event, "confidence")],
10186
+ { minConfidence },
10187
+ event.confidence
10188
+ )
10189
+ );
10190
+ }
10191
+ if (!event.parentId) continue;
10192
+ if (event.parentId === event.eventId) {
10193
+ findings.push(
10194
+ failFinding(
10195
+ "structure.relationship",
10196
+ "Event parentId points to itself.",
10197
+ [eventEvidence(event, "parentId")],
10198
+ "parentId references a distinct event",
10199
+ "self"
10200
+ )
10201
+ );
10202
+ continue;
10203
+ }
10204
+ const parent = byId.get(event.parentId);
10205
+ if (!parent) continue;
10206
+ if (options.requireParentBeforeChild) {
10207
+ const parentTime = eventStartMs(parent);
10208
+ const childTime = eventStartMs(event);
10209
+ if (parentTime !== void 0 && childTime !== void 0 && parentTime > childTime) {
10210
+ findings.push(
10211
+ failFinding(
10212
+ "structure.relationship",
10213
+ "Parent event starts after child event.",
10214
+ [eventEvidence(parent), eventEvidence(event, "parentId")],
10215
+ "parent start <= child start",
10216
+ { parentEventId: parent.eventId, childEventId: event.eventId }
10217
+ )
10218
+ );
10219
+ }
10220
+ }
10221
+ if (options.requireTraceParentSpan && parent.trace?.spanId && event.trace) {
10222
+ const actual = event.trace.parentSpanId;
10223
+ if (actual !== parent.trace.spanId) {
10224
+ findings.push(
10225
+ failFinding(
10226
+ "structure.relationship",
10227
+ "Trace parentSpanId does not match parent spanId.",
10228
+ [eventEvidence(event, "trace.parentSpanId")],
10229
+ { parentSpanId: parent.trace.spanId },
10230
+ actual ?? "missing"
10231
+ )
10232
+ );
10233
+ }
10234
+ }
10235
+ }
10236
+ return findings;
10237
+ }
10238
+ };
10239
+ }
10240
+ function createStructureParallelWidthRule(options) {
10241
+ return {
10242
+ id: "structure.parallelWidth",
10243
+ category: "structure",
10244
+ defaultSeverity: "error",
10245
+ evaluate(context) {
10246
+ const findings = [];
10247
+ const byId = eventMap(context.events);
10248
+ if (options.maxChildren !== void 0) {
10249
+ for (const [parentId, children] of context.childrenByParentId.entries()) {
10250
+ if (children.length <= options.maxChildren) continue;
10251
+ const parent = byId.get(parentId);
10252
+ findings.push(
10253
+ failFinding(
10254
+ "structure.parallelWidth",
10255
+ `Parent ${parentId} has ${children.length} children, exceeding ${options.maxChildren}.`,
10256
+ [
10257
+ ...parent ? [eventEvidence(parent)] : [{ runId: context.selectedRun?.runId, eventId: parentId }],
10258
+ ...children.map((child) => ({
10259
+ runId: child.event.runId,
10260
+ eventId: child.event.eventId,
10261
+ parentId: child.event.parentId,
10262
+ kind: child.event.kind,
10263
+ name: child.event.name,
10264
+ status: child.event.status
10265
+ }))
10266
+ ],
10267
+ { maxChildren: options.maxChildren },
10268
+ children.length
10269
+ )
10270
+ );
10271
+ }
10272
+ }
10273
+ if (options.maxConcurrent !== void 0) {
10274
+ const intervals = context.events.map((event) => ({ event, start: eventStartMs(event), end: eventEndMs(event) })).filter(
10275
+ (item) => item.start !== void 0 && item.end !== void 0 && item.end > item.start
10276
+ );
10277
+ const points = intervals.flatMap((item) => [
10278
+ { time: item.start, delta: 1, event: item.event },
10279
+ { time: item.end, delta: -1, event: item.event }
10280
+ ]);
10281
+ points.sort((a, b) => {
10282
+ const byTime = a.time - b.time;
10283
+ if (byTime !== 0) return byTime;
10284
+ const byDelta = a.delta - b.delta;
10285
+ if (byDelta !== 0) return byDelta;
10286
+ return a.event.eventId.localeCompare(b.event.eventId);
10287
+ });
10288
+ const active = /* @__PURE__ */ new Map();
10289
+ let maxActive = [];
10290
+ for (const point of points) {
10291
+ if (point.delta > 0) {
10292
+ active.set(point.event.eventId, point.event);
10293
+ if (active.size > maxActive.length) {
10294
+ maxActive = [...active.values()].sort((a, b) => a.eventId.localeCompare(b.eventId));
10295
+ }
10296
+ } else {
10297
+ active.delete(point.event.eventId);
10298
+ }
10299
+ }
10300
+ if (maxActive.length > options.maxConcurrent) {
10301
+ findings.push(
10302
+ failFinding(
10303
+ "structure.parallelWidth",
10304
+ `Concurrent event width ${maxActive.length} exceeded ${options.maxConcurrent}.`,
10305
+ maxActive.map((event) => eventEvidence(event)),
10306
+ { maxConcurrent: options.maxConcurrent },
10307
+ maxActive.length
10308
+ )
10309
+ );
10310
+ }
10311
+ }
10312
+ return findings;
10313
+ }
10314
+ };
10315
+ }
10316
+ function createSafetyRedactionRule(options = {}) {
10317
+ const sensitiveKeys = options.sensitiveKeys ?? DEFAULT_SENSITIVE_KEYS;
10318
+ const markers = options.redactedMarkers ?? ["[REDACTED]", "[REDACTED:"];
10319
+ return {
10320
+ id: "safety.redaction",
10321
+ category: "safety",
10322
+ defaultSeverity: "error",
10323
+ evaluate(context) {
10324
+ const findings = [];
10325
+ for (const event of context.events) {
10326
+ for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
10327
+ if (!isSensitiveKey(entry.key ?? lastPathSegment(entry.path), sensitiveKeys)) continue;
10328
+ if (typeof entry.value === "string" && hasRedactionMarker(entry.value, markers)) continue;
10329
+ findings.push(
10330
+ failFinding(
10331
+ "safety.redaction",
10332
+ `Sensitive-looking field at ${entry.path} is not redacted.`,
10333
+ [eventEvidence(event, entry.path)],
10334
+ "redaction marker",
10335
+ { path: entry.path, valueType: valueType(entry.value) }
10336
+ )
10337
+ );
10338
+ }
10339
+ }
10340
+ return limitFindings(findings, options.maxFindings);
10341
+ }
10342
+ };
10343
+ }
10344
+ function createSafetyRawContentRule(options = {}) {
10345
+ const forbiddenKeys = options.forbiddenKeys ?? DEFAULT_RAW_CONTENT_KEYS;
10346
+ return {
10347
+ id: "safety.rawPrompt",
10348
+ category: "safety",
10349
+ defaultSeverity: "error",
10350
+ evaluate(context) {
10351
+ const findings = [];
10352
+ for (const event of context.events) {
10353
+ for (const entry of eventValueEntries(event, { includeSummaries: options.includeSummaries })) {
10354
+ const key = entry.key ?? lastPathSegment(entry.path);
10355
+ if (!isRawContentKey(key, forbiddenKeys)) continue;
10356
+ findings.push(
10357
+ failFinding(
10358
+ "safety.rawPrompt",
10359
+ `Raw content-like field ${entry.path} is present.`,
10360
+ [eventEvidence(event, entry.path)],
10361
+ "metadata-only trace fields",
10362
+ { path: entry.path, valueType: valueType(entry.value) }
10363
+ )
10364
+ );
10365
+ }
10366
+ }
10367
+ return limitFindings(findings, options.maxFindings);
10368
+ }
10369
+ };
10370
+ }
10371
+ function createSafetySecretPatternRule(options = {}) {
10372
+ const patterns = options.patterns ?? DEFAULT_SECRET_PATTERNS;
10373
+ const maxStringLength = options.maxStringLength ?? 4096;
10374
+ return {
10375
+ id: "safety.secretPattern",
10376
+ category: "safety",
10377
+ defaultSeverity: "error",
10378
+ evaluate(context) {
10379
+ const findings = [];
10380
+ for (const event of context.events) {
10381
+ for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
10382
+ if (typeof entry.value !== "string") continue;
10383
+ const sample = entry.value.slice(0, maxStringLength);
10384
+ for (const pattern of patterns) {
10385
+ pattern.pattern.lastIndex = 0;
10386
+ if (!pattern.pattern.test(sample)) continue;
10387
+ pattern.pattern.lastIndex = 0;
10388
+ findings.push(
10389
+ failFinding(
10390
+ "safety.secretPattern",
10391
+ `Secret-like pattern ${pattern.id} matched at ${entry.path}.`,
10392
+ [eventEvidence(event, entry.path)],
10393
+ "no secret-like strings",
10394
+ { pattern: pattern.id, path: entry.path }
10395
+ )
10396
+ );
10397
+ break;
10398
+ }
10399
+ }
10400
+ }
10401
+ return limitFindings(findings, options.maxFindings);
10402
+ }
10403
+ };
10404
+ }
10405
+ function createSafetyOversizedAttributeRule(options) {
10406
+ return {
10407
+ id: "safety.oversizedAttribute",
10408
+ category: "safety",
10409
+ defaultSeverity: "error",
10410
+ evaluate(context) {
10411
+ const findings = [];
10412
+ for (const event of context.events) {
10413
+ for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
10414
+ if (typeof entry.value === "string" && options.maxStringLength !== void 0 && entry.value.length > options.maxStringLength) {
10415
+ findings.push(
10416
+ failFinding(
10417
+ "safety.oversizedAttribute",
10418
+ `String at ${entry.path} exceeds ${options.maxStringLength} characters.`,
10419
+ [eventEvidence(event, entry.path)],
10420
+ { maxStringLength: options.maxStringLength },
10421
+ { path: entry.path, length: entry.value.length }
10422
+ )
10423
+ );
10424
+ }
10425
+ if (Array.isArray(entry.value) && options.maxArrayLength !== void 0 && entry.value.length > options.maxArrayLength) {
10426
+ findings.push(
10427
+ failFinding(
10428
+ "safety.oversizedAttribute",
10429
+ `Array at ${entry.path} exceeds ${options.maxArrayLength} items.`,
10430
+ [eventEvidence(event, entry.path)],
10431
+ { maxArrayLength: options.maxArrayLength },
10432
+ { path: entry.path, length: entry.value.length }
10433
+ )
10434
+ );
10435
+ }
10436
+ if (isRecord14(entry.value) && options.maxObjectKeys !== void 0 && Object.keys(entry.value).length > options.maxObjectKeys) {
10437
+ findings.push(
10438
+ failFinding(
10439
+ "safety.oversizedAttribute",
10440
+ `Object at ${entry.path} exceeds ${options.maxObjectKeys} keys.`,
10441
+ [eventEvidence(event, entry.path)],
10442
+ { maxObjectKeys: options.maxObjectKeys },
10443
+ { path: entry.path, keys: Object.keys(entry.value).length }
10444
+ )
10445
+ );
10446
+ }
10447
+ if (options.maxSerializedBytes !== void 0) {
10448
+ const bytes = serializedByteLength(entry.value);
10449
+ if (bytes !== void 0 && bytes > options.maxSerializedBytes) {
10450
+ findings.push(
10451
+ failFinding(
10452
+ "safety.oversizedAttribute",
10453
+ `Value at ${entry.path} exceeds ${options.maxSerializedBytes} serialized bytes.`,
10454
+ [eventEvidence(event, entry.path)],
10455
+ { maxSerializedBytes: options.maxSerializedBytes },
10456
+ { path: entry.path, bytes }
10457
+ )
10458
+ );
10459
+ }
10460
+ }
10461
+ }
10462
+ }
10463
+ return limitFindings(findings, options.maxFindings);
10464
+ }
10465
+ };
10466
+ }
10467
+ function createBaselineRegressionRule(options) {
10468
+ return {
10469
+ id: "baseline.regression",
10470
+ category: "baseline",
10471
+ defaultSeverity: "error",
10472
+ evaluate(context) {
10473
+ const baselineSelection = resolveSelectedRun(options.baseline, options.baselineRunId);
10474
+ if (baselineSelection.diagnostics.length > 0 || !baselineSelection.run) {
10475
+ return [
10476
+ failFinding(
10477
+ "baseline.regression",
10478
+ "Baseline run could not be selected.",
10479
+ runEvidence(context.selectedRun),
10480
+ "selectable baseline run",
10481
+ baselineSelection.diagnostics.map((item) => item.code)
10482
+ )
10483
+ ];
10484
+ }
10485
+ const baselineFacts = buildFacts2(options.baseline, baselineSelection.run);
10486
+ const baselineContext = {
10487
+ ...baselineFacts,
10488
+ selectedRun: baselineSelection.run,
10489
+ sourceLabel: options.baseline.sourceLabel
10490
+ };
10491
+ const findings = [];
10492
+ const durationToleranceMs = options.durationToleranceMs ?? 0;
10493
+ if (baselineContext.format !== context.format) {
10494
+ findings.push(
10495
+ baselineDiffFinding(
10496
+ "Trace format differs from baseline.",
10497
+ runEvidence(context.selectedRun),
10498
+ baselineContext.format,
10499
+ context.format
10500
+ )
10501
+ );
10502
+ }
10503
+ const baselineRunStatus = baselineContext.selectedRun?.status ?? "unknown";
10504
+ const candidateRunStatus = context.selectedRun?.status ?? "unknown";
10505
+ if (baselineRunStatus !== candidateRunStatus) {
10506
+ findings.push(
10507
+ baselineDiffFinding(
10508
+ "Run status differs from baseline.",
10509
+ runEvidence(context.selectedRun),
10510
+ baselineRunStatus,
10511
+ candidateRunStatus
10512
+ )
10513
+ );
10514
+ }
10515
+ const baselineDuration = baselineContext.selectedRun?.durationMs;
10516
+ const candidateDuration = context.selectedRun?.durationMs;
10517
+ if (baselineDuration !== void 0 && candidateDuration !== void 0 && Math.abs(candidateDuration - baselineDuration) > durationToleranceMs) {
10518
+ findings.push(
10519
+ baselineDiffFinding(
10520
+ "Run duration differs from baseline beyond tolerance.",
10521
+ runEvidence(context.selectedRun),
10522
+ { durationMs: baselineDuration, toleranceMs: durationToleranceMs },
10523
+ candidateDuration
10524
+ )
10525
+ );
10526
+ }
10527
+ const comparisons = [
10528
+ {
10529
+ label: "Tree shape",
10530
+ path: "tree",
10531
+ expected: treeShape(baselineContext.rootNodes),
10532
+ actual: treeShape(context.rootNodes),
10533
+ evidence: runEvidence(context.selectedRun)
10534
+ },
10535
+ {
10536
+ label: "Event statuses",
10537
+ path: "status",
10538
+ expected: statusShape(baselineContext),
10539
+ actual: statusShape(context),
10540
+ evidence: runEvidence(context.selectedRun)
10541
+ },
10542
+ {
10543
+ label: "Tool usage",
10544
+ path: "tool",
10545
+ expected: toolShape(baselineContext),
10546
+ actual: toolShape(context),
10547
+ evidence: firstEvidenceForKind(context, "TOOL", "tool")
10548
+ },
10549
+ {
10550
+ label: "LLM usage",
10551
+ path: "llm",
10552
+ expected: llmShape(baselineContext),
10553
+ actual: llmShape(context),
10554
+ evidence: firstEvidenceForKind(context, "LLM", "llm")
10555
+ },
10556
+ {
10557
+ label: "Error profile",
10558
+ path: "error",
10559
+ expected: errorShape(baselineContext),
10560
+ actual: errorShape(context),
10561
+ evidence: firstEvidenceForKind(context, "ERROR", "error")
10562
+ },
10563
+ {
10564
+ label: "Retrieval signals",
10565
+ path: "retrieval",
10566
+ expected: retrievalShape(baselineContext),
10567
+ actual: retrievalShape(context),
10568
+ evidence: firstEvidenceForKind(context, "RETRIEVER", "retrieval")
10569
+ },
10570
+ {
10571
+ label: "Guardrail signals",
10572
+ path: "guardrail",
10573
+ expected: guardrailShape(baselineContext),
10574
+ actual: guardrailShape(context),
10575
+ evidence: guardrailEvents(context)[0] ? [eventEvidence(guardrailEvents(context)[0], "guardrail")] : runEvidence(context.selectedRun)
10576
+ }
10577
+ ];
10578
+ for (const comparison of comparisons) {
10579
+ if (JSON.stringify(comparison.expected) === JSON.stringify(comparison.actual)) {
10580
+ continue;
10581
+ }
10582
+ findings.push(
10583
+ baselineDiffFinding(
10584
+ `${comparison.label} differs from baseline.`,
10585
+ comparison.evidence.length > 0 ? comparison.evidence : [{ runId: context.selectedRun?.runId, path: comparison.path }],
10586
+ comparison.expected,
10587
+ comparison.actual
10588
+ )
10589
+ );
10590
+ }
10591
+ return findings;
10592
+ }
10593
+ };
10594
+ }
10595
+ function runTraceChecks(input3, options = {}) {
10596
+ const selected = resolveSelectedRun(input3, options.runId);
10597
+ if (selected.diagnostics.length > 0) {
10598
+ return errorResult(input3, selected.diagnostics, selected.run);
10599
+ }
10600
+ const rules = selectRules(options.rules ?? [], options.select);
10601
+ if (rules.diagnostics.length > 0) {
10602
+ return errorResult(input3, rules.diagnostics, selected.run);
10603
+ }
10604
+ const facts = buildFacts2(input3, selected.run);
10605
+ const context = {
10606
+ ...facts,
10607
+ ...selected.run ? { selectedRun: selected.run } : {},
10608
+ ...input3.sourceLabel ? { sourceLabel: input3.sourceLabel } : {}
10609
+ };
10610
+ const diagnostics = [];
10611
+ const findings = [];
10612
+ for (const rule of rules.rules) {
10613
+ try {
10614
+ findings.push(...rule.evaluate(context).map((finding) => normalizeFinding(rule, finding)));
10615
+ } catch (error) {
10616
+ const message = error instanceof Error ? error.message : String(error);
10617
+ diagnostics.push(
10618
+ diagnostic("AI_CHECK_INTERNAL_ERROR", `Rule ${rule.id} failed: ${message}`, rule.id)
10619
+ );
10620
+ }
10621
+ }
10622
+ if (diagnostics.length > 0) {
10623
+ return errorResult(input3, diagnostics, selected.run);
10624
+ }
10625
+ const eventById = new Map(input3.read.events.map((event) => [event.eventId, event]));
10626
+ const sortedFindings = findings.sort(compareFindings(eventById));
10627
+ const summary = summarize(sortedFindings, diagnostics);
10628
+ const status = summary.failed > 0 ? "fail" : "pass";
10629
+ return {
10630
+ ok: status === "pass",
10631
+ status,
10632
+ format: input3.read.format,
10633
+ ...selected.run ? { runId: selected.run.runId } : {},
10634
+ summary,
10635
+ findings: sortedFindings,
10636
+ diagnostics
10637
+ };
10638
+ }
10639
+
10640
+ // packages/cli/src/check.ts
10641
+ var DEFAULT_SELECT = ["run.status"];
10642
+ var CONFIG_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".js", ".mjs", ".cjs"]);
10643
+ var TS_CONFIG_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".mts", ".cts"]);
10644
+ function diagnostic2(code, message, severity = "error") {
10645
+ return { code, message, severity };
10646
+ }
10647
+ function errorResult2(code, message, format = "unknown") {
10648
+ const diagnostics = [diagnostic2(code, message)];
10649
+ return {
10650
+ ok: false,
10651
+ status: "error",
10652
+ format,
10653
+ summary: {
10654
+ passed: 0,
10655
+ failed: 0,
10656
+ warnings: 0,
10657
+ errors: 1
10658
+ },
10659
+ findings: [],
10660
+ diagnostics
10661
+ };
10662
+ }
10663
+ function parseNumber(value, label) {
10664
+ if (value === void 0) return void 0;
10665
+ const parsed = Number(value);
10666
+ if (!Number.isFinite(parsed) || parsed < 0) {
10667
+ throw new Error(`${label} must be a non-negative number.`);
10668
+ }
10669
+ return parsed;
10670
+ }
10671
+ function asStringArray(value) {
10672
+ if (value === void 0) return void 0;
10673
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
10674
+ throw new Error("Expected an array of strings.");
10675
+ }
10676
+ return value;
10677
+ }
10678
+ function asConfig(value) {
10679
+ if (value === void 0 || value === null) return {};
10680
+ if (typeof value !== "object" || Array.isArray(value)) {
10681
+ throw new Error("Config must export an object.");
10682
+ }
10683
+ return value;
10684
+ }
10685
+ async function loadConfig(configPath) {
10686
+ if (configPath === void 0) return {};
10687
+ const extension = path10.extname(configPath);
10688
+ if (TS_CONFIG_EXTENSIONS.has(extension)) {
10689
+ throw new Error(
10690
+ "TypeScript check configs require an explicit precompiled JavaScript config or future --config-loader support."
10691
+ );
10692
+ }
10693
+ if (!CONFIG_EXTENSIONS.has(extension)) {
10694
+ throw new Error("Unsupported check config extension. Use .json, .js, .mjs, or .cjs.");
10695
+ }
10696
+ const absolute = path10.resolve(configPath);
10697
+ if (extension === ".json") {
10698
+ const raw = await readFile(absolute, "utf-8");
10699
+ return asConfig(JSON.parse(raw));
10700
+ }
10701
+ const mod = await import(pathToFileURL(absolute).href);
10702
+ return asConfig("default" in mod ? mod.default : mod);
10703
+ }
10704
+ function normalizeConfig(config) {
10705
+ if (config.checks === void 0) return {};
10706
+ if (typeof config.checks !== "object" || Array.isArray(config.checks)) {
10707
+ throw new Error("checks config must be an object.");
10708
+ }
10709
+ return config.checks;
10710
+ }
10711
+ function buildRules(config, options) {
10712
+ const diagnostics = [];
10713
+ const checks = normalizeConfig(config);
10714
+ const run = checks.run ?? {};
10715
+ const tool = checks.tool ?? {};
10716
+ const llm = checks.llm ?? {};
10717
+ const structure = checks.structure ?? {};
10718
+ const safety = checks.safety ?? {};
10719
+ const maxDurationMs = parseNumber(options.maxDurationMs, "--max-duration-ms") ?? run.maxDurationMs;
10720
+ const maxTotalTokens = parseNumber(options.maxTotalTokens, "--max-total-tokens") ?? llm.maxTotalTokens;
10721
+ const rules = [
10722
+ createRunStatusRule(run),
10723
+ createStructureOrphanRule(),
10724
+ createStructureCycleRule(),
10725
+ createSafetyRawContentRule(),
10726
+ createSafetySecretPatternRule()
10727
+ ];
10728
+ if (maxDurationMs !== void 0) {
10729
+ rules.push(createRunDurationRule({ maxDurationMs }));
10730
+ }
10731
+ if (run.maxDepth !== void 0) {
10732
+ rules.push(createRunDepthRule({ maxDepth: run.maxDepth }));
10733
+ }
10734
+ const toolOptions = {
10735
+ ...tool,
10736
+ required: [...tool.required ?? [], ...options.requiredTool ?? []],
10737
+ forbidden: [...tool.forbidden ?? [], ...options.forbiddenTool ?? []]
10738
+ };
10739
+ if (toolOptions.required?.length || toolOptions.forbidden?.length || toolOptions.allowed?.length || toolOptions.minCount !== void 0 || toolOptions.maxCount !== void 0) {
10740
+ rules.push(createToolUsageRule(toolOptions));
10741
+ }
10742
+ const llmOptions = {
10743
+ ...llm,
10744
+ allowedModels: [...llm.allowedModels ?? [], ...options.allowedModel ?? []],
10745
+ ...maxTotalTokens !== void 0 ? { maxTotalTokens } : {}
10746
+ };
10747
+ if (llmOptions.allowedModels?.length || llmOptions.allowedProviders?.length || llmOptions.finishReasons?.length || llmOptions.maxCalls !== void 0 || llmOptions.maxInputTokens !== void 0 || llmOptions.maxOutputTokens !== void 0 || llmOptions.maxTotalTokens !== void 0 || llmOptions.maxCachedTokens !== void 0) {
10748
+ rules.push(createLlmUsageRule(llmOptions));
10749
+ }
10750
+ if (structure.minConfidence !== void 0 || structure.requireParentBeforeChild !== void 0 || structure.requireTraceParentSpan !== void 0) {
10751
+ rules.push(createStructureRelationshipRule(structure));
10752
+ }
10753
+ if (structure.maxChildren !== void 0 || structure.maxConcurrent !== void 0) {
10754
+ rules.push(
10755
+ createStructureParallelWidthRule({
10756
+ maxChildren: structure.maxChildren,
10757
+ maxConcurrent: structure.maxConcurrent
10758
+ })
10759
+ );
10760
+ }
10761
+ if (safety.redaction) rules.push(createSafetyRedactionRule());
10762
+ if (safety.maxStringLength !== void 0 || safety.maxArrayLength !== void 0 || safety.maxObjectKeys !== void 0 || safety.maxSerializedBytes !== void 0) {
10763
+ rules.push(createSafetyOversizedAttributeRule(safety));
10764
+ }
10765
+ const select = [
10766
+ ...asStringArray(checks.select) ?? [],
10767
+ ...options.rule ?? []
10768
+ ];
10769
+ return {
10770
+ rules,
10771
+ select: select.length > 0 ? select : DEFAULT_SELECT,
10772
+ diagnostics
10773
+ };
10774
+ }
10775
+ function exitCodeFor(result) {
10776
+ if (result.status === "pass") return 0;
10777
+ if (result.status === "fail") return 1;
10778
+ const codes = result.diagnostics.map((item) => item.code);
10779
+ if (codes.some(
10780
+ (code) => code === "AI_CHECK_UNSUPPORTED_FORMAT" || code === "AI_CHECK_AMBIGUOUS_FORMAT"
10781
+ )) {
10782
+ return 4;
10783
+ }
10784
+ if (codes.some(
10785
+ (code) => code === "AI_CHECK_TRACE_UNREADABLE" || code === "AI_CHECK_BASELINE_UNREADABLE"
10786
+ )) {
10787
+ return 3;
10788
+ }
10789
+ if (codes.some(
10790
+ (code) => code === "AI_CHECK_INVALID_ARGUMENTS" || code === "AI_CHECK_INVALID_CONFIG" || code === "AI_CHECK_CONFIG_LOAD_FAILED" || code === "AI_CHECK_RUN_SELECTION_REQUIRED"
10791
+ )) {
10792
+ return 2;
10793
+ }
10794
+ return 1;
10795
+ }
10796
+ function stable(value) {
10797
+ if (Array.isArray(value)) return value.map(stable);
10798
+ if (value === null || typeof value !== "object") return value;
10799
+ const record = value;
10800
+ return Object.fromEntries(
10801
+ Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable(record[key])])
10802
+ );
10803
+ }
10804
+ function printJson(result) {
10805
+ console.log(JSON.stringify(stable(result), null, 2));
10806
+ }
10807
+ function printHuman(result) {
10808
+ console.log(`Check status: ${result.status}`);
10809
+ console.log(`Format: ${result.format}`);
10810
+ if (result.runId !== void 0) console.log(`Run: ${result.runId}`);
10811
+ console.log(
10812
+ `Summary: ${result.summary.failed} failed, ${result.summary.warnings} warning(s), ${result.summary.errors} error(s)`
10813
+ );
10814
+ for (const diagnostic3 of result.diagnostics) {
10815
+ console.log(`- ${diagnostic3.code}: ${diagnostic3.message}`);
10816
+ }
10817
+ for (const finding of result.findings) {
10818
+ const path12 = finding.evidence[0]?.path;
10819
+ console.log(`- ${finding.ruleId}: ${finding.message}${path12 ? ` (${path12})` : ""}`);
10820
+ }
10821
+ }
10822
+ function readErrorResult(error) {
10823
+ if (error instanceof TraceReadError) {
10824
+ const code = error.code === "unsupported_format" ? "AI_CHECK_UNSUPPORTED_FORMAT" : error.code === "ambiguous_format" ? "AI_CHECK_AMBIGUOUS_FORMAT" : "AI_CHECK_TRACE_UNREADABLE";
10825
+ return errorResult2(code, error.message);
10826
+ }
10827
+ return errorResult2(
10828
+ "AI_CHECK_TRACE_UNREADABLE",
10829
+ error instanceof Error ? error.message : String(error)
10830
+ );
10831
+ }
10832
+ async function checkCommand(target, options = {}, stdin = process.stdin) {
10833
+ let result;
10834
+ let phase = "config";
10835
+ try {
10836
+ const config = await loadConfig(options.config);
10837
+ const built = buildRules(config, options);
10838
+ if (built.diagnostics.some((item) => item.severity === "error")) {
10839
+ result = errorResult2("AI_CHECK_INVALID_CONFIG", "Invalid check configuration.");
10840
+ result.diagnostics = [...built.diagnostics];
10841
+ } else {
10842
+ phase = "read";
10843
+ const input3 = await inputFromTarget(target, options, stdin);
10844
+ const read = await openTrace(input3, {
10845
+ ...options.format !== void 0 ? { format: options.format } : {}
10846
+ });
10847
+ result = runTraceChecks(
10848
+ { read },
10849
+ {
10850
+ rules: built.rules,
10851
+ select: built.select,
10852
+ ...options.run !== void 0 ? { runId: options.run } : {}
10853
+ }
10854
+ );
10855
+ }
10856
+ } catch (error) {
10857
+ if (phase === "config") {
10858
+ const message = error instanceof Error ? error.message : String(error);
10859
+ const code = message.startsWith("--") ? "AI_CHECK_INVALID_ARGUMENTS" : error instanceof SyntaxError || message.includes("Unsupported check config extension") || message.includes("TypeScript check configs") || message.includes("Config must") || message.includes("checks config") || message.includes("Expected an array") ? "AI_CHECK_INVALID_CONFIG" : "AI_CHECK_CONFIG_LOAD_FAILED";
10860
+ result = errorResult2(
10861
+ code,
10862
+ message
10863
+ );
10864
+ } else {
10865
+ result = readErrorResult(error);
10866
+ }
10867
+ }
10868
+ process.exitCode = exitCodeFor(result);
10869
+ if (options.json) printJson(result);
10870
+ else printHuman(result);
10871
+ }
10872
+
10873
+ // packages/cli/src/safety.ts
10874
+ var BEST_EFFORT_NOTE = "Best-effort local safety verification only; not a compliance, privacy, security, or regulatory certification.";
10875
+ var DEFAULT_MAX_STRING_LENGTH = 16384;
10876
+ var DEFAULT_MAX_ARRAY_LENGTH = 1e3;
10877
+ var DEFAULT_MAX_OBJECT_KEYS = 200;
10878
+ var DEFAULT_MAX_SERIALIZED_BYTES = 128 * 1024;
10879
+ function parseLimit3(value, label) {
10880
+ if (value === void 0) return void 0;
10881
+ const parsed = Number(value);
10882
+ if (!Number.isFinite(parsed) || parsed < 0) {
10883
+ throw new Error(`${label} must be a non-negative number.`);
10884
+ }
10885
+ return parsed;
10886
+ }
10887
+ function stable2(value) {
10888
+ if (Array.isArray(value)) return value.map(stable2);
10889
+ if (value === null || typeof value !== "object") return value;
10890
+ const record = value;
10891
+ return Object.fromEntries(
10892
+ Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable2(record[key])])
10893
+ );
10894
+ }
10895
+ function safetyDiagnostic(code, message, severity = "error") {
10896
+ return { code, message, severity };
10897
+ }
10898
+ function warningDiagnostics(warnings, unsupportedFields) {
10899
+ return [
10900
+ ...warnings.map(
10901
+ (warning) => safetyDiagnostic(
10902
+ warning.code,
10903
+ warning.message,
10904
+ warning.severity === "error" ? "error" : "warning"
10905
+ )
10906
+ ),
10907
+ ...unsupportedFields.map(
10908
+ (field) => safetyDiagnostic(
10909
+ "unsupported_field",
10910
+ `Reader reported unsupported field: ${field}`,
10911
+ "warning"
10912
+ )
10913
+ )
10914
+ ];
10915
+ }
10916
+ function diagnosticFromCheck(item) {
10917
+ return safetyDiagnostic(item.code, item.message, item.severity);
10918
+ }
10919
+ function statusFrom(findings, diagnostics) {
10920
+ if (diagnostics.some((item) => item.severity === "error")) return "UNKNOWN";
10921
+ if (findings.some((item) => item.severity === "error")) return "UNSAFE";
10922
+ if (diagnostics.some((item) => item.severity === "warning")) return "SAFE WITH WARNINGS";
10923
+ if (findings.some((item) => item.severity === "warning")) return "SAFE WITH WARNINGS";
10924
+ return "SAFE";
10925
+ }
10926
+ function resultFromParts(parts) {
10927
+ const findings = [...parts.findings ?? []];
10928
+ const diagnostics = [...parts.diagnostics ?? []];
10929
+ const warnings = [...parts.warnings ?? []];
10930
+ const unsupportedFields = [...parts.unsupportedFields ?? []];
10931
+ const status = statusFrom(findings, diagnostics);
10932
+ return {
10933
+ ok: status === "SAFE" || status === "SAFE WITH WARNINGS",
10934
+ command: parts.command,
10935
+ status,
10936
+ format: parts.format,
10937
+ ...parts.runId !== void 0 ? { runId: parts.runId } : {},
10938
+ summary: {
10939
+ findings: findings.length,
10940
+ warnings: diagnostics.filter((item) => item.severity === "warning").length + findings.filter((item) => item.severity === "warning").length,
10941
+ errors: diagnostics.filter((item) => item.severity === "error").length + findings.filter((item) => item.severity === "error").length
10942
+ },
10943
+ findings,
10944
+ diagnostics,
10945
+ warnings,
10946
+ unsupportedFields,
10947
+ note: BEST_EFFORT_NOTE
10948
+ };
10949
+ }
10950
+ function readErrorResult2(command, error) {
10951
+ if (error instanceof TraceReadError) {
10952
+ const code = error.code === "unsupported_format" ? "AI_SAFETY_UNSUPPORTED_FORMAT" : error.code === "ambiguous_format" ? "AI_SAFETY_AMBIGUOUS_FORMAT" : "AI_SAFETY_TRACE_UNREADABLE";
10953
+ return resultFromParts({
10954
+ command,
10955
+ format: "unknown",
10956
+ diagnostics: [safetyDiagnostic(code, error.message)],
10957
+ warnings: error.warnings
10958
+ });
10959
+ }
10960
+ return resultFromParts({
10961
+ command,
10962
+ format: "unknown",
10963
+ diagnostics: [
10964
+ safetyDiagnostic(
10965
+ "AI_SAFETY_TRACE_UNREADABLE",
10966
+ error instanceof Error ? error.message : String(error)
10967
+ )
10968
+ ]
10969
+ });
10970
+ }
10971
+ function invalidArgumentResult(command, error) {
10972
+ return resultFromParts({
10973
+ command,
10974
+ format: "unknown",
10975
+ diagnostics: [
10976
+ safetyDiagnostic(
10977
+ "AI_SAFETY_INVALID_ARGUMENTS",
10978
+ error instanceof Error ? error.message : String(error)
10979
+ )
10980
+ ]
10981
+ });
10982
+ }
10983
+ function buildSafetyRules(options) {
10984
+ const maxStringLength = parseLimit3(options.maxStringLength, "--max-string-length") ?? DEFAULT_MAX_STRING_LENGTH;
10985
+ const maxArrayLength = parseLimit3(options.maxArrayLength, "--max-array-length") ?? DEFAULT_MAX_ARRAY_LENGTH;
10986
+ const maxObjectKeys = parseLimit3(options.maxObjectKeys, "--max-object-keys") ?? DEFAULT_MAX_OBJECT_KEYS;
10987
+ const maxSerializedBytes = parseLimit3(options.maxSerializedBytes, "--max-serialized-bytes") ?? DEFAULT_MAX_SERIALIZED_BYTES;
10988
+ return [
10989
+ createSafetyRawContentRule(),
10990
+ createSafetyRedactionRule(),
10991
+ createSafetySecretPatternRule(),
10992
+ createSafetyOversizedAttributeRule({
10993
+ maxStringLength,
10994
+ maxArrayLength,
10995
+ maxObjectKeys,
10996
+ maxSerializedBytes
10997
+ })
10998
+ ];
10999
+ }
11000
+ function exitCodeFor2(result) {
11001
+ if (result.status === "SAFE" || result.status === "SAFE WITH WARNINGS") return 0;
11002
+ if (result.status === "UNSAFE") return 1;
11003
+ return 2;
11004
+ }
11005
+ function printJson2(result) {
11006
+ console.log(JSON.stringify(stable2(result), null, 2));
11007
+ }
11008
+ function printHuman2(result) {
11009
+ console.log(`Safety status: ${result.status}`);
11010
+ console.log(`Format: ${result.format}`);
11011
+ if (result.runId !== void 0) console.log(`Run: ${result.runId}`);
11012
+ console.log(
11013
+ `Summary: ${result.summary.findings} finding(s), ${result.summary.warnings} warning(s), ${result.summary.errors} error(s)`
11014
+ );
11015
+ for (const diagnostic3 of result.diagnostics) {
11016
+ console.log(`- ${diagnostic3.code}: ${diagnostic3.message}`);
11017
+ }
11018
+ for (const finding of result.findings) {
11019
+ const path12 = finding.evidence[0]?.path;
11020
+ console.log(`- ${finding.ruleId}: ${finding.message}${path12 ? ` (${path12})` : ""}`);
11021
+ }
11022
+ console.log(`Note: ${result.note}`);
11023
+ }
11024
+ async function safetyCommand(command, target, options, stdin) {
11025
+ let result;
11026
+ try {
11027
+ const rules = buildSafetyRules(options);
11028
+ const input3 = await inputFromTarget(target, options, stdin);
11029
+ const read = await openTrace(input3, {
11030
+ ...options.format !== void 0 ? { format: options.format } : {}
11031
+ });
11032
+ const checkResult = runTraceChecks(
11033
+ { read },
11034
+ {
11035
+ rules,
11036
+ ...options.run !== void 0 ? { runId: options.run } : {}
11037
+ }
11038
+ );
11039
+ result = resultFromParts({
11040
+ command,
11041
+ format: checkResult.format,
11042
+ runId: checkResult.runId,
11043
+ findings: checkResult.findings,
11044
+ diagnostics: [
11045
+ ...checkResult.diagnostics.map(diagnosticFromCheck),
11046
+ ...warningDiagnostics(read.warnings, read.unsupportedFields)
11047
+ ],
11048
+ warnings: read.warnings,
11049
+ unsupportedFields: read.unsupportedFields
11050
+ });
11051
+ } catch (error) {
11052
+ const message = error instanceof Error ? error.message : String(error);
11053
+ result = message.startsWith("--") ? invalidArgumentResult(command, error) : readErrorResult2(command, error);
11054
+ }
11055
+ process.exitCode = exitCodeFor2(result);
11056
+ if (options.json) printJson2(result);
11057
+ else printHuman2(result);
11058
+ }
11059
+ function scanCommand(target, options = {}, stdin = process.stdin) {
11060
+ return safetyCommand("scan", target, options, stdin);
11061
+ }
11062
+ function verifySafeCommand(target, options = {}, stdin = process.stdin) {
11063
+ return safetyCommand("verify-safe", target, options, stdin);
11064
+ }
11065
+ var NOTE = "Generated locally by AgentInspect. Artifacts are best-effort summaries, not compliance or security certification.";
11066
+ var SAFETY_RULES = [
11067
+ createSafetyRawContentRule(),
11068
+ createSafetyRedactionRule(),
11069
+ createSafetySecretPatternRule(),
11070
+ createSafetyOversizedAttributeRule({
11071
+ maxStringLength: 16384,
11072
+ maxArrayLength: 1e3,
11073
+ maxObjectKeys: 200,
11074
+ maxSerializedBytes: 128 * 1024
11075
+ })
11076
+ ];
11077
+ function stable3(value) {
11078
+ if (Array.isArray(value)) return value.map(stable3);
11079
+ if (value === null || typeof value !== "object") return value;
11080
+ const record = value;
11081
+ return Object.fromEntries(
11082
+ Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable3(record[key])])
11083
+ );
11084
+ }
11085
+ function writeJson3(value) {
11086
+ return `${JSON.stringify(stable3(value), null, 2)}
11087
+ `;
11088
+ }
11089
+ function escapeHtml2(value) {
11090
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
11091
+ }
11092
+ function markdownTable(rows) {
11093
+ const lines = ["| Field | Value |", "| --- | --- |"];
11094
+ for (const [key, value] of rows) {
11095
+ lines.push(`| ${key} | ${value ?? "unknown"} |`);
11096
+ }
11097
+ return lines.join("\n");
11098
+ }
11099
+ function increment(record, key) {
11100
+ const label = key && key.trim() !== "" ? key : "unknown";
11101
+ record[label] = (record[label] ?? 0) + 1;
11102
+ }
11103
+ function selectRun3(read, runId) {
11104
+ if (runId !== void 0) {
11105
+ return read.runs.find((run) => run.runId === runId);
11106
+ }
11107
+ return read.runs.length === 1 ? read.runs[0] : void 0;
11108
+ }
11109
+ function summarizeTrace(read, run) {
11110
+ const runId = run?.runId;
11111
+ const scopedEvents = runId === void 0 ? read.events : read.events.filter((event) => event.runId === runId);
11112
+ const eventsByKind = {};
11113
+ const eventsByStatus = {};
11114
+ for (const event of scopedEvents) {
11115
+ increment(eventsByKind, event.kind);
11116
+ increment(eventsByStatus, event.status);
11117
+ }
11118
+ return {
11119
+ format: read.format,
11120
+ ...runId !== void 0 ? { runId } : {},
11121
+ ...run?.status !== void 0 ? { runStatus: run.status } : {},
11122
+ ...run?.durationMs !== void 0 ? { runDurationMs: run.durationMs } : {},
11123
+ runCount: read.runs.length,
11124
+ eventCount: scopedEvents.length,
11125
+ eventsByKind: Object.fromEntries(Object.entries(eventsByKind).sort()),
11126
+ eventsByStatus: Object.fromEntries(Object.entries(eventsByStatus).sort()),
11127
+ readerWarnings: read.warnings.length,
11128
+ unsupportedFields: read.unsupportedFields.length
11129
+ };
11130
+ }
11131
+ function renderCheckSection(result) {
11132
+ const lines = [
11133
+ `Status: ${result.status}`,
11134
+ `Findings: ${result.findings.length}`,
11135
+ `Diagnostics: ${result.diagnostics.length}`
11136
+ ];
11137
+ for (const finding of result.findings.slice(0, 10)) {
11138
+ const path12 = finding.evidence[0]?.path ?? "(run)";
11139
+ lines.push(`- ${finding.ruleId}: ${finding.message} (${path12})`);
11140
+ }
11141
+ for (const diagnostic3 of result.diagnostics.slice(0, 10)) {
11142
+ lines.push(`- ${diagnostic3.code}: ${diagnostic3.message}`);
11143
+ }
11144
+ return lines.join("\n");
11145
+ }
11146
+ function renderMarkdown(trace, check, diff) {
11147
+ const lines = [
11148
+ "# AgentInspect CI Artifacts",
11149
+ "",
11150
+ NOTE,
11151
+ "",
11152
+ "## Trace",
11153
+ "",
11154
+ markdownTable([
11155
+ ["Format", trace.format],
11156
+ ["Run", trace.runId],
11157
+ ["Run status", trace.runStatus],
11158
+ ["Run duration ms", trace.runDurationMs],
11159
+ ["Runs", trace.runCount],
11160
+ ["Events", trace.eventCount],
11161
+ ["Reader warnings", trace.readerWarnings],
11162
+ ["Unsupported fields", trace.unsupportedFields]
11163
+ ]),
11164
+ "",
11165
+ "## Safety check",
11166
+ "",
11167
+ "```text",
11168
+ renderCheckSection(check),
11169
+ "```",
11170
+ "",
11171
+ "## Baseline diff",
11172
+ ""
11173
+ ];
11174
+ if (diff) {
11175
+ lines.push("```text", renderCheckSection(diff), "```", "");
11176
+ } else {
11177
+ lines.push("No baseline was supplied.", "");
11178
+ }
11179
+ return lines.join("\n");
11180
+ }
11181
+ function renderHtml(trace, check, diff) {
11182
+ const rows = [
11183
+ ["Format", trace.format],
11184
+ ["Run", trace.runId],
11185
+ ["Run status", trace.runStatus],
11186
+ ["Run duration ms", trace.runDurationMs],
11187
+ ["Runs", trace.runCount],
11188
+ ["Events", trace.eventCount],
11189
+ ["Reader warnings", trace.readerWarnings],
11190
+ ["Unsupported fields", trace.unsupportedFields]
11191
+ ];
11192
+ const table = rows.map(
11193
+ ([key, value]) => `<tr><th>${escapeHtml2(key)}</th><td>${escapeHtml2(String(value ?? "unknown"))}</td></tr>`
11194
+ ).join("");
11195
+ return `<!doctype html>
11196
+ <html lang="en">
11197
+ <head>
11198
+ <meta charset="utf-8"/>
11199
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
11200
+ <title>AgentInspect CI Artifacts</title>
11201
+ <style>body{font-family:system-ui,sans-serif;line-height:1.5;margin:1.5rem;max-width:960px;color:#111}table{border-collapse:collapse}th,td{border:1px solid #ddd;padding:0.35rem 0.5rem;text-align:left}pre{white-space:pre-wrap;background:#f8f8f8;padding:0.75rem;overflow:auto}</style>
11202
+ </head>
11203
+ <body>
11204
+ <h1>AgentInspect CI Artifacts</h1>
11205
+ <p>${escapeHtml2(NOTE)}</p>
11206
+ <h2>Trace</h2>
11207
+ <table><tbody>${table}</tbody></table>
11208
+ <h2>Safety check</h2>
11209
+ <pre>${escapeHtml2(renderCheckSection(check))}</pre>
11210
+ <h2>Baseline diff</h2>
11211
+ <pre>${escapeHtml2(diff ? renderCheckSection(diff) : "No baseline was supplied.")}</pre>
11212
+ </body>
11213
+ </html>
11214
+ `;
11215
+ }
11216
+ async function writeArtifact(outputDir, relativePath, content, files) {
11217
+ const outPath = path10.join(outputDir, relativePath);
11218
+ await mkdir(path10.dirname(outPath), { recursive: true });
11219
+ await writeFile(outPath, content, "utf-8");
11220
+ files.push(relativePath);
11221
+ }
11222
+ function readErrorMessage(error) {
11223
+ if (error instanceof TraceReadError) return error.message;
11224
+ return error instanceof Error ? error.message : String(error);
11225
+ }
11226
+ function manifestStatus(check, diff) {
11227
+ if (check.status === "error" || diff?.status === "error") return "unknown";
11228
+ if (check.status === "fail") return "unsafe";
11229
+ if (diff?.status === "fail") return "regression";
11230
+ if (check.summary.warnings > 0 || (diff?.summary.warnings ?? 0) > 0) return "warning";
11231
+ return "ok";
11232
+ }
11233
+ async function artifactsCommand(target, options = {}, stdin = process.stdin) {
11234
+ const outputDir = options.outputDir !== void 0 && options.outputDir.trim() !== "" ? path10.resolve(options.outputDir.trim()) : "";
11235
+ if (outputDir === "") {
11236
+ console.error("--output-dir is required.");
11237
+ process.exitCode = 1;
11238
+ return;
11239
+ }
11240
+ let read;
11241
+ try {
11242
+ const input3 = await inputFromTarget(target, options, stdin);
11243
+ read = await openTrace(input3, {
11244
+ ...options.format !== void 0 ? { format: options.format } : {}
11245
+ });
11246
+ } catch (error) {
11247
+ console.error(`[AgentInspect] artifacts failed: ${readErrorMessage(error)}`);
11248
+ process.exitCode = 1;
11249
+ return;
11250
+ }
11251
+ const selectedRun = selectRun3(read, options.run);
11252
+ const check = runTraceChecks(
11253
+ { read },
11254
+ {
11255
+ rules: SAFETY_RULES,
11256
+ ...options.run !== void 0 ? { runId: options.run } : {}
11257
+ }
11258
+ );
11259
+ const trace = summarizeTrace(read, selectedRun);
11260
+ let diff;
11261
+ if (options.baseline !== void 0 && options.baseline.trim() !== "") {
11262
+ try {
11263
+ const baselineInput = await inputFromTarget(options.baseline, options, stdin);
11264
+ const baselineRead = await openTrace(baselineInput, {
11265
+ ...options.format !== void 0 ? { format: options.format } : {}
11266
+ });
11267
+ diff = runTraceChecks(
11268
+ { read },
11269
+ {
11270
+ rules: [
11271
+ createBaselineRegressionRule({
11272
+ baseline: { read: baselineRead },
11273
+ ...options.baselineRun !== void 0 ? { baselineRunId: options.baselineRun } : {},
11274
+ compareFormat: true
11275
+ })
11276
+ ],
11277
+ ...options.run !== void 0 ? { runId: options.run } : {}
11278
+ }
11279
+ );
11280
+ } catch (error) {
11281
+ console.error(`[AgentInspect] baseline diff failed: ${readErrorMessage(error)}`);
11282
+ process.exitCode = 1;
11283
+ return;
11284
+ }
11285
+ }
11286
+ const files = [];
11287
+ await mkdir(outputDir, { recursive: true });
11288
+ await writeArtifact(outputDir, "trace.json", writeJson3(trace), files);
11289
+ await writeArtifact(outputDir, "check.json", writeJson3(check), files);
11290
+ await writeArtifact(
11291
+ outputDir,
11292
+ "diff.json",
11293
+ writeJson3(diff ?? { status: "not_requested", findings: [], diagnostics: [] }),
11294
+ files
11295
+ );
11296
+ await writeArtifact(outputDir, "summary.md", renderMarkdown(trace, check, diff), files);
11297
+ await writeArtifact(outputDir, "report.html", renderHtml(trace, check, diff), files);
11298
+ const summaryTarget = options.githubSummary ?? process.env.GITHUB_STEP_SUMMARY;
11299
+ if (summaryTarget !== void 0 && summaryTarget.trim() !== "") {
11300
+ await mkdir(path10.dirname(path10.resolve(summaryTarget)), { recursive: true });
11301
+ await appendFile(path10.resolve(summaryTarget), `
11302
+ ${renderMarkdown(trace, check, diff)}`, "utf-8");
11303
+ }
11304
+ const manifestFiles = [...files, "manifest.json"].sort((a, b) => a.localeCompare(b));
11305
+ const manifest = {
11306
+ status: manifestStatus(check, diff),
11307
+ outputDir,
11308
+ files: manifestFiles,
11309
+ trace,
11310
+ check: {
11311
+ status: check.status,
11312
+ findings: check.findings.length,
11313
+ diagnostics: check.diagnostics.length
11314
+ },
11315
+ diff: {
11316
+ status: diff?.status ?? "not_requested",
11317
+ findings: diff?.findings.length ?? 0,
11318
+ diagnostics: diff?.diagnostics.length ?? 0
11319
+ },
11320
+ ...summaryTarget !== void 0 && summaryTarget.trim() !== "" ? { githubSummary: path10.resolve(summaryTarget) } : {},
11321
+ note: NOTE
11322
+ };
11323
+ await writeFile(path10.join(outputDir, "manifest.json"), writeJson3(manifest), "utf-8");
11324
+ if (options.json === true) {
11325
+ console.log(writeJson3(manifest).trimEnd());
11326
+ } else {
11327
+ console.log(`Wrote AgentInspect artifacts to ${outputDir}`);
11328
+ console.log(`Status: ${manifest.status}`);
11329
+ for (const file of manifest.files) {
11330
+ console.log(`- ${file}`);
11331
+ }
11332
+ }
11333
+ }
11334
+
11335
+ // packages/cli/src/index.ts
11336
+ function runCommand(action) {
11337
+ void action().catch((error) => {
11338
+ const msg = error instanceof Error ? error.message : String(error);
11339
+ console.error(`[AgentInspect] ${msg}`);
11340
+ process.exitCode = 1;
11341
+ });
11342
+ }
11343
+ function createCliProgram() {
11344
+ const program = new Command("agent-inspect").description("Local-first execution-tree debugger for AI agents").version(version);
11345
+ program.command("list").description("List recent AgentInspect runs").option("--dir <path>", "trace directory").option("--limit <number>", "max runs to show (default 20, max 100)").addOption(
11346
+ new Option("--status <status>", "filter by run status").choices([
11347
+ "running",
11348
+ "success",
11349
+ "error",
11350
+ "unknown"
11351
+ ])
11352
+ ).option("--name <query>", "filter by run name or id (substring match)").option(
11353
+ "--since <duration>",
11354
+ "only include runs since a duration (e.g. 30s, 5m, 2h, 7d)"
11355
+ ).option("--json", "print runs as JSON").action(
11356
+ (opts) => {
11357
+ runCommand(() => list(opts));
11358
+ }
11359
+ );
11360
+ program.command("view").description("View a single run trace").argument("<run-id>", "run id (e.g. from list output)").option("--dir <path>", "trace directory").option("--summary", "print a run summary (counts, duration, max depth)").option("--metadata", "print trace metadata (file path/size, timestamps)").option("--errors-only", "show only error events / failed steps").option("--verbose", "show extra detail (types, metadata, error stacks)").option("--json", "print raw trace events as JSON").option(
11361
+ "--tui",
11362
+ "open optional interactive TUI viewer (requires @agent-inspect/tui)"
11363
+ ).action(
11364
+ (runId, opts) => {
11365
+ runCommand(() => view(runId, opts));
11366
+ }
11367
+ );
11368
+ program.command("clean").description("Safely delete old AgentInspect run traces").option("--dir <path>", "trace directory").option(
11369
+ "--older-than <duration>",
11370
+ "delete runs older than a duration (e.g. 30s, 5m, 2h, 7d)"
11371
+ ).option("--keep <count>", "keep N most recent runs (delete the rest)").option("--dry-run", "print what would be deleted (no changes)").option("--yes", "skip confirmation prompt").action(
11372
+ (opts) => {
11373
+ runCommand(() => clean(opts));
11374
+ }
11375
+ );
11376
+ program.command("logs").description("Parse structured logs into execution trees").argument("<file>", "path to log file").addOption(
11377
+ new Option("--format <format>", "log format").choices([
11378
+ "auto",
11379
+ "json",
11380
+ "log4js"
11381
+ ])
11382
+ ).option("--config <path>", "path to log ingest config (JSON)").option(
11383
+ "--run-id-key <keys>",
11384
+ "override run id keys (comma-separated, e.g. decisionId,requestId,jobId)"
11385
+ ).option("--event-key <key>", "override event key").option("--timestamp-key <key>", "override timestamp key").option("--message-key <key>", "override message key").option("--level-key <key>", "override level key").option("--parent-id-key <key>", "override parent id key").option("--duration-key <key>", "override duration key").option("--status-key <key>", "override status key").option("--json", "print result as JSON").option("--summary", "include summary section in human output").addOption(
11386
+ new Option("--warnings <mode>", "warning output mode").choices([
11387
+ "summary",
11388
+ "all",
11389
+ "none"
11390
+ ])
11391
+ ).option("--verbose", "show more detail (reserved for future)").option("--no-color", "disable color output").action((file, opts) => {
11392
+ runCommand(() => logs(file, opts));
11393
+ });
11394
+ program.command("tail").description("Live tail structured logs into execution trees").option("--file <path>", "tail a log file (default: read from stdin)").addOption(
11395
+ new Option("--format <format>", "log format").choices([
11396
+ "auto",
11397
+ "json",
11398
+ "log4js"
11399
+ ])
11400
+ ).option("--config <path>", "path to log ingest config (JSON)").option(
11401
+ "--run-id-key <keys>",
11402
+ "override run id keys (comma-separated, e.g. decisionId,requestId,jobId)"
11403
+ ).option("--event-key <key>", "override event key").option("--timestamp-key <key>", "override timestamp key").option("--message-key <key>", "override message key").option("--level-key <key>", "override level key").option("--parent-id-key <key>", "override parent id key").option("--duration-key <key>", "override duration key").option("--status-key <key>", "override status key").addOption(
11404
+ new Option("--warnings <mode>", "warning output mode").choices([
11405
+ "summary",
11406
+ "all",
11407
+ "none"
11408
+ ])
11409
+ ).option("--refresh <ms>", "minimum time between renders (ms)").option("--once", "read once and exit (for --file)").option("--json", "print newline-delimited JSON updates").option("--no-clear", "do not clear screen between renders").option("--verbose", "show more detail (reserved for future)").option("--no-color", "disable color output").action((opts) => {
11410
+ runCommand(() => tail(opts));
11411
+ });
11412
+ program.command("export").description("Export a manual trace run (Markdown, HTML, OpenInference-compatible JSON, OTLP JSON)").argument("<run-id>", "run id (e.g. from list output)").option("--dir <path>", "trace directory").addOption(
11413
+ new Option("--format <format>", "export format (default: markdown)").choices([
11414
+ "markdown",
11415
+ "html",
11416
+ "openinference",
11417
+ "otlp-json"
11418
+ ])
11419
+ ).option("-o, --output <path>", "write export to file (creates parent dirs)").option("--json", "emit JSON wrapper about the export (includes content when writing to stdout)").option("--validate", "validate exported payload shape after generation").option("--include-attributes", "include bounded attributes (review before sharing)").option("--no-metadata", "omit summary / metadata sections").option("--no-errors", "omit error sections").addOption(
11420
+ new Option(
11421
+ "--redaction-profile <profile>",
11422
+ "redaction profile for exported copies: local, share, strict (default: local)"
11423
+ ).choices(["local", "share", "strict"])
11424
+ ).action((runId, opts) => {
11425
+ runCommand(() => exportCommand(runId, opts));
11426
+ });
11427
+ program.command("open").description("Open any supported local trace through the reader pipeline").argument("[input]", "trace file, directory, or - for stdin").addOption(
11428
+ new Option("--format <format>", "trace input format").choices([
11429
+ "agent-inspect-jsonl",
11430
+ "openinference-json",
11431
+ "otlp-json"
11432
+ ])
11433
+ ).option("--json", "print result as JSON").option("--diagnostics", "print reader warnings and unsupported fields").option("--run <run-id>", "select a run when the trace contains multiple runs").action((input3, opts) => {
11434
+ runCommand(() => openCommand(input3, opts));
11435
+ });
11436
+ program.command("check").description("Run deterministic checks against a local trace").argument("<trace-path-or-run-id>", "trace file, directory, stdin -, or run id").option("--dir <path>", "trace directory for run-id lookup").addOption(
11437
+ new Option("--format <format>", "trace input format").choices([
11438
+ "agent-inspect-jsonl",
11439
+ "openinference-json",
11440
+ "otlp-json"
11441
+ ])
11442
+ ).option("--run <run-id>", "select a run when the trace contains multiple runs").option("--config <path>", "path to check config (.json, .js, .mjs, .cjs)").option("--json", "print deterministic JSON check result").option("--rule <id>", "select a rule id (repeatable)", (value, previous = []) => [
11443
+ ...previous,
11444
+ value
11445
+ ]).option("--max-duration-ms <number>", "add run.duration with a max duration").option("--required-tool <name>", "require a tool name (repeatable)", (value, previous = []) => [
11446
+ ...previous,
11447
+ value
11448
+ ]).option("--forbidden-tool <name>", "forbid a tool name (repeatable)", (value, previous = []) => [
11449
+ ...previous,
11450
+ value
11451
+ ]).option("--allowed-model <model>", "allow an LLM model (repeatable)", (value, previous = []) => [
11452
+ ...previous,
11453
+ value
11454
+ ]).option("--max-total-tokens <number>", "add llm.usage with a max total-token budget").action((target, opts) => {
11455
+ runCommand(() => checkCommand(target, opts));
11456
+ });
11457
+ 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(
11458
+ new Option("--format <format>", "trace input format").choices([
11459
+ "agent-inspect-jsonl",
11460
+ "openinference-json",
11461
+ "otlp-json"
11462
+ ])
11463
+ ).option("--run <run-id>", "select a run when the trace contains multiple runs").option("--json", "print deterministic JSON safety result").option("--max-string-length <number>", "unsafe threshold for string values").option("--max-array-length <number>", "unsafe threshold for array values").option("--max-object-keys <number>", "unsafe threshold for object key counts").option("--max-serialized-bytes <number>", "unsafe threshold for serialized values").action((target, opts) => {
11464
+ runCommand(() => scanCommand(target, opts));
11465
+ });
11466
+ program.command("verify-safe").description("Best-effort local trace safety verification").argument("<trace-path-or-run-id>", "trace file, directory, stdin -, or run id").option("--dir <path>", "trace directory for run-id lookup").addOption(
11467
+ new Option("--format <format>", "trace input format").choices([
11468
+ "agent-inspect-jsonl",
11469
+ "openinference-json",
11470
+ "otlp-json"
11471
+ ])
11472
+ ).option("--run <run-id>", "select a run when the trace contains multiple runs").option("--json", "print deterministic JSON safety result").option("--max-string-length <number>", "unsafe threshold for string values").option("--max-array-length <number>", "unsafe threshold for array values").option("--max-object-keys <number>", "unsafe threshold for object key counts").option("--max-serialized-bytes <number>", "unsafe threshold for serialized values").action((target, opts) => {
11473
+ runCommand(() => verifySafeCommand(target, opts));
11474
+ });
11475
+ program.command("artifacts").description("Create safe local CI trace artifacts").argument("<trace-path-or-run-id>", "trace file, directory, stdin -, or run id").requiredOption("--output-dir <path>", "directory for generated artifacts").option("--dir <path>", "trace directory for run-id lookup").addOption(
11476
+ new Option("--format <format>", "trace input format").choices([
11477
+ "agent-inspect-jsonl",
11478
+ "openinference-json",
11479
+ "otlp-json"
11480
+ ])
11481
+ ).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) => {
11482
+ runCommand(() => artifactsCommand(target, opts));
11483
+ });
11484
+ 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(
9188
11485
  "--duration-threshold <duration>",
9189
11486
  "ignore duration deltas at or below this (e.g. 500ms, 2s, 1m)"
9190
11487
  ).addOption(
@@ -9244,6 +11541,23 @@ function createCliProgram() {
9244
11541
  ).action((runId, opts) => {
9245
11542
  runCommand(() => reportCommand(runId, opts));
9246
11543
  });
11544
+ 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(
11545
+ new Option("--format <format>", "trace input format").choices([
11546
+ "agent-inspect-jsonl",
11547
+ "openinference-json",
11548
+ "otlp-json"
11549
+ ])
11550
+ ).option("--run <run-id>", "select a run when the trace contains multiple runs").option("--dry-run", "emit only the local facts payload that a provider could receive").option(
11551
+ "--provider <provider>",
11552
+ "reserved for explicit provider explain; currently rejected without network calls"
11553
+ ).option("--json", "print deterministic JSON explanation result").addOption(
11554
+ new Option(
11555
+ "--redaction-profile <profile>",
11556
+ "redaction profile for explanation payload: local, share, strict (default: local)"
11557
+ ).choices(["local", "share", "strict"])
11558
+ ).action((target, opts) => {
11559
+ runCommand(() => explainCommand(target, opts));
11560
+ });
9247
11561
  return program;
9248
11562
  }
9249
11563
  function isPrimaryModule() {
@@ -9251,9 +11565,9 @@ function isPrimaryModule() {
9251
11565
  if (!entry) return false;
9252
11566
  const selfPath = fileURLToPath(import.meta.url);
9253
11567
  try {
9254
- return realpathSync(path.resolve(entry)) === realpathSync(path.resolve(selfPath));
11568
+ return realpathSync(path10.resolve(entry)) === realpathSync(path10.resolve(selfPath));
9255
11569
  } catch {
9256
- return path.resolve(entry) === path.resolve(selfPath);
11570
+ return path10.resolve(entry) === path10.resolve(selfPath);
9257
11571
  }
9258
11572
  }
9259
11573
  if (isPrimaryModule()) {