codex-plugin-doctor 0.17.0 → 0.18.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
@@ -76,6 +76,7 @@ Output formats:
76
76
  - Shields-compatible badge JSON and static badge Markdown
77
77
  - validation history JSONL and trend summaries
78
78
  - deterministic local attestation artifacts
79
+ - output contract and rule catalog freeze metadata
79
80
  - `--output` file writing
80
81
  - CI summary and artifact generation
81
82
 
@@ -177,6 +178,8 @@ Run these from a Codex plugin package root:
177
178
  codex-plugin-doctor --version
178
179
  codex-plugin-doctor self-test
179
180
  codex-plugin-doctor doctor
181
+ codex-plugin-doctor doctor contract
182
+ codex-plugin-doctor doctor contract --json --output output-contract.json
180
183
  codex-plugin-doctor doctor npm codex-plugin-doctor
181
184
  codex-plugin-doctor doctor npm codex-plugin-doctor --json --output npm-preinstall.json
182
185
  codex-plugin-doctor doctor attest .
@@ -259,7 +262,7 @@ codex-plugin-doctor check . --json --runtime --verbose-runtime
259
262
 
260
263
  `self-test` runs the bundled runtime-complete sample through static validation, runtime MCP probes, and the compatibility scorecard. It is the fastest post-install check after `npm install -g codex-plugin-doctor`.
261
264
 
262
- `doctor` checks the local environment, including package version, platform, Node version, npm global prefix, Codex home, and Codex plugin cache visibility. The text output also includes recommended next commands for self-test, installed plugin discovery, runtime checks, compatibility scoring, and CI setup. `doctor npm <package>` runs a preinstall scan by packing the npm package with scripts disabled, extracting the publish tarball, and running validation, security, trust, and recommendation checks against the shipped contents. Add `--json` for automation or `--output npm-preinstall.json` to write the report to disk. `doctor attest <path>` creates a deterministic local attestation with a package fingerprint, report digest, validation/security/compatibility/trust summary, and unsigned verification metadata. Add `--json` for automation or `--output attestation.json` to write the artifact to disk. `doctor inspector <path>` builds a safe MCP Inspector launch command from a packaged `.mcp.json` file without starting the Inspector proxy automatically. Use `--server <name>` when the package contains multiple MCP server entries. `doctor diff --before <path> --after <path>` compares two package roots and reports new findings, resolved findings, trust score delta, and whether risk increased. `doctor recommend <path>` turns validation, security, and compatibility signals into a prioritized action plan with blocker, high, medium, and info actions. Add `--json` for automation or `--output recommendations.json` to write the report to disk. `doctor trust <path>` creates a local trust score from package lifecycle scripts, dependency specs, and MCP security findings. Use it before release when you want supply-chain risks summarized as one score. `doctor perf <path>` profiles the shared package analysis pipeline and reports per-stage durations for validation, config, security, compatibility, trust, recommendations, and total runtime. `doctor export --bundle <path>` creates a redacted operator handoff bundle that includes validation JSON, security scorecard data, compatibility matrix, recommendations, and trust score in one file. `doctor snapshot` creates a redacted diagnostics bundle with environment health, client config readiness, installed plugin metadata, and next commands. Add `--json` for machine-readable output or `--output doctor-snapshot.json` to write the bundle to disk. `doctor clients` reports local Codex, Claude Desktop, Cursor, Cline, and Windsurf config readiness. `doctor --update-check` compares the installed CLI version with the latest npm version and prints the upgrade command when a newer release is available.
265
+ `doctor` checks the local environment, including package version, platform, Node version, npm global prefix, Codex home, and Codex plugin cache visibility. The text output also includes recommended next commands for self-test, installed plugin discovery, runtime checks, compatibility scoring, and CI setup. `doctor contract` publishes the machine-readable output contract, including public JSON schema surfaces, stable-through-1.0 compatibility metadata, and a frozen rule catalog digest. Add `--json` for automation or `--output output-contract.json` to write the contract to disk. `doctor npm <package>` runs a preinstall scan by packing the npm package with scripts disabled, extracting the publish tarball, and running validation, security, trust, and recommendation checks against the shipped contents. Add `--json` for automation or `--output npm-preinstall.json` to write the report to disk. `doctor attest <path>` creates a deterministic local attestation with a package fingerprint, report digest, validation/security/compatibility/trust summary, and unsigned verification metadata. Add `--json` for automation or `--output attestation.json` to write the artifact to disk. `doctor inspector <path>` builds a safe MCP Inspector launch command from a packaged `.mcp.json` file without starting the Inspector proxy automatically. Use `--server <name>` when the package contains multiple MCP server entries. `doctor diff --before <path> --after <path>` compares two package roots and reports new findings, resolved findings, trust score delta, and whether risk increased. `doctor recommend <path>` turns validation, security, and compatibility signals into a prioritized action plan with blocker, high, medium, and info actions. Add `--json` for automation or `--output recommendations.json` to write the report to disk. `doctor trust <path>` creates a local trust score from package lifecycle scripts, dependency specs, and MCP security findings. Use it before release when you want supply-chain risks summarized as one score. `doctor perf <path>` profiles the shared package analysis pipeline and reports per-stage durations for validation, config, security, compatibility, trust, recommendations, and total runtime. `doctor export --bundle <path>` creates a redacted operator handoff bundle that includes validation JSON, security scorecard data, compatibility matrix, recommendations, and trust score in one file. `doctor snapshot` creates a redacted diagnostics bundle with environment health, client config readiness, installed plugin metadata, and next commands. Add `--json` for machine-readable output or `--output doctor-snapshot.json` to write the bundle to disk. `doctor clients` reports local Codex, Claude Desktop, Cursor, Cline, and Windsurf config readiness. `doctor --update-check` compares the installed CLI version with the latest npm version and prints the upgrade command when a newer release is available.
263
266
 
