agent-inspect 6.1.0 → 6.3.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.
@@ -11,7 +11,7 @@ import { createServer } from 'http';
11
11
  import { createRequire } from 'module';
12
12
 
13
13
  // package.json
14
- var version = "6.1.0";
14
+ var version = "6.3.0";
15
15
 
16
16
  // packages/cli/src/trace-dir-scale.ts
17
17
  var TRACE_COUNT_WARN = 1e3;
@@ -3129,13 +3129,13 @@ function pairSteps(left, right) {
3129
3129
  return pairs;
3130
3130
  }
3131
3131
  function compareLeafSteps(L, R, segments, opts, out) {
3132
- const path23 = buildPath(segments);
3132
+ const path25 = buildPath(segments);
3133
3133
  if (L.name !== R.name) {
3134
3134
  out.push({
3135
3135
  kind: "structure",
3136
3136
  severity: "warning",
3137
3137
  message: "Step name differs",
3138
- path: path23,
3138
+ path: path25,
3139
3139
  left: L.name,
3140
3140
  right: R.name
3141
3141
  });
@@ -3145,7 +3145,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
3145
3145
  kind: "step-type",
3146
3146
  severity: "warning",
3147
3147
  message: "Step type differs",
3148
- path: path23,
3148
+ path: path25,
3149
3149
  left: L.type,
3150
3150
  right: R.type
3151
3151
  });
@@ -3155,7 +3155,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
3155
3155
  kind: "step-status",
3156
3156
  severity: "warning",
3157
3157
  message: "Step status differs",
3158
- path: path23,
3158
+ path: path25,
3159
3159
  left: L.status,
3160
3160
  right: R.status
3161
3161
  });
@@ -3167,7 +3167,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
3167
3167
  kind: "error",
3168
3168
  severity: "error",
3169
3169
  message: "Step error message differs",
3170
- path: path23,
3170
+ path: path25,
3171
3171
  left: le || void 0,
3172
3172
  right: re || void 0
3173
3173
  });
@@ -3185,7 +3185,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
3185
3185
  kind: "duration",
3186
3186
  severity: "info",
3187
3187
  message: "Step duration differs",
3188
- path: path23,
3188
+ path: path25,
3189
3189
  left: ld,
3190
3190
  right: rd
3191
3191
  });
@@ -3198,7 +3198,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
3198
3198
  kind: "metadata",
3199
3199
  severity: "info",
3200
3200
  message: "Step metadata differs",
3201
- path: path23,
3201
+ path: path25,
3202
3202
  left: L.metadata,
3203
3203
  right: R.metadata
3204
3204
  });
@@ -3210,7 +3210,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
3210
3210
  kind: "output",
3211
3211
  severity: "info",
3212
3212
  message: "Output preview differs",
3213
- path: path23,
3213
+ path: path25,
3214
3214
  left: L.outputPreview,
3215
3215
  right: R.outputPreview
3216
3216
  });
@@ -3370,11 +3370,11 @@ function diffRuns(left, right, options) {
3370
3370
  }
3371
3371
 
3372
3372
  // packages/core/src/diff/renderer.ts
