agent-inspect 5.1.0 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 52c2539: Add CI quality gates: `agent-inspect gate` with suite/threshold checks, stable exit codes, and JUnit/GitHub step-summary artifacts.
8
+
3
9
  ## 5.1.0
4
10
 
5
11
  ### Minor Changes
package/docs/CLI.md CHANGED
@@ -963,6 +963,38 @@ npx agent-inspect cohort \
963
963
 
964
964
  Recipe: [cohort-baseline-candidate](../examples/recipes/cohort-baseline-candidate/README.md).
965
965
 
966
+ ### 6.27 `gate`
967
+
968
+ Run **deterministic CI quality gates** over local traces or suite configs (v5.2+). No agent replay, no model calls, no upload.
969
+
970
+ ```bash
971
+ agent-inspect gate --suite <path> [options]
972
+ agent-inspect gate --dir <path> --max-error-rate <percent> [options]
973
+ ```
974
+
975
+ Options:
976
+
977
+ - `--dir <path>` — trace directory for threshold checks
978
+ - `--suite <path>` — suite config (`.json`, `.js`, `.mjs`, `.cjs`)
979
+ - `--max-error-rate <percent>` — maximum allowed error rate
980
+ - `--max-p95-duration <ms>` — maximum allowed p95 run duration
981
+ - `--forbid-tool <name>` — forbidden tool (repeatable or comma-separated)
982
+ - `--require-observation <name>` — required passed observation (repeatable or comma-separated)
983
+ - `--format <format>` — `markdown`, `json`, `html`, `junit`, or `github` (default: `markdown`)
984
+ - `-o, --output <dir>` — write `gate-results.json`, `gate-summary.md`, `gate-report.html`, `junit.xml`, `github-step-summary.md`
985
+ - `--json` — print deterministic JSON result
986
+
987
+ Exit codes: **0** pass, **1** gate failed, **2** invalid config, **3** trace read failure, **4** unsupported format.
988
+
989
+ Example:
990
+
991
+ ```bash
992
+ npx agent-inspect gate --suite fixtures/configs/outcome-suite.suite.json --output ./gate-artifacts
993
+ npx agent-inspect gate --dir fixtures/cohorts/before-after --max-error-rate 5 --forbid-tool deleteAccount
994
+ ```
995
+
996
+ Recipe: [github-actions-gate](../examples/recipes/github-actions-gate/README.md).
997
+
966
998
  ## 7. Optional TUI behavior
967
999
 
968
1000
  `view --tui` delegates to `@agent-inspect/tui` and requires an interactive terminal. If the package is not installed, the CLI prints a short install hint.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-inspect",
3
- "version": "5.1.0",
3
+ "version": "5.2.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Trace, check, and safely share TypeScript AI-agent runs locally — no account, no upload, metadata-only by default",
@@ -2732,9 +2732,9 @@ async function searchTraces(metas, options) {
2732
2732
  }
2733
2733
  const limit = options.limit ?? 50;
2734
2734
  const sessionId = options.session?.trim();
2735
- const observationStatus = parseObservationFilter(options.observation);
2735
+ const observationStatus2 = parseObservationFilter(options.observation);
2736
2736
  const hasContentFilter = Boolean(
2737
- options.status || stepTypeFilter || nameQuery || toolQuery || durationFilter || observationStatus
2737
+ options.status || stepTypeFilter || nameQuery || toolQuery || durationFilter || observationStatus2
2738
2738
  );
2739
2739
  const results = [];
2740
2740
  const sessionLabel = sessionId && sessionId !== "" ? sessionId : void 0;
@@ -2779,9 +2779,9 @@ async function searchTraces(metas, options) {
2779
2779
  statusFilter: options.status
2780
2780
  });
2781
2781
  results.push(...stepMatches);