264
267
  `audit --installed` runs a local ecosystem audit against every discovered Codex plugin in the installed plugin cache. Add `--security` to include security scorecards, `--compat` to include the all-client compatibility matrix, and `--json --output local-audit.json` when you want a shareable machine-readable report. Add `--cache` to reuse unchanged plugin results between runs; add `--changed` to only report plugins whose fingerprint changed since the last cached audit. Use `--cache-file path/to/audit-cache.json` when CI or scripted runs need an explicit cache location.
265
268
 
@@ -0,0 +1,40 @@
1
+ import { type RuleCategory, type RuleSeverity } from "../rules/rule-catalog.js";
2
+ type JsonSchema = Record<string, unknown>;
3
+ export interface OutputContractRule {
4
+ id: string;
5
+ category: RuleCategory;
6
+ defaultSeverity: RuleSeverity;
7
+ }
8
+ export interface OutputContractSchema {
9
+ id: string;
10
+ command: string;
11
+ schemaVersion: "1.0.0";
12
+ stability: "stable-through-1.0";
13
+ outputKind: string | null;
14
+ schema: JsonSchema;
15
+ }
16
+ export interface DoctorOutputContract {
17
+ schemaVersion: "1.0.0";
18
+ kind: "doctor.output.contract";
19
+ generatedAt: string;
20
+ version: string;
21
+ contract: {
22
+ frozenSince: "0.18.0";
23
+ stability: "stable-through-1.0";
24
+ compatibility: string;
25
+ };
26
+ ruleCatalog: {
27
+ status: "frozen";
28
+ frozenSince: "0.18.0";
29
+ ruleCount: number;
30
+ digest: string;
31
+ rules: OutputContractRule[];
32
+ };
33
+ schemas: OutputContractSchema[];
34
+ }
35
+ export declare function buildDoctorOutputContract(generatedAt?: string): DoctorOutputContract;
36
+ export declare function renderDoctorOutputContractJson(contract: DoctorOutputContract): string;
37
+ export declare function renderDoctorOutputContract(contract: DoctorOutputContract, options?: {
38
+ outputPath?: string | null;
39
+ }): string;
40
+ export {};
@@ -0,0 +1,202 @@
1
+ import { createHash } from "node:crypto";
2
+ import { packageVersion } from "../version.js";
3
+ import { ruleCatalog } from "../rules/rule-catalog.js";
4
+ const publicSchemaDefinitions = [
5
+ {
6
+ id: "doctor.check.json",
7
+ command: "codex-plugin-doctor check <path> --json",
8
+ required: ["schemaVersion", "generatedAt", "summary", "findings"]
9
+ },
10
+ {
11
+ id: "doctor.security.json",
12
+ command: "codex-plugin-doctor security <path> --json",
13
+ required: ["schemaVersion", "generatedAt", "targetPath", "status", "score", "findings"]
14
+ },
15
+ {
16
+ id: "doctor.compatibility.json",
17
+ command: "codex-plugin-doctor compat <path> --json",
18
+ required: ["schemaVersion", "targetPath", "results"]
19
+ },
20
+ {
21
+ id: "doctor.mcp.json",
22
+ command: "codex-plugin-doctor mcp <path> --json",
23
+ required: ["schemaVersion", "targetPath", "status", "summary", "findings"]
24
+ },
25
+ {
26
+ id: "doctor.audit.json",
27
+ command: "codex-plugin-doctor audit --installed --json",
28
+ required: ["schemaVersion", "generatedAt", "summary", "items"]
29
+ },
30
+ {
31
+ id: "doctor.fix.plan.json",
32
+ command: "codex-plugin-doctor fix <path> --dry-run --json",
33
+ required: ["schemaVersion", "targetPath", "mode", "actions"]
34
+ },
35
+ {
36
+ id: "doctor.history.json",
37
+ command: "codex-plugin-doctor history <history.jsonl> --json",
38
+ required: ["schemaVersion", "entryCount", "latest", "previous", "delta", "regression"]
39
+ },
40
+ {
41
+ id: "doctor.environment.json",
42
+ command: "codex-plugin-doctor doctor --json",
43
+ required: ["schemaVersion", "generatedAt", "version", "platform", "node", "checks"]
44
+ },
45
+ {
46
+ id: "doctor.recommendations.json",
47
+ command: "codex-plugin-doctor doctor recommend <path> --json",
48
+ required: ["schemaVersion", "generatedAt", "targetPath", "status", "summary", "actions"]
49
+ },
50
+ {
51
+ id: "doctor.trust.json",
52
+ command: "codex-plugin-doctor doctor trust <path> --json",
53
+ required: ["schemaVersion", "generatedAt", "targetPath", "status", "score", "findings"]
54
+ },
55
+ {
56
+ id: "doctor.performance.json",
57
+ command: "codex-plugin-doctor doctor perf <path> --json",
58
+ outputKind: "doctor.perf",
59
+ required: ["schemaVersion", "kind", "generatedAt", "targetPath", "summary", "stages"]
60
+ },
61
+ {
62
+ id: "doctor.export.bundle.json",
63
+ command: "codex-plugin-doctor doctor export --bundle <path> --json",
64
+ outputKind: "doctor.export.bundle",
65
+ required: ["schemaVersion", "kind", "generatedAt", "targetPath", "validation", "security", "compatibility", "recommendations", "trust"]
66
+ },
67
+ {
68
+ id: "doctor.attestation.json",
69
+ command: "codex-plugin-doctor doctor attest <path> --json",
70
+ outputKind: "doctor.attestation",
71
+ required: ["schemaVersion", "kind", "generatedAt", "targetPath", "subject", "packageFingerprint", "reportDigest", "summary", "verification", "signature"]
72
+ },
73
+ {
74
+ id: "doctor.npm.json",
75
+ command: "codex-plugin-doctor doctor npm <package> --json",
76
+ outputKind: "doctor.npm",
77
+ required: ["schemaVersion", "kind", "generatedAt", "package", "summary", "validation", "security", "trust", "recommendations"]
78
+ },
79
+ {
80
+ id: "doctor.risk.diff.json",
81
+ command: "codex-plugin-doctor doctor diff --before <path> --after <path> --json",
82
+ outputKind: "doctor.risk.diff",
83
+ required: ["schemaVersion", "kind", "generatedAt", "before", "after", "summary", "risk"]
84
+ },
85
+ {
86
+ id: "doctor.inspector.json",
87
+ command: "codex-plugin-doctor doctor inspector <path> --json",
88
+ outputKind: "doctor.inspector",
89
+ required: ["schemaVersion", "kind", "generatedAt", "targetPath", "status"]
90
+ },
91
+ {
92
+ id: "doctor.snapshot.json",
93
+ command: "codex-plugin-doctor doctor snapshot --json",
94
+ required: ["schemaVersion", "generatedAt", "version", "environment", "clients", "installedPlugins", "nextCommands"]
95
+ }
96
+ ];
97
+ function sha256(value) {
98
+ return `sha256:${createHash("sha256").update(value).digest("hex")}`;
99
+ }
100
+ function isPlainObject(value) {
101
+ return typeof value === "object" && value !== null && !Array.isArray(value);
102
+ }
103
+ function stableStringify(value) {
104
+ if (Array.isArray(value)) {
105
+ return `[${value.map((item) => stableStringify(item)).join(",")}]`;
106
+ }
107
+ if (isPlainObject(value)) {
108
+ return `{${Object.keys(value)
109
+ .sort()
110
+ .map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`)
111
+ .join(",")}}`;
112
+ }
113
+ return JSON.stringify(value);
114
+ }
115
+ function contractRules(rules) {
116
+ return rules
117
+ .map((rule) => ({
118
+ id: rule.id,
119
+ category: rule.category,
120
+ defaultSeverity: rule.defaultSeverity
121
+ }))
122
+ .sort((left, right) => left.id.localeCompare(right.id));
123
+ }
124
+ function buildSchema(id, outputKind, required) {
125
+ const properties = {
126
+ schemaVersion: {
127
+ const: "1.0.0"
128
+ }
129
+ };
130
+ if (outputKind) {
131
+ properties.kind = {
132
+ const: outputKind
133
+ };
134
+ }
135
+ return {
136
+ $schema: "https://json-schema.org/draft/2020-12/schema",
137
+ $id: `https://github.com/Esquetta/CodexPluginDoctor/schemas/${id}`,
138
+ title: id,
139
+ type: "object",
140
+ required: [...new Set(["schemaVersion", ...required])],
141
+ properties,
142
+ additionalProperties: true
143
+ };
144
+ }
145
+ function buildSchemas() {
146
+ return publicSchemaDefinitions.map((definition) => {
147
+ const outputKind = definition.outputKind ?? null;
148
+ return {
149
+ id: definition.id,
150
+ command: definition.command,
151
+ schemaVersion: "1.0.0",
152
+ stability: "stable-through-1.0",
153
+ outputKind,
154
+ schema: buildSchema(definition.id, outputKind, definition.required ?? [])
155
+ };
156
+ });
157
+ }
158
+ export function buildDoctorOutputContract(generatedAt = new Date().toISOString()) {
159
+ const rules = contractRules(ruleCatalog);
160
+ return {
161
+ schemaVersion: "1.0.0",
162
+ kind: "doctor.output.contract",
163
+ generatedAt,
164
+ version: packageVersion,
165
+ contract: {
166
+ frozenSince: "0.18.0",
167
+ stability: "stable-through-1.0",
168
+ compatibility: "Public JSON schema surfaces and existing rule IDs/default severities are treated as stable through 1.0.0; breaking changes require a schemaVersion or major version change."
169
+ },
170
+ ruleCatalog: {
171
+ status: "frozen",
172
+ frozenSince: "0.18.0",
173
+ ruleCount: rules.length,
174
+ digest: sha256(stableStringify(rules)),
175
+ rules
176
+ },
177
+ schemas: buildSchemas()
178
+ };
179
+ }
180
+ export function renderDoctorOutputContractJson(contract) {
181
+ return JSON.stringify(contract, null, 2);
182
+ }
183
+ export function renderDoctorOutputContract(contract, options = {}) {
184
+ const lines = [
185
+ "Doctor Output Contract",
186
+ "======================",
187
+ `Version: ${contract.version}`,
188
+ `Contract: ${contract.contract.stability} (since ${contract.contract.frozenSince})`,
189
+ `Rule catalog: ${contract.ruleCatalog.status}`,
190
+ `Rule digest: ${contract.ruleCatalog.digest}`,
191
+ `Rules: ${contract.ruleCatalog.ruleCount}`,
192
+ `Schemas: ${contract.schemas.length}`
193
+ ];
194
+ if (options.outputPath) {
195
+ lines.push(`Output: ${options.outputPath}`);
196
+ }
197
+ lines.push("", "Schema surfaces", "---------------");
198
+ for (const schema of contract.schemas) {
199
+ lines.push(`- ${schema.id}: ${schema.command}`);
200
+ }
201
+ return lines.join("\n");
202
+ }
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export { buildDoctorSnapshot, renderDoctorSnapshot, renderDoctorSnapshotJson, ty
5
5
  export { buildDoctorRecommendations, renderDoctorRecommendations, renderDoctorRecommendationsJson, type DoctorRecommendationAction, type DoctorRecommendationsReport } from "./core/doctor-recommendations.js";
6
6
  export { buildDoctorExportBundle, renderDoctorExportBundle, renderDoctorExportBundleJson, type DoctorExportBundle } from "./core/doctor-export-bundle.js";
7
7
  export { buildDoctorAttestation, renderDoctorAttestation, renderDoctorAttestationJson, type DoctorAttestation, type Digest, type PackageFingerprint } from "./core/attestation.js";
8
+ export { buildDoctorOutputContract, renderDoctorOutputContract, renderDoctorOutputContractJson, type DoctorOutputContract, type OutputContractRule, type OutputContractSchema } from "./core/output-contract.js";
8
9
  export { buildDoctorExportBundleFromAnalysis, buildDoctorRecommendationsFromAnalysis, buildPackageAnalysis, type PackageAnalysis, type PackageAnalysisOptions, type PackageAnalysisStage, type PackageAnalysisTiming } from "./core/package-analysis.js";
9
10
  export { buildDoctorPerformanceReport, renderDoctorPerformanceReport, renderDoctorPerformanceReportJson, type BuildDoctorPerformanceReportOptions, type DoctorPerformanceReport, type DoctorPerformanceStage, type DoctorPerformanceStageName } from "./core/performance-report.js";
10
11
  export { buildDoctorNpmPackageReport, renderDoctorNpmPackageReport, renderDoctorNpmPackageReportJson, type BuildDoctorNpmPackageReportOptions, type DoctorNpmPackageReport } from "./core/npm-package-doctor.js";
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ export { buildDoctorSnapshot, renderDoctorSnapshot, renderDoctorSnapshotJson } f
5
5
  export { buildDoctorRecommendations, renderDoctorRecommendations, renderDoctorRecommendationsJson } from "./core/doctor-recommendations.js";
6
6
  export { buildDoctorExportBundle, renderDoctorExportBundle, renderDoctorExportBundleJson } from "./core/doctor-export-bundle.js";
7
7
  export { buildDoctorAttestation, renderDoctorAttestation, renderDoctorAttestationJson } from "./core/attestation.js";
8
+ export { buildDoctorOutputContract, renderDoctorOutputContract, renderDoctorOutputContractJson } from "./core/output-contract.js";
8
9
  export { buildDoctorExportBundleFromAnalysis, buildDoctorRecommendationsFromAnalysis, buildPackageAnalysis } from "./core/package-analysis.js";
9
10
  export { buildDoctorPerformanceReport, renderDoctorPerformanceReport, renderDoctorPerformanceReportJson } from "./core/performance-report.js";
10
11
  export { buildDoctorNpmPackageReport, renderDoctorNpmPackageReport, renderDoctorNpmPackageReportJson } from "./core/npm-package-doctor.js";
package/dist/run-cli.js CHANGED
@@ -17,6 +17,7 @@ import { buildDoctorSnapshot, renderDoctorSnapshot, renderDoctorSnapshotJson } f
17
17
  import { buildDoctorRecommendations, renderDoctorRecommendations, renderDoctorRecommendationsJson } from "./core/doctor-recommendations.js";
18
18
  import { buildDoctorExportBundle, renderDoctorExportBundle, renderDoctorExportBundleJson } from "./core/doctor-export-bundle.js";
19
19
  import { buildDoctorAttestation, renderDoctorAttestation, renderDoctorAttestationJson } from "./core/attestation.js";
20
+ import { buildDoctorOutputContract, renderDoctorOutputContract, renderDoctorOutputContractJson } from "./core/output-contract.js";
20
21
  import { buildDoctorPerformanceReport, renderDoctorPerformanceReport, renderDoctorPerformanceReportJson } from "./core/performance-report.js";
21
22
  import { buildDoctorNpmPackageReport, renderDoctorNpmPackageReport, renderDoctorNpmPackageReportJson } from "./core/npm-package-doctor.js";
22
23
  import { buildDoctorRiskDiffReport, renderDoctorRiskDiffReport, renderDoctorRiskDiffReportJson } from "./core/risk-diff.js";
@@ -66,7 +67,7 @@ const defaultIo = {
66
67
  }
67
68
  };
