codex-plugin-doctor 1.46.1 → 1.48.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/dist/run-cli.js CHANGED
@@ -20,6 +20,8 @@ import { buildDoctorExportBundle, renderDoctorExportBundle, renderDoctorExportBu
20
20
  import { buildDoctorAttestation, renderDoctorAttestation, renderDoctorAttestationJson, renderDoctorAttestationVerification, renderDoctorAttestationVerificationJson, verifyDoctorAttestation } from "./core/attestation.js";
21
21
  import { buildDoctorOutputContract, renderDoctorOutputContract, renderDoctorOutputContractJson } from "./core/output-contract.js";
22
22
  import { buildDoctorValidationCorpusReport, renderDoctorValidationCorpusJson, renderDoctorValidationCorpusReport } from "./core/validation-corpus.js";
23
+ import { buildExternalValidationCorpusReport, ExternalCorpusManifestError } from "./core/external-validation-corpus.js";
24
+ import { buildCorpusQualityMetricsReport, CorpusMetricsManifestError, renderCorpusQualityMetricsJson, renderCorpusQualityMetricsMarkdown, renderCorpusQualityMetricsText } from "./core/corpus-quality-metrics.js";
23
25
  import { buildDoctorPerformanceReport, renderDoctorPerformanceReport, renderDoctorPerformanceReportJson } from "./core/performance-report.js";
24
26
  import { buildDoctorRuntimePlan, evaluateRuntimeApproval, renderDoctorRuntimePlan, renderDoctorRuntimePlanMarkdown, renderDoctorRuntimePlanJson, runtimeApprovalPassed } from "./core/runtime-plan.js";
25
27
  import { buildDoctorRuntimePolicyReport, renderDoctorRuntimePolicy, renderDoctorRuntimePolicyJson } from "./core/runtime-policy.js";
@@ -81,8 +83,24 @@ const defaultIo = {
81
83
  }
82
84
  }
83
85
  };
