codex-plugin-doctor 1.44.0 → 1.46.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
@@ -287,10 +287,10 @@ codex-plugin-doctor check . --ascii
287
287
  codex-plugin-doctor check . --no-animations
288
288
  codex-plugin-doctor check . --runtime
289
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
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
290
+ 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
294
294
  codex-plugin-doctor history validation-history.jsonl
295
295
  codex-plugin-doctor history validation-history.jsonl --json
296
296
  codex-plugin-doctor history validation-history.jsonl --fail-on-regression
@@ -336,11 +336,11 @@ codex-plugin-doctor check . --json --runtime --verbose-runtime
336
336
 
337
337
  `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
338
 
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.
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.
344
344
 
345
345
  Optional local policy file:
346
346
 
@@ -406,9 +406,9 @@ jobs:
406
406
  runs-on: ubuntu-latest
407
407
  steps:
408
408
  - uses: actions/checkout@v5
409
- - uses: Esquetta/CodexPluginDoctor@v1.44.0
409
+ - uses: Esquetta/CodexPluginDoctor@v1.46.0
410
410
  with:
411
- version: "1.44.0"
411
+ version: "1.46.0"
412
412
  path: .
413
413
  runtime: "true"
414
414
  policy: codex-publish
@@ -0,0 +1,13 @@
1
+ export interface ConfigValidationFinding {
2
+ field: string;
3
+ message: string;
4
+ severity: "fail" | "warn";
5
+ }
6
+ export interface ConfigValidationReport {
7
+ configPath: string;
8
+ status: "pass" | "fail";
9
+ findings: ConfigValidationFinding[];
10
+ }
11
+ export declare function validateConfigFile(configPath: string): Promise<ConfigValidationReport>;
12
+ export declare function renderConfigValidation(report: ConfigValidationReport): string;
13
+ export declare function renderConfigValidationJson(report: ConfigValidationReport): string;
@@ -0,0 +1,133 @@
1
+ import { readFile, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ const knownFields = ["ignoreRules", "failOnWarnings", "suppressions"];
4
+ export async function validateConfigFile(configPath) {
5
+ const resolvedPath = path.resolve(configPath);
6
+ const findings = [];
7
+ try {
8
+ await stat(resolvedPath);
9
+ }
10
+ catch {
11
+ return {
12
+ configPath: resolvedPath,
13
+ status: "fail",
14
+ findings: [
15
+ { field: "file", message: `Config file not found: ${resolvedPath}`, severity: "fail" }
16
+ ]
17
+ };
18
+ }
19
+ let parsed;
20
+ try {
21
+ parsed = JSON.parse(await readFile(resolvedPath, "utf8"));
22
+ }
23
+ catch (error) {
24
+ return {
25
+ configPath: resolvedPath,
26
+ status: "fail",
27
+ findings: [
28
+ { field: "json", message: `Invalid JSON: ${error.message}`, severity: "fail" }
29
+ ]
30
+ };
31
+ }
32
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
33
+ return {
34
+ configPath: resolvedPath,
35
+ status: "fail",
36
+ findings: [
37
+ { field: "root", message: "Config must be a JSON object.", severity: "fail" }
38
+ ]
39
+ };
40
+ }
41
+ const config = parsed;
42
+ for (const key of Object.keys(config)) {
43
+ if (!knownFields.includes(key)) {
44
+ findings.push({
45
+ field: key,
46
+ message: `Unknown field: "${key}". Known fields: ${knownFields.join(", ")}`,
47
+ severity: "warn"
48
+ });
49
+ }
50
+ }
51
+ if ("ignoreRules" in config) {
52
+ const ignoreRules = config.ignoreRules;
53
+ if (!Array.isArray(ignoreRules)) {
54
+ findings.push({ field: "ignoreRules", message: "Must be an array of strings.", severity: "fail" });
55
+ }
56
+ else if (!ignoreRules.every((r) => typeof r === "string")) {
57
+ findings.push({ field: "ignoreRules", message: "All entries must be strings.", severity: "fail" });
58
+ }
59
+ }
60
+ if ("failOnWarnings" in config) {
61
+ if (typeof config.failOnWarnings !== "boolean") {
62
+ findings.push({ field: "failOnWarnings", message: "Must be a boolean.", severity: "fail" });
63
+ }
64
+ }
65
+ if ("suppressions" in config) {
66
+ const suppressions = config.suppressions;
67
+ if (!Array.isArray(suppressions)) {
68
+ findings.push({ field: "suppressions", message: "Must be an array.", severity: "fail" });
69
+ }
70
+ else {
71
+ for (let i = 0; i < suppressions.length; i += 1) {
72
+ const entry = suppressions[i];
73
+ if (!entry || typeof entry !== "object") {
74
+ findings.push({ field: `suppressions[${i}]`, message: "Must be an object.", severity: "fail" });
75
+ continue;
76
+ }
77
+ if (typeof entry.fingerprint !== "string" || !/^[a-f0-9]{64}$/.test(entry.fingerprint)) {
78
+ findings.push({
79
+ field: `suppressions[${i}].fingerprint`,
80
+ message: "Must be a 64-character lowercase hex string.",
81
+ severity: "fail"
82
+ });
83
+ }
84
+ if (typeof entry.reason !== "string" || entry.reason.trim().length === 0) {
85
+ findings.push({
86
+ field: `suppressions[${i}].reason`,
87
+ message: "Must be a non-empty string.",
88
+ severity: "fail"
89
+ });
90
+ }
91
+ if (typeof entry.expiresAt !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(entry.expiresAt)) {
92
+ findings.push({
93
+ field: `suppressions[${i}].expiresAt`,
94
+ message: "Must be a YYYY-MM-DD date string.",
95
+ severity: "fail"
96
+ });
97
+ }
98
+ }
99
+ }
100
+ }
101
+ const hasFail = findings.some((f) => f.severity === "fail");
102
+ return {
103
+ configPath: resolvedPath,
104
+ status: hasFail ? "fail" : findings.length === 0 ? "pass" : "warn",
105
+ findings
106
+ };
107
+ }
108
+ export function renderConfigValidation(report) {
109
+ const lines = [
110
+ "Codex Plugin Doctor Config Validation",
111
+ "=====================================",
112
+ `Path: ${report.configPath}`,
113
+ `Status: ${report.status.toUpperCase()}`,
114
+ ""
115
+ ];
116
+ if (report.findings.length === 0) {
117
+ lines.push("Config is valid.");
118
+ return lines.join("\n");
119
+ }
120
+ for (const finding of report.findings) {
121
+ const tag = finding.severity === "fail" ? "FAIL" : "WARN";
122
+ lines.push(`${tag} ${finding.field}: ${finding.message}`);
123
+ }
124
+ return lines.join("\n");
125
+ }
126
+ export function renderConfigValidationJson(report) {
127
+ return JSON.stringify({
128
+ schemaVersion: "1.0.0",
129
+ configPath: report.configPath,
130
+ status: report.status,
131
+ findings: report.findings
132
+ }, null, 2);
133
+ }
@@ -0,0 +1,35 @@
1
+ export interface ReleaseCheckReport {
2
+ targetPath: string;
3
+ status: "pass" | "fail";
4
+ checks: {
5
+ validation: {
6
+ status: string;
7
+ findings: number;
8
+ exitCode: number;
9
+ };
10
+ security: {
11
+ status: string;
12
+ score: number;
13
+ };
14
+ dependencies: {
15
+ status: string;
16
+ vulnerabilities: number;
17
+ };
18
+ compatibility: {
19
+ status: string;
20
+ score: number;
21
+ overallStatus: string;
22
+ };
23
+ trust: {
24
+ status: string;
25
+ score: number;
26
+ };
27
+ };
28
+ ready: boolean;
29
+ }
30
+ export declare function buildReleaseCheck(targetPath: string, options?: {
31
+ env?: Record<string, string | undefined>;
32
+ platform?: NodeJS.Platform;
33
+ }): Promise<ReleaseCheckReport>;
34
+ export declare function renderReleaseCheck(report: ReleaseCheckReport): string;
35
+ export declare function renderReleaseCheckJson(report: ReleaseCheckReport): string;
@@ -0,0 +1,103 @@
1
+ import path from "node:path";
2
+ import { validatePlugin } from "./validate-plugin.js";
3
+ import { buildSecurityAudit } from "../security/security-audit.js";
4
+ import { buildDepAudit } from "./dep-audit.js";
5
+ import { buildCompatibilityMatrix } from "../compatibility/compatibility-matrix.js";
6
+ import { buildTrustScore } from "../security/trust-score.js";
7
+ function matrixOverallStatus(matrix) {
8
+ const statuses = matrix.results.map((r) => r.status);
9
+ const passCount = statuses.filter((s) => s === "pass").length;
10
+ const total = statuses.length;
11
+ if (total === 0) {
12
+ return "pass";
13
+ }
14
+ if (passCount === total) {
15
+ return "pass";
16
+ }
17
+ const failCount = statuses.filter((s) => s === "fail").length;
18
+ return failCount > 0 ? "fail" : "warn";
19
+ }
20
+ function matrixScore(matrix) {
21
+ const scoreMap = { pass: 100, warn: 70, fail: 0, skipped: 0 };
22
+ const scores = matrix.results.map((r) => scoreMap[r.status] ?? 0);
23
+ if (scores.length === 0) {
24
+ return 0;
25
+ }
26
+ return Math.round(scores.reduce((a, b) => a + b, 0) / scores.length);
27
+ }
28
+ export async function buildReleaseCheck(targetPath, options = {}) {
29
+ const resolvedPath = path.resolve(targetPath);
30
+ const env = options.env ?? process.env;
31
+ const platform = options.platform ?? process.platform;
32
+ const validationResult = await validatePlugin(resolvedPath, { runtime: true });
33
+ const securityResult = await buildSecurityAudit(resolvedPath);
34
+ const depResult = await buildDepAudit(resolvedPath);
35
+ const compatResult = await buildCompatibilityMatrix(resolvedPath, { env, platform });
36
+ const trustResult = await buildTrustScore(resolvedPath);
37
+ const checks = {
38
+ validation: {
39
+ status: validationResult.status,
40
+ findings: validationResult.findings.length,
41
+ exitCode: validationResult.exitCode
42
+ },
43
+ security: {
44
+ status: securityResult.status,
45
+ score: securityResult.score
46
+ },
47
+ dependencies: {
48
+ status: depResult.status,
49
+ vulnerabilities: depResult.totalVulnerabilities
50
+ },
51
+ compatibility: {
52
+ status: matrixOverallStatus(compatResult),
53
+ score: matrixScore(compatResult),
54
+ overallStatus: matrixOverallStatus(compatResult)
55
+ },
56
+ trust: {
57
+ status: trustResult.status,
58
+ score: trustResult.score
59
+ }
60
+ };
61
+ const ready = checks.validation.status === "pass" &&
62
+ checks.security.status === "pass" &&
63
+ checks.dependencies.status !== "fail" &&
64
+ checks.compatibility.status !== "fail" &&
65
+ checks.trust.status === "pass";
66
+ return {
67
+ targetPath: resolvedPath,
68
+ status: ready ? "pass" : "fail",
69
+ checks,
70
+ ready
71
+ };
72
+ }
73
+ export function renderReleaseCheck(report) {
74
+ const icon = (pass) => pass ? "PASS" : pass === undefined ? "SKIP" : "FAIL";
75
+ const lines = [
76
+ "Codex Plugin Doctor Release Check",
77
+ "==================================",
78
+ `Path: ${report.targetPath}`,
79
+ `Ready: ${report.ready ? "YES" : "NO"}`,
80
+ "",
81
+ "Checks",
82
+ "------",
83
+ ` ${icon(report.checks.validation.status === "pass")} validation ${report.checks.validation.status} (${report.checks.validation.findings} findings)`,
84
+ ` ${icon(report.checks.security.status === "pass")} security ${report.checks.security.status} (score: ${report.checks.security.score})`,
85
+ ` ${icon(report.checks.dependencies.status === "pass")} dependencies ${report.checks.dependencies.status} (${report.checks.dependencies.vulnerabilities} vulns)`,
86
+ ` ${icon(report.checks.compatibility.status === "pass")} compatibility ${report.checks.compatibility.status} (score: ${report.checks.compatibility.score})`,
87
+ ` ${icon(report.checks.trust.status === "pass")} trust ${report.checks.trust.status} (score: ${report.checks.trust.score})`,
88
+ "",
89
+ report.ready
90
+ ? "All release checks passed. Ready to publish."
91
+ : "Some checks failed. Fix issues before publishing."
92
+ ];
93
+ return lines.join("\n");
94
+ }
95
+ export function renderReleaseCheckJson(report) {
96
+ return JSON.stringify({
97
+ schemaVersion: "1.0.0",
98
+ targetPath: report.targetPath,
99
+ status: report.status,
100
+ ready: report.ready,
101
+ checks: report.checks
102
+ }, null, 2);
103
+ }
@@ -0,0 +1,2 @@
1
+ export type CompletionShell = "bash" | "zsh" | "fish";
2
+ export declare function generateCompletion(shell: CompletionShell): string;
@@ -0,0 +1,97 @@
1
+ const topLevelCommands = [
2
+ "check",
3
+ "audit",
4
+ "mcp",
5
+ "security",
6
+ "compat",
7
+ "suppress",
8
+ "fix",
9
+ "history",
10
+ "watch",
11
+ "doctor",
12
+ "init",
13
+ "init-ci",
14
+ "init-git-hooks",
15
+ "self-test",
16
+ "list",
17
+ "explain",
18
+ "config"
19
+ ];
20
+ function bashCompletion() {
21
+ return [
22
+ "_codex_plugin_doctor() {",
23
+ " local cur prev",
24
+ " COMPREPLY=()",
25
+ " cur=\"${COMP_WORDS[COMP_CWORD]}\"",
26
+ " prev=\"${COMP_WORDS[COMP_CWORD-1]}\"",
27
+ "",
28
+ ` local commands="${topLevelCommands.join(" ")}"`,
29
+ "",
30
+ " case \"${prev}\" in",
31
+ " codex-plugin-doctor)",
32
+ " COMPREPLY=( $(compgen -W \"${commands}\" -- \"${cur}\") )",
33
+ " return 0",
34
+ " ;;",
35
+ " esac",
36
+ "",
37
+ " case \"${cur}\" in",
38
+ " --*)",
39
+ " local flags=\"--json --output --runtime --policy --help\"",
40
+ " COMPREPLY=( $(compgen -W \"${flags}\" -- \"${cur}\") )",
41
+ " return 0",
42
+ " ;;",
43
+ " *)",
44
+ " COMPREPLY=( $(compgen -W \"${commands}\" -- \"${cur}\") )",
45
+ " return 0",
46
+ " ;;",
47
+ " esac",
48
+ "}",
49
+ "",
50
+ "complete -F _codex_plugin_doctor codex-plugin-doctor",
51
+ ""
52
+ ].join("\n");
53
+ }
54
+ function zshCompletion() {
55
+ return [
56
+ "#compdef codex-plugin-doctor",
57
+ "",
58
+ "_codex_plugin_doctor() {",
59
+ " local context state line",
60
+ " typeset -A opt_args",
61
+ "",
62
+ ` local commands=(${topLevelCommands.join(" ")})`,
63
+ "",
64
+ " _arguments -C \\",
65
+ " '1:command:(${commands})' \\",
66
+ " '*--json[Output as JSON]' \\",
67
+ " '*--output[Write to file]:file:_files' \\",
68
+ " '*--runtime[Enable runtime probes]' \\",
69
+ " '*--policy[Apply policy]:policy:(codex-publish mcp-strict security)'",
70
+ "}",
71
+ "",
72
+ "_codex_plugin_doctor \"$@\"",
73
+ ""
74
+ ].join("\n");
75
+ }
76
+ function fishCompletion() {
77
+ return [
78
+ "complete -c codex-plugin-doctor -f",
79
+ `complete -c codex-plugin-doctor -n "not __fish_seen_subcommand_from ${topLevelCommands.join(" ")}" -a "${topLevelCommands.join(" ")}"`,
80
+ "complete -c codex-plugin-doctor -s h -l help -d 'Show help'",
81
+ "complete -c codex-plugin-doctor -l json -d 'Output as JSON'",
82
+ "complete -c codex-plugin-doctor -l output -d 'Write to file' -r",
83
+ "complete -c codex-plugin-doctor -l runtime -d 'Enable runtime probes'",
84
+ "complete -c codex-plugin-doctor -l policy -d 'Apply policy' -x -a 'codex-publish mcp-strict security'",
85
+ ""
86
+ ].join("\n");
87
+ }
88
+ export function generateCompletion(shell) {
89
+ switch (shell) {
90
+ case "bash":
91
+ return bashCompletion();
92
+ case "zsh":
93
+ return zshCompletion();
94
+ case "fish":
95
+ return fishCompletion();
96
+ }
97
+ }
package/dist/index.d.ts CHANGED
@@ -27,4 +27,7 @@ export { buildGenericMcpDoctor, renderGenericMcpDoctor, renderGenericMcpDoctorJs
27
27
  export { watchPlugin, type WatchPluginOptions, type WatchPluginResult } from "./core/watch-plugin.js";
28
28
  export { buildDepAudit, buildDepAuditRecommendations, renderDepAudit, renderDepAuditJson, renderDepAuditSarif, type DepAuditRecommendation, type DepAuditReport, type DepAuditVulnerability } from "./core/dep-audit.js";
29
29
  export { initGitHooks, removeGitHooks, type InitGitHooksResult, type RemoveGitHooksResult } from "./core/init-git-hooks.js";
30
+ export { generateCompletion, type CompletionShell } from "./core/shell-completion.js";
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";
30
33
  export declare function runCheck(targetPath: string, options?: CheckOptions): Promise<CheckResult>;
package/dist/index.js CHANGED
@@ -27,6 +27,9 @@ export { buildGenericMcpDoctor, renderGenericMcpDoctor, renderGenericMcpDoctorJs
27
27
  export { watchPlugin } from "./core/watch-plugin.js";
28
28
  export { buildDepAudit, buildDepAuditRecommendations, renderDepAudit, renderDepAuditJson, renderDepAuditSarif } from "./core/dep-audit.js";
29
29
  export { initGitHooks, removeGitHooks } from "./core/init-git-hooks.js";
30
+ export { generateCompletion } from "./core/shell-completion.js";
31
+ export { validateConfigFile, renderConfigValidation, renderConfigValidationJson } from "./core/config-validate.js";
32
+ export { buildReleaseCheck, renderReleaseCheck, renderReleaseCheckJson } from "./core/release-check.js";
30
33
  export async function runCheck(targetPath, options = {}) {
31
34
  return validatePlugin(targetPath, options);
32
35
  }
package/dist/run-cli.js CHANGED
@@ -34,6 +34,9 @@ import { initCiWorkflow } from "./core/init-ci.js";
34
34
  import { watchPlugin } from "./core/watch-plugin.js";
35
35
  import { buildDepAudit, renderDepAudit, renderDepAuditJson, renderDepAuditSarif } from "./core/dep-audit.js";
36
36
  import { initGitHooks, removeGitHooks } from "./core/init-git-hooks.js";
37
+ import { generateCompletion } from "./core/shell-completion.js";
38
+ import { renderConfigValidation, renderConfigValidationJson, validateConfigFile } from "./core/config-validate.js";
39
+ import { buildReleaseCheck, renderReleaseCheck, renderReleaseCheckJson } from "./core/release-check.js";
37
40
  import { initPluginPackage, initPluginTemplates, isInitPluginTemplate } from "./core/init-plugin.js";
38
41
  import { runCheck } from "./index.js";
39
42
  import { buildGenericMcpDoctor, renderGenericMcpDoctor, renderGenericMcpDoctorJson } from "./mcp/generic-mcp-doctor.js";
@@ -79,7 +82,7 @@ const defaultIo = {
79
82
  }
80
83
  };
81
84
  function printUsage(io) {
82
- 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 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");
83
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]");
84
87
  io.writeStderr("Baseline gating: codex-plugin-doctor baseline create <path> --output <path> [--runtime]\n codex-plugin-doctor check <path> --baseline <path>");
85
88
  }
@@ -2220,6 +2223,54 @@ export async function runCli(args, io = defaultIo, options = {}) {
2220
2223
  io.writeStdout(report);
2221
2224
  return matrixExitCode(matrix);
2222
2225
  }
2226
+ if (command === "completion") {
2227
+ const shell = maybePath;
2228
+ if (!shell || !["bash", "zsh", "fish"].includes(shell)) {
2229
+ io.writeStderr("Usage: codex-plugin-doctor completion bash|zsh|fish");
2230
+ return 2;
2231
+ }
2232
+ io.writeStdout(generateCompletion(shell));
2233
+ return 0;
2234
+ }
2235
+ if (command === "config") {
2236
+ const subCommand = maybePath;
2237
+ if (subCommand !== "validate") {
2238
+ io.writeStderr("Usage: codex-plugin-doctor config validate <path> [--json]");
2239
+ return 2;
2240
+ }
2241
+ const configPath = remainingArgs[0] && !remainingArgs[0].startsWith("--") ? remainingArgs[0] : ".codex-doctor.json";
2242
+ const configFlags = remainingArgs[0] && remainingArgs[0].startsWith("--")
2243
+ ? remainingArgs
2244
+ : remainingArgs.slice(1);
2245
+ const jsonOutput = configFlags.includes("--json");
2246
+ const report = await validateConfigFile(configPath);
2247
+ const output = jsonOutput
2248
+ ? renderConfigValidationJson(report)
2249
+ : renderConfigValidation(report);
2250
+ io.writeStdout(output);
2251
+ return report.status === "fail" ? 1 : 0;
2252
+ }
2253
+ if (command === "release") {
2254
+ const subCommand = maybePath;
2255
+ if (subCommand !== "check") {
2256
+ io.writeStderr("Usage: codex-plugin-doctor release check <path> [--json]");
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 report = await buildReleaseCheck(targetPath, {
2265
+ env: terminalContext.env,
2266
+ platform: terminalContext.platform
2267
+ });
2268
+ const output = jsonOutput
2269
+ ? renderReleaseCheckJson(report)
2270
+ : renderReleaseCheck(report);
2271
+ io.writeStdout(output);
2272
+ return report.status === "fail" ? 1 : 0;
2273
+ }
2223
2274
  if (command === "suppress") {
2224
2275
  const parsedSuppressCommand = parseSuppressCommand([maybePath ?? "", ...remainingArgs]);
2225
2276
  if ("message" in parsedSuppressCommand) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-plugin-doctor",
3
- "version": "1.44.0",
3
+ "version": "1.46.0",
4
4
  "description": "CLI-first validator for Codex plugins, skills, and MCP package surfaces with runtime MCP protocol validation.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",