68
69
  function printUsage(io) {
69
- 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] [--verbose-runtime] [--explain] [--no-animations] [--ascii]\n codex-plugin-doctor audit --installed [filter] [--policy codex-publish|mcp-strict|security] [--security] [--compat] [--json] [--output <path>] [--cache] [--changed]\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 fix <path> (--dry-run|--interactive --backup|--apply --backup)\n codex-plugin-doctor history <history.jsonl> [--json] [--fail-on-regression]\n codex-plugin-doctor doctor [npm <package>|attest <path>|inspector <path>|diff --before <path> --after <path>|recommend <path>|trust <path>|perf <path>|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 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");
70
+ 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] [--verbose-runtime] [--explain] [--no-animations] [--ascii]\n codex-plugin-doctor audit --installed [filter] [--policy codex-publish|mcp-strict|security] [--security] [--compat] [--json] [--output <path>] [--cache] [--changed]\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 fix <path> (--dry-run|--interactive --backup|--apply --backup)\n codex-plugin-doctor history <history.jsonl> [--json] [--fail-on-regression]\n codex-plugin-doctor doctor [npm <package>|contract|attest <path>|inspector <path>|diff --before <path> --after <path>|recommend <path>|trust <path>|perf <path>|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 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");
70
71
  }
71
72
  function renderInstalledPlugins(plugins) {
72
73
  const lines = [
@@ -231,6 +232,24 @@ export async function runCli(args, io = defaultIo, options = {}) {
231
232
  io.writeStdout(renderedReport);
232
233
  return report.exitCode;
233
234
  }
235
+ if (maybePath === "contract") {
236
+ const jsonOutput = remainingArgs.includes("--json");
237
+ const outputIndex = remainingArgs.indexOf("--output");
238
+ const outputPath = outputIndex === -1 ? null : remainingArgs[outputIndex + 1];
239
+ if (outputIndex !== -1 && (!outputPath || outputPath.startsWith("--"))) {
240
+ io.writeStderr("Missing path after --output.");
241
+ return 2;
242
+ }
243
+ const contract = buildDoctorOutputContract();
244
+ const contractJson = renderDoctorOutputContractJson(contract);
245
+ if (outputPath) {
246
+ await writeFile(outputPath, contractJson, "utf8");
247
+ }
248
+ io.writeStdout(jsonOutput
249
+ ? contractJson
250
+ : renderDoctorOutputContract(contract, { outputPath }));
251
+ return 0;
252
+ }
234
253
  if (maybePath === "attest") {
235
254
  const targetPath = remainingArgs[0] && !remainingArgs[0].startsWith("--")
236
255
  ? remainingArgs[0]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-plugin-doctor",
3
- "version": "0.17.0",
3
+ "version": "0.18.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",