codex-plugin-doctor 1.27.0 → 1.28.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
@@ -11,23 +11,23 @@ It catches packaging, metadata, security, and runtime protocol problems before a
11
11
 
12
12
  ## Status
13
13
 
14
- Codex Plugin Doctor is a public stable CLI for local and CI validation.
14
+ Codex Plugin Doctor is a public stable CLI for local and CI validation.
15
15
 
16
16
  - Primary surface: GitHub repository and npm package
17
17
  - Distribution today: `npm install -g`, local source install, `npm link`, `npm pack`, GitHub Releases
18
18
  - Public npm package: `codex-plugin-doctor`
19
19
  - License: [MIT](./LICENSE)
20
20
 
21
- ## 1.0 Stability
22
-
23
- The 1.0 line is the stable compatibility baseline for plugin authors and CI consumers.
24
-
25
- - Public JSON schema surfaces and existing rule IDs/default severities are treated as stable through 1.0.
26
- - Runtime probing remains opt-in because it executes package-local MCP servers.
27
- - The project remains Codex-first; broader MCP Doctor positioning stays a post-1.0 expansion path.
28
- - Post-1.0 feature work should stay additive unless a documented major-version decision is made.
29
-
30
- See [v1.0 Readiness Checklist](./docs/engineering/v1.0-readiness-checklist.md) for the stable release evidence and smoke plan.
21
+ ## 1.0 Stability
22
+
23
+ The 1.0 line is the stable compatibility baseline for plugin authors and CI consumers.
24
+
25
+ - Public JSON schema surfaces and existing rule IDs/default severities are treated as stable through 1.0.
26
+ - Runtime probing remains opt-in because it executes package-local MCP servers.
27
+ - The project remains Codex-first; broader MCP Doctor positioning stays a post-1.0 expansion path.
28
+ - Post-1.0 feature work should stay additive unless a documented major-version decision is made.
29
+
30
+ See the [documentation index](./docs/README.md) for architecture, usage, security, and contributor guides.
31
31
 
32
32
  ## Why This Exists
33
33
 
@@ -64,7 +64,7 @@ Security scorecard with `security`:
64
64
  - MCP server `cwd` paths that escape the package root
65
65
  - plain HTTP remote transport warnings
66
66
 
67
- Runtime MCP validation with `--runtime`:
67
+ Runtime MCP validation with `--runtime`:
68
68
 
69
69
  - `initialize`
70
70
  - `notifications/initialized`
@@ -76,9 +76,9 @@ Runtime MCP validation with `--runtime`:
76
76
  - `prompts/list`
77
77
  - `prompts/get`
78
78
  - paginated list responses
79
- - runtime capability scorecard
80
- - redacted verbose transcript with `--verbose-runtime`
81
- - optional runtime approval gating with a precomputed `doctor runtime-plan` digest
79
+ - runtime capability scorecard
80
+ - redacted verbose transcript with `--verbose-runtime`
81
+ - optional runtime approval gating with a precomputed `doctor runtime-plan` digest
82
82
 
83
83
  Output formats:
84
84
 
@@ -195,14 +195,14 @@ codex-plugin-doctor doctor contract
195
195
  codex-plugin-doctor doctor contract --json --output output-contract.json
196
196
  codex-plugin-doctor doctor corpus
197
197
  codex-plugin-doctor doctor corpus --json --output validation-corpus.json
198
- codex-plugin-doctor doctor npm <published-plugin-package>
199
- codex-plugin-doctor doctor npm <published-plugin-package> --json --output npm-preinstall.json
200
- codex-plugin-doctor doctor attest .
201
- codex-plugin-doctor doctor attest . --json --output attestation.json
202
- codex-plugin-doctor doctor attest . --json --sign-key-env CODEX_PLUGIN_DOCTOR_SIGNING_KEY --output attestation.json
203
- codex-plugin-doctor doctor attest verify attestation.json --target . --sign-key-env CODEX_PLUGIN_DOCTOR_SIGNING_KEY
204
- codex-plugin-doctor doctor attest verify attestation.json --target . --json --sign-key-env CODEX_PLUGIN_DOCTOR_SIGNING_KEY
205
- codex-plugin-doctor doctor inspector .
198
+ codex-plugin-doctor doctor npm <published-plugin-package>
199
+ codex-plugin-doctor doctor npm <published-plugin-package> --json --output npm-preinstall.json
200
+ codex-plugin-doctor doctor attest .
201
+ codex-plugin-doctor doctor attest . --json --output attestation.json
202
+ codex-plugin-doctor doctor attest . --json --sign-key-env CODEX_PLUGIN_DOCTOR_SIGNING_KEY --output attestation.json
203
+ codex-plugin-doctor doctor attest verify attestation.json --target . --sign-key-env CODEX_PLUGIN_DOCTOR_SIGNING_KEY
204
+ codex-plugin-doctor doctor attest verify attestation.json --target . --json --sign-key-env CODEX_PLUGIN_DOCTOR_SIGNING_KEY
205
+ codex-plugin-doctor doctor inspector .
206
206
  codex-plugin-doctor doctor inspector . --server context7 --json --output inspector-command.json
207
207
  codex-plugin-doctor doctor diff --before ./old-plugin --after ./new-plugin
208
208
  codex-plugin-doctor doctor diff --before ./old-plugin --after ./new-plugin --json --output risk-diff.json
@@ -210,20 +210,20 @@ codex-plugin-doctor doctor recommend .
210
210
  codex-plugin-doctor doctor recommend . --json --output recommendations.json