86
+ class CliUsageError extends Error {
87
+ }
88
+ function parseRuntimeSandbox(flags, options = { requireRuntime: true }) {
89
+ const index = flags.indexOf("--sandbox");
90
+ if (index === -1) {
91
+ return null;
92
+ }
93
+ const value = flags[index + 1];
94
+ if (value !== "docker") {
95
+ throw new CliUsageError("Expected --sandbox docker.");
96
+ }
97
+ if (options.requireRuntime !== false && !flags.includes("--runtime")) {
98
+ throw new CliUsageError("--sandbox docker requires --runtime.");
99
+ }
100
+ return value;
101
+ }
84
102
  function printUsage(io) {
85
- io.writeStderr("Usage: codex-plugin-doctor check <path|--installed> [filter] [--policy codex-publish|mcp-strict|security] [--compat] [--json|--markdown|--badge-json|--badge-markdown] [--output <path>] [--history <path>] [--runtime] [--require-runtime-approval --runtime-approval-digest <digest>] [--verbose-runtime] [--explain] [--no-animations] [--ascii] [--changed-since <ref>]\n codex-plugin-doctor audit --installed [filter] [--policy codex-publish|mcp-strict|security] [--security] [--compat] [--json] [--output <path>] [--cache] [--changed]\n codex-plugin-doctor audit deps <path> [--policy codex-publish|mcp-strict|security] [--recommend] [--json|--sarif] [--output <path>]\n codex-plugin-doctor mcp <path> [--json] [--output <path>]\n codex-plugin-doctor security <path> [--policy security] [--json|--scorecard]\n codex-plugin-doctor compat <path> [--all|--client <client>] [--json] [--scorecard] [--output <path>] [--install-preview|--apply --backup]\n codex-plugin-doctor suppress add <path> [--fingerprint <sha256> --reason <text> --expires-at YYYY-MM-DD] [--config <path>] [--json]\n codex-plugin-doctor suppress list <path> [--config <path>] [--json]\n codex-plugin-doctor suppress remove <path> [--fingerprint <sha256>|--index <n>] [--config <path>] [--json]\n codex-plugin-doctor fix <path> (--dry-run|--interactive --backup|--apply --backup)\n codex-plugin-doctor history <history.jsonl> [--json] [--fail-on-regression]\n codex-plugin-doctor watch <path> [--runtime] [--json] [--output <path>] [--debounce-ms <ms>] [--max-iterations <n>] [--fail-fast] [--accumulate-json <path>]\n codex-plugin-doctor doctor [npm <package>|contract|corpus|runtime-plan <path> [--json|--markdown] [--output <path>]|runtime-policy <path> [--json] [--output <path>]|review-bundle <path> --output <dir> --sign-key-env NAME [--json] [--allow-dirty] [--allow-untagged]|review-bundle verify <bundle-dir> --target <path> --sign-key-env NAME [--json] [--output <path>] [--failures-only]|review-bundle diff --before <dir> --after <dir> [--json]|attest <path> [--sign-key-env NAME]|attest verify <attestation.json> --target <path> --sign-key-env NAME|release-evidence <path> --sign-key-env NAME [--allow-dirty] [--allow-untagged] [--require-runtime-approval --runtime-approval-digest <digest>]|release-evidence verify <evidence.json> --target <path> --sign-key-env NAME|release-evidence asset <path> --tag <tag> --output <evidence.json> --sign-key-env NAME [--upload]|mcp <path>|inspector <path>|diff --before <path> --after <path>|recommend <path>|trust <path>|perf <path> [--max-total-ms <ms>] [--max-stage-ms stage=ms]|export --bundle <path>|snapshot|clients|--json|--update-check]\n codex-plugin-doctor init [path] [--template skill-only|mcp-stdio|mcp-http|full-runtime]\n codex-plugin-doctor init-ci [path]\n codex-plugin-doctor init-git-hooks [path] [--force] [--json]\n codex-plugin-doctor init-git-hooks [path] --remove [--json]\n codex-plugin-doctor completion bash|zsh|fish\n codex-plugin-doctor config validate <path> [--json]\n codex-plugin-doctor release check <path> [--json]\n codex-plugin-doctor self-test\n codex-plugin-doctor list --installed\n codex-plugin-doctor explain <finding-id>\n codex-plugin-doctor --version\n\nFirst run:\n codex-plugin-doctor doctor\n codex-plugin-doctor self-test\n codex-plugin-doctor init my-plugin\n codex-plugin-doctor check . --runtime --explain");
103
+ io.writeStderr("Usage: codex-plugin-doctor check <path|--installed> [filter] [--policy codex-publish|mcp-strict|security] [--compat] [--json|--markdown|--badge-json|--badge-markdown] [--output <path>] [--history <path>] [--runtime] [--sandbox docker] [--require-runtime-approval --runtime-approval-digest <digest>] [--verbose-runtime] [--explain] [--no-animations] [--ascii] [--changed-since <ref>]\n codex-plugin-doctor audit --installed [filter] [--policy codex-publish|mcp-strict|security] [--security] [--compat] [--json] [--output <path>] [--cache] [--changed]\n codex-plugin-doctor audit deps <path> [--policy codex-publish|mcp-strict|security] [--recommend] [--json|--sarif] [--output <path>]\n codex-plugin-doctor mcp <path> [--json] [--output <path>]\n codex-plugin-doctor security <path> [--policy security] [--json|--scorecard]\n codex-plugin-doctor compat <path> [--all|--client <client>] [--json] [--scorecard] [--output <path>] [--install-preview|--apply --backup]\n codex-plugin-doctor suppress add <path> [--fingerprint <sha256> --reason <text> --expires-at YYYY-MM-DD] [--config <path>] [--json]\n codex-plugin-doctor suppress list <path> [--config <path>] [--json]\n codex-plugin-doctor suppress remove <path> [--fingerprint <sha256>|--index <n>] [--config <path>] [--json]\n codex-plugin-doctor fix <path> (--dry-run|--interactive --backup|--apply --backup)\n codex-plugin-doctor history <history.jsonl> [--json] [--fail-on-regression]\n codex-plugin-doctor watch <path> [--runtime] [--json] [--output <path>] [--debounce-ms <ms>] [--max-iterations <n>] [--fail-fast] [--accumulate-json <path>]\n codex-plugin-doctor doctor [npm <package>|contract|corpus [--manifest <corpus.json>] [--json] [--output <path>]|corpus metrics --manifest <corpus.json> [--json|--markdown] [--output <path>] [--min-precision <0..1>] [--min-recall <0..1>] [--max-false-positive-rate <0..1>]|runtime-plan <path> [--runtime --sandbox docker] [--json|--markdown] [--output <path>]|runtime-policy <path> [--runtime --sandbox docker] [--json] [--output <path>]|review-bundle <path> --output <dir> --sign-key-env NAME [--json] [--allow-dirty] [--allow-untagged]|review-bundle verify <bundle-dir> --target <path> --sign-key-env NAME [--json] [--output <path>] [--failures-only]|review-bundle diff --before <dir> --after <dir> [--json]|attest <path> [--sign-key-env NAME]|attest verify <attestation.json> --target <path> --sign-key-env NAME|release-evidence <path> --sign-key-env NAME [--runtime --sandbox docker] [--allow-dirty] [--allow-untagged] [--require-runtime-approval --runtime-approval-digest <digest>]|release-evidence verify <evidence.json> --target <path> --sign-key-env NAME|release-evidence asset <path> --tag <tag> --output <evidence.json> --sign-key-env NAME [--upload]|mcp <path>|inspector <path>|diff --before <path> --after <path>|recommend <path>|trust <path>|perf <path> [--max-total-ms <ms>] [--max-stage-ms stage=ms]|export --bundle <path>|snapshot|clients|--json|--update-check]\n codex-plugin-doctor init [path] [--template skill-only|mcp-stdio|mcp-http|full-runtime]\n codex-plugin-doctor init-ci [path]\n codex-plugin-doctor init-git-hooks [path] [--force] [--json]\n codex-plugin-doctor init-git-hooks [path] --remove [--json]\n codex-plugin-doctor completion bash|zsh|fish\n codex-plugin-doctor config validate <path> [--json]\n codex-plugin-doctor release check <path> [--json] [--runtime --sandbox docker]\n codex-plugin-doctor self-test\n codex-plugin-doctor list --installed\n codex-plugin-doctor explain <finding-id>\n codex-plugin-doctor --version\n\nFirst run:\n codex-plugin-doctor doctor\n codex-plugin-doctor self-test\n codex-plugin-doctor init my-plugin\n codex-plugin-doctor check . --runtime --explain");
86
104
  io.writeStderr("Suppression governance: codex-plugin-doctor suppress list <path> [--fail-on-expired] [--fail-on-invalid] [--warn-expiring-within-days <days>]\n codex-plugin-doctor suppress prune <path> [--apply] [--json]");
87
105
  io.writeStderr("Baseline gating: codex-plugin-doctor baseline create <path> --output <path> [--runtime]\n codex-plugin-doctor check <path> --baseline <path>");
88
106
  }