3373
- function formatPath(path23) {
3374
- if (path23 === void 0 || path23.path.length === 0) {
3373
+ function formatPath(path25) {
3374
+ if (path25 === void 0 || path25.path.length === 0) {
3375
3375
  return "(run)";
3376
3376
  }
3377
- return path23.path.map((s) => s.name).join(" > ");
3377
+ return path25.path.map((s) => s.name).join(" > ");
3378
3378
  }
3379
3379
  function formatValue(v, verbose) {
3380
3380
  if (v === void 0) return "(undefined)";
@@ -4479,17 +4479,17 @@ function applyRule(rule, value, replacement) {
4479
4479
  }
4480
4480
  return value;
4481
4481
  }
4482
- function childPath(path23, key) {
4482
+ function childPath(path25, key) {
4483
4483
  if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
4484
- return path23 ? `${path23}.${key}` : key;
4484
+ return path25 ? `${path25}.${key}` : key;
4485
4485
  }
4486
- return `${path23 || "$"}[${JSON.stringify(key)}]`;
4486
+ return `${path25 || "$"}[${JSON.stringify(key)}]`;
4487
4487
  }
4488
- function indexPath(path23, index) {
4489
- return `${path23 || "$"}[${index}]`;
4488
+ function indexPath(path25, index) {
4489
+ return `${path25 || "$"}[${index}]`;
4490
4490
  }
4491
- function makeFinding(path23, detector, action, matchKind, severity = "warning", preview) {
4492
- return preview === void 0 ? { path: path23, detector, action, severity, matchKind } : { path: path23, detector, action, severity, matchKind, preview };
4491
+ function makeFinding(path25, detector, action, matchKind, severity = "warning", preview) {
4492
+ return preview === void 0 ? { path: path25, detector, action, severity, matchKind } : { path: path25, detector, action, severity, matchKind, preview };
4493
4493
  }
4494
4494
  function createRedactionProfile(profile = "local") {
4495
4495
  switch (profile) {
@@ -4558,11 +4558,11 @@ var Redactor2 = class {
4558
4558
  #recordFinding(state, finding) {
4559
4559
  if (this.#collectFindings) state.findings.push(finding);
4560
4560
  }
4561
- #redactValue(value, key, path23, depth, state) {
4561
+ #redactValue(value, key, path25, depth, state) {
4562
4562
  if (depth > this.#maxDepth) {
4563
4563
  this.#recordFinding(
4564
4564
  state,
4565
- makeFinding(path23, "structure.maxDepth", "truncate", "value", "warning")
4565
+ makeFinding(path25, "structure.maxDepth", "truncate", "value", "warning")
4566
4566
  );
4567
4567
  return "[Truncated]";
4568
4568
  }
@@ -4571,19 +4571,19 @@ var Redactor2 = class {
4571
4571
  if (rule) {
4572
4572
  this.#recordFinding(
4573
4573
  state,
4574
- makeFinding(path23, `key.${rule.key}`, actionForRule(rule), "key", "warning")
4574
+ makeFinding(path25, `key.${rule.key}`, actionForRule(rule), "key", "warning")
4575
4575
  );
4576
4576
  return applyRule(rule, value, this.#replacement);
4577
4577
  }
4578
4578
  }
4579
4579
  for (const detector of this.#detectors) {
4580
- const detections = detector.detect({ path: path23, key, value });
4580
+ const detections = detector.detect({ path: path25, key, value });
4581
4581
  for (const detection of detections) {
4582
4582
  const action = detection.action ?? "replace";
4583
4583
  this.#recordFinding(
4584
4584
  state,
4585
4585
  makeFinding(
4586
- path23,
4586
+ path25,
4587
4587
  detector.id,
4588
4588
  action,
4589
4589
  detection.matchKind ?? detector.matchKind ?? "custom",
@@ -4601,7 +4601,7 @@ var Redactor2 = class {
4601
4601
  const out = [];
4602
4602
  state.seen.set(value, out);
4603
4603
  value.forEach((item, index) => {
4604
- out[index] = this.#redactValue(item, void 0, indexPath(path23, index), depth + 1, state);
4604
+ out[index] = this.#redactValue(item, void 0, indexPath(path25, index), depth + 1, state);
4605
4605
  });
4606
4606
  return out;
4607
4607
  }
@@ -4613,7 +4613,7 @@ var Redactor2 = class {
4613
4613
  out[entryKey] = this.#redactValue(
4614
4614
  entryValue,
4615
4615
  entryKey,
4616
- childPath(path23 === "$" ? "" : path23, entryKey),
4616
+ childPath(path25 === "$" ? "" : path25, entryKey),
4617
4617
  depth + 1,
4618
4618
  state
4619
4619
  );
@@ -5540,23 +5540,23 @@ function evaluatePromptInjection(text, options = {}) {
5540
5540
  }
5541
5541
  return fail(ruleId, `Matched ${evidence.length} injection pattern(s).`, evidence, "warning");
5542
5542
  }
5543
- function validateSchemaField(value, field, path23, evidence) {
5543
+ function validateSchemaField(value, field, path25, evidence) {
5544
5544
  const ruleId = "guardrail.structured-output";
5545
5545
  if (field.type) {
5546
5546
  const actual = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
5547
5547
  if (actual !== field.type) {
5548
- evidence.push({ ruleId, path: path23, preview: `expected ${field.type}, got ${actual}` });
5548
+ evidence.push({ ruleId, path: path25, preview: `expected ${field.type}, got ${actual}` });
5549
5549
  return;
5550
5550
  }
5551
5551
  }
5552
5552
  if (field.enum && !field.enum.some((item) => Object.is(item, value))) {
5553
- evidence.push({ ruleId, path: path23, preview: "value not in enum" });
5553
+ evidence.push({ ruleId, path: path25, preview: "value not in enum" });
5554
5554
  }
5555
5555
  if (field.type === "object" && field.required && value && typeof value === "object" && !Array.isArray(value)) {
5556
5556
  const record = value;
5557
5557
  for (const key of field.required) {
5558
5558
  if (!(key in record)) {
5559
- evidence.push({ ruleId, path: `${path23}.${key}`, preview: "missing required key" });
5559
+ evidence.push({ ruleId, path: `${path25}.${key}`, preview: "missing required key" });
5560
5560
  }
5561
5561
  }
5562
5562
  }
@@ -6057,10 +6057,10 @@ function printHuman(result) {
6057
6057
  console.log(`- ${diagnostic3.code}: ${diagnostic3.message}`);
6058
6058
  }
6059
6059
  for (const finding of result.findings) {
6060
- const path23 = finding.evidence[0]?.path;
6060
+ const path25 = finding.evidence[0]?.path;
6061
6061
  const run = finding.evidence[0]?.runId;
6062
6062
  const runPrefix = run ? `[${run}] ` : "";
6063
- console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path23 ? ` (${path23})` : ""}`);
6063
+ console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path25 ? ` (${path25})` : ""}`);
6064
6064
  }
6065
6065
  }
6066
6066
  function readErrorResult(error) {
@@ -7404,10 +7404,10 @@ async function evalRun(input3, options = {}) {
7404
7404
  diagnostics: []
7405
7405
  };
7406
7406
  }
7407
- function evidenceForRun(run, path23) {
7408
- return [{ runId: run.runId, ...path23 !== void 0 ? { path: path23 } : {} }];
7407
+ function evidenceForRun(run, path25) {
7408
+ return [{ runId: run.runId, ...path25 !== void 0 ? { path: path25 } : {} }];
7409
7409
  }
7410
- function evidenceForEvent(event, path23) {
7410
+ function evidenceForEvent(event, path25) {
7411
7411
  return [
7412
7412
  {
7413
7413
  runId: event.runId,
@@ -7415,7 +7415,7 @@ function evidenceForEvent(event, path23) {
7415
7415
  ...event.parentId !== void 0 ? { parentId: event.parentId } : {},
7416
7416
  kind: event.kind,
7417
7417
  name: event.name,
7418
- ...path23 !== void 0 ? { path: path23 } : {}
7418
+ ...path25 !== void 0 ? { path: path25 } : {}
7419
7419
  }
7420
7420
  ];
7421
7421
  }
@@ -7573,9 +7573,9 @@ function collectTextFields(nodes, keys, preferredKinds = []) {
7573
7573
  function tokenize(text) {
7574
7574
  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));
7575
7575
  }
7576
- function firstEvidence(fields, run, path23) {
7576
+ function firstEvidence(fields, run, path25) {
7577
7577
  const first = fields[0];
7578
- return first === void 0 ? evidenceForRun(run, path23) : evidenceForEvent(first.node.event, first.path);
7578
+ return first === void 0 ? evidenceForRun(run, path25) : evidenceForEvent(first.node.event, first.path);
7579
7579
  }
7580
7580
  function collectSourceIds(nodes, keys) {
7581
7581
  const wanted = keySet(keys);
@@ -7952,8 +7952,8 @@ function renderEvalMarkdown(result) {
7952
7952
  if (result.findings.length > 0) {
7953
7953
  lines.push("", "## Findings");
7954
7954
  for (const finding of result.findings) {
7955
- const path23 = finding.evidence[0]?.path;
7956
- lines.push(`- ${finding.ruleId}: ${finding.message}${path23 ? ` (${path23})` : ""}`);
7955
+ const path25 = finding.evidence[0]?.path;
7956
+ lines.push(`- ${finding.ruleId}: ${finding.message}${path25 ? ` (${path25})` : ""}`);
7957
7957
  }
7958
7958
  }
7959
7959
  return `${lines.join("\n")}
@@ -8146,8 +8146,8 @@ function printHuman2(result) {
8146
8146
  console.log(`- ${diagnostic3.code}: ${diagnostic3.message}`);
8147
8147
  }
8148
8148
  for (const finding of result.findings) {
8149
- const path23 = finding.evidence[0]?.path;
8150
- console.log(`- ${finding.ruleId}: ${finding.message}${path23 ? ` (${path23})` : ""}`);
8149
+ const path25 = finding.evidence[0]?.path;
8150
+ console.log(`- ${finding.ruleId}: ${finding.message}${path25 ? ` (${path25})` : ""}`);
8151
8151
  }
8152
8152
  }
8153
8153
  function readErrorResult2(error) {
@@ -8385,8 +8385,8 @@ function printHuman3(result) {
8385
8385
  console.log(`- ${diagnostic3.code}: ${diagnostic3.message}`);
8386
8386
  }
8387
8387
  for (const finding of result.findings) {
8388
- const path23 = finding.evidence[0]?.path;
8389
- console.log(`- ${finding.ruleId}: ${finding.message}${path23 ? ` (${path23})` : ""}`);
8388
+ const path25 = finding.evidence[0]?.path;
8389
+ console.log(`- ${finding.ruleId}: ${finding.message}${path25 ? ` (${path25})` : ""}`);
8390
8390
  }
8391
8391
  console.log(`Note: ${result.note}`);
8392
8392
  }
@@ -8537,8 +8537,8 @@ function renderCheckSection(result) {
8537
8537
  `Diagnostics: ${result.diagnostics.length}`
8538
8538
  ];
8539
8539
  for (const finding of result.findings.slice(0, 10)) {
8540
- const path23 = finding.evidence[0]?.path ?? "(run)";
8541
- lines.push(`- ${finding.ruleId}: ${finding.message} (${path23})`);
8540
+ const path25 = finding.evidence[0]?.path ?? "(run)";
8541
+ lines.push(`- ${finding.ruleId}: ${finding.message} (${path25})`);
8542
8542
  }
8543
8543
  for (const diagnostic3 of result.diagnostics.slice(0, 10)) {
8544
8544
  lines.push(`- ${diagnostic3.code}: ${diagnostic3.message}`);
@@ -9863,6 +9863,176 @@ Summary: ${failed} failed, ${warned} warnings`);
9863
9863
  if (failed > 0) process2.exitCode = 1;
9864
9864
  }
9865
9865
 
9866
+ // packages/adapter-sdk/src/privacy.ts
9867
+ var PRIVACY_CHECKLIST_ITEMS = [
9868
+ {
9869
+ id: "capture-metadata-only-default",
9870
+ label: "Default capture is metadata-only (no full prompts/outputs on disk)",
9871
+ required: true
9872
+ },
9873
+ {
9874
+ id: "no-network-by-default",
9875
+ label: "Adapter tests and defaults do not call external networks",
9876
+ required: true
9877
+ },
9878
+ {
9879
+ id: "no-upload",
9880
+ label: "Adapter does not upload traces or logs to vendors by default",
9881
+ required: true
9882
+ },
9883
+ {
9884
+ id: "redaction-documented",
9885
+ label: "Redaction/capture modes are documented for adapter users",
9886
+ required: true
9887
+ },
9888
+ {
9889
+ id: "framework-deps-scoped",
9890
+ label: "Framework SDK dependencies stay in the optional adapter package",
9891
+ required: true
9892
+ }
9893
+ ];
9894
+ function runPrivacyChecklist(input3 = {}) {
9895
+ const captureMode = input3.captureMode ?? "metadata-only";
9896
+ const items = [
9897
+ {
9898
+ id: "capture-metadata-only-default",
9899
+ ok: captureMode === "metadata-only",
9900
+ detail: captureMode === "metadata-only" ? void 0 : `captureMode is ${captureMode}; metadata-only is required by default`
9901
+ },
9902
+ {
9903
+ id: "no-network-by-default",
9904
+ ok: input3.networkAllowed !== true,
9905
+ detail: input3.networkAllowed ? "network must be disabled in adapter defaults" : void 0
9906
+ },
9907
+ {
9908
+ id: "no-upload",
9909
+ ok: input3.uploadAllowed !== true,
9910
+ detail: input3.uploadAllowed ? "upload must not be enabled by default" : void 0
9911
+ },
9912
+ {
9913
+ id: "redaction-documented",
9914
+ ok: input3.redactionDocumented !== false,
9915
+ detail: input3.redactionDocumented === false ? "document capture/redaction behavior in adapter README" : void 0
9916
+ },
9917
+ {
9918
+ id: "framework-deps-scoped",
9919
+ ok: input3.frameworkDepsPackageScoped !== false,
9920
+ detail: input3.frameworkDepsPackageScoped === false ? "keep framework SDK deps out of agent-inspect root/core" : void 0
9921
+ }
9922
+ ];
9923
+ const requiredFailed = items.some(
9924
+ (item) => PRIVACY_CHECKLIST_ITEMS.find((def) => def.id === item.id)?.required && !item.ok
9925
+ );
9926
+ return { ok: !requiredFailed, items };
9927
+ }
9928
+ var PLUGIN_MANIFEST_FILENAME = "agent-inspect-plugin.manifest.json";
9929
+ var PLUGIN_NAME_PREFIXES = [
9930
+ "agent-inspect-plugin-",
9931
+ "agent-inspect-adapter-",
9932
+ "agent-inspect-renderer-",
9933
+ "agent-inspect-check-",
9934
+ "agent-inspect-importer-"
9935
+ ];
9936
+ function isPlainObject2(value) {
9937
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9938
+ }
9939
+ function isPluginPackageName(name) {
9940
+ return PLUGIN_NAME_PREFIXES.some((prefix) => name.startsWith(prefix));
9941
+ }
9942
+ function inferPluginTypeFromName(name) {
9943
+ if (name.startsWith("agent-inspect-adapter-")) return "adapter";
9944
+ if (name.startsWith("agent-inspect-renderer-")) return "renderer";
9945
+ if (name.startsWith("agent-inspect-check-")) return "check";
9946
+ if (name.startsWith("agent-inspect-importer-")) return "importer";
9947
+ if (name.startsWith("agent-inspect-plugin-")) return "plugin";
9948
+ return void 0;
9949
+ }
9950
+ function parsePluginManifest(input3) {
9951
+ const errors = [];
9952
+ const warnings = [];
9953
+ if (!isPlainObject2(input3)) {
9954
+ return { ok: false, errors: ["manifest must be a JSON object"], warnings };
9955
+ }
9956
+ if (input3.schemaVersion !== "1.0") {
9957
+ errors.push('schemaVersion must be "1.0"');
9958
+ }
9959
+ const id = typeof input3.id === "string" ? input3.id.trim() : "";
9960
+ if (!id || !isPluginPackageName(id)) {
9961
+ errors.push("id must use an agent-inspect plugin naming prefix");
9962
+ }
9963
+ const type = typeof input3.type === "string" ? input3.type.trim() : "";
9964
+ const allowedTypes = [
9965
+ "plugin",
9966
+ "adapter",
9967
+ "renderer",
9968
+ "check",
9969
+ "importer"
9970
+ ];
9971
+ if (!allowedTypes.includes(type)) {
9972
+ errors.push(`type must be one of: ${allowedTypes.join(", ")}`);
9973
+ }
9974
+ const name = typeof input3.name === "string" ? input3.name.trim() : "";
9975
+ if (!name) errors.push("name must be a non-empty string");
9976
+ const version2 = typeof input3.version === "string" ? input3.version.trim() : "";
9977
+ if (!version2) errors.push("version must be a non-empty string");
9978
+ let privacy;
9979
+ if (input3.privacy !== void 0) {
9980
+ if (!isPlainObject2(input3.privacy)) {
9981
+ errors.push("privacy must be an object");
9982
+ } else {
9983
+ privacy = {};
9984
+ if (input3.privacy.captureMode !== void 0) {
9985
+ privacy.captureMode = String(input3.privacy.captureMode);
9986
+ }
9987
+ if (input3.privacy.networkAllowed !== void 0) {
9988
+ privacy.networkAllowed = Boolean(input3.privacy.networkAllowed);
9989
+ }
9990
+ if (input3.privacy.uploadAllowed !== void 0) {
9991
+ privacy.uploadAllowed = Boolean(input3.privacy.uploadAllowed);
9992
+ }
9993
+ if (input3.privacy.redactionDocumented !== void 0) {
9994
+ privacy.redactionDocumented = Boolean(input3.privacy.redactionDocumented);
9995
+ }
9996
+ if (input3.privacy.frameworkDepsPackageScoped !== void 0) {
9997
+ privacy.frameworkDepsPackageScoped = Boolean(input3.privacy.frameworkDepsPackageScoped);
9998
+ }
9999
+ if (privacy.networkAllowed === true || privacy.uploadAllowed === true) {
10000
+ warnings.push("plugin declares network or upload \u2014 review before install");
10001
+ }
10002
+ }
10003
+ } else {
10004
+ warnings.push("privacy block missing; plugins doctor will assume conservative defaults");
10005
+ }
10006
+ if (errors.length > 0) return { ok: false, errors, warnings };
10007
+ return {
10008
+ ok: true,
10009
+ manifest: {
10010
+ schemaVersion: "1.0",
10011
+ id,
10012
+ type,
10013
+ name,
10014
+ version: version2,
10015
+ ...privacy ? { privacy } : {}
10016
+ },
10017
+ errors: [],
10018
+ warnings
10019
+ };
10020
+ }
10021
+ async function readPluginManifestFile(packageDir) {
10022
+ const manifestPath = path14.join(packageDir, PLUGIN_MANIFEST_FILENAME);
10023
+ try {
10024
+ const raw = await readFile(manifestPath, "utf8");
10025
+ const parsed = parsePluginManifest(JSON.parse(raw));
10026
+ return { ...parsed, path: manifestPath };
10027
+ } catch (error) {
10028
+ const message = error instanceof Error ? error.message : String(error);
10029
+ return { ok: false, path: manifestPath, errors: [message], warnings: [] };
10030
+ }
10031
+ }
10032
+ function validatePluginPrivacy(manifest) {
10033
+ return runPrivacyChecklist(manifest.privacy ?? {});
10034
+ }
10035
+
9866
10036
  // packages/adapter-sdk/src/indexer.ts
9867
10037
  function defineIndexer(indexer) {
9868
10038
  if (!indexer.id.trim()) throw new Error("indexer id is required");
@@ -9920,7 +10090,115 @@ function createTraceDirectoryIndexer() {
9920
10090
  });
9921
10091
  }
9922
10092
 
9923
- // packages/cli/src/index-cmd.ts
10093
+ // packages/cli/src/plugins.ts
10094
+ async function readPackageName(packageDir) {
10095
+ try {
10096
+ const raw = await readFile(path14.join(packageDir, "package.json"), "utf8");
10097
+ const parsed = JSON.parse(raw);
10098
+ return typeof parsed.name === "string" ? parsed.name : void 0;
10099
+ } catch {
10100
+ return void 0;
10101
+ }
10102
+ }
10103
+ async function discoverPlugins(cwd = process.cwd()) {
10104
+ const nodeModules = path14.join(cwd, "node_modules");
10105
+ const found = [];
10106
+ let entries;
10107
+ try {
10108
+ entries = await readdir(nodeModules);
10109
+ } catch {
10110
+ return found;
10111
+ }
10112
+ for (const entry of entries.sort()) {
10113
+ if (entry.startsWith("@")) continue;
10114
+ if (!isPluginPackageName(entry)) continue;
10115
+ const packageDir = path14.join(nodeModules, entry);
10116
+ const manifestRead = await readPluginManifestFile(packageDir);
10117
+ found.push({
10118
+ packageName: entry,
10119
+ packageDir,
10120
+ ...manifestRead.manifest ? { manifest: manifestRead.manifest } : {},
10121
+ manifestErrors: manifestRead.errors,
10122
+ warnings: manifestRead.warnings
10123
+ });
10124
+ }
10125
+ return found;
10126
+ }
10127
+ async function validatePluginPackage(packageRef, cwd = process.cwd()) {
10128
+ const packageDir = path14.isAbsolute(packageRef) ? packageRef : path14.join(cwd, "node_modules", packageRef);
10129
+ const packageName = await readPackageName(packageDir) ?? packageRef;
10130
+ const errors = [];
10131
+ const warnings = [];
10132
+ if (!isPluginPackageName(packageName)) {
10133
+ errors.push(`package name ${packageName} does not match plugin naming convention`);
10134
+ }
10135
+ const manifestRead = await readPluginManifestFile(packageDir);
10136
+ errors.push(...manifestRead.errors);
10137
+ warnings.push(...manifestRead.warnings);
10138
+ if (manifestRead.manifest) {
10139
+ const inferred = inferPluginTypeFromName(packageName);
10140
+ if (inferred && inferred !== manifestRead.manifest.type) {
10141
+ warnings.push(`manifest type ${manifestRead.manifest.type} differs from name prefix (${inferred})`);
10142
+ }
10143
+ const privacy = validatePluginPrivacy(manifestRead.manifest);
10144
+ if (!privacy.ok) {
10145
+ errors.push(
10146
+ ...privacy.items.filter((item) => !item.ok).map((item) => item.detail ?? item.id)
10147
+ );
10148
+ }
10149
+ } else if (manifestRead.errors.some((e) => e.includes("ENOENT"))) {
10150
+ warnings.push(`missing ${PLUGIN_MANIFEST_FILENAME}; add manifest for doctor/validate`);
10151
+ }
10152
+ return {
10153
+ ok: errors.length === 0,
10154
+ packageName,
10155
+ packageDir,
10156
+ errors,
10157
+ warnings,
10158
+ manifest: manifestRead.manifest
10159
+ };
10160
+ }
10161
+ async function pluginsListCommand(cwd) {
10162
+ const plugins = await discoverPlugins(cwd);
10163
+ if (plugins.length === 0) {
10164
+ console.log("No agent-inspect plugins found in node_modules.");
10165
+ return;
10166
+ }
10167
+ for (const plugin of plugins) {
10168
+ const type = plugin.manifest?.type ?? inferPluginTypeFromName(plugin.packageName) ?? "unknown";
10169
+ console.log(`${plugin.packageName} (${type})`);
10170
+ }
10171
+ }
10172
+ async function pluginsDoctorCommand(cwd) {
10173
+ const plugins = await discoverPlugins(cwd);
10174
+ if (plugins.length === 0) {
10175
+ console.log("No agent-inspect plugins found.");
10176
+ return;
10177
+ }
10178
+ let issues = 0;
10179
+ for (const plugin of plugins) {
10180
+ const result = await validatePluginPackage(plugin.packageName, cwd);
10181
+ if (result.ok && result.warnings.length === 0) {
10182
+ console.log(`OK ${plugin.packageName}`);
10183
+ continue;
10184
+ }
10185
+ issues += 1;
10186
+ console.log(`WARN ${plugin.packageName}`);
10187
+ for (const warning of result.warnings) console.log(` - ${warning}`);
10188
+ for (const error of result.errors) console.log(` ! ${error}`);
10189
+ }
10190
+ if (issues > 0) process.exitCode = 1;
10191
+ }
10192
+ async function pluginsValidateCommand(packageRef, cwd) {
10193
+ const result = await validatePluginPackage(packageRef, cwd);
10194
+ for (const warning of result.warnings) console.warn(`warning: ${warning}`);
10195
+ if (!result.ok) {
10196
+ for (const error of result.errors) console.error(`error: ${error}`);
10197
+ process.exitCode = 1;
10198
+ return;
10199
+ }
10200
+ console.log(`Valid: ${result.packageName}`);
10201
+ }
9924
10202
  var INDEX_FILENAME = ".agent-inspect-index.json";
9925
10203
  function traceIndexPath(traceDir) {
9926
10204
  return path14.join(traceDir, INDEX_FILENAME);
@@ -11113,6 +11391,16 @@ function createCliProgram() {
11113
11391
  ).action((opts) => {
11114
11392
  runCommand(() => doctorCommand(opts));
11115
11393
  });
11394
+ const pluginsCmd = program.command("plugins").description("List and validate explicit-install agent-inspect plugins (v6.2+)");
11395
+ pluginsCmd.command("list").description("List installed plugin packages in node_modules").action(() => {
11396
+ runCommand(() => pluginsListCommand());
11397
+ });
11398
+ pluginsCmd.command("doctor").description("Validate plugin manifests and privacy checklists").action(() => {
11399
+ runCommand(() => pluginsDoctorCommand());
11400
+ });
11401
+ pluginsCmd.command("validate").description("Validate one plugin package by name or path").argument("<package>", "plugin package name or directory path").action((packageRef) => {
11402
+ runCommand(() => pluginsValidateCommand(packageRef));
11403
+ });
11116
11404
  const indexCmd = program.command("index").description("Optional local trace directory index (rebuildable metadata cache)");
11117
11405
  indexCmd.command("build").description("Build or refresh .agent-inspect-index.json").option("--dir <path>", "trace directory").option("--json", "print JSON result").option("--max-entries <n>", "cap indexed entries (default 10000)").action((opts) => {
11118
11406
  runCommand(() => indexBuildCommand(opts));