211
211
  codex-plugin-doctor doctor trust .
212
212
  codex-plugin-doctor doctor trust . --json --output trust-score.json
213
- codex-plugin-doctor doctor perf .
214
- codex-plugin-doctor doctor perf . --json --output perf.json
215
- codex-plugin-doctor doctor perf . --max-total-ms 2500 --max-stage-ms validation=500
216
- codex-plugin-doctor doctor runtime-plan .
217
- codex-plugin-doctor doctor runtime-plan . --json --output runtime-plan.json
218
- codex-plugin-doctor doctor runtime-plan . --markdown --output runtime-plan.md
219
- codex-plugin-doctor doctor runtime-policy .
220
- codex-plugin-doctor doctor runtime-policy . --json --output runtime-policy.json
221
- codex-plugin-doctor doctor review-bundle . --output review-bundle --sign-key-env CODEX_PLUGIN_DOCTOR_SIGNING_KEY
222
- codex-plugin-doctor doctor review-bundle verify review-bundle --target . --sign-key-env CODEX_PLUGIN_DOCTOR_SIGNING_KEY
223
- codex-plugin-doctor doctor review-bundle diff --before old-review-bundle --after review-bundle
224
- codex-plugin-doctor doctor mcp .
225
- codex-plugin-doctor doctor mcp . --json --output mcp-healthcheck.json
226
- codex-plugin-doctor doctor export --bundle .
213
+ codex-plugin-doctor doctor perf .
214
+ codex-plugin-doctor doctor perf . --json --output perf.json
215
+ codex-plugin-doctor doctor perf . --max-total-ms 2500 --max-stage-ms validation=500
216
+ codex-plugin-doctor doctor runtime-plan .
217
+ codex-plugin-doctor doctor runtime-plan . --json --output runtime-plan.json
218
+ codex-plugin-doctor doctor runtime-plan . --markdown --output runtime-plan.md
219
+ codex-plugin-doctor doctor runtime-policy .
220
+ codex-plugin-doctor doctor runtime-policy . --json --output runtime-policy.json
221
+ codex-plugin-doctor doctor review-bundle . --output review-bundle --sign-key-env CODEX_PLUGIN_DOCTOR_SIGNING_KEY
222
+ codex-plugin-doctor doctor review-bundle verify review-bundle --target . --sign-key-env CODEX_PLUGIN_DOCTOR_SIGNING_KEY
223
+ codex-plugin-doctor doctor review-bundle diff --before old-review-bundle --after review-bundle
224
+ codex-plugin-doctor doctor mcp .
225
+ codex-plugin-doctor doctor mcp . --json --output mcp-healthcheck.json
226
+ codex-plugin-doctor doctor export --bundle .
227
227
  codex-plugin-doctor doctor export --bundle . --output doctor-bundle.json
228
228
  codex-plugin-doctor doctor snapshot
229
229
  codex-plugin-doctor doctor snapshot --json
@@ -232,18 +232,18 @@ codex-plugin-doctor doctor clients
232
232
  codex-plugin-doctor doctor --update-check
233
233
  codex-plugin-doctor audit --installed
234
234
  codex-plugin-doctor audit --installed --security --compat
235
- codex-plugin-doctor audit --installed --security --compat --json --output local-audit.json
236
- codex-plugin-doctor audit --installed --security --compat --cache
237
- codex-plugin-doctor audit --installed --changed --cache
238
- codex-plugin-doctor audit deps .
239
- codex-plugin-doctor audit deps . --json --output dependency-audit.json
240
- codex-plugin-doctor watch .
241
- codex-plugin-doctor watch . --json --output watch-latest.json --debounce-ms 500
242
- codex-plugin-doctor init-git-hooks .
243
- codex-plugin-doctor init-git-hooks . --json
244
- codex-plugin-doctor mcp .
245
- codex-plugin-doctor mcp . --json
246
- codex-plugin-doctor mcp . --json --output mcp-doctor.json
235
+ codex-plugin-doctor audit --installed --security --compat --json --output local-audit.json
236
+ codex-plugin-doctor audit --installed --security --compat --cache
237
+ codex-plugin-doctor audit --installed --changed --cache
238
+ codex-plugin-doctor audit deps .
239
+ codex-plugin-doctor audit deps . --json --output dependency-audit.json
240
+ codex-plugin-doctor watch .
241
+ codex-plugin-doctor watch . --json --output watch-latest.json --debounce-ms 500
242
+ codex-plugin-doctor init-git-hooks .
243
+ codex-plugin-doctor init-git-hooks . --json
244
+ codex-plugin-doctor mcp .
245
+ codex-plugin-doctor mcp . --json
246
+ codex-plugin-doctor mcp . --json --output mcp-doctor.json
247
247
  codex-plugin-doctor init my-plugin
248
248
  codex-plugin-doctor init my-mcp --template mcp-stdio
249
249
  codex-plugin-doctor init remote-mcp --template mcp-http
@@ -282,10 +282,10 @@ codex-plugin-doctor check . --badge-json --output doctor-badge.json
282
282
  codex-plugin-doctor check . --badge-markdown
283
283
  codex-plugin-doctor check . --sarif --output results.sarif
284
284
  codex-plugin-doctor check . --ascii