@@ -805,6 +823,17 @@ function parseSelectedFixActionIndexes(answer, actionCount) {
805
823
  }
806
824
  export async function runCli(args, io = defaultIo, options = {}) {
807
825
  const [command, maybePath, ...remainingArgs] = args;
826
+ const runtimeSandboxSupported = (command === "check" &&
827
+ !args.includes("--installed") &&
828
+ !args.includes("--changed-since")) ||
829
+ (command === "release" && maybePath === "check") ||
830
+ (command === "doctor" &&
831
+ (["runtime-plan", "runtime-policy"].includes(maybePath ?? "") ||
832
+ (maybePath === "release-evidence" && remainingArgs[0] !== "verify")));
833
+ if (args.includes("--sandbox") && !runtimeSandboxSupported) {
834
+ io.writeStderr("--sandbox is supported only by single-package check, release check, runtime-plan, runtime-policy, and release-evidence.");
835
+ return 2;
836
+ }
808
837
  if (command === "--version" || command === "-v" || command === "version") {
809
838
  io.writeStdout(packageVersion);
810
839
  return 0;
@@ -933,6 +962,14 @@ export async function runCli(args, io = defaultIo, options = {}) {
933
962
  const markdownOutput = runtimePlanFlags.includes("--markdown");
934
963
  const outputIndex = runtimePlanFlags.indexOf("--output");
935
964
  const outputPath = outputIndex === -1 ? null : runtimePlanFlags[outputIndex + 1];
965
+ let runtimeSandbox;
966
+ try {
967
+ runtimeSandbox = parseRuntimeSandbox(runtimePlanFlags, { requireRuntime: false });
968
+ }
969
+ catch (error) {
970
+ io.writeStderr(error.message);
971
+ return 2;
972
+ }
936
973
  if (!targetPath) {
937
974
  io.writeStderr("Missing target path for runtime plan.");
938
975
  return 2;
@@ -945,7 +982,7 @@ export async function runCli(args, io = defaultIo, options = {}) {
945
982
  io.writeStderr("Use either --json or --markdown, not both.");
946
983
  return 2;
947
984
  }
948
- const plan = await buildDoctorRuntimePlan(targetPath);
985
+ const plan = await buildDoctorRuntimePlan(targetPath, new Date().toISOString(), runtimeSandbox ? { sandbox: runtimeSandbox } : {});
949
986
  const renderedPlan = jsonOutput
950
987
  ? renderDoctorRuntimePlanJson(plan)
951
988
  : markdownOutput
@@ -965,6 +1002,14 @@ export async function runCli(args, io = defaultIo, options = {}) {
965
1002
  const jsonOutput = runtimePolicyFlags.includes("--json");
966
1003
  const outputIndex = runtimePolicyFlags.indexOf("--output");
967
1004
  const outputPath = outputIndex === -1 ? null : runtimePolicyFlags[outputIndex + 1];
1005
+ let runtimeSandbox;
1006
+ try {
1007
+ runtimeSandbox = parseRuntimeSandbox(runtimePolicyFlags, { requireRuntime: false });
1008
+ }
1009
+ catch (error) {
1010
+ io.writeStderr(error.message);
1011
+ return 2;
1012
+ }
968
1013
  if (!targetPath) {
969
1014
  io.writeStderr("Missing target path for runtime policy.");
970
1015
  return 2;
@@ -973,7 +1018,7 @@ export async function runCli(args, io = defaultIo, options = {}) {
973
1018
  io.writeStderr("Missing path after --output.");
974
1019
  return 2;
975
1020
  }
976
- const report = await buildDoctorRuntimePolicyReport(targetPath);
1021
+ const report = await buildDoctorRuntimePolicyReport(targetPath, new Date().toISOString(), runtimeSandbox ? { sandbox: runtimeSandbox } : {});
977
1022
  const renderedReport = jsonOutput
978
1023
  ? renderDoctorRuntimePolicyJson(report)
979
1024
  : renderDoctorRuntimePolicy(report);
@@ -1138,6 +1183,15 @@ export async function runCli(args, io = defaultIo, options = {}) {
1138
1183
  const runtimeApprovalDigest = runtimeApprovalDigestIndex === -1
1139
1184
  ? null
1140
1185
  : assetFlags[runtimeApprovalDigestIndex + 1];
1186
+ const runtime = assetFlags.includes("--runtime");
1187
+ let runtimeSandbox;
1188
+ try {
1189
+ runtimeSandbox = parseRuntimeSandbox(assetFlags);
1190
+ }
1191
+ catch (error) {
1192
+ io.writeStderr(error.message);
1193
+ return 2;
1194
+ }
1141
1195
  if (!targetPath) {
1142
1196
  io.writeStderr("Missing target path for release evidence asset.");
1143
1197
  return 2;
@@ -1193,13 +1247,13 @@ export async function runCli(args, io = defaultIo, options = {}) {
1193
1247
  allowUntagged,
1194
1248
  requireRuntimeApproval,
1195
1249
  runtimeApprovalDigest,
1250
+ runtime,
1251
+ ...(runtimeSandbox ? { sandbox: runtimeSandbox } : {}),
1196
1252
  environment: {
1197
1253
  env: terminalContext.env,
1198
1254
  platform: terminalContext.platform
1199
1255
  },
1200
- runCheck: options.runCheckImpl
1201
- ? (pathToCheck) => options.runCheckImpl(pathToCheck)
1202
- : undefined,
1256
+ runCheck: options.runCheckImpl ?? runCheck,
1203
1257
  performanceThresholds: parsedThresholds.thresholds
1204
1258
  });
1205
1259
  await writeFile(resolvedOutputPath, renderDoctorReleaseEvidenceJson(evidence), "utf8");
@@ -1298,6 +1352,15 @@ export async function runCli(args, io = defaultIo, options = {}) {
1298
1352
  const runtimeApprovalDigest = runtimeApprovalDigestIndex === -1
1299
1353
  ? null
1300
1354
  : evidenceFlags[runtimeApprovalDigestIndex + 1];
1355
+ const runtime = evidenceFlags.includes("--runtime");
1356
+ let runtimeSandbox;
1357
+ try {
1358
+ runtimeSandbox = parseRuntimeSandbox(evidenceFlags);
1359
+ }
1360
+ catch (error) {
1361
+ io.writeStderr(error.message);
1362
+ return 2;
1363
+ }
1301
1364
  if (outputIndex !== -1 && (!outputPath || outputPath.startsWith("--"))) {
1302
1365
  io.writeStderr("Missing path after --output.");
1303
1366
  return 2;
@@ -1336,13 +1399,13 @@ export async function runCli(args, io = defaultIo, options = {}) {
1336
1399
  allowUntagged,
1337
1400
  requireRuntimeApproval,
1338
1401
  runtimeApprovalDigest,
1402
+ runtime,
1403
+ ...(runtimeSandbox ? { sandbox: runtimeSandbox } : {}),
1339
1404
  environment: {
1340
1405
  env: terminalContext.env,
1341
1406
  platform: terminalContext.platform
1342
1407
  },
1343
- runCheck: options.runCheckImpl
1344
- ? (pathToCheck) => options.runCheckImpl(pathToCheck)
1345
- : undefined,
1408
+ runCheck: options.runCheckImpl ?? runCheck,
1346
1409
  performanceThresholds: parsedThresholds.thresholds
1347
1410
  });
1348
1411
  const reportJson = renderDoctorReleaseEvidenceJson(report);
@@ -1354,19 +1417,133 @@ export async function runCli(args, io = defaultIo, options = {}) {
1354
1417
  return report.exitCode;
1355
1418
  }
1356
1419
  if (maybePath === "corpus") {
1420
+ if (remainingArgs[0] === "metrics") {
1421
+ const metricsArgs = remainingArgs.slice(1);
1422
+ const jsonOutput = metricsArgs.includes("--json");
1423
+ const markdownOutput = metricsArgs.includes("--markdown");
1424
+ const valueOptions = new Map();
1425
+ const knownValueFlags = new Set([
1426
+ "--manifest",
1427
+ "--output",
1428
+ "--min-precision",
1429
+ "--min-recall",
1430
+ "--max-false-positive-rate"
1431
+ ]);
1432
+ for (let index = 0; index < metricsArgs.length; index += 1) {
1433
+ const argument = metricsArgs[index];
1434
+ if (argument === "--json" || argument === "--markdown")
1435
+ continue;
1436
+ if (!knownValueFlags.has(argument)) {
1437
+ io.writeStderr(`Unknown corpus metrics argument: ${argument}.`);
1438
+ return 2;
1439
+ }
1440
+ const value = metricsArgs[index + 1];
1441
+ if (!value || value.startsWith("--")) {
1442
+ const label = argument === "--manifest"
1443
+ ? "path after --manifest"
1444
+ : argument === "--output"
1445
+ ? "path after --output"
1446
+ : `value after ${argument}`;
1447
+ io.writeStderr(`Missing ${label}.`);
1448
+ return 2;
1449
+ }
1450
+ valueOptions.set(argument, value);
1451
+ index += 1;
1452
+ }
1453
+ const manifestPath = valueOptions.get("--manifest");
1454
+ const outputPath = valueOptions.get("--output") ?? null;
1455
+ if (!manifestPath) {
1456
+ io.writeStderr("Corpus metrics requires --manifest <path>.");
1457
+ return 2;
1458
+ }
1459
+ if (jsonOutput && markdownOutput) {
1460
+ io.writeStderr("Use either --json or --markdown, not both.");
1461
+ return 2;
1462
+ }
1463
+ const thresholds = {};
1464
+ const thresholdFlags = [
1465
+ ["--min-precision", "minPrecision"],
1466
+ ["--min-recall", "minRecall"],
1467
+ ["--max-false-positive-rate", "maxFalsePositiveRate"]
1468
+ ];
1469
+ for (const [flag, key] of thresholdFlags) {
1470
+ const raw = valueOptions.get(flag);
1471
+ if (raw === undefined)
1472
+ continue;
1473
+ const value = Number(raw);
1474
+ if (!Number.isFinite(value) || value < 0 || value > 1) {
1475
+ io.writeStderr(`${flag} must be between 0 and 1.`);
1476
+ return 2;
1477
+ }
1478
+ thresholds[key] = value;
1479
+ }
1480
+ let report;
1481
+ try {
1482
+ const builder = options.buildCorpusMetricsReportImpl ?? buildCorpusQualityMetricsReport;
1483
+ const buildOptions = {
1484
+ thresholds,
1485
+ environment: {
1486
+ env: terminalContext.env,
1487
+ platform: terminalContext.platform
1488
+ }
1489
+ };
1490
+ report = await builder(manifestPath, buildOptions);
1491
+ }
1492
+ catch (error) {
1493
+ if (error instanceof CorpusMetricsManifestError) {
1494
+ io.writeStderr(error.message);
1495
+ return 2;
1496
+ }
1497
+ const message = error instanceof Error ? error.message : "Unknown error";
1498
+ io.writeStderr(`Corpus metrics analysis failed: ${message}`);
1499
+ return 2;
1500
+ }
1501
+ const rendered = jsonOutput
1502
+ ? renderCorpusQualityMetricsJson(report)
1503
+ : markdownOutput
1504
+ ? renderCorpusQualityMetricsMarkdown(report)
1505
+ : renderCorpusQualityMetricsText(report, { outputPath });
1506
+ if (outputPath)
1507
+ await writeFile(outputPath, rendered, "utf8");
1508
+ io.writeStdout(rendered);
1509
+ return report.exitCode;
1510
+ }
1357
1511
  const jsonOutput = remainingArgs.includes("--json");
1358
1512
  const outputIndex = remainingArgs.indexOf("--output");
1359
1513
  const outputPath = outputIndex === -1 ? null : remainingArgs[outputIndex + 1];
1514
+ const manifestIndex = remainingArgs.indexOf("--manifest");
1515
+ const manifestPath = manifestIndex === -1 ? null : remainingArgs[manifestIndex + 1];
1360
1516
  if (outputIndex !== -1 && (!outputPath || outputPath.startsWith("--"))) {
1361
1517
  io.writeStderr("Missing path after --output.");
1362
1518
  return 2;
1363
1519
  }
1364
- const report = await buildDoctorValidationCorpusReport({
1365
- environment: {
1366
- env: terminalContext.env,
1367
- platform: terminalContext.platform
1520
+ if (manifestIndex !== -1 && (!manifestPath || manifestPath.startsWith("--"))) {
1521
+ io.writeStderr("Missing path after --manifest.");
1522
+ return 2;
1523
+ }
1524
+ let report;
1525
+ try {
1526
+ report = manifestPath
1527
+ ? await buildExternalValidationCorpusReport(manifestPath, {
1528
+ environment: {
1529
+ env: terminalContext.env,
1530
+ platform: terminalContext.platform
1531
+ }
1532
+ })
1533
+ : await buildDoctorValidationCorpusReport({
1534
+ environment: {
1535
+ env: terminalContext.env,
1536
+ platform: terminalContext.platform
1537
+ }
1538
+ });
1539
+ }
1540
+ catch (error) {
1541
+ if (error instanceof ExternalCorpusManifestError) {
1542
+ io.writeStderr(error.message);
1543
+ return 2;
1368
1544
  }
1369
- });
1545
+ throw error;
1546
+ }
1370
1547
  const reportJson = renderDoctorValidationCorpusJson(report);
1371
1548
  if (outputPath) {
1372
1549
  await writeFile(outputPath, reportJson, "utf8");
@@ -2262,6 +2439,14 @@ export async function runCli(args, io = defaultIo, options = {}) {
2262
2439
  : remainingArgs.slice(1);
2263
2440
  const jsonOutput = releaseFlags.includes("--json");
2264
2441
  const runtimeProbeEnabled = releaseFlags.includes("--runtime");
2442
+ let runtimeSandbox;
2443
+ try {
2444
+ runtimeSandbox = parseRuntimeSandbox(releaseFlags);
2445
+ }
2446
+ catch (error) {
2447
+ io.writeStderr(error.message);
2448
+ return 2;
2449
+ }
2265
2450
  const requireRuntimeApproval = releaseFlags.includes("--require-runtime-approval");
2266
2451
  const runtimeApprovalDigestIndex = releaseFlags.indexOf("--runtime-approval-digest");
2267
2452
  const runtimeApprovalDigest = runtimeApprovalDigestIndex === -1
@@ -2277,7 +2462,7 @@ export async function runCli(args, io = defaultIo, options = {}) {
2277
2462
  return 2;
2278
2463
  }
2279
2464
  if (requireRuntimeApproval) {
2280
- const runtimePlan = await buildDoctorRuntimePlan(targetPath);
2465
+ const runtimePlan = await buildDoctorRuntimePlan(targetPath, new Date().toISOString(), runtimeSandbox ? { sandbox: runtimeSandbox } : {});
2281
2466
  const approval = evaluateRuntimeApproval(runtimePlan, {
2282
2467
  required: true,
2283
2468
  approvedDigest: runtimeApprovalDigest
@@ -2291,6 +2476,7 @@ export async function runCli(args, io = defaultIo, options = {}) {
2291
2476
  env: terminalContext.env,
2292
2477
  platform: terminalContext.platform,
2293
2478
  runtime: runtimeProbeEnabled,
2479
+ ...(runtimeSandbox ? { runtimeSandbox } : {}),
2294
2480
  runCheck: options.runCheckImpl
2295
2481
  });
2296
2482
  const output = jsonOutput
@@ -2331,6 +2517,14 @@ export async function runCli(args, io = defaultIo, options = {}) {
2331
2517
  : maybePath && maybePath.startsWith("--")
2332
2518
  ? [maybePath, ...remainingArgs]
2333
2519
  : remainingArgs;
2520
+ let runtimeSandbox;
2521
+ try {
2522
+ runtimeSandbox = parseRuntimeSandbox(normalizedFlags);
2523
+ }
2524
+ catch (error) {
2525
+ io.writeStderr(error.message);
2526
+ return 2;
2527
+ }
2334
2528
  const jsonOutput = normalizedFlags.includes("--json");
2335
2529
  const markdownOutput = normalizedFlags.includes("--markdown");
2336
2530
  const badgeJsonOutput = normalizedFlags.includes("--badge-json");
@@ -2458,7 +2652,7 @@ export async function runCli(args, io = defaultIo, options = {}) {
2458
2652
  });
2459
2653
  const runCheckImpl = options.runCheckImpl ?? runCheck;
2460
2654
  if (!checkInstalled && effectiveRuntimeProbeEnabled && requireRuntimeApproval) {
2461
- const runtimePlan = await buildDoctorRuntimePlan(targetPath);
2655
+ const runtimePlan = await buildDoctorRuntimePlan(targetPath, new Date().toISOString(), runtimeSandbox ? { sandbox: runtimeSandbox } : {});
2462
2656
  const approval = evaluateRuntimeApproval(runtimePlan, {
2463
2657
  required: true,
2464
2658
  approvedDigest: runtimeApprovalDigest
@@ -2580,9 +2774,10 @@ export async function runCli(args, io = defaultIo, options = {}) {
2580
2774
  const doctorConfig = applyPolicyToDoctorConfig(applyCheckProfile(await loadDoctorConfig(targetPath, configPath), checkProfile), policy);
2581
2775
  const configuredResult = applyDoctorConfig(await runCheckImpl(targetPath, {
2582
2776
  runtime: effectiveRuntimeProbeEnabled,
2583
- runtimeTranscript: effectiveRuntimeProbeEnabled && verboseRuntime
2584
- ? (line) => io.writeStderr(line)
2585
- : undefined
2777
+ ...(runtimeSandbox ? { runtimeSandbox } : {}),
2778
+ ...(effectiveRuntimeProbeEnabled && verboseRuntime
2779
+ ? { runtimeTranscript: (line) => io.writeStderr(line) }
2780
+ : {})
2586
2781
  }), doctorConfig);
2587
2782
  const result = baseline
2588
2783
  ? applyValidationBaseline(configuredResult, baseline, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-plugin-doctor",
3
- "version": "1.46.1",
3
+ "version": "1.48.0",
4
4
  "description": "CLI-first validator for Codex plugins, skills, and MCP package surfaces with runtime MCP protocol validation.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",