codex-plugin-doctor 1.55.0 → 1.57.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/README.md CHANGED
@@ -480,9 +480,9 @@ jobs:
480
480
  runs-on: ubuntu-latest
481
481
  steps:
482
482
  - uses: actions/checkout@v5
483
- - uses: Esquetta/CodexPluginDoctor@v1.55.0
483
+ - uses: Esquetta/CodexPluginDoctor@v1.57.0
484
484
  with:
485
- version: "1.55.0"
485
+ version: "1.57.0"
486
486
  path: .
487
487
  runtime: "true"
488
488
  policy: codex-publish
@@ -0,0 +1,20 @@
1
+ export interface DoctorSizeReport {
2
+ targetPath: string;
3
+ status: "pass" | "warn" | "fail";
4
+ totalSize: number;
5
+ totalSizeHuman: string;
6
+ fileCount: number;
7
+ largeFiles: Array<{
8
+ path: string;
9
+ size: number;
10
+ sizeHuman: string;
11
+ }>;
12
+ warnings: string[];
13
+ npmPackSize?: number;
14
+ npmPackSizeHuman?: string;
15
+ }
16
+ export declare function buildDoctorSize(targetPath: string, options?: {
17
+ npmPack?: boolean;
18
+ }): Promise<DoctorSizeReport>;
19
+ export declare function renderDoctorSize(report: DoctorSizeReport): string;
20
+ export declare function renderDoctorSizeJson(report: DoctorSizeReport): string;
@@ -0,0 +1,198 @@
1
+ import { readdir, stat } from "node:fs/promises";
2
+ import { execFile } from "node:child_process";
3
+ import path from "node:path";
4
+ const skippedDirs = new Set([
5
+ "node_modules",
6
+ ".git",
7
+ ".cache",
8
+ "dist",
9
+ "coverage",
10
+ "__pycache__",
11
+ ".turbo",
12
+ ".next",
13
+ ".nuxt"
14
+ ]);
15
+ const largeFileThreshold = 1024 * 1024;
16
+ const packageSizeWarnThreshold = 10 * 1024 * 1024;
17
+ const packageSizeFailThreshold = 50 * 1024 * 1024;
18
+ function formatSize(bytes) {
19
+ if (bytes < 1024)
20
+ return `${bytes} B`;
21
+ if (bytes < 1024 * 1024)
22
+ return `${(bytes / 1024).toFixed(1)} KB`;
23
+ if (bytes < 1024 * 1024 * 1024)
24
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
25
+ return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GB`;
26
+ }
27
+ async function scanDirectory(rootPath, currentPath = rootPath) {
28
+ let totalSize = 0;
29
+ let fileCount = 0;
30
+ const largeFiles = [];
31
+ let entries;
32
+ try {
33
+ entries = await readdir(currentPath, { withFileTypes: true });
34
+ }
35
+ catch {
36
+ return { totalSize: 0, fileCount: 0, largeFiles: [] };
37
+ }
38
+ for (const entry of entries) {
39
+ const entryPath = path.join(currentPath, entry.name);
40
+ if (entry.isDirectory()) {
41
+ if (skippedDirs.has(entry.name)) {
42
+ continue;
43
+ }
44
+ const subResult = await scanDirectory(rootPath, entryPath);
45
+ totalSize += subResult.totalSize;
46
+ fileCount += subResult.fileCount;
47
+ largeFiles.push(...subResult.largeFiles);
48
+ continue;
49
+ }
50
+ if (!entry.isFile()) {
51
+ continue;
52
+ }
53
+ let details;
54
+ try {
55
+ details = await stat(entryPath);
56
+ }
57
+ catch {
58
+ continue;
59
+ }
60
+ totalSize += details.size;
61
+ fileCount += 1;
62
+ if (details.size >= largeFileThreshold) {
63
+ largeFiles.push({
64
+ path: path.relative(rootPath, entryPath).replace(/\\/g, "/"),
65
+ size: details.size,
66
+ sizeHuman: formatSize(details.size)
67
+ });
68
+ }
69
+ }
70
+ return { totalSize, fileCount, largeFiles };
71
+ }
72
+ export async function buildDoctorSize(targetPath, options = {}) {
73
+ const resolvedPath = path.resolve(targetPath);
74
+ const { totalSize, fileCount, largeFiles } = await scanDirectory(resolvedPath);
75
+ const warnings = [];
76
+ let status = "pass";
77
+ let npmPackSize;
78
+ let npmPackSizeHuman;
79
+ if (options.npmPack) {
80
+ try {
81
+ const packOutput = await new Promise((resolve, reject) => {
82
+ execFile("npm", ["pack", "--dry-run"], { cwd: resolvedPath, timeout: 30000, shell: process.platform === "win32" }, (error, stdout, stderr) => {
83
+ if (error) {
84
+ reject(new Error(stderr.trim() || error.message));
85
+ return;
86
+ }
87
+ resolve(stdout);
88
+ });
89
+ });
90
+ const tarballLine = packOutput.split("\n").find((line) => line.includes("total files") || line.includes("package size"));
91
+ if (tarballLine) {
92
+ const totalMatch = tarballLine.match(/(\d[\d.]*)\s*([KMG]?B)/i);
93
+ if (totalMatch) {
94
+ const value = Number.parseFloat(totalMatch[1]);
95
+ const unit = totalMatch[2].toUpperCase();
96
+ switch (unit) {
97
+ case "KB":
98
+ npmPackSize = Math.round(value * 1024);
99
+ break;
100
+ case "MB":
101
+ npmPackSize = Math.round(value * 1024 * 1024);
102
+ break;
103
+ case "GB":
104
+ npmPackSize = Math.round(value * 1024 * 1024 * 1024);
105
+ break;
106
+ default:
107
+ npmPackSize = Math.round(value);
108
+ }
109
+ npmPackSizeHuman = formatSize(npmPackSize);
110
+ }
111
+ }
112
+ if (npmPackSize === undefined) {
113
+ warnings.push("Could not determine npm pack size from output.");
114
+ }
115
+ else {
116
+ const diffPercent = totalSize > 0
117
+ ? Math.abs(Math.round(((npmPackSize - totalSize) / totalSize) * 100))
118
+ : 0;
119
+ if (diffPercent > 50 && npmPackSize > totalSize) {
120
+ warnings.push(`npm pack size (${npmPackSizeHuman}) differs from local size (${formatSize(totalSize)}) by ${diffPercent}%.`);
121
+ }
122
+ }
123
+ }
124
+ catch (error) {
125
+ warnings.push(`npm pack failed: ${error.message}`);
126
+ }
127
+ }
128
+ if (totalSize >= packageSizeFailThreshold) {
129
+ status = "fail";
130
+ warnings.push(`Package size (${formatSize(totalSize)}) exceeds ${formatSize(packageSizeFailThreshold)}.`);
131
+ }
132
+ else if (totalSize >= packageSizeWarnThreshold) {
133
+ status = "warn";
134
+ warnings.push(`Package size (${formatSize(totalSize)}) exceeds ${formatSize(packageSizeWarnThreshold)}.`);
135
+ }
136
+ if (largeFiles.length > 0) {
137
+ if (status === "pass")
138
+ status = "warn";
139
+ warnings.push(`${largeFiles.length} file(s) exceed ${formatSize(largeFileThreshold)}.`);
140
+ }
141
+ largeFiles.sort((a, b) => b.size - a.size);
142
+ return {
143
+ targetPath: resolvedPath,
144
+ status,
145
+ totalSize,
146
+ totalSizeHuman: formatSize(totalSize),
147
+ fileCount,
148
+ largeFiles: largeFiles.slice(0, 10),
149
+ warnings,
150
+ npmPackSize,
151
+ npmPackSizeHuman
152
+ };
153
+ }
154
+ export function renderDoctorSize(report) {
155
+ const lines = [
156
+ "Codex Plugin Doctor Size Analysis",
157
+ "=================================",
158
+ `Path: ${report.targetPath}`,
159
+ `Status: ${report.status.toUpperCase()}`,
160
+ `Total size: ${report.totalSizeHuman} (${report.fileCount} files)`,
161
+ report.npmPackSizeHuman ? `npm pack size: ${report.npmPackSizeHuman}` : "",
162
+ ""
163
+ ];
164
+ if (report.warnings.length > 0) {
165
+ lines.push("Warnings", "--------");
166
+ for (const warning of report.warnings) {
167
+ lines.push(` - ${warning}`);
168
+ }
169
+ lines.push("");
170
+ }
171
+ if (report.largeFiles.length > 0) {
172
+ lines.push("Large Files", "-----------");
173
+ for (const file of report.largeFiles) {
174
+ lines.push(` ${file.sizeHuman.padEnd(8)} ${file.path}`);
175
+ }
176
+ lines.push("");
177
+ }
178
+ if (report.status === "pass") {
179
+ lines.push("Package size within acceptable limits.");
180
+ }
181
+ return lines.join("\n");
182
+ }
183
+ export function renderDoctorSizeJson(report) {
184
+ return JSON.stringify({
185
+ schemaVersion: "1.0.0",
186
+ targetPath: report.targetPath,
187
+ status: report.status,
188
+ totalSize: report.totalSize,
189
+ totalSizeHuman: report.totalSizeHuman,
190
+ fileCount: report.fileCount,
191
+ largeFiles: report.largeFiles,
192
+ warnings: report.warnings,
193
+ ...(report.npmPackSize !== undefined ? {
194
+ npmPackSize: report.npmPackSize,
195
+ npmPackSizeHuman: report.npmPackSizeHuman
196
+ } : {})
197
+ }, null, 2);
198
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { CheckOptions, CheckResult } from "./domain/types.js";
2
+ export { buildDoctorSize, renderDoctorSize, renderDoctorSizeJson, type DoctorSizeReport } from "./core/doctor-size.js";
2
3
  export { applyDoctorConfig, loadDoctorConfig, type DoctorConfig } from "./core/doctor-config.js";
3
4
  export { readRawDoctorConfig, resolveDoctorConfigPath, writeRawDoctorConfig, type LoadedDoctorConfig, type RawDoctorConfig } from "./core/doctor-config-store.js";
4
5
  export { classifySuppressionRecord, isValidSuppressionFingerprint, isSuppressionExpired, validateSuppressionRecord, type ClassifiedSuppressionRecord, type InvalidSuppressionRecord, type SuppressionRecordField, type ValidSuppressionRecord } from "./core/suppression-record.js";
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { validatePlugin } from "./core/validate-plugin.js";
2
+ export { buildDoctorSize, renderDoctorSize, renderDoctorSizeJson } from "./core/doctor-size.js";
2
3
  export { applyDoctorConfig, loadDoctorConfig } from "./core/doctor-config.js";
3
4
  export { readRawDoctorConfig, resolveDoctorConfigPath, writeRawDoctorConfig } from "./core/doctor-config-store.js";
4
5
  export { classifySuppressionRecord, isValidSuppressionFingerprint, isSuppressionExpired, validateSuppressionRecord } from "./core/suppression-record.js";
package/dist/run-cli.js CHANGED
@@ -34,6 +34,7 @@ import { buildDoctorRiskDiffReport, renderDoctorRiskDiffReport, renderDoctorRisk
34
34
  import { buildDoctorInspectorReport, renderDoctorInspectorReport, renderDoctorInspectorReportJson } from "./core/inspector-bridge.js";
35
35
  import { applyFixPlan, buildFixPlan, renderApplyFixResult, renderFixPlanJsonReport, renderFixPlan } from "./core/fix-plan.js";
36
36
  import { renderClientDoctor, renderEnvironmentDoctor, renderEnvironmentDoctorJson } from "./core/environment-doctor.js";
37
+ import { buildDoctorSize, renderDoctorSize, renderDoctorSizeJson } from "./core/doctor-size.js";
37
38
  import { initCiWorkflow } from "./core/init-ci.js";
38
39
  import { watchPlugin } from "./core/watch-plugin.js";
39
40
  import { buildDepAudit, renderDepAudit, renderDepAuditJson, renderDepAuditSarif } from "./core/dep-audit.js";
@@ -168,7 +169,7 @@ function parseRemoteNetworkFlags(flags, runtime) {
168
169
  return { allowNetwork, allowLocalNetwork, allowSessionLifecycle, requireRemoteReliability };
169
170
  }
170
171
  function printUsage(io) {
171
- 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 [--allow-network [--allow-local-network]]] [--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> [--runtime [--allow-network [--allow-local-network]]] [--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> [--sandbox docker] [--json|--markdown] [--output <path>]|runtime-policy <path> [--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 [--allow-network [--allow-local-network]]] [--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 [--runtime [--allow-network [--allow-local-network]]] [--sandbox docker] [--allow-dirty] [--allow-untagged] [--require-runtime-approval --runtime-approval-digest <digest>] [--upload]|mcp <path> [--runtime [--allow-network [--allow-local-network]]]|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 [--allow-network [--allow-local-network]]] [--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");
172
+ 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 [--allow-network [--allow-local-network]]] [--sandbox docker] [--require-runtime-approval --runtime-approval-digest <digest>] [--verbose-runtime] [--explain] [--no-animations] [--ascii] [--changed-since <ref>] [--fail-on <rule-id>]\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> [--runtime [--allow-network [--allow-local-network]]] [--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> [--sandbox docker] [--json|--markdown] [--output <path>]|runtime-policy <path> [--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 [--allow-network [--allow-local-network]]] [--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 [--runtime [--allow-network [--allow-local-network]]] [--sandbox docker] [--allow-dirty] [--allow-untagged] [--require-runtime-approval --runtime-approval-digest <digest>] [--upload]|mcp <path> [--runtime [--allow-network [--allow-local-network]]]|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 [--allow-network [--allow-local-network]]] [--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");
172
173
  io.writeStderr("Registry readiness: codex-plugin-doctor registry check <server.json|directory> [--json] [--output <path>] [--require-registry-readiness]\n"
173
174
  + " codex-plugin-doctor registry inspect <server-name> --allow-network [--json] [--output <path>] [--require-registry-readiness]\n"
174
175
  + "Registry publication preflight: codex-plugin-doctor registry preflight <server.json|directory> [--allow-network] [--json] [--output <path>] [--require-publish-ready]");
@@ -2101,6 +2102,20 @@ export async function runCli(args, io = defaultIo, options = {}) {
2101
2102
  io.writeStdout(await renderClientDoctor(terminalContext));
2102
2103
  return 0;
2103
2104
  }
2105
+ if (maybePath === "size") {
2106
+ const sizeTarget = remainingArgs[0] && !remainingArgs[0].startsWith("--") ? remainingArgs[0] : ".";
2107
+ const sizeFlags = remainingArgs[0] && remainingArgs[0].startsWith("--")
2108
+ ? remainingArgs
2109
+ : remainingArgs.slice(1);
2110
+ const jsonOutput = sizeFlags.includes("--json");
2111
+ const npmPack = sizeFlags.includes("--npm");
2112
+ const report = await buildDoctorSize(sizeTarget, { npmPack });
2113
+ const output = jsonOutput
2114
+ ? renderDoctorSizeJson(report)
2115
+ : renderDoctorSize(report);
2116
+ io.writeStdout(output);
2117
+ return report.status === "fail" ? 1 : 0;
2118
+ }
2104
2119
  io.writeStdout(maybePath === "--json"
2105
2120
  ? await renderEnvironmentDoctorJson(terminalContext)
2106
2121
  : await renderEnvironmentDoctor(terminalContext));
@@ -2891,6 +2906,13 @@ export async function runCli(args, io = defaultIo, options = {}) {
2891
2906
  : normalizedFlags[runtimeApprovalDigestIndex + 1];
2892
2907
  const changedSinceIndex = normalizedFlags.indexOf("--changed-since");
2893
2908
  const changedSinceRef = changedSinceIndex === -1 ? null : normalizedFlags[changedSinceIndex + 1];
2909
+ const failOnRules = [];
2910
+ for (let i = 0; i < normalizedFlags.length; i += 1) {
2911
+ if (normalizedFlags[i] === "--fail-on" && normalizedFlags[i + 1] && !normalizedFlags[i + 1].startsWith("--")) {
2912
+ failOnRules.push(normalizedFlags[i + 1]);
2913
+ i += 1;
2914
+ }
2915
+ }
2894
2916
  const baselineIndex = normalizedFlags.indexOf("--baseline");
2895
2917
  const baselinePath = baselineIndex === -1 ? null : normalizedFlags[baselineIndex + 1];
2896
2918
  if (outputIndex !== -1 && (!outputPath || outputPath.startsWith("--"))) {
@@ -3136,6 +3158,28 @@ export async function runCli(args, io = defaultIo, options = {}) {
3136
3158
  failOnWarnings: doctorConfig.failOnWarnings
3137
3159
  })
3138
3160
  : configuredResult;
3161
+ if (failOnRules.length > 0) {
3162
+ result.findings = result.findings.map((finding) => {
3163
+ if (finding.severity === "warn" && failOnRules.includes(finding.id)) {
3164
+ return { ...finding, severity: "fail" };
3165
+ }
3166
+ return finding;
3167
+ });
3168
+ const hasFail = result.findings.some((f) => f.severity === "fail");
3169
+ const hasWarn = result.findings.some((f) => f.severity === "warn");
3170
+ if (hasFail) {
3171
+ result.status = "fail";
3172
+ result.exitCode = 1;
3173
+ }
3174
+ else if (hasWarn) {
3175
+ result.status = "warn";
3176
+ result.exitCode = 0;
3177
+ }
3178
+ else {
3179
+ result.status = "pass";
3180
+ result.exitCode = 0;
3181
+ }
3182
+ }
3139
3183
  if (renderer) {
3140
3184
  if (result.status === "fail") {
3141
3185
  renderer.stopFailure("Validation failed");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-plugin-doctor",
3
- "version": "1.55.0",
3
+ "version": "1.57.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",