agent-inspect 3.5.4 → 4.0.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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { realpathSync, createReadStream } from 'fs';
2
+ import { realpathSync, createReadStream, constants as constants$1 } from 'fs';
3
3
  import path14 from 'path';
4
4
  import { fileURLToPath, pathToFileURL } from 'url';
5
5
  import { Command, Option } from 'commander';
@@ -14,7 +14,7 @@ import { createServer } from 'http';
14
14
  import { createRequire } from 'module';
15
15
 
16
16
  // package.json
17
- var version = "3.5.4";
17
+ var version = "4.0.0";
18
18
 
19
19
  // packages/core/src/correlation-metadata.ts
20
20
  var TRACE_CORRELATION_KEYS = [
@@ -3060,12 +3060,12 @@ function buildCriticalPath(runs, handoffs) {
3060
3060
  handoffs.filter((edge) => edge.confidence === "explicit").map((edge) => edge.from)
3061
3061
  );
3062
3062
  const ordered = [...runs].sort(compareRuns);
3063
- const path19 = [];
3063
+ const path20 = [];
3064
3064
  const visited = /* @__PURE__ */ new Set();
3065
3065
  const pushRun = (run, confidence, source) => {
3066
3066
  if (visited.has(run.runId)) return;
3067
3067
  visited.add(run.runId);
3068
- path19.push({
3068
+ path20.push({
3069
3069
  runId: run.runId,
3070
3070
  name: run.name,
3071
3071
  startedAt: run.startedAt,
@@ -3090,7 +3090,7 @@ function buildCriticalPath(runs, handoffs) {
3090
3090
  const confidence = explicitTargets.has(run.runId) || explicitSources.has(run.runId) ? "explicit" : "correlated";
3091
3091
  pushRun(run, confidence, confidence === "explicit" ? "manual" : "inferred");
3092
3092
  }
3093
- return path19;
3093
+ return path20;
3094
3094
  }
3095
3095
  function metaRunIdMatches(run, token, runById) {
3096
3096
  const meta = extractSessionWorkflowMetadata(run.metadata);
@@ -3326,8 +3326,7 @@ async function list(options = {}) {
3326
3326
  const filtered = filterTraces(metas, {
3327
3327
  status: options.status,
3328
3328
  name: options.name,
3329
- since: options.since,
3330
- limit
3329
+ since: options.since
3331
3330
  });
3332
3331
  const shown = filtered.slice(0, limit);
3333
3332
  if (options.json) {
@@ -8101,7 +8100,10 @@ function buildRunReport(events, options) {
8101
8100
  '<section class="tree">',
8102
8101
  "</section>"
8103
8102
  );
8104
- const errorsSection = treeHtml.content.includes("<h2>Errors</h2>") ? extractHtmlFragment(treeHtml.content, "<h2>Errors</h2>", "<footer>") : "";
8103
+ const errorsSectionEnd = treeHtml.content.includes(
8104
+ "<h2>Attributes (bounded)</h2>"
8105
+ ) ? "<h2>Attributes (bounded)</h2>" : "<footer>";
8106
+ const errorsSection = treeHtml.content.includes("<h2>Errors</h2>") ? extractHtmlFragment(treeHtml.content, "<h2>Errors</h2>", errorsSectionEnd) : "";
8105
8107
  const attrsSection = treeHtml.content.includes("<h2>Attributes (bounded)</h2>") ? extractHtmlFragment(
8106
8108
  treeHtml.content,
8107
8109
  "<h2>Attributes (bounded)</h2>",
@@ -8391,13 +8393,13 @@ function pairSteps(left, right) {
8391
8393
  return pairs;
8392
8394
  }
8393
8395
  function compareLeafSteps(L, R, segments, opts, out) {
8394
- const path19 = buildPath(segments);
8396
+ const path20 = buildPath(segments);
8395
8397
  if (L.name !== R.name) {
8396
8398
  out.push({
8397
8399
  kind: "structure",
8398
8400
  severity: "warning",
8399
8401
  message: "Step name differs",
8400
- path: path19,
8402
+ path: path20,
8401
8403
  left: L.name,
8402
8404
  right: R.name
8403
8405
  });
@@ -8407,7 +8409,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
8407
8409
  kind: "step-type",
8408
8410
  severity: "warning",
8409
8411
  message: "Step type differs",
8410
- path: path19,
8412
+ path: path20,
8411
8413
  left: L.type,
8412
8414
  right: R.type
8413
8415
  });
@@ -8417,7 +8419,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
8417
8419
  kind: "step-status",
8418
8420
  severity: "warning",
8419
8421
  message: "Step status differs",
8420
- path: path19,
8422
+ path: path20,
8421
8423
  left: L.status,
8422
8424
  right: R.status
8423
8425
  });
@@ -8429,7 +8431,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
8429
8431
  kind: "error",
8430
8432
  severity: "error",
8431
8433
  message: "Step error message differs",
8432
- path: path19,
8434
+ path: path20,
8433
8435
  left: le || void 0,
8434
8436
  right: re || void 0
8435
8437
  });
@@ -8447,20 +8449,20 @@ function compareLeafSteps(L, R, segments, opts, out) {
8447
8449
  kind: "duration",
8448
8450
  severity: "info",
8449
8451
  message: "Step duration differs",
8450
- path: path19,
8452
+ path: path20,
8451
8453
  left: ld,
8452
8454
  right: rd
8453
8455
  });
8454
8456
  }
8455
8457
  }
8456
8458
  const lm = stableJson(L.metadata ?? {});
8457
- const rm2 = stableJson(R.metadata ?? {});
8458
- if (lm !== rm2) {
8459
+ const rm3 = stableJson(R.metadata ?? {});
8460
+ if (lm !== rm3) {
8459
8461
  out.push({
8460
8462
  kind: "metadata",
8461
8463
  severity: "info",
8462
8464
  message: "Step metadata differs",
8463
- path: path19,
8465
+ path: path20,
8464
8466
  left: L.metadata,
8465
8467
  right: R.metadata
8466
8468
  });
@@ -8472,7 +8474,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
8472
8474
  kind: "output",
8473
8475
  severity: "info",
8474
8476
  message: "Output preview differs",
8475
- path: path19,
8477
+ path: path20,
8476
8478
  left: L.outputPreview,
8477
8479
  right: R.outputPreview
8478
8480
  });
@@ -8632,11 +8634,11 @@ function diffRuns(left, right, options) {
8632
8634
  }
8633
8635
 
8634
8636
  // packages/core/src/diff/renderer.ts
8635
- function formatPath(path19) {
8636
- if (path19 === void 0 || path19.path.length === 0) {
8637
+ function formatPath(path20) {
8638
+ if (path20 === void 0 || path20.path.length === 0) {
8637
8639
  return "(run)";
8638
8640
  }
8639
- return path19.path.map((s) => s.name).join(" > ");
8641
+ return path20.path.map((s) => s.name).join(" > ");
8640
8642
  }
8641
8643
  function formatValue(v, verbose) {
8642
8644
  if (v === void 0) return "(undefined)";
@@ -9518,17 +9520,17 @@ function applyRule(rule, value, replacement) {
9518
9520
  }
9519
9521
  return value;
9520
9522
  }
9521
- function childPath(path19, key) {
9523
+ function childPath(path20, key) {
9522
9524
  if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
9523
- return path19 ? `${path19}.${key}` : key;
9525
+ return path20 ? `${path20}.${key}` : key;
9524
9526
  }
9525
- return `${path19 || "$"}[${JSON.stringify(key)}]`;
9527
+ return `${path20 || "$"}[${JSON.stringify(key)}]`;
9526
9528
  }
9527
- function indexPath(path19, index) {
9528
- return `${path19 || "$"}[${index}]`;
9529
+ function indexPath(path20, index) {
9530
+ return `${path20 || "$"}[${index}]`;
9529
9531
  }
9530
- function makeFinding(path19, detector, action, matchKind, severity = "warning", preview) {
9531
- return preview === void 0 ? { path: path19, detector, action, severity, matchKind } : { path: path19, detector, action, severity, matchKind, preview };
9532
+ function makeFinding(path20, detector, action, matchKind, severity = "warning", preview) {
9533
+ return preview === void 0 ? { path: path20, detector, action, severity, matchKind } : { path: path20, detector, action, severity, matchKind, preview };
9532
9534
  }
9533
9535
  function createRedactionProfile(profile = "local") {
9534
9536
  switch (profile) {
@@ -9597,11 +9599,11 @@ var Redactor2 = class {
9597
9599
  #recordFinding(state, finding) {
9598
9600
  if (this.#collectFindings) state.findings.push(finding);
9599
9601
  }
9600
- #redactValue(value, key, path19, depth, state) {
9602
+ #redactValue(value, key, path20, depth, state) {
9601
9603
  if (depth > this.#maxDepth) {
9602
9604
  this.#recordFinding(
9603
9605
  state,
9604
- makeFinding(path19, "structure.maxDepth", "truncate", "value", "warning")
9606
+ makeFinding(path20, "structure.maxDepth", "truncate", "value", "warning")
9605
9607
  );
9606
9608
  return "[Truncated]";
9607
9609
  }
@@ -9610,19 +9612,19 @@ var Redactor2 = class {
9610
9612
  if (rule) {
9611
9613
  this.#recordFinding(
9612
9614
  state,
9613
- makeFinding(path19, `key.${rule.key}`, actionForRule(rule), "key", "warning")
9615
+ makeFinding(path20, `key.${rule.key}`, actionForRule(rule), "key", "warning")
9614
9616
  );
9615
9617
  return applyRule(rule, value, this.#replacement);
9616
9618
  }
9617
9619
  }
9618
9620
  for (const detector of this.#detectors) {
9619
- const detections = detector.detect({ path: path19, key, value });
9621
+ const detections = detector.detect({ path: path20, key, value });
9620
9622
  for (const detection of detections) {
9621
9623
  const action = detection.action ?? "replace";
9622
9624
  this.#recordFinding(
9623
9625
  state,
9624
9626
  makeFinding(
9625
- path19,
9627
+ path20,
9626
9628
  detector.id,
9627
9629
  action,
9628
9630
  detection.matchKind ?? detector.matchKind ?? "custom",
@@ -9640,7 +9642,7 @@ var Redactor2 = class {
9640
9642
  const out = [];
9641
9643
  state.seen.set(value, out);
9642
9644
  value.forEach((item, index) => {
9643
- out[index] = this.#redactValue(item, void 0, indexPath(path19, index), depth + 1, state);
9645
+ out[index] = this.#redactValue(item, void 0, indexPath(path20, index), depth + 1, state);
9644
9646
  });
9645
9647
  return out;
9646
9648
  }
@@ -9652,7 +9654,7 @@ var Redactor2 = class {
9652
9654
  out[entryKey] = this.#redactValue(
9653
9655
  entryValue,
9654
9656
  entryKey,
9655
- childPath(path19 === "$" ? "" : path19, entryKey),
9657
+ childPath(path20 === "$" ? "" : path20, entryKey),
9656
9658
  depth + 1,
9657
9659
  state
9658
9660
  );
@@ -10524,7 +10526,7 @@ function stripPrefix(name, prefixes) {
10524
10526
  }
10525
10527
  return name;
10526
10528
  }
10527
- function eventEvidence(event, path19) {
10529
+ function eventEvidence(event, path20) {
10528
10530
  return {
10529
10531
  runId: event.runId,
10530
10532
  eventId: event.eventId,
@@ -10534,7 +10536,7 @@ function eventEvidence(event, path19) {
10534
10536
  kind: event.kind,
10535
10537
  name: event.name,
10536
10538
  status: event.status,
10537
- ...path19 ? { path: path19 } : {}
10539
+ ...path20 ? { path: path20 } : {}
10538
10540
  };
10539
10541
  }
10540
10542
  function runEvidence(run) {
@@ -10597,9 +10599,9 @@ function eventEndMs(event) {
10597
10599
  function normalizedKey(value) {
10598
10600
  return value.toLowerCase().replace(/[^a-z0-9_]/g, "");
10599
10601
  }
10600
- function lastPathSegment(path19) {
10601
- const parts = path19.split(".");
10602
- return parts[parts.length - 1] ?? path19;
10602
+ function lastPathSegment(path20) {
10603
+ const parts = path20.split(".");
10604
+ return parts[parts.length - 1] ?? path20;
10603
10605
  }
10604
10606
  function valueType(value) {
10605
10607
  if (Array.isArray(value)) return "array";
@@ -10613,12 +10615,12 @@ function serializedByteLength(value) {
10613
10615
  return void 0;
10614
10616
  }
10615
10617
  }
10616
- function pushValueEntries(entries, event, value, path19, key, depth = 0) {
10617
- entries.push({ event, path: path19, key, value });
10618
+ function pushValueEntries(entries, event, value, path20, key, depth = 0) {
10619
+ entries.push({ event, path: path20, key, value });
10618
10620
  if (depth >= 8) return;
10619
10621
  if (Array.isArray(value)) {
10620
10622
  for (const [index, item] of value.entries()) {
10621
- pushValueEntries(entries, event, item, `${path19}.${index}`, String(index), depth + 1);
10623
+ pushValueEntries(entries, event, item, `${path20}.${index}`, String(index), depth + 1);
10622
10624
  }
10623
10625
  return;
10624
10626
  }
@@ -10628,7 +10630,7 @@ function pushValueEntries(entries, event, value, path19, key, depth = 0) {
10628
10630
  entries,
10629
10631
  event,
10630
10632
  value[nestedKey],
10631
- `${path19}.${nestedKey}`,
10633
+ `${path20}.${nestedKey}`,
10632
10634
  nestedKey,
10633
10635
  depth + 1
10634
10636
  );
@@ -10709,9 +10711,9 @@ function eventDurationMs(event) {
10709
10711
  }
10710
10712
  function treeShape(nodes) {
10711
10713
  const lines = [];
10712
- const visit = (node, path19) => {
10713
- lines.push(`${path19}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
10714
- node.children.forEach((child, index) => visit(child, `${path19}.${index}`));
10714
+ const visit = (node, path20) => {
10715
+ lines.push(`${path20}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
10716
+ node.children.forEach((child, index) => visit(child, `${path20}.${index}`));
10715
10717
  };
10716
10718
  nodes.forEach((node, index) => visit(node, String(index)));
10717
10719
  return lines;
@@ -10760,9 +10762,9 @@ function retrievalShape(context) {
10760
10762
  function guardrailShape(context) {
10761
10763
  return guardrailEvents(context).map((event) => signalName(event, ["guardrailName", "guardrail", "guardrailId"], ["guardrail:"])).sort((a, b) => a.localeCompare(b));
10762
10764
  }
10763
- function firstEvidenceForKind(context, kind, path19) {
10765
+ function firstEvidenceForKind(context, kind, path20) {
10764
10766
  const event = context.events.find((candidate) => candidate.kind === kind);
10765
- return event ? [eventEvidence(event, path19)] : runEvidence(context.selectedRun);
10767
+ return event ? [eventEvidence(event, path20)] : runEvidence(context.selectedRun);
10766
10768
  }
10767
10769
  function baselineDiffFinding(message, evidence, expected, actual) {
10768
10770
  return failFinding("baseline.regression", message, evidence, expected, actual);
@@ -11112,13 +11114,13 @@ function createStructureCycleRule() {
11112
11114
  const seenCycles = /* @__PURE__ */ new Set();
11113
11115
  const findings = [];
11114
11116
  for (const event of [...context.events].sort((a, b) => a.eventId.localeCompare(b.eventId))) {
11115
- const path19 = [];
11117
+ const path20 = [];
11116
11118
  const seenAt = /* @__PURE__ */ new Map();
11117
11119
  let current = event;
11118
11120
  while (current) {
11119
11121
  const existing = seenAt.get(current.eventId);
11120
11122
  if (existing !== void 0) {
11121
- const cycle = path19.slice(existing);
11123
+ const cycle = path20.slice(existing);
11122
11124
  const key = cycle.map((item) => item.eventId).sort().join("\0");
11123
11125
  if (!seenCycles.has(key)) {
11124
11126
  seenCycles.add(key);
@@ -11134,8 +11136,8 @@ function createStructureCycleRule() {
11134
11136
  }
11135
11137
  break;
11136
11138
  }
11137
- seenAt.set(current.eventId, path19.length);
11138
- path19.push(current);
11139
+ seenAt.set(current.eventId, path20.length);
11140
+ path20.push(current);
11139
11141
  current = current.parentId ? byId.get(current.parentId) : void 0;
11140
11142
  }
11141
11143
  }
@@ -11932,23 +11934,23 @@ function evaluatePromptInjection(text, options = {}) {
11932
11934
  }
11933
11935
  return fail(ruleId, `Matched ${evidence.length} injection pattern(s).`, evidence, "warning");
11934
11936
  }
11935
- function validateSchemaField(value, field, path19, evidence) {
11937
+ function validateSchemaField(value, field, path20, evidence) {
11936
11938
  const ruleId = "guardrail.structured-output";
11937
11939
  if (field.type) {
11938
11940
  const actual = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
11939
11941
  if (actual !== field.type) {
11940
- evidence.push({ ruleId, path: path19, preview: `expected ${field.type}, got ${actual}` });
11942
+ evidence.push({ ruleId, path: path20, preview: `expected ${field.type}, got ${actual}` });
11941
11943
  return;
11942
11944
  }
11943
11945
  }
11944
11946
  if (field.enum && !field.enum.some((item) => Object.is(item, value))) {
11945
- evidence.push({ ruleId, path: path19, preview: "value not in enum" });
11947
+ evidence.push({ ruleId, path: path20, preview: "value not in enum" });
11946
11948
  }
11947
11949
  if (field.type === "object" && field.required && value && typeof value === "object" && !Array.isArray(value)) {
11948
11950
  const record = value;
11949
11951
  for (const key of field.required) {
11950
11952
  if (!(key in record)) {
11951
- evidence.push({ ruleId, path: `${path19}.${key}`, preview: "missing required key" });
11953
+ evidence.push({ ruleId, path: `${path20}.${key}`, preview: "missing required key" });
11952
11954
  }
11953
11955
  }
11954
11956
  }
@@ -12433,10 +12435,10 @@ function printHuman(result) {
12433
12435
  console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
12434
12436
  }
12435
12437
  for (const finding of result.findings) {
12436
- const path19 = finding.evidence[0]?.path;
12438
+ const path20 = finding.evidence[0]?.path;
12437
12439
  const run = finding.evidence[0]?.runId;
12438
12440
  const runPrefix = run ? `[${run}] ` : "";
12439
- console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
12441
+ console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path20 ? ` (${path20})` : ""}`);
12440
12442
  }
12441
12443
  }
12442
12444
  function readErrorResult(error) {
@@ -12974,10 +12976,10 @@ async function evalRun(input3, options = {}) {
12974
12976
  diagnostics: []
12975
12977
  };
12976
12978
  }
12977
- function evidenceForRun(run, path19) {
12978
- return [{ runId: run.runId, ...path19 !== void 0 ? { path: path19 } : {} }];
12979
+ function evidenceForRun(run, path20) {
12980
+ return [{ runId: run.runId, ...path20 !== void 0 ? { path: path20 } : {} }];
12979
12981
  }
12980
- function evidenceForEvent(event, path19) {
12982
+ function evidenceForEvent(event, path20) {
12981
12983
  return [
12982
12984
  {
12983
12985
  runId: event.runId,
@@ -12985,7 +12987,7 @@ function evidenceForEvent(event, path19) {
12985
12987
  ...event.parentId !== void 0 ? { parentId: event.parentId } : {},
12986
12988
  kind: event.kind,
12987
12989
  name: event.name,
12988
- ...path19 !== void 0 ? { path: path19 } : {}
12990
+ ...path20 !== void 0 ? { path: path20 } : {}
12989
12991
  }
12990
12992
  ];
12991
12993
  }
@@ -13143,9 +13145,9 @@ function collectTextFields(nodes, keys, preferredKinds = []) {
13143
13145
  function tokenize(text) {
13144
13146
  return [...text.toLowerCase().matchAll(/[a-z0-9][a-z0-9'-]{2,}/g)].map((match) => match[0].replace(/^['-]+|['-]+$/g, "")).filter((token) => token.length > 2 && !STOP_WORDS.has(token));
13145
13147
  }
13146
- function firstEvidence(fields, run, path19) {
13148
+ function firstEvidence(fields, run, path20) {
13147
13149
  const first = fields[0];
13148
- return first === void 0 ? evidenceForRun(run, path19) : evidenceForEvent(first.node.event, first.path);
13150
+ return first === void 0 ? evidenceForRun(run, path20) : evidenceForEvent(first.node.event, first.path);
13149
13151
  }
13150
13152
  function collectSourceIds(nodes, keys) {
13151
13153
  const wanted = keySet(keys);
@@ -13522,8 +13524,8 @@ function renderEvalMarkdown(result) {
13522
13524
  if (result.findings.length > 0) {
13523
13525
  lines.push("", "## Findings");
13524
13526
  for (const finding of result.findings) {
13525
- const path19 = finding.evidence[0]?.path;
13526
- lines.push(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
13527
+ const path20 = finding.evidence[0]?.path;
13528
+ lines.push(`- ${finding.ruleId}: ${finding.message}${path20 ? ` (${path20})` : ""}`);
13527
13529
  }
13528
13530
  }
13529
13531
  return `${lines.join("\n")}
@@ -13716,8 +13718,8 @@ function printHuman2(result) {
13716
13718
  console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
13717
13719
  }
13718
13720
  for (const finding of result.findings) {
13719
- const path19 = finding.evidence[0]?.path;
13720
- console.log(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
13721
+ const path20 = finding.evidence[0]?.path;
13722
+ console.log(`- ${finding.ruleId}: ${finding.message}${path20 ? ` (${path20})` : ""}`);
13721
13723
  }
13722
13724
  }
13723
13725
  function readErrorResult2(error) {
@@ -13955,8 +13957,8 @@ function printHuman3(result) {
13955
13957
  console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
13956
13958
  }
13957
13959
  for (const finding of result.findings) {
13958
- const path19 = finding.evidence[0]?.path;
13959
- console.log(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
13960
+ const path20 = finding.evidence[0]?.path;
13961
+ console.log(`- ${finding.ruleId}: ${finding.message}${path20 ? ` (${path20})` : ""}`);
13960
13962
  }
13961
13963
  console.log(`Note: ${result.note}`);
13962
13964
  }
@@ -14075,8 +14077,8 @@ function renderCheckSection(result) {
14075
14077
  `Diagnostics: ${result.diagnostics.length}`
14076
14078
  ];
14077
14079
  for (const finding of result.findings.slice(0, 10)) {
14078
- const path19 = finding.evidence[0]?.path ?? "(run)";
14079
- lines.push(`- ${finding.ruleId}: ${finding.message} (${path19})`);
14080
+ const path20 = finding.evidence[0]?.path ?? "(run)";
14081
+ lines.push(`- ${finding.ruleId}: ${finding.message} (${path20})`);
14080
14082
  }
14081
14083
  for (const diagnostic4 of result.diagnostics.slice(0, 10)) {
14082
14084
  lines.push(`- ${diagnostic4.code}: ${diagnostic4.message}`);
@@ -15246,6 +15248,608 @@ async function indexCleanCommand(options = {}) {
15246
15248
  }
15247
15249
  }
15248
15250
 
15251
+ // packages/core/src/workspace/types.ts
15252
+ var WORKSPACE_SCHEMA_VERSION = "1.0";
15253
+ var WORKSPACE_DIR_NAME = ".agent-inspect";
15254
+ var WORKSPACE_MANIFEST_FILENAME = "workspace.json";
15255
+
15256
+ // packages/core/src/workspace/manifest.ts
15257
+ var DEFAULT_WORKSPACE_LAYOUT = {
15258
+ traceDirs: ["runs"],
15259
+ reportsDir: "reports",
15260
+ artifactsDir: "artifacts",
15261
+ bundlesDir: "bundles",
15262
+ notesDir: "notes"
15263
+ };
15264
+ var DEFAULT_REDACTION_PROFILE = "share";
15265
+ var REDACTION_PROFILES2 = [
15266
+ "local",
15267
+ "share",
15268
+ "strict"
15269
+ ];
15270
+ var INDEX_TYPES = ["none", "sqlite", "custom"];
15271
+ var MAX_WORKSPACE_MANIFEST_BYTES = 64 * 1024;
15272
+ function createDefaultWorkspaceManifest(options) {
15273
+ const project = typeof options.project === "string" ? options.project.trim() : "";
15274
+ const index = {
15275
+ enabled: options.index?.enabled ?? false,
15276
+ type: options.index?.type ?? "none",
15277
+ ...options.index?.path !== void 0 ? { path: options.index.path } : {}
15278
+ };
15279
+ return {
15280
+ schemaVersion: WORKSPACE_SCHEMA_VERSION,
15281
+ project,
15282
+ createdAt: options.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
15283
+ traceDirs: options.traceDirs ? [...options.traceDirs] : [...DEFAULT_WORKSPACE_LAYOUT.traceDirs],
15284
+ reportsDir: options.reportsDir ?? DEFAULT_WORKSPACE_LAYOUT.reportsDir,
15285
+ artifactsDir: options.artifactsDir ?? DEFAULT_WORKSPACE_LAYOUT.artifactsDir,
15286
+ bundlesDir: options.bundlesDir ?? DEFAULT_WORKSPACE_LAYOUT.bundlesDir,
15287
+ notesDir: options.notesDir ?? DEFAULT_WORKSPACE_LAYOUT.notesDir,
15288
+ redactionProfile: options.redactionProfile ?? DEFAULT_REDACTION_PROFILE,
15289
+ index
15290
+ };
15291
+ }
15292
+ function isPlainObject(value) {
15293
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15294
+ }
15295
+ function isSafeRelativeWorkspacePath(p) {
15296
+ if (typeof p !== "string") return false;
15297
+ const trimmed = p.trim();
15298
+ if (trimmed === "") return false;
15299
+ if (trimmed.startsWith("/") || trimmed.startsWith("\\")) return false;
15300
+ if (/^[a-zA-Z]:/.test(trimmed)) return false;
15301
+ const segments = trimmed.split(/[/\\]+/);
15302
+ return !segments.some((seg) => seg === "..");
15303
+ }
15304
+ function validateDirField(value, field, errors) {
15305
+ if (typeof value !== "string" || value.trim() === "") {
15306
+ errors.push(`${field} must be a non-empty string`);
15307
+ return;
15308
+ }
15309
+ if (!isSafeRelativeWorkspacePath(value)) {
15310
+ errors.push(
15311
+ `${field} must be a relative path inside the workspace (no absolute paths or ".." traversal)`
15312
+ );
15313
+ }
15314
+ }
15315
+ function validateIndex(value, errors) {
15316
+ if (!isPlainObject(value)) {
15317
+ errors.push("index must be an object");
15318
+ return void 0;
15319
+ }
15320
+ if (typeof value.enabled !== "boolean") {
15321
+ errors.push("index.enabled must be a boolean");
15322
+ }
15323
+ if (!INDEX_TYPES.includes(value.type)) {
15324
+ errors.push(`index.type must be one of: ${INDEX_TYPES.join(", ")}`);
15325
+ }
15326
+ if (value.path !== void 0 && !isSafeRelativeWorkspacePath(value.path)) {
15327
+ errors.push(
15328
+ 'index.path must be a relative path inside the workspace (no absolute paths or ".." traversal)'
15329
+ );
15330
+ }
15331
+ if (errors.length > 0) return void 0;
15332
+ return {
15333
+ enabled: value.enabled,
15334
+ type: value.type,
15335
+ ...value.path !== void 0 ? { path: value.path } : {}
15336
+ };
15337
+ }
15338
+ function validateWorkspaceManifest(input3) {
15339
+ const errors = [];
15340
+ const warnings = [];
15341
+ if (!isPlainObject(input3)) {
15342
+ return { ok: false, errors: ["manifest must be an object"], warnings };
15343
+ }
15344
+ if (input3.schemaVersion !== WORKSPACE_SCHEMA_VERSION) {
15345
+ errors.push(
15346
+ `schemaVersion must be "${WORKSPACE_SCHEMA_VERSION}" (received ${JSON.stringify(
15347
+ input3.schemaVersion
15348
+ )})`
15349
+ );
15350
+ }
15351
+ if (typeof input3.project !== "string" || input3.project.trim() === "") {
15352
+ errors.push("project must be a non-empty string");
15353
+ }
15354
+ if (typeof input3.createdAt !== "string" || input3.createdAt.trim() === "") {
15355
+ errors.push("createdAt must be a non-empty ISO-8601 string");
15356
+ } else if (Number.isNaN(Date.parse(input3.createdAt))) {
15357
+ errors.push("createdAt must be a valid ISO-8601 date string");
15358
+ }
15359
+ if (!Array.isArray(input3.traceDirs) || input3.traceDirs.length === 0) {
15360
+ errors.push("traceDirs must be a non-empty array");
15361
+ } else {
15362
+ input3.traceDirs.forEach((dir, i) => {
15363
+ if (typeof dir !== "string" || dir.trim() === "") {
15364
+ errors.push(`traceDirs[${i}] must be a non-empty string`);
15365
+ } else if (!isSafeRelativeWorkspacePath(dir)) {
15366
+ errors.push(
15367
+ `traceDirs[${i}] must be a relative path inside the workspace (no absolute paths or ".." traversal)`
15368
+ );
15369
+ }
15370
+ });
15371
+ }
15372
+ validateDirField(input3.reportsDir, "reportsDir", errors);
15373
+ validateDirField(input3.artifactsDir, "artifactsDir", errors);
15374
+ validateDirField(input3.bundlesDir, "bundlesDir", errors);
15375
+ validateDirField(input3.notesDir, "notesDir", errors);
15376
+ if (!REDACTION_PROFILES2.includes(input3.redactionProfile)) {
15377
+ errors.push(`redactionProfile must be one of: ${REDACTION_PROFILES2.join(", ")}`);
15378
+ }
15379
+ const indexErrors = [];
15380
+ const index = validateIndex(input3.index, indexErrors);
15381
+ errors.push(...indexErrors);
15382
+ if (index && index.type !== "none" && !index.enabled) {
15383
+ warnings.push(`index.type is "${index.type}" but index.enabled is false`);
15384
+ }
15385
+ if (errors.length > 0 || index === void 0) {
15386
+ return { ok: false, errors, warnings };
15387
+ }
15388
+ const manifest = {
15389
+ schemaVersion: WORKSPACE_SCHEMA_VERSION,
15390
+ project: input3.project.trim(),
15391
+ createdAt: input3.createdAt,
15392
+ traceDirs: input3.traceDirs.map((d) => d.trim()),
15393
+ reportsDir: input3.reportsDir.trim(),
15394
+ artifactsDir: input3.artifactsDir.trim(),
15395
+ bundlesDir: input3.bundlesDir.trim(),
15396
+ notesDir: input3.notesDir.trim(),
15397
+ redactionProfile: input3.redactionProfile,
15398
+ index
15399
+ };
15400
+ return { ok: true, manifest, errors, warnings };
15401
+ }
15402
+ function parseWorkspaceManifest(json) {
15403
+ const warnings = [];
15404
+ if (typeof json !== "string") {
15405
+ return { ok: false, errors: ["manifest input must be a string"], warnings };
15406
+ }
15407
+ if (json.length > MAX_WORKSPACE_MANIFEST_BYTES) {
15408
+ return {
15409
+ ok: false,
15410
+ errors: [
15411
+ `manifest exceeds maximum size of ${MAX_WORKSPACE_MANIFEST_BYTES} bytes`
15412
+ ],
15413
+ warnings
15414
+ };
15415
+ }
15416
+ let parsed;
15417
+ try {
15418
+ parsed = JSON.parse(json);
15419
+ } catch {
15420
+ return { ok: false, errors: ["manifest is not valid JSON"], warnings };
15421
+ }
15422
+ return validateWorkspaceManifest(parsed);
15423
+ }
15424
+ function serializeWorkspaceManifest(manifest) {
15425
+ return `${JSON.stringify(manifest, null, 2)}
15426
+ `;
15427
+ }
15428
+ var INDEX_DIR_NAME = "index";
15429
+ function resolveWorkspaceLocation(cwd = process.cwd()) {
15430
+ const projectRoot = path14.resolve(cwd);
15431
+ const workspaceDir = path14.join(projectRoot, WORKSPACE_DIR_NAME);
15432
+ return {
15433
+ projectRoot,
15434
+ workspaceDir,
15435
+ manifestPath: path14.join(workspaceDir, WORKSPACE_MANIFEST_FILENAME)
15436
+ };
15437
+ }
15438
+ function resolveInsideWorkspace(workspaceDir, relative) {
15439
+ const base = path14.resolve(workspaceDir);
15440
+ const resolved = path14.resolve(base, relative);
15441
+ const rel = path14.relative(base, resolved);
15442
+ if (rel === "" || rel === "." || !rel.startsWith("..") && !path14.isAbsolute(rel)) {
15443
+ return resolved;
15444
+ }
15445
+ throw new Error(
15446
+ `Workspace path "${relative}" resolves outside the workspace directory`
15447
+ );
15448
+ }
15449
+ async function pathExists(p) {
15450
+ try {
15451
+ await access(p);
15452
+ return true;
15453
+ } catch {
15454
+ return false;
15455
+ }
15456
+ }
15457
+ async function isWritable(p) {
15458
+ try {
15459
+ await access(p, constants$1.W_OK);
15460
+ return true;
15461
+ } catch {
15462
+ return false;
15463
+ }
15464
+ }
15465
+ async function listJsonl(dir) {
15466
+ try {
15467
+ const entries = await readdir(dir);
15468
+ return entries.filter((f) => f.endsWith(".jsonl"));
15469
+ } catch {
15470
+ return [];
15471
+ }
15472
+ }
15473
+ async function countFiles(dir) {
15474
+ try {
15475
+ const entries = await readdir(dir, { withFileTypes: true });
15476
+ return entries.filter((e) => e.isFile()).length;
15477
+ } catch {
15478
+ return 0;
15479
+ }
15480
+ }
15481
+ async function readWorkspaceManifestFile(location) {
15482
+ let raw;
15483
+ try {
15484
+ raw = await readFile(location.manifestPath, "utf-8");
15485
+ } catch {
15486
+ return { exists: false, ok: false, errors: ["workspace.json not found"], warnings: [] };
15487
+ }
15488
+ const parsed = parseWorkspaceManifest(raw);
15489
+ return {
15490
+ exists: true,
15491
+ ok: parsed.ok,
15492
+ ...parsed.manifest ? { manifest: parsed.manifest } : {},
15493
+ errors: parsed.errors,
15494
+ warnings: parsed.warnings
15495
+ };
15496
+ }
15497
+ async function createWorkspace(options = {}) {
15498
+ const location = resolveWorkspaceLocation(options.cwd);
15499
+ const dryRun = options.dryRun === true;
15500
+ const existing = await readWorkspaceManifestFile(location);
15501
+ const topLevelTraces = await listJsonl(location.workspaceDir);
15502
+ const detectedExistingTraces = topLevelTraces.length > 0;
15503
+ let manifest;
15504
+ let created;
15505
+ let adopted;
15506
+ if (existing.exists && existing.ok && existing.manifest) {
15507
+ manifest = existing.manifest;
15508
+ created = false;
15509
+ adopted = true;
15510
+ } else {
15511
+ const project = options.project?.trim() || path14.basename(location.projectRoot) || "workspace";
15512
+ const traceDirs = detectedExistingTraces ? ["runs", "."] : ["runs"];
15513
+ manifest = createDefaultWorkspaceManifest({
15514
+ project,
15515
+ traceDirs,
15516
+ ...options.redactionProfile ? { redactionProfile: options.redactionProfile } : {}
15517
+ });
15518
+ created = true;
15519
+ adopted = detectedExistingTraces || existing.exists && !existing.ok;
15520
+ }
15521
+ const relDirs = uniqueDirs([
15522
+ ...manifest.traceDirs.filter((d) => d !== "."),
15523
+ manifest.reportsDir,
15524
+ manifest.artifactsDir,
15525
+ manifest.bundlesDir,
15526
+ manifest.notesDir,
15527
+ INDEX_DIR_NAME
15528
+ ]);
15529
+ const createdDirs = [];
15530
+ for (const rel of relDirs) {
15531
+ const abs = resolveInsideWorkspace(location.workspaceDir, rel);
15532
+ if (await pathExists(abs)) continue;
15533
+ createdDirs.push(rel);
15534
+ if (!dryRun) await mkdir(abs, { recursive: true });
15535
+ }
15536
+ if (!dryRun && created) {
15537
+ await mkdir(location.workspaceDir, { recursive: true });
15538
+ await writeFile(location.manifestPath, serializeWorkspaceManifest(manifest), "utf-8");
15539
+ }
15540
+ return {
15541
+ location,
15542
+ manifest,
15543
+ created,
15544
+ adopted,
15545
+ createdDirs,
15546
+ detectedExistingTraces,
15547
+ dryRun
15548
+ };
15549
+ }
15550
+ function uniqueDirs(dirs) {
15551
+ const seen = /* @__PURE__ */ new Set();
15552
+ const out = [];
15553
+ for (const d of dirs) {
15554
+ const t = d.trim();
15555
+ if (t === "" || t === "." || seen.has(t)) continue;
15556
+ seen.add(t);
15557
+ out.push(t);
15558
+ }
15559
+ return out;
15560
+ }
15561
+ async function getWorkspaceStatus(location, manifest) {
15562
+ let traceFiles = 0;
15563
+ for (const rel of manifest.traceDirs) {
15564
+ const abs = resolveInsideWorkspace(location.workspaceDir, rel);
15565
+ traceFiles += (await listJsonl(abs)).length;
15566
+ }
15567
+ const reports = await countFiles(
15568
+ resolveInsideWorkspace(location.workspaceDir, manifest.reportsDir)
15569
+ );
15570
+ const artifacts = await countFiles(
15571
+ resolveInsideWorkspace(location.workspaceDir, manifest.artifactsDir)
15572
+ );
15573
+ const bundles = await countFiles(
15574
+ resolveInsideWorkspace(location.workspaceDir, manifest.bundlesDir)
15575
+ );
15576
+ const notes = await countFiles(
15577
+ resolveInsideWorkspace(location.workspaceDir, manifest.notesDir)
15578
+ );
15579
+ const indexPath2 = manifest.index.path ? resolveInsideWorkspace(location.workspaceDir, manifest.index.path) : resolveInsideWorkspace(location.workspaceDir, INDEX_DIR_NAME);
15580
+ return {
15581
+ project: manifest.project,
15582
+ traceFiles,
15583
+ reports,
15584
+ artifacts,
15585
+ bundles,
15586
+ notes,
15587
+ index: {
15588
+ enabled: manifest.index.enabled,
15589
+ type: manifest.index.type,
15590
+ exists: await pathExists(indexPath2)
15591
+ }
15592
+ };
15593
+ }
15594
+ async function doctorWorkspace(location) {
15595
+ const checks2 = [];
15596
+ const manifestResult = await readWorkspaceManifestFile(location);
15597
+ if (!manifestResult.exists) {
15598
+ checks2.push({
15599
+ id: "manifest",
15600
+ status: "fail",
15601
+ message: "workspace.json not found (run `agent-inspect workspace init`)"
15602
+ });
15603
+ return { ok: false, checks: checks2 };
15604
+ }
15605
+ if (!manifestResult.ok || !manifestResult.manifest) {
15606
+ checks2.push({
15607
+ id: "manifest",
15608
+ status: "fail",
15609
+ message: `workspace.json is invalid: ${manifestResult.errors.join("; ")}`
15610
+ });
15611
+ return { ok: false, checks: checks2 };
15612
+ }
15613
+ const manifest = manifestResult.manifest;
15614
+ checks2.push({ id: "manifest", status: "pass", message: "workspace.json is valid" });
15615
+ for (const warning of manifestResult.warnings) {
15616
+ checks2.push({ id: "manifest-warning", status: "warn", message: warning });
15617
+ }
15618
+ const dirFields = [
15619
+ ...manifest.traceDirs.filter((d) => d !== ".").map((d, i) => [`traceDir[${i}]`, d]),
15620
+ ["reportsDir", manifest.reportsDir],
15621
+ ["artifactsDir", manifest.artifactsDir],
15622
+ ["bundlesDir", manifest.bundlesDir],
15623
+ ["notesDir", manifest.notesDir]
15624
+ ];
15625
+ for (const [id, rel] of dirFields) {
15626
+ let abs;
15627
+ try {
15628
+ abs = resolveInsideWorkspace(location.workspaceDir, rel);
15629
+ } catch (error) {
15630
+ checks2.push({
15631
+ id,
15632
+ status: "fail",
15633
+ message: error instanceof Error ? error.message : String(error)
15634
+ });
15635
+ continue;
15636
+ }
15637
+ if (!await pathExists(abs)) {
15638
+ checks2.push({ id, status: "warn", message: `${rel}/ does not exist yet` });
15639
+ } else if (!await isWritable(abs)) {
15640
+ checks2.push({ id, status: "fail", message: `${rel}/ is not writable` });
15641
+ } else {
15642
+ checks2.push({ id, status: "pass", message: `${rel}/ is present and writable` });
15643
+ }
15644
+ }
15645
+ let newestTraceMtime = 0;
15646
+ for (const rel of manifest.traceDirs) {
15647
+ const abs = resolveInsideWorkspace(location.workspaceDir, rel);
15648
+ for (const file of await listJsonl(abs)) {
15649
+ try {
15650
+ const s = await stat(path14.join(abs, file));
15651
+ newestTraceMtime = Math.max(newestTraceMtime, s.mtimeMs);
15652
+ } catch {
15653
+ checks2.push({ id: "trace-readability", status: "warn", message: `cannot stat ${rel}/${file}` });
15654
+ }
15655
+ }
15656
+ }
15657
+ if (manifest.index.enabled) {
15658
+ const indexPath2 = manifest.index.path ? resolveInsideWorkspace(location.workspaceDir, manifest.index.path) : resolveInsideWorkspace(location.workspaceDir, INDEX_DIR_NAME);
15659
+ if (!await pathExists(indexPath2)) {
15660
+ checks2.push({ id: "index", status: "warn", message: "index enabled but not built" });
15661
+ } else {
15662
+ try {
15663
+ const s = await stat(indexPath2);
15664
+ if (newestTraceMtime > s.mtimeMs) {
15665
+ checks2.push({ id: "index", status: "warn", message: "index is stale (traces are newer)" });
15666
+ } else {
15667
+ checks2.push({ id: "index", status: "pass", message: "index is present" });
15668
+ }
15669
+ } catch {
15670
+ checks2.push({ id: "index", status: "warn", message: "cannot stat index" });
15671
+ }
15672
+ }
15673
+ }
15674
+ const ok = !checks2.some((c) => c.status === "fail");
15675
+ return { ok, checks: checks2 };
15676
+ }
15677
+ async function cleanWorkspace(location, manifest, options = {}) {
15678
+ const dryRun = options.confirm !== true;
15679
+ const targets = uniqueDirs([
15680
+ manifest.reportsDir,
15681
+ manifest.artifactsDir,
15682
+ manifest.bundlesDir,
15683
+ manifest.index.path ?? INDEX_DIR_NAME
15684
+ ]);
15685
+ const removed = [];
15686
+ for (const rel of targets) {
15687
+ const abs = resolveInsideWorkspace(location.workspaceDir, rel);
15688
+ let entries;
15689
+ try {
15690
+ entries = await readdir(abs);
15691
+ } catch {
15692
+ continue;
15693
+ }
15694
+ for (const entry of entries) {
15695
+ const relPath = `${rel}/${entry}`;
15696
+ removed.push(relPath);
15697
+ if (!dryRun) {
15698
+ await rm(path14.join(abs, entry), { recursive: true, force: true });
15699
+ }
15700
+ }
15701
+ }
15702
+ return { dryRun, removed };
15703
+ }
15704
+
15705
+ // packages/cli/src/workspace.ts
15706
+ function printJson4(payload) {
15707
+ console.log(JSON.stringify(payload, null, 2));
15708
+ }
15709
+ function fail3(json, message) {
15710
+ if (json) {
15711
+ printJson4({ ok: false, error: message });
15712
+ } else {
15713
+ console.error(`[AgentInspect] workspace: ${message}`);
15714
+ }
15715
+ process.exitCode = 1;
15716
+ }
15717
+ async function workspaceInitCommand(options = {}) {
15718
+ try {
15719
+ const result = await createWorkspace({
15720
+ ...options.cwd ? { cwd: options.cwd } : {},
15721
+ ...options.project ? { project: options.project } : {},
15722
+ ...options.redactionProfile ? { redactionProfile: options.redactionProfile } : {},
15723
+ dryRun: options.dryRun === true
15724
+ });
15725
+ if (options.json) {
15726
+ printJson4({
15727
+ ok: true,
15728
+ version,
15729
+ dryRun: result.dryRun,
15730
+ created: result.created,
15731
+ adopted: result.adopted,
15732
+ detectedExistingTraces: result.detectedExistingTraces,
15733
+ workspaceDir: result.location.workspaceDir,
15734
+ manifestPath: result.location.manifestPath,
15735
+ createdDirs: result.createdDirs,
15736
+ project: result.manifest.project
15737
+ });
15738
+ return;
15739
+ }
15740
+ console.log("AgentInspect workspace");
15741
+ console.log(`Project: ${result.manifest.project}`);
15742
+ console.log(`Directory: ${result.location.workspaceDir}`);
15743
+ if (result.dryRun) {
15744
+ console.log("Dry run \u2014 would create:");
15745
+ for (const dir of result.createdDirs) console.log(`- ${dir}/`);
15746
+ if (result.created) console.log(`- ${result.location.manifestPath}`);
15747
+ return;
15748
+ }
15749
+ if (result.adopted && !result.created) {
15750
+ console.log("Adopted existing workspace.json (left unchanged).");
15751
+ } else if (result.created) {
15752
+ console.log("Created workspace.json");
15753
+ }
15754
+ if (result.detectedExistingTraces) {
15755
+ console.log("Detected existing traces \u2014 preserved without changes.");
15756
+ }
15757
+ for (const dir of result.createdDirs) console.log(`Created ${dir}/`);
15758
+ console.log("\nNext: `agent-inspect workspace status`");
15759
+ } catch (error) {
15760
+ fail3(options.json, error instanceof Error ? error.message : String(error));
15761
+ }
15762
+ }
15763
+ async function workspaceStatusCommand(options = {}) {
15764
+ const location = resolveWorkspaceLocation(options.cwd);
15765
+ const manifestResult = await readWorkspaceManifestFile(location);
15766
+ if (!manifestResult.ok || !manifestResult.manifest) {
15767
+ fail3(
15768
+ options.json,
15769
+ manifestResult.exists ? `invalid workspace.json: ${manifestResult.errors.join("; ")}` : "no workspace found (run `agent-inspect workspace init`)"
15770
+ );
15771
+ return;
15772
+ }
15773
+ try {
15774
+ const status = await getWorkspaceStatus(location, manifestResult.manifest);
15775
+ if (options.json) {
15776
+ printJson4({ ok: true, ...status });
15777
+ return;
15778
+ }
15779
+ console.log(`Project: ${status.project}`);
15780
+ console.log(`Traces: ${status.traceFiles}`);
15781
+ console.log(`Reports: ${status.reports}`);
15782
+ console.log(`Artifacts: ${status.artifacts}`);
15783
+ console.log(`Bundles: ${status.bundles}`);
15784
+ console.log(`Notes: ${status.notes}`);
15785
+ console.log(
15786
+ `Index: ${status.index.enabled ? status.index.type : "disabled"}${status.index.exists ? " (present)" : ""}`
15787
+ );
15788
+ } catch (error) {
15789
+ fail3(options.json, error instanceof Error ? error.message : String(error));
15790
+ }
15791
+ }
15792
+ async function workspaceDoctorCommand(options = {}) {
15793
+ const location = resolveWorkspaceLocation(options.cwd);
15794
+ const result = await doctorWorkspace(location);
15795
+ if (options.json) {
15796
+ printJson4({ ok: result.ok, checks: result.checks });
15797
+ } else {
15798
+ console.log("AgentInspect workspace doctor");
15799
+ for (const check of result.checks) {
15800
+ const mark = check.status === "pass" ? "\u2713" : check.status === "warn" ? "!" : "\u2717";
15801
+ console.log(`${mark} ${check.id}: ${check.message}`);
15802
+ }
15803
+ console.log(result.ok ? "\nWorkspace looks healthy." : "\nWorkspace has problems.");
15804
+ }
15805
+ if (!result.ok) process.exitCode = 1;
15806
+ }
15807
+ async function workspaceCleanCommand(options = {}) {
15808
+ const location = resolveWorkspaceLocation(options.cwd);
15809
+ const manifestResult = await readWorkspaceManifestFile(location);
15810
+ if (!manifestResult.ok || !manifestResult.manifest) {
15811
+ fail3(
15812
+ options.json,
15813
+ manifestResult.exists ? `invalid workspace.json: ${manifestResult.errors.join("; ")}` : "no workspace found (run `agent-inspect workspace init`)"
15814
+ );
15815
+ return;
15816
+ }
15817
+ try {
15818
+ const result = await cleanWorkspace(location, manifestResult.manifest, {
15819
+ confirm: options.yes === true
15820
+ });
15821
+ if (options.json) {
15822
+ printJson4({ ok: true, dryRun: result.dryRun, removed: result.removed });
15823
+ return;
15824
+ }
15825
+ if (result.dryRun) {
15826
+ console.log("Dry run \u2014 would remove (traces are never deleted):");
15827
+ if (result.removed.length === 0) console.log("- (nothing)");
15828
+ for (const item of result.removed) console.log(`- ${item}`);
15829
+ console.log("\nRe-run with --yes to delete.");
15830
+ return;
15831
+ }
15832
+ console.log("Removed generated workspace content (traces preserved):");
15833
+ if (result.removed.length === 0) console.log("- (nothing)");
15834
+ for (const item of result.removed) console.log(`- ${item}`);
15835
+ } catch (error) {
15836
+ fail3(options.json, error instanceof Error ? error.message : String(error));
15837
+ }
15838
+ }
15839
+ async function workspacePathCommand(options = {}) {
15840
+ const location = resolveWorkspaceLocation(options.cwd);
15841
+ if (options.json) {
15842
+ printJson4({
15843
+ ok: true,
15844
+ projectRoot: location.projectRoot,
15845
+ workspaceDir: location.workspaceDir,
15846
+ manifestPath: location.manifestPath
15847
+ });
15848
+ return;
15849
+ }
15850
+ console.log(location.workspaceDir);
15851
+ }
15852
+
15249
15853
  // packages/cli/src/index.ts
15250
15854
  function runCommand(action) {
15251
15855
  void action().catch((error) => {
@@ -15577,6 +16181,28 @@ function createCliProgram() {
15577
16181
  indexCmd.command("clean").description("Remove the local index file").option("--dir <path>", "trace directory").option("--json", "print JSON result").action((opts) => {
15578
16182
  runCommand(() => indexCleanCommand(opts));
15579
16183
  });
16184
+ const workspaceCmd = program.command("workspace").description("Manage a project-local AgentInspect workspace (.agent-inspect)");
16185
+ workspaceCmd.command("init").description("Create or adopt a local workspace (never deletes traces)").option("--project <name>", "project name (default: directory name)").addOption(
16186
+ new Option("--redaction-profile <profile>", "default redaction posture").choices([
16187
+ "local",
16188
+ "share",
16189
+ "strict"
16190
+ ])
16191
+ ).option("--dry-run", "print planned changes without writing").option("--json", "print deterministic JSON result").action((opts) => {
16192
+ runCommand(() => workspaceInitCommand(opts));
16193
+ });
16194
+ workspaceCmd.command("status").description("Show workspace counts and index status (read-only)").option("--json", "print deterministic JSON result").action((opts) => {
16195
+ runCommand(() => workspaceStatusCommand(opts));
16196
+ });
16197
+ workspaceCmd.command("doctor").description("Validate workspace config, permissions, and index freshness").option("--json", "print deterministic JSON result").action((opts) => {
16198
+ runCommand(() => workspaceDoctorCommand(opts));
16199
+ });
16200
+ workspaceCmd.command("clean").description("Remove generated content (reports/artifacts/bundles/index); traces are never deleted").option("--yes", "actually delete (default is a dry-run)").option("--json", "print deterministic JSON result").action((opts) => {
16201
+ runCommand(() => workspaceCleanCommand(opts));
16202
+ });
16203
+ workspaceCmd.command("path").description("Print resolved workspace paths").option("--json", "print deterministic JSON result").action((opts) => {
16204
+ runCommand(() => workspacePathCommand(opts));
16205
+ });
15580
16206
  return program;
15581
16207
  }
15582
16208
  function isPrimaryModule() {