285
- codex-plugin-doctor check . --no-animations
286
- codex-plugin-doctor check . --runtime
287
- codex-plugin-doctor check . --runtime --require-runtime-approval --runtime-approval-digest sha256:<approved-plan-digest>
288
- codex-plugin-doctor check . --config .codex-doctor.json
285
+ codex-plugin-doctor check . --no-animations
286
+ codex-plugin-doctor check . --runtime
287
+ codex-plugin-doctor check . --runtime --require-runtime-approval --runtime-approval-digest sha256:<approved-plan-digest>
288
+ codex-plugin-doctor check . --config .codex-doctor.json
289
289
  codex-plugin-doctor check . --history validation-history.jsonl
290
290
  codex-plugin-doctor history validation-history.jsonl
291
291
  codex-plugin-doctor history validation-history.jsonl --json
@@ -298,23 +298,23 @@ codex-plugin-doctor check . --json --runtime --verbose-runtime
298
298
 
299
299
  `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`.
300
300
 
301
- `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 corpus` runs the bundled validation corpus against healthy runtime, risky security, starter skill, and generic MCP packages, then reports whether each case matched its expected outcome. Add `--json` for automation or `--output validation-corpus.json` to write the corpus report 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. The JSON report includes tarball metadata such as filename, path, integrity, shasum, sizes, file count, and extracted package root so automation can prove which archive was scanned. Use a published Codex plugin package as the target; scanning `codex-plugin-doctor` itself intentionally reports a missing plugin manifest because this CLI package is not a plugin package. Add `--json` for automation or `--output npm-preinstall.json` to write the report to disk. `doctor attest <path>` creates a local attestation with stable package/report digests, validation/security/compatibility/trust summary, and verification metadata. Add `--sign-key-env NAME` to attach a local HMAC-SHA256 signature without printing the secret, or `--json --output attestation.json` to write the artifact to disk. `doctor attest verify <attestation.json> --target <path> --sign-key-env NAME` recomputes the package fingerprint, report digest, and HMAC signature offline; verification intentionally treats `generatedAt`, `targetPath`, `verification`, and `signature.keyHint` as unsigned display metadata. `doctor runtime-plan <path>` creates a non-executing runtime plan that lists MCP server commands, safe probe methods, risk reasons, and a stable approval digest before any local server is started. Add `--markdown --output runtime-plan.md` to preserve a review-ready approval artifact with the execution boundary, checklist, servers, probes, and risk reasons. `doctor runtime-policy <path>` evaluates the same runtime plan and security signals, then recommends `allow`, `review`, `sandbox_recommended`, or `deny` before local MCP execution starts. `doctor review-bundle <path> --output <dir> --sign-key-env NAME` writes a signed review directory with runtime plan, runtime policy, attestation, release evidence, manifest, Markdown summary files, and SHA-256 file integrity digests. `doctor review-bundle verify <bundle-dir> --target <path> --sign-key-env NAME` verifies the bundle manifest, expected files, manifest integrity digests, runtime artifacts, signed attestation, and signed release evidence offline before a reviewer trusts the handoff. `doctor review-bundle diff --before <dir> --after <dir>` compares two review bundles and flags risk-increasing changes in status, runtime policy, release readiness, signatures, release evidence, and runtime plan digest. `check --runtime --require-runtime-approval --runtime-approval-digest <digest>` refuses to run runtime probes unless the current plan digest matches the approved digest. `doctor release-evidence <path> --sign-key-env NAME` creates one redacted release bundle with signed attestation, offline verification, corpus, performance, security, trust, package metadata, git release gates, and runtime approval status. Strict release evidence requires a clean tagged worktree; use `--allow-dirty` or `--allow-untagged` only for local rehearsal. `doctor release-evidence verify <evidence.json> --target <path> --sign-key-env NAME` verifies a shared release evidence artifact offline against an explicit package path; the artifact target path is treated as display metadata, not trusted input. `doctor release-evidence asset <path> --tag <tag> --output <evidence.json> --sign-key-env NAME` writes a signed release evidence file and prints the `gh release upload` command; add `--upload` to run the upload through GitHub CLI with `--clobber`. `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. Add `--max-total-ms <ms>` or repeatable `--max-stage-ms stage=ms` to fail CI when a budget is exceeded. `doctor mcp <path>` exposes the generic MCP static health report under the doctor command family without starting local MCP servers. `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.
301
+ `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 corpus` runs the bundled validation corpus against healthy runtime, risky security, starter skill, and generic MCP packages, then reports whether each case matched its expected outcome. Add `--json` for automation or `--output validation-corpus.json` to write the corpus report 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. The JSON report includes tarball metadata such as filename, path, integrity, shasum, sizes, file count, and extracted package root so automation can prove which archive was scanned. Use a published Codex plugin package as the target; scanning `codex-plugin-doctor` itself intentionally reports a missing plugin manifest because this CLI package is not a plugin package. Add `--json` for automation or `--output npm-preinstall.json` to write the report to disk. `doctor attest <path>` creates a local attestation with stable package/report digests, validation/security/compatibility/trust summary, and verification metadata. Add `--sign-key-env NAME` to attach a local HMAC-SHA256 signature without printing the secret, or `--json --output attestation.json` to write the artifact to disk. `doctor attest verify <attestation.json> --target <path> --sign-key-env NAME` recomputes the package fingerprint, report digest, and HMAC signature offline; verification intentionally treats `generatedAt`, `targetPath`, `verification`, and `signature.keyHint` as unsigned display metadata. `doctor runtime-plan <path>` creates a non-executing runtime plan that lists MCP server commands, safe probe methods, risk reasons, and a stable approval digest before any local server is started. Add `--markdown --output runtime-plan.md` to preserve a review-ready approval artifact with the execution boundary, checklist, servers, probes, and risk reasons. `doctor runtime-policy <path>` evaluates the same runtime plan and security signals, then recommends `allow`, `review`, `sandbox_recommended`, or `deny` before local MCP execution starts. `doctor review-bundle <path> --output <dir> --sign-key-env NAME` writes a signed review directory with runtime plan, runtime policy, attestation, release evidence, manifest, Markdown summary files, and SHA-256 file integrity digests. `doctor review-bundle verify <bundle-dir> --target <path> --sign-key-env NAME` verifies the bundle manifest, expected files, manifest integrity digests, runtime artifacts, signed attestation, and signed release evidence offline before a reviewer trusts the handoff. `doctor review-bundle diff --before <dir> --after <dir>` compares two review bundles and flags risk-increasing changes in status, runtime policy, release readiness, signatures, release evidence, and runtime plan digest. `check --runtime --require-runtime-approval --runtime-approval-digest <digest>` refuses to run runtime probes unless the current plan digest matches the approved digest. `doctor release-evidence <path> --sign-key-env NAME` creates one redacted release bundle with signed attestation, offline verification, corpus, performance, security, trust, package metadata, git release gates, and runtime approval status. Strict release evidence requires a clean tagged worktree; use `--allow-dirty` or `--allow-untagged` only for local rehearsal. `doctor release-evidence verify <evidence.json> --target <path> --sign-key-env NAME` verifies a shared release evidence artifact offline against an explicit package path; the artifact target path is treated as display metadata, not trusted input. `doctor release-evidence asset <path> --tag <tag> --output <evidence.json> --sign-key-env NAME` writes a signed release evidence file and prints the `gh release upload` command; add `--upload` to run the upload through GitHub CLI with `--clobber`. `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. Add `--max-total-ms <ms>` or repeatable `--max-stage-ms stage=ms` to fail CI when a budget is exceeded. `doctor mcp <path>` exposes the generic MCP static health report under the doctor command family without starting local MCP servers. `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.
302
+
303
+ `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.
304
+
305
+ `audit deps <path>` runs `npm audit --json` for the target package and normalizes dependency vulnerability results into a stable `schemaVersion: "1.0.0"` report. Use `--json --output dependency-audit.json` when CI needs machine-readable status, vulnerability counts, and the raw npm audit payload.
306
+
307
+ `watch <path>` continuously validates a plugin package after file changes. Use `--debounce-ms <ms>` to tune noisy repositories, `--runtime` when runtime probes are required, and `--json --output watch-latest.json` when another process should consume the latest validation snapshot.
302
308
 
303
- `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.
304
-
305
- `audit deps <path>` runs `npm audit --json` for the target package and normalizes dependency vulnerability results into a stable `schemaVersion: "1.0.0"` report. Use `--json --output dependency-audit.json` when CI needs machine-readable status, vulnerability counts, and the raw npm audit payload.
306
-
307
- `watch <path>` continuously validates a plugin package after file changes. Use `--debounce-ms <ms>` to tune noisy repositories, `--runtime` when runtime probes are required, and `--json --output watch-latest.json` when another process should consume the latest validation snapshot.
308
-
309
- `init-git-hooks [path]` installs generated pre-commit and pre-push hooks that run Codex Plugin Doctor validation before local commits and pushes. Add `--force` to overwrite existing hooks and `--json` when setup automation needs the written hook paths.
310
-
311
- `--policy codex-publish|mcp-strict|security` applies opinionated gates without requiring a local `.codex-doctor.json`. `codex-publish` fails warnings and enables runtime probes for release checks, `mcp-strict` does the same for MCP-heavy packages, and `security` fails warning-level security findings so advisory risks can block a local audit or CI gate.
309
+ `init-git-hooks [path]` installs generated pre-commit and pre-push hooks that run Codex Plugin Doctor validation before local commits and pushes. Add `--force` to overwrite existing hooks and `--json` when setup automation needs the written hook paths.
310
+
311
+ `--policy codex-publish|mcp-strict|security` applies opinionated gates without requiring a local `.codex-doctor.json`. `codex-publish` fails warnings and enables runtime probes for release checks, `mcp-strict` does the same for MCP-heavy packages, and `security` fails warning-level security findings so advisory risks can block a local audit or CI gate.
312
312
 