2782
- if (observationStatus) {
2782
+ if (observationStatus2) {
2783
2783
  const outcomes = extractOutcomesFromTraceEvents(events);
2784
- const matched = outcomes.filter((outcome) => outcome.status === observationStatus);
2784
+ const matched = outcomes.filter((outcome) => outcome.status === observationStatus2);
2785
2785
  for (const outcome of matched) {
2786
2786
  results.push({
2787
2787
  runId: m.runId,
@@ -3460,12 +3460,12 @@ function buildCriticalPath(runs, handoffs) {
3460
3460
  handoffs.filter((edge) => edge.confidence === "explicit").map((edge) => edge.from)
3461
3461
  );
3462
3462
  const ordered = [...runs].sort(compareRuns);
3463
- const path11 = [];
3463
+ const path12 = [];
3464
3464
  const visited = /* @__PURE__ */ new Set();
3465
3465
  const pushRun = (run, confidence, source) => {
3466
3466
  if (visited.has(run.runId)) return;
3467
3467
  visited.add(run.runId);
3468
- path11.push({
3468
+ path12.push({
3469
3469
  runId: run.runId,
3470
3470
  name: run.name,
3471
3471
  startedAt: run.startedAt,
@@ -3490,7 +3490,7 @@ function buildCriticalPath(runs, handoffs) {
3490
3490
  const confidence = explicitTargets.has(run.runId) || explicitSources.has(run.runId) ? "explicit" : "correlated";
3491
3491
  pushRun(run, confidence, confidence === "explicit" ? "manual" : "inferred");
3492
3492
  }
3493
- return path11;
3493
+ return path12;
3494
3494
  }
3495
3495
  function metaRunIdMatches(run, token, runById) {
3496
3496
  const meta = extractSessionWorkflowMetadata(run.metadata);
@@ -4396,7 +4396,7 @@ function stripPrefix(name, prefixes) {
4396
4396
  }
4397
4397
  return name;
4398
4398
  }
4399
- function eventEvidence(event, path11) {
4399
+ function eventEvidence(event, path12) {
4400
4400
  return {
4401
4401
  runId: event.runId,
4402
4402
  eventId: event.eventId,
@@ -4406,7 +4406,7 @@ function eventEvidence(event, path11) {
4406
4406
  kind: event.kind,
4407
4407
  name: event.name,
4408
4408
  status: event.status,
4409
- ...path11 ? { path: path11 } : {}
4409
+ ...path12 ? { path: path12 } : {}
4410
4410
  };
4411
4411
  }
4412
4412
  function runEvidence(run) {
@@ -4469,9 +4469,9 @@ function eventEndMs(event) {
4469
4469
  function normalizedKey(value) {
4470
4470
  return value.toLowerCase().replace(/[^a-z0-9_]/g, "");
4471
4471
  }
4472
- function lastPathSegment(path11) {
4473
- const parts = path11.split(".");
4474
- return parts[parts.length - 1] ?? path11;
4472
+ function lastPathSegment(path12) {
4473
+ const parts = path12.split(".");
4474
+ return parts[parts.length - 1] ?? path12;
4475
4475
  }
4476
4476
  function valueType(value) {
4477
4477
  if (Array.isArray(value)) return "array";
@@ -4485,12 +4485,12 @@ function serializedByteLength(value) {
4485
4485
  return void 0;
4486
4486
  }
4487
4487
  }
4488
- function pushValueEntries(entries, event, value, path11, key, depth = 0) {
4489
- entries.push({ event, path: path11, key, value });
4488
+ function pushValueEntries(entries, event, value, path12, key, depth = 0) {
4489
+ entries.push({ event, path: path12, key, value });
4490
4490
  if (depth >= 8) return;
4491
4491
  if (Array.isArray(value)) {
4492
4492
  for (const [index, item] of value.entries()) {
4493
- pushValueEntries(entries, event, item, `${path11}.${index}`, String(index), depth + 1);
4493
+ pushValueEntries(entries, event, item, `${path12}.${index}`, String(index), depth + 1);
4494
4494
  }
4495
4495
  return;
4496
4496
  }
@@ -4500,7 +4500,7 @@ function pushValueEntries(entries, event, value, path11, key, depth = 0) {
4500
4500
  entries,
4501
4501
  event,
4502
4502
  value[nestedKey],
4503
- `${path11}.${nestedKey}`,
4503
+ `${path12}.${nestedKey}`,
4504
4504
  nestedKey,
4505
4505
  depth + 1
4506
4506
  );
@@ -4581,9 +4581,9 @@ function eventDurationMs(event) {
4581
4581
  }
4582
4582
  function treeShape(nodes) {
4583
4583
  const lines = [];
4584
- const visit = (node, path11) => {
4585
- lines.push(`${path11}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
4586
- node.children.forEach((child, index) => visit(child, `${path11}.${index}`));
4584
+ const visit = (node, path12) => {
4585
+ lines.push(`${path12}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
4586
+ node.children.forEach((child, index) => visit(child, `${path12}.${index}`));
4587
4587
  };
4588
4588
  nodes.forEach((node, index) => visit(node, String(index)));
4589
4589
  return lines;
@@ -4632,9 +4632,9 @@ function retrievalShape(context) {
4632
4632
  function guardrailShape(context) {
4633
4633
  return guardrailEvents(context).map((event) => signalName(event, ["guardrailName", "guardrail", "guardrailId"], ["guardrail:"])).sort((a, b) => a.localeCompare(b));
4634
4634
  }
4635
- function firstEvidenceForKind(context, kind, path11) {
4635
+ function firstEvidenceForKind(context, kind, path12) {
4636
4636
  const event = context.events.find((candidate) => candidate.kind === kind);
4637
- return event ? [eventEvidence(event, path11)] : runEvidence(context.selectedRun);
4637
+ return event ? [eventEvidence(event, path12)] : runEvidence(context.selectedRun);
4638
4638
  }
4639
4639
  function baselineDiffFinding(message, evidence, expected, actual) {
4640
4640
  return failFinding("baseline.regression", message, evidence, expected, actual);
@@ -4984,13 +4984,13 @@ function createStructureCycleRule() {
4984
4984
  const seenCycles = /* @__PURE__ */ new Set();
4985
4985
  const findings = [];
4986
4986
  for (const event of [...context.events].sort((a, b) => a.eventId.localeCompare(b.eventId))) {
4987
- const path11 = [];
4987
+ const path12 = [];
4988
4988
  const seenAt = /* @__PURE__ */ new Map();
4989
4989
  let current = event;
4990
4990
  while (current) {
4991
4991
  const existing = seenAt.get(current.eventId);
4992
4992
  if (existing !== void 0) {
4993
- const cycle = path11.slice(existing);
4993
+ const cycle = path12.slice(existing);
4994
4994
  const key = cycle.map((item) => item.eventId).sort().join("\0");
4995
4995
  if (!seenCycles.has(key)) {
4996
4996
  seenCycles.add(key);
@@ -5006,8 +5006,8 @@ function createStructureCycleRule() {
5006
5006
  }
5007
5007
  break;
5008
5008
  }
5009
- seenAt.set(current.eventId, path11.length);
5010
- path11.push(current);
5009
+ seenAt.set(current.eventId, path12.length);
5010
+ path12.push(current);
5011
5011
  current = current.parentId ? byId.get(current.parentId) : void 0;
5012
5012
  }
5013
5013
  }
@@ -8500,6 +8500,394 @@ function renderCohortReport(result, options = {}) {
8500
8500
  return renderCohortSummaryMarkdown(result);
8501
8501
  }
8502
8502
 
8503
- export { COHORT_METRIC_IDS, DEFAULT_SUITE_ARTIFACTS_DIR, Redactor, TraceDirectory, TraceReadError, TreeBuilder, __commonJS, __require, __toESM, aggregateBundleSafeStatus, aggregateSessionCheckResults, analyzeCohort, applyProfileMetadataCaps, buildActivitySummary, buildBundleMetadata, buildBundleSummaryMarkdown, buildLocalExplanation, buildPlaceholderArtifact, buildRunSummary, buildRunTimeline, buildRunWhatSummary, buildSessionIndex, buildTraceStats, bundleFailsOnSafety, compactAttributes3 as compactAttributes, createBaselineRegressionRule, createLlmUsageRule, createMaxStepDurationRule, createObservedOutcomeRule, createRequireCompletedRule, createRunDepthRule, createRunDurationRule, createRunStatusRule, createSafetyOversizedAttributeRule, createSafetyRawContentRule, createSafetyRedactionRule, createSafetySecretPatternRule, createStallDetectionRule, createStructureCycleRule, createStructureOrphanRule, createStructureParallelWidthRule, createStructureRelationshipRule, createToolUsageRule, defaultBundleOutputPath, defaultSuiteConfigTemplate, enrichSessionRunRecord, escapeHtml, escapeMarkdown, extractMetadata, extractOutcomesFromTraceEvents, filterMetasBySessionScope, filterTraces, flattenTree, formatDuration2 as formatDuration, formatTimestamp, getIndent, getTraceFilePath, isAgentInspectTrace, isPersistedInspectEvent, loadSessionRunRecords, loadSuiteConfig, loadTraceMetadataList, nanoid, normalizeBundleOutputPath, openTrace, parseCohortMetricList, parseDuration, parseDurationFilter, parseTraceJsonl, persistedInspectEventsToTraceEvents, renderActivitySummaryHuman, renderCohortReport, renderErrorLine, renderObservedOutcomesHtml, renderObservedOutcomesMarkdown, renderRunWhat, renderStepLine, renderSuiteReport, renderTimeline, renderTraceStats, resolveBundleRunIds, resolveRedactionProfile, resolveTraceDir, runSuite, runTraceChecks, safeString, searchTraces, source_default, stableJson, summarizeObservedOutcomes, traceEventToPersistedInspectEvent, truncateName, truncateStringForProfile, validateEvent, validateSuiteConfig, zeroKinds };
8504
- //# sourceMappingURL=chunk-TSIQUIPF.mjs.map
8505
- //# sourceMappingURL=chunk-TSIQUIPF.mjs.map
8503
+ // packages/core/src/gate/parse.ts
8504
+ function parseGateList(value) {
8505
+ if (value === void 0 || value.trim() === "") return [];
8506
+ return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
8507
+ }
8508
+
8509
+ // packages/core/src/gate/evaluate.ts
8510
+ function percentile3(values, p) {
8511
+ if (values.length === 0) return void 0;
8512
+ const sorted = [...values].sort((a, b) => a - b);
8513
+ const idx = Math.min(
8514
+ sorted.length - 1,
8515
+ Math.max(0, Math.ceil(p / 100 * sorted.length) - 1)
8516
+ );
8517
+ return sorted[idx];
8518
+ }
8519
+ function hasThresholds(options) {
8520
+ return options.maxErrorRate !== void 0 || options.maxP95DurationMs !== void 0 || (options.forbidTools?.length ?? 0) > 0 || (options.requireObservations?.length ?? 0) > 0;
8521
+ }
8522
+ function gateHasThresholds(options) {
8523
+ return hasThresholds(options);
8524
+ }
8525
+ async function loadRunMetrics(runs) {
8526
+ const metrics = [];
8527
+ for (const run of runs) {
8528
+ if (run.filePath === void 0) continue;
8529
+ metrics.push(
8530
+ await computeCohortRunMetrics({
8531
+ runId: run.runId,
8532
+ filePath: run.filePath,
8533
+ metadata: run.metadata,
8534
+ status: run.status,
8535
+ durationMs: run.durationMs,
8536
+ groupKey: "all"
8537
+ })
8538
+ );
8539
+ }
8540
+ return metrics;
8541
+ }
8542
+ async function observationStatus(filePath, name) {
8543
+ const events = await readTraceEventsFromFile(filePath);
8544
+ const outcomes = extractOutcomesFromTraceEvents(events);
8545
+ const match = outcomes.find((item) => item.name === name);
8546
+ if (!match) return "missing";
8547
+ return match.status === "passed" ? "passed" : "failed";
8548
+ }
8549
+ async function evaluateGateThresholds(runs, options) {
8550
+ const checks = [];
8551
+ const readErrors = [];
8552
+ if (!hasThresholds(options)) {
8553
+ return { checks, readErrors };
8554
+ }
8555
+ if (runs.length === 0) {
8556
+ readErrors.push("No trace runs found in the gate directory.");
8557
+ return { checks, readErrors };
8558
+ }
8559
+ let runMetrics;
8560
+ try {
8561
+ runMetrics = await loadRunMetrics(runs);
8562
+ } catch (error) {
8563
+ const message = error instanceof Error ? error.message : String(error);
8564
+ readErrors.push(message);
8565
+ return { checks, readErrors };
8566
+ }
8567
+ if (options.maxErrorRate !== void 0) {
8568
+ const errors = runMetrics.filter((run) => run.error).length;
8569
+ const actual = runMetrics.length > 0 ? errors / runMetrics.length * 100 : 0;
8570
+ const ok = actual <= options.maxErrorRate;
8571
+ checks.push({
8572
+ id: "maxErrorRate",
8573
+ name: "Max error rate",
8574
+ ok,
8575
+ expected: options.maxErrorRate,
8576
+ actual: Math.round(actual * 10) / 10,
8577
+ message: ok ? `Error rate ${actual.toFixed(1)}% within limit ${options.maxErrorRate}%` : `Error rate ${actual.toFixed(1)}% exceeds limit ${options.maxErrorRate}%`
8578
+ });
8579
+ }
8580
+ if (options.maxP95DurationMs !== void 0) {
8581
+ const durations = runMetrics.map((run) => run.durationMs).filter((value) => typeof value === "number");
8582
+ const actual = percentile3(durations, 95);
8583
+ const ok = actual !== void 0 && actual <= options.maxP95DurationMs;
8584
+ checks.push({
8585
+ id: "maxP95Duration",
8586
+ name: "Max p95 duration (ms)",
8587
+ ok,
8588
+ expected: options.maxP95DurationMs,
8589
+ actual: actual ?? "n/a",
8590
+ message: actual === void 0 ? "No duration samples available for p95 check." : ok ? `P95 duration ${Math.round(actual)} ms within limit ${options.maxP95DurationMs} ms` : `P95 duration ${Math.round(actual)} ms exceeds limit ${options.maxP95DurationMs} ms`
8591
+ });
8592
+ }
8593
+ for (const tool of options.forbidTools ?? []) {
8594
+ let violated = false;
8595
+ for (const run of runMetrics) {
8596
+ const used = run.toolChoices.includes(tool) || run.toolOrdering.includes(tool);
8597
+ if (used) {
8598
+ violated = true;
8599
+ checks.push({
8600
+ id: "forbidTool",
8601
+ name: `Forbid tool: ${tool}`,
8602
+ ok: false,
8603
+ expected: `not used`,
8604
+ actual: "used",
8605
+ runId: run.runId,
8606
+ message: `Forbidden tool "${tool}" used in run ${run.runId}`
8607
+ });
8608
+ }
8609
+ }
8610
+ if (!violated) {
8611
+ checks.push({
8612
+ id: "forbidTool",
8613
+ name: `Forbid tool: ${tool}`,
8614
+ ok: true,
8615
+ message: `Forbidden tool "${tool}" not used`
8616
+ });
8617
+ }
8618
+ }
8619
+ for (const observation of options.requireObservations ?? []) {
8620
+ for (const run of runs) {
8621
+ if (run.filePath === void 0) continue;
8622
+ try {
8623
+ const status = await observationStatus(run.filePath, observation);
8624
+ const ok = status === "passed";
8625
+ checks.push({
8626
+ id: "requireObservation",
8627
+ name: `Require observation: ${observation}`,
8628
+ ok,
8629
+ expected: "passed",
8630
+ actual: status,
8631
+ runId: run.runId,
8632
+ message: ok ? `Observation "${observation}" passed in run ${run.runId}` : status === "missing" ? `Observation "${observation}" missing in run ${run.runId}` : `Observation "${observation}" failed in run ${run.runId}`
8633
+ });
8634
+ } catch (error) {
8635
+ const message = error instanceof Error ? error.message : String(error);
8636
+ readErrors.push(`Run ${run.runId}: ${message}`);
8637
+ }
8638
+ }
8639
+ }
8640
+ return { checks, readErrors };
8641
+ }
8642
+ function checksFromSuiteResult(suiteResult) {
8643
+ const checks = [
8644
+ {
8645
+ id: "suite",
8646
+ name: `Suite: ${suiteResult.suiteName}`,
8647
+ ok: suiteResult.ok,
8648
+ message: suiteResult.ok ? `Suite passed (${suiteResult.summary.passed} cases)` : `Suite failed (${suiteResult.summary.failed} failed, ${suiteResult.summary.errors} errors)`
8649
+ }
8650
+ ];
8651
+ for (const suiteCase of suiteResult.cases) {
8652
+ if (suiteCase.status === "pass") continue;
8653
+ checks.push({
8654
+ id: "suite",
8655
+ name: `Case: ${suiteCase.id}`,
8656
+ ok: false,
8657
+ message: suiteCase.message ?? `Case status: ${suiteCase.status}`
8658
+ });
8659
+ }
8660
+ return checks;
8661
+ }
8662
+ function resolveExitCode(input) {
8663
+ if (input.configError) return 2;
8664
+ if (input.readError) return 3;
8665
+ if (!input.ok) return 1;
8666
+ return 0;
8667
+ }
8668
+ function validateOptions(options) {
8669
+ const errors = [];
8670
+ const hasSuite = options.suitePath !== void 0 && options.suitePath.trim() !== "";
8671
+ const hasThresholds2 = gateHasThresholds(options);
8672
+ if (!hasSuite && !hasThresholds2) {
8673
+ errors.push(
8674
+ "No gate rules specified. Pass --suite or at least one threshold flag."
8675
+ );
8676
+ }
8677
+ if (hasThresholds2 && (options.traceDir === void 0 || options.traceDir.trim() === "")) {
8678
+ if (!hasSuite) {
8679
+ errors.push("Threshold flags require --dir <trace-directory>.");
8680
+ }
8681
+ }
8682
+ if (options.maxErrorRate !== void 0 && options.maxErrorRate < 0) {
8683
+ errors.push("--max-error-rate must be a non-negative percentage.");
8684
+ }
8685
+ if (options.maxP95DurationMs !== void 0 && options.maxP95DurationMs < 0) {
8686
+ errors.push("--max-p95-duration must be a non-negative millisecond value.");
8687
+ }
8688
+ return errors;
8689
+ }
8690
+ function isConfigLoadError(error) {
8691
+ if (!(error instanceof Error)) return false;
8692
+ const ext = path7.extname(error.message);
8693
+ if (error.message.includes("Unsupported suite config extension")) return true;
8694
+ if (error.message.includes("TypeScript suite configs require")) return true;
8695
+ if (error.message.includes("No suite config found")) return true;
8696
+ if (error.message.includes("AI_SUITE_CONFIG")) return true;
8697
+ if (ext === ".ts" || ext === ".mts" || ext === ".cts") return true;
8698
+ return "diagnostics" in error;
8699
+ }
8700
+ async function runGate(runs, options) {
8701
+ const diagnostics = [];
8702
+ const checks = [];
8703
+ const validationErrors = validateOptions(options);
8704
+ if (validationErrors.length > 0) {
8705
+ return {
8706
+ ok: false,
8707
+ exitCode: 2,
8708
+ runCount: 0,
8709
+ checks,
8710
+ diagnostics: validationErrors
8711
+ };
8712
+ }
8713
+ let traceDir = options.traceDir?.trim();
8714
+ let suiteResult;
8715
+ if (options.suitePath !== void 0 && options.suitePath.trim() !== "") {
8716
+ try {
8717
+ suiteResult = await runSuite({
8718
+ configPath: options.suitePath,
8719
+ cwd: options.cwd
8720
+ });
8721
+ traceDir = traceDir ?? suiteResult.tracesDir;
8722
+ checks.push(...checksFromSuiteResult(suiteResult));
8723
+ } catch (error) {
8724
+ const message = error instanceof Error ? error.message : String(error);
8725
+ diagnostics.push(message);
8726
+ return {
8727
+ ok: false,
8728
+ exitCode: isConfigLoadError(error) ? 2 : 3,
8729
+ traceDir,
8730
+ suitePath: options.suitePath,
8731
+ runCount: 0,
8732
+ checks,
8733
+ diagnostics
8734
+ };
8735
+ }
8736
+ }
8737
+ if (gateHasThresholds(options)) {
8738
+ const thresholdDir = traceDir;
8739
+ if (thresholdDir === void 0 || thresholdDir.trim() === "") {
8740
+ return {
8741
+ ok: false,
8742
+ exitCode: 2,
8743
+ traceDir,
8744
+ suitePath: options.suitePath,
8745
+ runCount: runs.length,
8746
+ checks,
8747
+ diagnostics: ["Threshold evaluation requires a trace directory."],
8748
+ ...suiteResult !== void 0 ? { suiteResult } : {}
8749
+ };
8750
+ }
8751
+ const thresholdRuns = runs.length > 0 ? runs : [];
8752
+ const { checks: thresholdChecks, readErrors } = await evaluateGateThresholds(
8753
+ thresholdRuns,
8754
+ options
8755
+ );
8756
+ checks.push(...thresholdChecks);
8757
+ diagnostics.push(...readErrors);
8758
+ if (readErrors.length > 0) {
8759
+ const ok2 = checks.length > 0 && checks.every((item) => item.ok);
8760
+ return {
8761
+ ok: ok2,
8762
+ exitCode: resolveExitCode({
8763
+ ok: ok2,
8764
+ configError: false,
8765
+ readError: true
8766
+ }),
8767
+ traceDir: thresholdDir,
8768
+ suitePath: options.suitePath,
8769
+ runCount: thresholdRuns.length,
8770
+ checks,
8771
+ diagnostics,
8772
+ ...suiteResult !== void 0 ? { suiteResult } : {}
8773
+ };
8774
+ }
8775
+ }
8776
+ const ok = checks.length > 0 && checks.every((item) => item.ok);
8777
+ return {
8778
+ ok,
8779
+ exitCode: resolveExitCode({ ok, configError: false, readError: false }),
8780
+ traceDir,
8781
+ suitePath: options.suitePath,
8782
+ runCount: runs.length,
8783
+ checks,
8784
+ diagnostics,
8785
+ ...suiteResult !== void 0 ? { suiteResult } : {}
8786
+ };
8787
+ }
8788
+
8789
+ // packages/core/src/gate/render.ts
8790
+ function escapeXml(value) {
8791
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
8792
+ }
8793
+ function renderGateSummaryMarkdown(result) {
8794
+ const lines = [];
8795
+ lines.push("# AgentInspect gate");
8796
+ lines.push("");
8797
+ lines.push(`Status: **${result.ok ? "PASS" : "FAIL"}** (exit ${result.exitCode})`);
8798
+ if (result.traceDir !== void 0) {
8799
+ lines.push(`Trace directory: \`${result.traceDir}\``);
8800
+ }
8801
+ if (result.suitePath !== void 0) {
8802
+ lines.push(`Suite config: \`${result.suitePath}\``);
8803
+ }
8804
+ lines.push(`Runs evaluated: ${result.runCount}`);
8805
+ lines.push("");
8806
+ if (result.diagnostics.length > 0) {
8807
+ lines.push("## Diagnostics");
8808
+ for (const item of result.diagnostics) lines.push(`- ${item}`);
8809
+ lines.push("");
8810
+ }
8811
+ lines.push("## Checks");
8812
+ for (const check of result.checks) {
8813
+ const flag = check.ok ? "PASS" : "FAIL";
8814
+ lines.push(`- [${flag}] ${check.name}: ${check.message}`);
8815
+ }
8816
+ lines.push("");
8817
+ return lines.join("\n").trimEnd();
8818
+ }
8819
+ function renderGateGithubStepSummary(result) {
8820
+ const lines = [];
8821
+ lines.push(`## AgentInspect gate: ${result.ok ? "PASS" : "FAIL"}`);
8822
+ lines.push("");
8823
+ lines.push("| Check | Status | Details |");
8824
+ lines.push("| --- | --- | --- |");
8825
+ for (const check of result.checks) {
8826
+ lines.push(
8827
+ `| ${check.name} | ${check.ok ? "pass" : "fail"} | ${check.message.replace(/\|/g, "/")} |`
8828
+ );
8829
+ }
8830
+ if (result.diagnostics.length > 0) {
8831
+ lines.push("");
8832
+ lines.push("**Diagnostics**");
8833
+ for (const item of result.diagnostics) lines.push(`- ${item}`);
8834
+ }
8835
+ return lines.join("\n").trimEnd();
8836
+ }
8837
+ function renderGateReportHtml(result) {
8838
+ const rows = result.checks.map(
8839
+ (check) => `<tr><td>${escapeHtml(check.name)}</td><td>${check.ok ? "PASS" : "FAIL"}</td><td>${escapeHtml(check.message)}</td></tr>`
8840
+ ).join("");
8841
+ return `<!DOCTYPE html>
8842
+ <html lang="en">
8843
+ <head>
8844
+ <meta charset="utf-8" />
8845
+ <title>Gate report</title>
8846
+ <style>
8847
+ body { font-family: system-ui, sans-serif; margin: 2rem; }
8848
+ table { border-collapse: collapse; width: 100%; }
8849
+ th, td { border: 1px solid #ddd; padding: 0.5rem; text-align: left; }
8850
+ th { background: #f6f6f6; }
8851
+ </style>
8852
+ </head>
8853
+ <body>
8854
+ <h1>AgentInspect gate</h1>
8855
+ <p>Status: <strong>${result.ok ? "PASS" : "FAIL"}</strong> (exit ${result.exitCode})</p>
8856
+ <h2>Checks</h2>
8857
+ <table>
8858
+ <thead><tr><th>Check</th><th>Status</th><th>Details</th></tr></thead>
8859
+ <tbody>${rows}</tbody>
8860
+ </table>
8861
+ </body>
8862
+ </html>`;
8863
+ }
8864
+ function renderGateJUnit(result) {
8865
+ const failures = result.checks.filter((check) => !check.ok).length;
8866
+ const tests = result.checks.length;
8867
+ const cases = result.checks.map((check) => {
8868
+ if (check.ok) {
8869
+ return ` <testcase name="${escapeXml(check.name)}" classname="gate" />`;
8870
+ }
8871
+ return ` <testcase name="${escapeXml(check.name)}" classname="gate">
8872
+ <failure message="${escapeXml(check.message)}">${escapeXml(check.message)}</failure>
8873
+ </testcase>`;
8874
+ }).join("\n");
8875
+ return `<?xml version="1.0" encoding="UTF-8"?>
8876
+ <testsuites tests="${tests}" failures="${failures}" errors="0" time="0">
8877
+ <testsuite name="agent-inspect-gate" tests="${tests}" failures="${failures}" errors="0" time="0">
8878
+ ${cases}
8879
+ </testsuite>
8880
+ </testsuites>`;
8881
+ }
8882
+ function renderGateReport(result, options = {}) {
8883
+ const format = options.format ?? "markdown";
8884
+ if (format === "json") return JSON.stringify(result, null, 2);
8885
+ if (format === "html") return renderGateReportHtml(result);
8886
+ if (format === "junit") return renderGateJUnit(result);
8887
+ if (format === "github") return renderGateGithubStepSummary(result);
8888
+ return renderGateSummaryMarkdown(result);
8889
+ }
8890
+
8891
+ export { COHORT_METRIC_IDS, DEFAULT_SUITE_ARTIFACTS_DIR, Redactor, TraceDirectory, TraceReadError, TreeBuilder, __commonJS, __require, __toESM, aggregateBundleSafeStatus, aggregateSessionCheckResults, analyzeCohort, applyProfileMetadataCaps, buildActivitySummary, buildBundleMetadata, buildBundleSummaryMarkdown, buildLocalExplanation, buildPlaceholderArtifact, buildRunSummary, buildRunTimeline, buildRunWhatSummary, buildSessionIndex, buildTraceStats, bundleFailsOnSafety, compactAttributes3 as compactAttributes, createBaselineRegressionRule, createLlmUsageRule, createMaxStepDurationRule, createObservedOutcomeRule, createRequireCompletedRule, createRunDepthRule, createRunDurationRule, createRunStatusRule, createSafetyOversizedAttributeRule, createSafetyRawContentRule, createSafetyRedactionRule, createSafetySecretPatternRule, createStallDetectionRule, createStructureCycleRule, createStructureOrphanRule, createStructureParallelWidthRule, createStructureRelationshipRule, createToolUsageRule, defaultBundleOutputPath, defaultSuiteConfigTemplate, enrichSessionRunRecord, escapeHtml, escapeMarkdown, extractMetadata, extractOutcomesFromTraceEvents, filterMetasBySessionScope, filterTraces, flattenTree, formatDuration2 as formatDuration, formatTimestamp, gateHasThresholds, getIndent, getTraceFilePath, isAgentInspectTrace, isPersistedInspectEvent, loadSessionRunRecords, loadSuiteConfig, loadTraceMetadataList, nanoid, normalizeBundleOutputPath, openTrace, parseCohortMetricList, parseDuration, parseDurationFilter, parseGateList, parseTraceJsonl, persistedInspectEventsToTraceEvents, renderActivitySummaryHuman, renderCohortReport, renderErrorLine, renderGateReport, renderObservedOutcomesHtml, renderObservedOutcomesMarkdown, renderRunWhat, renderStepLine, renderSuiteReport, renderTimeline, renderTraceStats, resolveBundleRunIds, resolveRedactionProfile, resolveTraceDir, runGate, runSuite, runTraceChecks, safeString, searchTraces, source_default, stableJson, summarizeObservedOutcomes, traceEventToPersistedInspectEvent, truncateName, truncateStringForProfile, validateEvent, validateSuiteConfig, zeroKinds };
8892
+ //# sourceMappingURL=chunk-SJ5R2XBE.mjs.map
8893
+ //# sourceMappingURL=chunk-SJ5R2XBE.mjs.map