codex-plugin-doctor 1.45.0 → 1.46.1

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
@@ -285,12 +285,16 @@ codex-plugin-doctor check . --badge-markdown
285
285
  codex-plugin-doctor check . --sarif --output results.sarif
286
286
  codex-plugin-doctor check . --ascii
287
287
  codex-plugin-doctor check . --no-animations
288
- codex-plugin-doctor check . --runtime
289
- codex-plugin-doctor check . --runtime --require-runtime-approval --runtime-approval-digest sha256:<approved-plan-digest>
288
+ codex-plugin-doctor check . --runtime
289
+ codex-plugin-doctor check . --runtime --require-runtime-approval --runtime-approval-digest sha256:<approved-plan-digest>
290
+ codex-plugin-doctor release check .
291
+ codex-plugin-doctor release check . --json
292
+ codex-plugin-doctor release check . --runtime
293
+ codex-plugin-doctor release check . --runtime --require-runtime-approval --runtime-approval-digest sha256:<approved-plan-digest>
290
294
  codex-plugin-doctor check . --config .codex-doctor.json
291
- codex-plugin-doctor baseline create . --output .codex-doctor-baseline.json
292
- codex-plugin-doctor check . --baseline .codex-doctor-baseline.json
293
- codex-plugin-doctor check . --history validation-history.jsonl
295
+ codex-plugin-doctor baseline create . --output .codex-doctor-baseline.json
296
+ codex-plugin-doctor check . --baseline .codex-doctor-baseline.json
297
+ codex-plugin-doctor check . --history validation-history.jsonl
294
298
  codex-plugin-doctor history validation-history.jsonl
295
299
  codex-plugin-doctor history validation-history.jsonl --json
296
300
  codex-plugin-doctor history validation-history.jsonl --fail-on-regression
@@ -336,11 +340,11 @@ codex-plugin-doctor check . --json --runtime --verbose-runtime
336
340
 
337
341
  `check --badge-json` emits Shields endpoint-compatible JSON such as `{"schemaVersion":1,"label":"doctor","message":"PASS","color":"brightgreen"}`. `check --badge-markdown` emits a static shields.io Markdown badge for README or release notes. Badge output is intentionally limited to single package checks, not `check --installed`.
338
342
 
339
- `check --history <path>` appends a compact JSONL validation snapshot after a single package check. `history <path>` reads the JSONL file and compares the latest run to the previous run, including status, finding-count deltas, and whether the latest run regressed. Add `history --json` for automation output or `history --fail-on-regression` when CI should fail after a worse latest run.
340
-
341
- `baseline create <path> --output <path>` records the current fingerprinted findings for gradual CI adoption. A later `check <path> --baseline <path>` keeps matched findings visible in baseline metadata but gates only new active findings; reports summarize new, matched, and resolved counts. Commit the baseline for review and regenerate it deliberately after accepted findings change.
342
-
343
- `fix --dry-run` renders safe automatic fix plans without changing files. `fix --interactive --backup` shows the same numbered plan, then applies everything after `yes` or only selected action numbers such as `1,3`. `fix --apply --backup` applies supported safe fixes, such as manifest defaults and missing skills directories, after creating backups.
343
+ `check --history <path>` appends a compact JSONL validation snapshot after a single package check. `history <path>` reads the JSONL file and compares the latest run to the previous run, including status, finding-count deltas, and whether the latest run regressed. Add `history --json` for automation output or `history --fail-on-regression` when CI should fail after a worse latest run.
344
+
345
+ `baseline create <path> --output <path>` records the current fingerprinted findings for gradual CI adoption. A later `check <path> --baseline <path>` keeps matched findings visible in baseline metadata but gates only new active findings; reports summarize new, matched, and resolved counts. Commit the baseline for review and regenerate it deliberately after accepted findings change.
346
+
347
+ `fix --dry-run` renders safe automatic fix plans without changing files. `fix --interactive --backup` shows the same numbered plan, then applies everything after `yes` or only selected action numbers such as `1,3`. `fix --apply --backup` applies supported safe fixes, such as manifest defaults and missing skills directories, after creating backups.
344
348
 
345
349
  Optional local policy file:
346
350
 
@@ -406,9 +410,9 @@ jobs:
406
410
  runs-on: ubuntu-latest
407
411
  steps:
408
412
  - uses: actions/checkout@v5
409
- - uses: Esquetta/CodexPluginDoctor@v1.45.0
413
+ - uses: Esquetta/CodexPluginDoctor@v1.46.1
410
414
  with:
411
- version: "1.45.0"
415
+ version: "1.46.1"
412
416
  path: .