313
313
  `mcp <path>` diagnoses generic MCP packages that may not have a Codex plugin manifest. It looks for `.mcp.json` or a manifest `mcpServers` reference, validates the top-level `mcpServers` object and server transports, adds MCP command-surface security findings, and includes the all-client compatibility matrix in the same report.
314
314
 
315
315
  `init [path] --template ...` creates targeted starter packages. `skill-only` is the default minimal skill package, `mcp-stdio` adds a local stdio MCP config and mock server, `mcp-http` scaffolds a streamable HTTP MCP config, and `full-runtime` generates a stdio sample that passes the runtime protocol probes.
316
316
 
317
- `security <path>` renders a focused package security scorecard. It reuses the existing package security findings, then adds deeper MCP command-surface checks for shell wrappers, encoded shell payloads, remote pipe-to-shell startup patterns, `cwd` values outside the plugin root, and plain HTTP URLs. Findings include structured, instance-specific evidence such as manifest fields, skill paths, server names, protocol methods, dependency sections, env keys, and bounded payload lengths. Deterministic SHA-256 fingerprints derive from the rule ID and canonical evidence, with package paths normalized for cross-machine stability. Secrets, prompt bodies, resource contents, tool payloads, stderr, and encoded commands are not copied into evidence. Use `--json` for automation or `--scorecard` for a compact status view.
317
+ `security <path>` renders a focused package security scorecard. It reuses the existing package security findings, then adds deeper MCP command-surface checks for shell wrappers, encoded shell payloads, remote pipe-to-shell startup patterns, `cwd` values outside the plugin root, and plain HTTP URLs. Findings include structured, instance-specific evidence such as manifest fields, skill paths, server names, protocol methods, dependency sections, env keys, and bounded payload lengths. Deterministic SHA-256 fingerprints derive from the rule ID and canonical evidence, with package paths normalized for cross-machine stability. Secrets, prompt bodies, resource contents, tool payloads, stderr, and encoded commands are not copied into evidence. Use `--json` for automation or `--scorecard` for a compact status view.
318
318
 
