codex-plugin-doctor 1.46.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,9 +285,13 @@ 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>
290
- codex-plugin-doctor check . --config .codex-doctor.json
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>
294
+ codex-plugin-doctor check . --config .codex-doctor.json
291
295
  codex-plugin-doctor baseline create . --output .codex-doctor-baseline.json
292
296
  codex-plugin-doctor check . --baseline .codex-doctor-baseline.json
293
297
  codex-plugin-doctor check . --history validation-history.jsonl
@@ -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.46.0
413
+ - uses: Esquetta/CodexPluginDoctor@v1.46.1
410
414
  with:
411
- version: "1.46.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
 
@@ -1,6 +1,8 @@
1
+ import type { CheckOptions, CheckResult } from "../domain/types.js";
1
2
  export interface ReleaseCheckReport {
2
3
  targetPath: string;
3
4
  status: "pass" | "fail";
5
+ runtimeProbeEnabled?: boolean;
4
6
  checks: {
5
7
  validation: {
6
8
  status: string;
@@ -24,12 +26,25 @@ export interface ReleaseCheckReport {
24
26
  status: string;
25
27
  score: number;
26
28
  };
29
+ metadata?: ReleaseMetadataCheck;
27
30
  };
28
31
  ready: boolean;
29
32
  }
30
- export declare function buildReleaseCheck(targetPath: string, options?: {
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 {
31
41
  env?: Record<string, string | undefined>;
32
42
  platform?: NodeJS.Platform;
33
- }): Promise<ReleaseCheckReport>;
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>;
34
49
  export declare function renderReleaseCheck(report: ReleaseCheckReport): string;
35
50
  export declare function renderReleaseCheckJson(report: ReleaseCheckReport): string;
@@ -1,9 +1,83 @@
1
+ import { readFile } from "node:fs/promises";
1
2
  import path from "node:path";
2
3
  import { validatePlugin } from "./validate-plugin.js";
3
4
  import { buildSecurityAudit } from "../security/security-audit.js";
4
5
  import { buildDepAudit } from "./dep-audit.js";
5
6
  import { buildCompatibilityMatrix } from "../compatibility/compatibility-matrix.js";
6
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
+ }
7
81
  function matrixOverallStatus(matrix) {
8
82
  const statuses = matrix.results.map((r) => r.status);
9
83
  const passCount = statuses.filter((s) => s === "pass").length;
@@ -29,11 +103,15 @@ export async function buildReleaseCheck(targetPath, options = {}) {
29
103
  const resolvedPath = path.resolve(targetPath);
30
104
  const env = options.env ?? process.env;
31
105
  const platform = options.platform ?? process.platform;
32
- const validationResult = await validatePlugin(resolvedPath, { runtime: true });
106
+ const runtimeProbeEnabled = options.runtime ?? false;
107
+ const validationResult = await (options.runCheck ?? validatePlugin)(resolvedPath, {
108
+ runtime: runtimeProbeEnabled
109
+ });
33
110
  const securityResult = await buildSecurityAudit(resolvedPath);
34
111
  const depResult = await buildDepAudit(resolvedPath);
35
112
  const compatResult = await buildCompatibilityMatrix(resolvedPath, { env, platform });
36
113
  const trustResult = await buildTrustScore(resolvedPath);
114
+ const metadataResult = await buildReleaseMetadataCheck(resolvedPath);
37
115
  const checks = {
38
116
  validation: {
39
117
  status: validationResult.status,
@@ -56,16 +134,19 @@ export async function buildReleaseCheck(targetPath, options = {}) {
56
134
  trust: {
57
135
  status: trustResult.status,
58
136
  score: trustResult.score
59
- }
137
+ },
138
+ metadata: metadataResult
60
139
  };
61
140
  const ready = checks.validation.status === "pass" &&
62
141
  checks.security.status === "pass" &&
63
142
  checks.dependencies.status !== "fail" &&
64
143
  checks.compatibility.status !== "fail" &&
65
- checks.trust.status === "pass";
144
+ checks.trust.status === "pass" &&
145
+ checks.metadata.status !== "fail";
66
146
  return {
67
147
  targetPath: resolvedPath,
68
148
  status: ready ? "pass" : "fail",
149
+ runtimeProbeEnabled,
69
150
  checks,
70
151
  ready
71
152
  };
@@ -77,6 +158,7 @@ export function renderReleaseCheck(report) {
77
158
  "==================================",
78
159
  `Path: ${report.targetPath}`,
79
160
  `Ready: ${report.ready ? "YES" : "NO"}`,
161
+ `Runtime probe: ${report.runtimeProbeEnabled ? "enabled" : "disabled"}`,
80
162
  "",
81
163
  "Checks",
82
164
  "------",
@@ -85,6 +167,9 @@ export function renderReleaseCheck(report) {
85
167
  ` ${icon(report.checks.dependencies.status === "pass")} dependencies ${report.checks.dependencies.status} (${report.checks.dependencies.vulnerabilities} vulns)`,
86
168
  ` ${icon(report.checks.compatibility.status === "pass")} compatibility ${report.checks.compatibility.status} (score: ${report.checks.compatibility.score})`,
87
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
+ : []),
88
173
  "",
89
174
  report.ready
90
175
  ? "All release checks passed. Ready to publish."
@@ -97,6 +182,7 @@ export function renderReleaseCheckJson(report) {
97
182
  schemaVersion: "1.0.0",
98
183
  targetPath: report.targetPath,
99
184
  status: report.status,
185
+ runtimeProbeEnabled: report.runtimeProbeEnabled ?? false,
100
186
  ready: report.ready,
101
187
  checks: report.checks
102
188
  }, null, 2);
package/dist/index.d.ts CHANGED
@@ -29,5 +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 { buildReleaseCheck, renderReleaseCheck, renderReleaseCheckJson, type ReleaseCheckReport } from "./core/release-check.js";
32
+ export { assertReleaseMetadataSync, buildReleaseCheck, buildReleaseMetadataCheck, renderReleaseCheck, renderReleaseCheckJson, type BuildReleaseCheckOptions, type ReleaseMetadataCheck, type ReleaseCheckReport } from "./core/release-check.js";
33
33
  export declare function runCheck(targetPath: string, options?: CheckOptions): Promise<CheckResult>;
package/dist/index.js CHANGED
@@ -29,7 +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 { buildReleaseCheck, renderReleaseCheck, renderReleaseCheckJson } from "./core/release-check.js";
32
+ export { assertReleaseMetadataSync, buildReleaseCheck, buildReleaseMetadataCheck, renderReleaseCheck, renderReleaseCheckJson } from "./core/release-check.js";
33
33
  export async function runCheck(targetPath, options = {}) {
34
34
  return validatePlugin(targetPath, options);
35
35
  }
package/dist/run-cli.js CHANGED
@@ -2253,7 +2253,7 @@ export async function runCli(args, io = defaultIo, options = {}) {
2253
2253
  if (command === "release") {
2254
2254
  const subCommand = maybePath;
2255
2255
  if (subCommand !== "check") {
2256
- io.writeStderr("Usage: codex-plugin-doctor release check <path> [--json]");
2256
+ io.writeStderr("Usage: codex-plugin-doctor release check <path> [--json] [--runtime] [--require-runtime-approval --runtime-approval-digest <digest>]");
2257
2257
  return 2;
2258
2258
  }
2259
2259
  const targetPath = remainingArgs[0] && !remainingArgs[0].startsWith("--") ? remainingArgs[0] : ".";
@@ -2261,9 +2261,37 @@ export async function runCli(args, io = defaultIo, options = {}) {
2261
2261
  ? remainingArgs
2262
2262
  : remainingArgs.slice(1);
2263
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
+ }
2264
2290
  const report = await buildReleaseCheck(targetPath, {
2265
2291
  env: terminalContext.env,
2266
- platform: terminalContext.platform
2292
+ platform: terminalContext.platform,
2293
+ runtime: runtimeProbeEnabled,
2294
+ runCheck: options.runCheckImpl
2267
2295
  });
2268
2296
  const output = jsonOutput
2269
2297
  ? renderReleaseCheckJson(report)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-plugin-doctor",
3
- "version": "1.46.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",