413
417
  runtime: "true"
414
418
  policy: codex-publish
@@ -463,7 +467,9 @@ npm run prepare-release
463
467
  npm run release-check
464
468
  ```
465
469
 
466
- `prepare-release` runs tests, builds the TypeScript output, and performs `npm pack --dry-run`. `release-check` adds release preflight checks for a clean git tree, existing npm versions, existing version tags, tests, build, pack dry-run, fresh temp install audit, and publish dry-run.
470
+ `prepare-release` runs tests, builds the TypeScript output, and performs `npm pack --dry-run`. `release-check` adds release preflight checks for a clean git tree, existing npm versions, existing version tags, package/lockfile/changelog version consistency, tests, build, pack dry-run, fresh temp install audit, and publish dry-run.
471
+
472
+ For plugin packages, `codex-plugin-doctor release check .` performs static readiness checks by default. Runtime probing executes package-local MCP servers and therefore requires an explicit `--runtime`; protected workflows can also require the approved runtime-plan digest.
467
473
 
468
474
  Related docs:
469
475
 
@@ -0,0 +1,50 @@
1
+ import type { CheckOptions, CheckResult } from "../domain/types.js";
2
+ export interface ReleaseCheckReport {
3
+ targetPath: string;
4
+ status: "pass" | "fail";
5
+ runtimeProbeEnabled?: boolean;
6
+ checks: {
7
+ validation: {
8
+ status: string;
9
+ findings: number;
10
+ exitCode: number;
11
+ };
12
+ security: {
13
+ status: string;
14
+ score: number;
15
+ };
16
+ dependencies: {
17
+ status: string;
18
+ vulnerabilities: number;
19
+ };
20
+ compatibility: {
21
+ status: string;
22
+ score: number;
23
+ overallStatus: string;
24
+ };
25
+ trust: {
26
+ status: string;
27
+ score: number;
28
+ };
29
+ metadata?: ReleaseMetadataCheck;
30
+ };
31
+ ready: boolean;
32
+ }
33
+ export interface ReleaseMetadataCheck {
34
+ status: "pass" | "fail" | "skipped";
35
+ packageVersion: string | null;
36
+ lockfileVersion: string | null;
37
+ changelogVersion: boolean | null;
38
+ findings: string[];
39
+ }
40
+ export interface BuildReleaseCheckOptions {
41
+ env?: Record<string, string | undefined>;
42
+ platform?: NodeJS.Platform;
43
+ runtime?: boolean;
44
+ runCheck?: (targetPath: string, options: CheckOptions) => Promise<CheckResult>;
45
+ }
46
+ export declare function buildReleaseMetadataCheck(targetPath: string): Promise<ReleaseMetadataCheck>;
47
+ export declare function assertReleaseMetadataSync(targetPath: string): Promise<void>;
48
+ export declare function buildReleaseCheck(targetPath: string, options?: BuildReleaseCheckOptions): Promise<ReleaseCheckReport>;
49
+ export declare function renderReleaseCheck(report: ReleaseCheckReport): string;
50
+ export declare function renderReleaseCheckJson(report: ReleaseCheckReport): string;
@@ -0,0 +1,189 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { validatePlugin } from "./validate-plugin.js";
4
+ import { buildSecurityAudit } from "../security/security-audit.js";
5
+ import { buildDepAudit } from "./dep-audit.js";
6
+ import { buildCompatibilityMatrix } from "../compatibility/compatibility-matrix.js";
7
+ import { buildTrustScore } from "../security/trust-score.js";
8
+ async function readOptionalFile(filePath) {
9
+ try {
10
+ return await readFile(filePath, "utf8");
11
+ }
12
+ catch (error) {
13
+ if (error.code === "ENOENT") {
14
+ return null;
15
+ }
16
+ throw error;
17
+ }
18
+ }
19
+ export async function buildReleaseMetadataCheck(targetPath) {
20
+ const packageContent = await readOptionalFile(path.join(targetPath, "package.json"));
21
+ if (packageContent === null) {
22
+ return {
23
+ status: "skipped",
24
+ packageVersion: null,
25
+ lockfileVersion: null,
26
+ changelogVersion: null,
27
+ findings: []
28
+ };
29
+ }
30
+ const findings = [];
31
+ let packageVersion = null;
32
+ let lockfileVersion = null;
33
+ try {
34
+ const packageJson = JSON.parse(packageContent);
35
+ packageVersion = typeof packageJson.version === "string" ? packageJson.version : null;
36
+ }
37
+ catch {
38
+ findings.push("package.json is not valid JSON.");
39
+ }
40
+ if (!packageVersion) {
41
+ findings.push("package.json must contain a version.");
42
+ }
43
+ const lockContent = await readOptionalFile(path.join(targetPath, "package-lock.json"));
44
+ if (lockContent !== null) {
45
+ try {
46
+ const lock = JSON.parse(lockContent);
47
+ const rootVersion = lock.packages?.[""]?.version ?? lock.version;
48
+ lockfileVersion = typeof rootVersion === "string" ? rootVersion : null;
49
+ }
50
+ catch {
51
+ findings.push("package-lock.json is not valid JSON.");
52
+ }
53
+ if (!lockfileVersion) {
54
+ findings.push("package-lock.json must contain the root package version.");
55
+ }
56
+ else if (packageVersion && lockfileVersion !== packageVersion) {
57
+ findings.push(`package-lock.json version ${lockfileVersion} does not match package.json ${packageVersion}.`);
58
+ }
59
+ }
60
+ const changelog = await readOptionalFile(path.join(targetPath, "CHANGELOG.md"));
61
+ const changelogVersion = changelog === null || !packageVersion
62
+ ? null
63
+ : new RegExp(`^## \\[${packageVersion.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\](?:\\s|$)`, "m").test(changelog);
64
+ if (changelogVersion === false) {
65
+ findings.push(`CHANGELOG.md does not contain a ${packageVersion} release section.`);
66
+ }
67
+ return {
68
+ status: findings.length === 0 ? "pass" : "fail",
69
+ packageVersion,
70
+ lockfileVersion,
71
+ changelogVersion,
72
+ findings
73
+ };
74
+ }
75
+ export async function assertReleaseMetadataSync(targetPath) {
76
+ const metadata = await buildReleaseMetadataCheck(path.resolve(targetPath));
77
+ if (metadata.status === "fail") {
78
+ throw new Error(`Release metadata is inconsistent: ${metadata.findings.join(" ")}`);
79
+ }
80
+ }
81
+ function matrixOverallStatus(matrix) {
82
+ const statuses = matrix.results.map((r) => r.status);
83
+ const passCount = statuses.filter((s) => s === "pass").length;
84
+ const total = statuses.length;
85
+ if (total === 0) {
86
+ return "pass";
87
+ }
88
+ if (passCount === total) {
89
+ return "pass";
90
+ }
91
+ const failCount = statuses.filter((s) => s === "fail").length;
92
+ return failCount > 0 ? "fail" : "warn";
93
+ }
94
+ function matrixScore(matrix) {
95
+ const scoreMap = { pass: 100, warn: 70, fail: 0, skipped: 0 };
96
+ const scores = matrix.results.map((r) => scoreMap[r.status] ?? 0);
97
+ if (scores.length === 0) {
98
+ return 0;
99
+ }
100
+ return Math.round(scores.reduce((a, b) => a + b, 0) / scores.length);
101
+ }
102
+ export async function buildReleaseCheck(targetPath, options = {}) {
103
+ const resolvedPath = path.resolve(targetPath);
104
+ const env = options.env ?? process.env;
105
+ const platform = options.platform ?? process.platform;
106
+ const runtimeProbeEnabled = options.runtime ?? false;
107
+ const validationResult = await (options.runCheck ?? validatePlugin)(resolvedPath, {
108
+ runtime: runtimeProbeEnabled
109
+ });
110
+ const securityResult = await buildSecurityAudit(resolvedPath);
111
+ const depResult = await buildDepAudit(resolvedPath);
112
+ const compatResult = await buildCompatibilityMatrix(resolvedPath, { env, platform });
113
+ const trustResult = await buildTrustScore(resolvedPath);
114
+ const metadataResult = await buildReleaseMetadataCheck(resolvedPath);
115
+ const checks = {
116
+ validation: {
117
+ status: validationResult.status,
118
+ findings: validationResult.findings.length,
119
+ exitCode: validationResult.exitCode
120
+ },
121
+ security: {
122
+ status: securityResult.status,
123
+ score: securityResult.score
124
+ },
125
+ dependencies: {
126
+ status: depResult.status,
127
+ vulnerabilities: depResult.totalVulnerabilities
128
+ },
129
+ compatibility: {
130
+ status: matrixOverallStatus(compatResult),
131
+ score: matrixScore(compatResult),
132
+ overallStatus: matrixOverallStatus(compatResult)
133
+ },
134
+ trust: {
135
+ status: trustResult.status,
136
+ score: trustResult.score
137
+ },
138
+ metadata: metadataResult
139
+ };
140
+ const ready = checks.validation.status === "pass" &&
141
+ checks.security.status === "pass" &&
142
+ checks.dependencies.status !== "fail" &&
143
+ checks.compatibility.status !== "fail" &&
144
+ checks.trust.status === "pass" &&
145
+ checks.metadata.status !== "fail";
146
+ return {
147
+ targetPath: resolvedPath,
148
+ status: ready ? "pass" : "fail",
149
+ runtimeProbeEnabled,
150
+ checks,
151
+ ready
152
+ };
153
+ }
154
+ export function renderReleaseCheck(report) {
155
+ const icon = (pass) => pass ? "PASS" : pass === undefined ? "SKIP" : "FAIL";
156
+ const lines = [
157
+ "Codex Plugin Doctor Release Check",
158
+ "==================================",
159
+ `Path: ${report.targetPath}`,
160
+ `Ready: ${report.ready ? "YES" : "NO"}`,
161
+ `Runtime probe: ${report.runtimeProbeEnabled ? "enabled" : "disabled"}`,
162
+ "",
163
+ "Checks",
164
+ "------",
165
+ ` ${icon(report.checks.validation.status === "pass")} validation ${report.checks.validation.status} (${report.checks.validation.findings} findings)`,
166
+ ` ${icon(report.checks.security.status === "pass")} security ${report.checks.security.status} (score: ${report.checks.security.score})`,
167
+ ` ${icon(report.checks.dependencies.status === "pass")} dependencies ${report.checks.dependencies.status} (${report.checks.dependencies.vulnerabilities} vulns)`,
168
+ ` ${icon(report.checks.compatibility.status === "pass")} compatibility ${report.checks.compatibility.status} (score: ${report.checks.compatibility.score})`,
169
+ ` ${icon(report.checks.trust.status === "pass")} trust ${report.checks.trust.status} (score: ${report.checks.trust.score})`,
170
+ ...(report.checks.metadata
171
+ ? [` ${icon(report.checks.metadata.status !== "fail")} metadata ${report.checks.metadata.status} (${report.checks.metadata.findings.length} findings)`]
172
+ : []),
173
+ "",
174
+ report.ready
175
+ ? "All release checks passed. Ready to publish."
176
+ : "Some checks failed. Fix issues before publishing."
177
+ ];
178
+ return lines.join("\n");
179
+ }
180
+ export function renderReleaseCheckJson(report) {
181
+ return JSON.stringify({
182
+ schemaVersion: "1.0.0",
183
+ targetPath: report.targetPath,
184
+ status: report.status,
185
+ runtimeProbeEnabled: report.runtimeProbeEnabled ?? false,
186
+ ready: report.ready,
187
+ checks: report.checks
188
+ }, null, 2);
189
+ }
package/dist/index.d.ts CHANGED
@@ -29,4 +29,5 @@ export { buildDepAudit, buildDepAuditRecommendations, renderDepAudit, renderDepA
29
29
  export { initGitHooks, removeGitHooks, type InitGitHooksResult, type RemoveGitHooksResult } from "./core/init-git-hooks.js";
30
30
  export { generateCompletion, type CompletionShell } from "./core/shell-completion.js";
31
31
  export { validateConfigFile, renderConfigValidation, renderConfigValidationJson, type ConfigValidationFinding, type ConfigValidationReport } from "./core/config-validate.js";
32
+ export { assertReleaseMetadataSync, buildReleaseCheck, buildReleaseMetadataCheck, renderReleaseCheck, renderReleaseCheckJson, type BuildReleaseCheckOptions, type ReleaseMetadataCheck, type ReleaseCheckReport } from "./core/release-check.js";
32
33
  export declare function runCheck(targetPath: string, options?: CheckOptions): Promise<CheckResult>;
package/dist/index.js CHANGED
@@ -29,6 +29,7 @@ export { buildDepAudit, buildDepAuditRecommendations, renderDepAudit, renderDepA
29
29
  export { initGitHooks, removeGitHooks } from "./core/init-git-hooks.js";
30
30
  export { generateCompletion } from "./core/shell-completion.js";
31
31
  export { validateConfigFile, renderConfigValidation, renderConfigValidationJson } from "./core/config-validate.js";
32
+ export { assertReleaseMetadataSync, buildReleaseCheck, buildReleaseMetadataCheck, renderReleaseCheck, renderReleaseCheckJson } from "./core/release-check.js";
32
33
  export async function runCheck(targetPath, options = {}) {
33
34
  return validatePlugin(targetPath, options);
34
35
  }
package/dist/run-cli.js CHANGED
@@ -36,6 +36,7 @@ import { buildDepAudit, renderDepAudit, renderDepAuditJson, renderDepAuditSarif
36
36
  import { initGitHooks, removeGitHooks } from "./core/init-git-hooks.js";
37
37
  import { generateCompletion } from "./core/shell-completion.js";
38
38
  import { renderConfigValidation, renderConfigValidationJson, validateConfigFile } from "./core/config-validate.js";
39
+ import { buildReleaseCheck, renderReleaseCheck, renderReleaseCheckJson } from "./core/release-check.js";
39
40
  import { initPluginPackage, initPluginTemplates, isInitPluginTemplate } from "./core/init-plugin.js";
40
41
  import { runCheck } from "./index.js";
41
42
  import { buildGenericMcpDoctor, renderGenericMcpDoctor, renderGenericMcpDoctorJson } from "./mcp/generic-mcp-doctor.js";
@@ -81,7 +82,7 @@ const defaultIo = {
81
82
  }
82
83
  };
83
84
  function printUsage(io) {
84
- 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 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");
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");
85
86
  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]");
86
87
  io.writeStderr("Baseline gating: codex-plugin-doctor baseline create <path> --output <path> [--runtime]\n codex-plugin-doctor check <path> --baseline <path>");
87
88
  }
@@ -2249,6 +2250,55 @@ export async function runCli(args, io = defaultIo, options = {}) {
2249
2250
  io.writeStdout(output);
2250
2251
  return report.status === "fail" ? 1 : 0;
2251
2252
  }
2253
+ if (command === "release") {
2254
+ const subCommand = maybePath;
2255
+ if (subCommand !== "check") {
2256
+ io.writeStderr("Usage: codex-plugin-doctor release check <path> [--json] [--runtime] [--require-runtime-approval --runtime-approval-digest <digest>]");
2257
+ return 2;
2258
+ }
2259
+ const targetPath = remainingArgs[0] && !remainingArgs[0].startsWith("--") ? remainingArgs[0] : ".";
2260
+ const releaseFlags = remainingArgs[0] && remainingArgs[0].startsWith("--")
2261
+ ? remainingArgs
2262
+ : remainingArgs.slice(1);
2263
+ const jsonOutput = releaseFlags.includes("--json");
2264
+ const runtimeProbeEnabled = releaseFlags.includes("--runtime");
2265
+ const requireRuntimeApproval = releaseFlags.includes("--require-runtime-approval");
2266
+ const runtimeApprovalDigestIndex = releaseFlags.indexOf("--runtime-approval-digest");
2267
+ const runtimeApprovalDigest = runtimeApprovalDigestIndex === -1
2268
+ ? null
2269
+ : releaseFlags[runtimeApprovalDigestIndex + 1];
2270
+ if (requireRuntimeApproval && !runtimeProbeEnabled) {
2271
+ io.writeStderr("Runtime approval requires --runtime.");
2272
+ return 2;
2273
+ }
2274
+ if (runtimeApprovalDigestIndex !== -1 &&
2275
+ (!runtimeApprovalDigest || runtimeApprovalDigest.startsWith("--"))) {
2276
+ io.writeStderr("Missing digest after --runtime-approval-digest.");
2277
+ return 2;
2278
+ }
2279
+ if (requireRuntimeApproval) {
2280
+ const runtimePlan = await buildDoctorRuntimePlan(targetPath);
2281
+ const approval = evaluateRuntimeApproval(runtimePlan, {
2282
+ required: true,
2283
+ approvedDigest: runtimeApprovalDigest
2284
+ });
2285
+ if (!runtimeApprovalPassed(approval)) {
2286
+ io.writeStderr(`${approval.message}\nCurrent runtime plan digest: ${runtimePlan.digest}`);
2287
+ return 1;
2288
+ }
2289
+ }
2290
+ const report = await buildReleaseCheck(targetPath, {
2291
+ env: terminalContext.env,
2292
+ platform: terminalContext.platform,
2293
+ runtime: runtimeProbeEnabled,
2294
+ runCheck: options.runCheckImpl
2295
+ });
2296
+ const output = jsonOutput
2297
+ ? renderReleaseCheckJson(report)
2298
+ : renderReleaseCheck(report);
2299
+ io.writeStdout(output);
2300
+ return report.status === "fail" ? 1 : 0;
2301
+ }
2252
2302
  if (command === "suppress") {
2253
2303
  const parsedSuppressCommand = parseSuppressCommand([maybePath ?? "", ...remainingArgs]);
2254
2304
  if ("message" in parsedSuppressCommand) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-plugin-doctor",
3
- "version": "1.45.0",
3
+ "version": "1.46.1",
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",