319
319
  `compat --client claude-desktop` checks whether the MCP package can be added to the local Claude Desktop setup. On Windows it looks for `%APPDATA%\Claude\claude_desktop_config.json`; on macOS it looks for `~/Library/Application Support/Claude/claude_desktop_config.json`. A valid existing config returns `PASS`, a missing Claude Desktop install returns `WARN`, and a malformed local config returns `FAIL` so you do not add new servers into a broken config file. If the package server name already exists in Claude Desktop, the command returns `WARN` with the duplicate server name. Add `--install-preview` to print the JSON snippet that should be merged into `claude_desktop_config.json`; it does not modify files. Use `--apply --backup` only when you want the CLI to create a timestamped backup and merge the server config. Apply mode refuses to overwrite duplicate server names.
320
320
 
@@ -336,27 +336,46 @@ codex-plugin-doctor check . --json --runtime --verbose-runtime
336
336
 
337
337
  `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.
338
338
 
339
- Optional local policy file:
340
-
341
- ```json
342
- {
343
- "ignoreRules": ["plugin.heuristic.description.too_long"],
344
- "suppressions": [
345
- {
346
- "fingerprint": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
347
- "reason": "Accepted until the upstream package layout is corrected.",
348
- "expiresAt": "2026-07-31"
349
- }
350
- ],
351
- "failOnWarnings": true
352
- }
353
- ```
354
-
355
- `ignoreRules` disables every finding with a matching rule ID. Prefer `suppressions` when accepting one specific finding instance: each record requires the exact 64-character lowercase SHA-256 fingerprint shown in Doctor reports, a non-empty review reason, and a real `YYYY-MM-DD` expiration date.
356
-
357
- Active suppressions remove only the matching finding from the active result and preserve it under `suppressedFindings` for audit output. Expired or invalid records do not suppress findings; Doctor emits `suppression.expired` or `suppression.invalid` warnings instead. Duplicate active records use the first valid match.
358
-
359
- Run these when you want Codex Plugin Doctor to find plugins from the local Codex installation:
339
+ Optional local policy file:
340
+
341
+ ```json
342
+ {
343
+ "ignoreRules": ["plugin.heuristic.description.too_long"],
344
+ "suppressions": [
345
+ {
346
+ "fingerprint": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
347
+ "reason": "Accepted until the upstream package layout is corrected.",
348
+ "expiresAt": "2026-07-31"
349
+ }
350
+ ],
351
+ "failOnWarnings": true
352
+ }
353
+ ```
354
+
355
+ `ignoreRules` disables every finding with a matching rule ID. Prefer `suppressions` when accepting one specific finding instance: each record requires the exact 64-character lowercase SHA-256 fingerprint shown in Doctor reports, a non-empty review reason, and a real `YYYY-MM-DD` expiration date.
356
+
357
+ Active suppressions remove only the matching finding from the active result and preserve it under `suppressedFindings` for audit output. Expired or invalid records do not suppress findings; Doctor emits `suppression.expired` or `suppression.invalid` warnings instead. Duplicate active records use the first valid match.
358
+
359
+ ### Manage suppressions
360
+
361
+ ```bash
362
+ codex-plugin-doctor suppress add .
363
+ codex-plugin-doctor suppress list .
364
+ codex-plugin-doctor suppress remove .
365
+ ```
366
+
367
+ Interactive add selects one active fingerprinted finding and defaults to an expiration 30 local calendar days from today. The stored `YYYY-MM-DD` record remains active through that UTC calendar date and expires at `00:00 UTC` on the following day. Suppressions match only the exact finding fingerprint; invalid or expired records never hide findings.
368
+
369
+ For automation, supply all add fields or one remove selector and add `--json` when machine-readable output is required:
370
+
371
+ ```bash
372
+ codex-plugin-doctor suppress add . --fingerprint <sha256> --reason "Accepted temporarily." --expires-at 2026-07-31 --json
373
+ codex-plugin-doctor suppress list . --json
374
+ codex-plugin-doctor suppress remove . --index 0 --json
375
+ codex-plugin-doctor suppress remove . --fingerprint <sha256> --json
376
+ ```
377
+
378
+ Run these when you want Codex Plugin Doctor to find plugins from the local Codex installation:
360
379
 
361
380
  ```bash
362
381
  codex-plugin-doctor list --installed
@@ -380,22 +399,22 @@ jobs:
380
399
  doctor:
381
400
  runs-on: ubuntu-latest
382
401
  steps:
383
- - uses: actions/checkout@v5
384
- - uses: Esquetta/CodexPluginDoctor@v1.27.0
385
- with:
386
- version: "1.27.0"
402
+ - uses: actions/checkout@v5
403
+ - uses: Esquetta/CodexPluginDoctor@v1.28.0
404
+ with:
405
+ version: "1.28.0"
387
406
  path: .
388
- runtime: "true"
389
- policy: codex-publish
390
- corpus: "true"
391
- contract: "true"
392
- upload-artifact: "true"
393
- artifact-name: codex-plugin-doctor-reports
394
- review-bundle: "true"
395
- review-bundle-verify: "true"
396
- ```
397
-
398
- The action writes `codex-plugin-doctor-summary.md`, `codex-plugin-doctor-report.json`, `codex-plugin-doctor-action-manifest.json`, optional `codex-plugin-doctor.sarif`, optional `validation-corpus.json`, optional `output-contract.json`, and optional signed `review-bundle/` files to `codex-plugin-doctor-reports`, appends the Markdown report to the GitHub Actions step summary, uploads the report directory as an artifact, and then returns the real validation exit code. Review bundle generation requires a signing key environment variable such as `CODEX_PLUGIN_DOCTOR_SIGNING_KEY`. For runtime probing, SARIF output, corpus and contract artifacts, review bundle artifacts, installed plugin cache checks, CI policy presets, and pinned release examples, see [GitHub Action Usage](./docs/engineering/github-action-usage.md).
407
+ runtime: "true"
408
+ policy: codex-publish
409
+ corpus: "true"
410
+ contract: "true"
411
+ upload-artifact: "true"
412
+ artifact-name: codex-plugin-doctor-reports
413
+ review-bundle: "true"
414
+ review-bundle-verify: "true"
415
+ ```
416
+
417
+ The action writes `codex-plugin-doctor-summary.md`, `codex-plugin-doctor-report.json`, `codex-plugin-doctor-action-manifest.json`, optional `codex-plugin-doctor.sarif`, optional `validation-corpus.json`, optional `output-contract.json`, and optional signed `review-bundle/` files to `codex-plugin-doctor-reports`, appends the Markdown report to the GitHub Actions step summary, uploads the report directory as an artifact, and then returns the real validation exit code. Review bundle generation requires a signing key environment variable such as `CODEX_PLUGIN_DOCTOR_SIGNING_KEY`. For runtime probing, SARIF output, corpus and contract artifacts, review bundle artifacts, installed plugin cache checks, CI policy presets, and pinned release examples, see [GitHub Action Usage](./docs/guides/github-action.md).
399
418
 
400
419
  To self-test this repository after cloning it:
401
420
 
@@ -406,7 +425,7 @@ codex-plugin-doctor check examples/codex-doctor-runtime --runtime --no-animation
406
425
  ## Repository Layout
407
426
 
408
427
  ```text
409
- docs/ Product, engineering, security, and release docs
428
+ docs/ Public architecture, guides, rules, security, and contributor docs
410
429
  examples/ Manual plugin packs for local CLI testing
411
430
  src/ CLI, validation logic, runtime probing, reports
412
431
  tests/ Fixture-based regression tests
@@ -417,7 +436,7 @@ validation-sessions/ Real-world validation waves and tuning notes
417
436
 
418
437
  The validator is tuned against local fixtures and real marketplace-style plugin packages. See:
419
438
 
420
- - [Real-World Validation Workflow](./docs/engineering/real-world-validation-workflow.md)
439
+ - [Real-World Validation Workflow](./docs/guides/real-world-validation.md)
421
440
  - [Validation Sessions](./validation-sessions/README.md)
422
441
  - [Examples](./examples/README.md)
423
442
  - [Rule Catalog](./docs/rules/catalog.md)
@@ -446,9 +465,9 @@ Related docs:
446
465
  - [Contributing](./CONTRIBUTING.md)
447
466
  - [Security Policy](./SECURITY.md)
448
467
  - [Code of Conduct](./CODE_OF_CONDUCT.md)
449
- - [NPM Release Checklist](./docs/engineering/npm-release-checklist.md)
450
- - [Release Candidate Workflow](./docs/engineering/release-candidate-workflow.md)
451
- - [v1.0 Readiness Checklist](./docs/engineering/v1.0-readiness-checklist.md)
468
+ - [NPM Release Checklist](./docs/contributing/npm-release-checklist.md)
469
+ - [Release Candidate Workflow](./docs/contributing/release-candidates.md)
470
+ - [Versioning and Releases](./docs/contributing/releasing.md)
452
471
 
453
472
  ## Contributing
454
473
 
@@ -0,0 +1,12 @@
1
+ import { rename as renameFile } from "node:fs/promises";
2
+ export type RawDoctorConfig = Record<string, unknown>;
3
+ export interface LoadedDoctorConfig {
4
+ configPath: string;
5
+ exists: boolean;
6
+ value: RawDoctorConfig;
7
+ }
8
+ export declare function resolveDoctorConfigPath(targetPath: string, explicitConfigPath?: string | null): string;
9
+ export declare function readRawDoctorConfig(targetPath: string, explicitConfigPath?: string | null): Promise<LoadedDoctorConfig>;
10
+ export declare function writeRawDoctorConfig(configPath: string, value: RawDoctorConfig, options?: {
11
+ rename?: typeof renameFile;
12
+ }): Promise<void>;
@@ -0,0 +1,61 @@
1
+ import { mkdir, readFile, rename as renameFile, rm, writeFile } from "node:fs/promises";
2
+ import { randomUUID } from "node:crypto";
3
+ import path from "node:path";
4
+ import { parseJsonText } from "./read-json-file.js";
5
+ export function resolveDoctorConfigPath(targetPath, explicitConfigPath) {
6
+ return explicitConfigPath
7
+ ? path.resolve(explicitConfigPath)
8
+ : path.join(path.resolve(targetPath), ".codex-doctor.json");
9
+ }
10
+ function isRawDoctorConfig(value) {
11
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12
+ }
13
+ export async function readRawDoctorConfig(targetPath, explicitConfigPath) {
14
+ const configPath = resolveDoctorConfigPath(targetPath, explicitConfigPath);
15
+ try {
16
+ const parsed = parseJsonText(await readFile(configPath, "utf8"));
17
+ if (!isRawDoctorConfig(parsed)) {
18
+ throw new Error("Doctor config root must be a JSON object");
19
+ }
20
+ return {
21
+ configPath,
22
+ exists: true,
23
+ value: parsed
24
+ };
25
+ }
26
+ catch (error) {
27
+ if (typeof error === "object" &&
28
+ error !== null &&
29
+ "code" in error &&
30
+ error.code === "ENOENT") {
31
+ return {
32
+ configPath,
33
+ exists: false,
34
+ value: {}
35
+ };
36
+ }
37
+ if (error instanceof SyntaxError) {
38
+ throw new Error(`Unable to parse Doctor config at ${configPath}: ${error.message}`, { cause: error });
39
+ }
40
+ throw error;
41
+ }
42
+ }
43
+ export async function writeRawDoctorConfig(configPath, value, options = {}) {
44
+ const rename = options.rename ?? renameFile;
45
+ const directoryPath = path.dirname(configPath);
46
+ const tempPath = path.join(directoryPath, `${path.basename(configPath)}.${process.pid}.${randomUUID()}.tmp`);
47
+ await mkdir(directoryPath, { recursive: true });
48
+ try {
49
+ await writeFile(tempPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
50
+ await rename(tempPath, configPath);
51
+ }
52
+ catch (error) {
53
+ try {
54
+ await rm(tempPath, { force: true });
55
+ }
56
+ catch {
57
+ // Best effort cleanup should not replace the original failure.
58
+ }
59
+ throw error;
60
+ }
61
+ }
@@ -1,6 +1,7 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { withFindingFingerprint } from "../reporting/finding-fingerprint.js";
4
+ import { classifySuppressionRecord } from "./suppression-record.js";
4
5
  const defaultConfig = {
5
6
  ignoreRules: [],
6
7
  failOnWarnings: false,
@@ -42,28 +43,28 @@ export function applyDoctorConfig(result, config, options = {}) {
42
43
  invalid: 0
43
44
  };
44
45
  for (const [index, suppression] of config.suppressions.entries()) {
45
- const validation = validateSuppression(suppression);
46
- if (!validation.valid) {
46
+ const classified = classifySuppressionRecord(suppression, now);
47
+ if (classified.status === "invalid") {
47
48
  summary.invalid += 1;
48
49
  governanceWarnings.push(buildGovernanceWarning(result.targetPath, "suppression.invalid", "A targeted suppression record is invalid and was not applied.", "Invalid suppression records can create false confidence because they do not match or expire as intended.", "Fix or remove the invalid suppression record in `.codex-doctor.json`.", {
49
50
  suppressionIndex: index,
50
- field: validation.field
51
+ field: classified.field
51
52
  }));
52
53
  continue;
53
54
  }
54
- if (isExpired(validation.expiresAt, now)) {
55
+ if (classified.status === "expired") {
55
56
  summary.expired += 1;
56
57
  governanceWarnings.push(buildGovernanceWarning(result.targetPath, "suppression.expired", "A targeted suppression has expired and was not applied.", "Expired risk acceptance must be reviewed again before the finding can be suppressed.", "Remove the expired suppression or replace it with a newly reviewed expiration date.", {
57
58
  suppressionIndex: index,
58
- fingerprint: validation.fingerprint,
59
- expiresAt: validation.expiresAt
59
+ fingerprint: classified.fingerprint,
60
+ expiresAt: classified.expiresAt
60
61
  }));
61
62
  continue;
62
63
  }
63
- if (!activeSuppressions.has(validation.fingerprint)) {
64
- activeSuppressions.set(validation.fingerprint, {
65
- reason: validation.reason,
66
- expiresAt: validation.expiresAt
64
+ if (!activeSuppressions.has(classified.fingerprint)) {
65
+ activeSuppressions.set(classified.fingerprint, {
66
+ reason: classified.reason,
67
+ expiresAt: classified.expiresAt
67
68
  });
68
69
  }
69
70
  }
@@ -105,47 +106,6 @@ export function applyDoctorConfig(result, config, options = {}) {
105
106
  : {})
106
107
  };
107
108
  }
108
- function isPlainObject(value) {
109
- return typeof value === "object" && value !== null && !Array.isArray(value);
110
- }
111
- function isRealDate(value) {
112
- const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
113
- if (!match) {
114
- return false;
115
- }
116
- const year = Number(match[1]);
117
- const month = Number(match[2]);
118
- const day = Number(match[3]);
119
- const date = new Date(Date.UTC(year, month - 1, day));
120
- return (date.getUTCFullYear() === year &&
121
- date.getUTCMonth() === month - 1 &&
122
- date.getUTCDate() === day);
123
- }
124
- function validateSuppression(value) {
125
- if (!isPlainObject(value)) {
126
- return { valid: false, field: "record" };
127
- }
128
- if (typeof value.fingerprint !== "string" ||
129
- !/^[a-f0-9]{64}$/.test(value.fingerprint)) {
130
- return { valid: false, field: "fingerprint" };
131
- }
132
- if (typeof value.reason !== "string" || value.reason.trim().length === 0) {
133
- return { valid: false, field: "reason" };
134
- }
135
- if (typeof value.expiresAt !== "string" || !isRealDate(value.expiresAt)) {
136
- return { valid: false, field: "expiresAt" };
137
- }
138
- return {
139
- valid: true,
140
- fingerprint: value.fingerprint,
141
- reason: value.reason.trim(),
142
- expiresAt: value.expiresAt
143
- };
144
- }
145
- function isExpired(expiresAt, now) {
146
- const expirationBoundary = Date.parse(`${expiresAt}T00:00:00.000Z`) + 86_400_000;
147
- return now.getTime() >= expirationBoundary;
148
- }
149
109
  function buildGovernanceWarning(targetPath, id, message, impact, suggestedFix, evidence) {
150
110
  return withFindingFingerprint({
151
111
  id,
@@ -16,6 +16,13 @@ function npmCommand(args) {
16
16
  }
17
17
  return { command: "npm", args };
18
18
  }
19
+ function npmPackEnvironment() {
20
+ const environment = Object.fromEntries(Object.entries(process.env).filter(([key]) => key.toLowerCase() !== "npm_config_dry_run"));
21
+ return {
22
+ ...environment,
23
+ npm_config_dry_run: "false"
24
+ };
25
+ }
19
26
  function isPathWithinRoot(rootPath, candidatePath) {
20
27
  const relativePath = path.relative(rootPath, candidatePath);
21
28
  return (relativePath === "" ||
@@ -96,6 +103,7 @@ async function packNpmPackage(packageSpec, destinationPath) {
96
103
  ]);
97
104
  const { stdout } = await execFileAsync(npmPackCommand.command, npmPackCommand.args, {
98
105
  cwd: destinationPath,
106
+ env: npmPackEnvironment(),
99
107
  maxBuffer: 10 * 1024 * 1024
100
108
  });
101
109
  const packEntries = JSON.parse(stdout);
@@ -55,6 +55,24 @@ const publicSchemaDefinitions = [
55
55
  outputKind: "doctor.git.hooks",
56
56
  required: ["schemaVersion", "kind", "rootPath", "hookPaths", "preExisting"]
57
57
  },
58
+ {
59
+ id: "doctor.suppress.add.json",
60
+ command: "codex-plugin-doctor suppress add <path> --fingerprint <sha256> --reason <text> --expires-at YYYY-MM-DD --json",
61
+ outputKind: "doctor.suppress.add",
62
+ required: ["schemaVersion", "kind", "command", "configPath", "index", "suppression"]
63
+ },
64
+ {
65
+ id: "doctor.suppress.list.json",
66
+ command: "codex-plugin-doctor suppress list <path> --json",
67
+ outputKind: "doctor.suppress.list",
68
+ required: ["schemaVersion", "kind", "command", "configPath", "suppressions"]
69
+ },
70
+ {
71
+ id: "doctor.suppress.remove.json",
72
+ command: "codex-plugin-doctor suppress remove <path> --index <n> --json",
73
+ outputKind: "doctor.suppress.remove",
74
+ required: ["schemaVersion", "kind", "command", "configPath", "index", "suppression"]
75
+ },
58
76
  {
59
77
  id: "doctor.history.json",
60
78
  command: "codex-plugin-doctor history <history.jsonl> --json",
@@ -0,0 +1,34 @@
1
+ import type { RawDoctorConfig } from "./doctor-config-store.js";
2
+ import { type SuppressionRecordField } from "./suppression-record.js";
3
+ export interface ManagedSuppressionRecord {
4
+ index: number;
5
+ status: "active" | "expired" | "invalid";
6
+ fingerprint?: string;
7
+ reason?: string;
8
+ expiresAt?: string;
9
+ invalidField?: SuppressionRecordField;
10
+ }
11
+ export interface SuppressionMutationResult {
12
+ config: RawDoctorConfig;
13
+ index: number;
14
+ suppression: unknown;
15
+ }
16
+ export type SuppressionManagementErrorCode = "suppression_non_array" | "suppression_invalid_record" | "suppression_duplicate_fingerprint" | "suppression_invalid_index" | "suppression_invalid_fingerprint" | "suppression_fingerprint_not_found" | "suppression_fingerprint_ambiguous";
17
+ type SuppressionManagementErrorDetails = {
18
+ code: SuppressionManagementErrorCode;
19
+ field?: SuppressionRecordField;
20
+ index?: number;
21
+ indexes?: number[];
22
+ };
23
+ export declare class SuppressionManagementError extends Error {
24
+ readonly code: SuppressionManagementErrorCode;
25
+ readonly field?: SuppressionRecordField;
26
+ readonly index?: number;
27
+ readonly indexes?: number[];
28
+ constructor(message: string, details: SuppressionManagementErrorDetails);
29
+ }
30
+ export declare function listSuppressions(config: RawDoctorConfig, now?: Date): ManagedSuppressionRecord[];
31
+ export declare function addSuppression(config: RawDoctorConfig, record: unknown): SuppressionMutationResult;
32
+ export declare function removeSuppressionByIndex(config: RawDoctorConfig, index: number): SuppressionMutationResult;
33
+ export declare function removeSuppressionByFingerprint(config: RawDoctorConfig, fingerprint: string): SuppressionMutationResult;
34
+ export {};