codex-plugin-doctor 0.8.0 → 0.9.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
@@ -166,6 +166,7 @@ codex-plugin-doctor self-test
166
166
  codex-plugin-doctor doctor
167
167
  codex-plugin-doctor init my-plugin
168
168
  codex-plugin-doctor compat .
169
+ codex-plugin-doctor compat . --all --scorecard
169
170
  codex-plugin-doctor compat . --client codex
170
171
  codex-plugin-doctor compat . --client generic-mcp
171
172
  codex-plugin-doctor compat . --client claude-desktop
@@ -184,6 +185,7 @@ codex-plugin-doctor check . --profile ci
184
185
  codex-plugin-doctor check . --profile strict
185
186
  codex-plugin-doctor check . --profile publish
186
187
  codex-plugin-doctor check . --json
188
+ codex-plugin-doctor check . --explain
187
189
  codex-plugin-doctor check . --json --output report.json
188
190
  codex-plugin-doctor check . --markdown --output report.md
189
191
  codex-plugin-doctor check . --badge-json --output doctor-badge.json
@@ -198,13 +200,14 @@ codex-plugin-doctor history validation-history.jsonl
198
200
  codex-plugin-doctor history validation-history.jsonl --json
199
201
  codex-plugin-doctor history validation-history.jsonl --fail-on-regression
200
202
  codex-plugin-doctor fix . --dry-run
203
+ codex-plugin-doctor fix . --interactive --backup
201
204
  codex-plugin-doctor fix . --apply --backup
202
205
  codex-plugin-doctor check . --json --runtime --verbose-runtime
203
206
  ```
204
207
 
205
208
  `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`.
206
209
 
207
- `doctor` checks the local environment, including package version, platform, Node version, npm global prefix, Codex home, and Codex plugin cache visibility.
210
+ `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.
208
211
 
209
212
  `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.
210
213
 
@@ -212,15 +215,17 @@ codex-plugin-doctor check . --json --runtime --verbose-runtime
212
215
 
213
216
  `compat --client cline` checks whether the MCP package can be added to Cline. It uses `CLINE_DIR/data/settings/cline_mcp_settings.json` when `CLINE_DIR` is set, otherwise `~/.cline/data/settings/cline_mcp_settings.json`. Add `--install-preview` to print the JSON snippet that should be merged into `cline_mcp_settings.json`.
214
217
 
215
- `compat --scorecard` turns the compatibility matrix into a compact score summary. `PASS` maps to `100`, `WARN` maps to `70`, and `FAIL` or `SKIPPED` maps to `0`.
218
+ `compat --all` makes the all-client matrix explicit when you want Codex, Generic MCP, Claude Desktop, Cursor, Cline, and Windsurf in one run. `compat --scorecard` turns the compatibility matrix into a compact score summary. `PASS` maps to `100`, `WARN` maps to `70`, and `FAIL` or `SKIPPED` maps to `0`.
216
219
 
217
220
  `check --profile ci|strict|publish` applies named validation policies. `ci` keeps default behavior, `strict` fails on warnings, and `publish` fails on warnings while enabling runtime probing by default.
218
221
 
222
+ `check --explain` adds inline rule catalog context to text reports, including why a finding matters, a more detailed fix path, and a compact example.
223
+
219
224
  `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`.
220
225
 
221
226
  `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.
222
227
 
223
- `fix --dry-run` renders safe automatic fix plans without changing files. `fix --apply --backup` applies only supported safe fixes, such as manifest defaults and missing skills directories, after creating backups.
228
+ `fix --dry-run` renders safe automatic fix plans without changing files. `fix --interactive --backup` shows the same plan, then applies only after you type `yes`. `fix --apply --backup` applies supported safe fixes, such as manifest defaults and missing skills directories, after creating backups.
224
229
 
225
230
  Optional local policy file:
226
231
 
@@ -255,9 +260,9 @@ jobs:
255
260
  runs-on: ubuntu-latest
256
261
  steps:
257
262
  - uses: actions/checkout@v4
258
- - uses: Esquetta/CodexPluginDoctor@v0.8.0
263
+ - uses: Esquetta/CodexPluginDoctor@v0.9.0
259
264
  with:
260
- version: "0.8.0"
265
+ version: "0.9.0"
261
266
  path: .
262
267
  runtime: "false"
263
268
  ```
@@ -300,11 +305,12 @@ Recent validation waves covered:
300
305
 
301
306
  Release preparation is reproducible from the repository:
302
307
 
303
- ```bash
304
- npm run prepare-release
305
- ```
306
-
307
- This runs tests, builds the TypeScript output, and performs `npm pack --dry-run`.
308
+ ```bash
309
+ npm run prepare-release
310
+ npm run release-check
311
+ ```
312
+
313
+ `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, and pack dry-run.
308
314
 
309
315
  Related docs:
310
316
 
@@ -32,7 +32,15 @@ export async function renderEnvironmentDoctor(terminalContext) {
32
32
  `Node: ${report.node}`,
33
33
  `npm global prefix: ${report.npmGlobalPrefix}`,
34
34
  `Codex home: ${report.codexHome.status.toUpperCase()}${report.codexHome.path ? ` (${report.codexHome.path})` : ""}`,
35
- `Codex plugin cache: ${report.codexPluginCache.status.toUpperCase()}${report.codexPluginCache.path ? ` (${report.codexPluginCache.path})` : ""}`
35
+ `Codex plugin cache: ${report.codexPluginCache.status.toUpperCase()}${report.codexPluginCache.path ? ` (${report.codexPluginCache.path})` : ""}`,
36
+ "",
37
+ "Recommended next commands",
38
+ "-------------------------",
39
+ "codex-plugin-doctor self-test",
40
+ "codex-plugin-doctor list --installed",
41
+ "codex-plugin-doctor check . --runtime --explain",
42
+ "codex-plugin-doctor compat . --all --scorecard",
43
+ "codex-plugin-doctor init-ci ."
36
44
  ].join("\n");
37
45
  }
38
46
  export async function buildEnvironmentDoctorReport(terminalContext) {
@@ -25,7 +25,7 @@ export interface FixPlanJsonReport {
25
25
  }>;
26
26
  }
27
27
  export declare function buildFixPlan(targetPath: string): Promise<FixPlan>;
28
- export declare function renderFixPlan(plan: FixPlan, mode: "dry-run"): string;
28
+ export declare function renderFixPlan(plan: FixPlan, mode: "dry-run" | "interactive"): string;
29
29
  export declare function applyFixPlan(targetPath: string): Promise<ApplyFixPlanResult>;
30
30
  export declare function renderApplyFixResult(result: ApplyFixPlanResult): string;
31
31
  export declare function renderFixPlanJsonReport(plan: FixPlan, options: {
@@ -1,4 +1,5 @@
1
1
  import type { CheckResult } from "../domain/types.js";
2
2
  export declare function renderTextReport(result: CheckResult, options?: {
3
3
  ascii?: boolean;
4
+ explain?: boolean;
4
5
  }): string;
@@ -1,3 +1,4 @@
1
+ import { findRuleDefinition } from "../rules/rule-catalog.js";
1
2
  function getCounts(result) {
2
3
  const failCount = result.findings.filter((finding) => finding.severity === "fail").length;
3
4
  const warnCount = result.findings.filter((finding) => finding.severity === "warn").length;
@@ -22,6 +23,7 @@ function getGlyphs(ascii) {
22
23
  }
23
24
  export function renderTextReport(result, options = {}) {
24
25
  const ascii = options.ascii ?? false;
26
+ const explain = options.explain ?? false;
25
27
  const glyphs = getGlyphs(ascii);
26
28
  const { failCount, warnCount, totalCount } = getCounts(result);
27
29
  const lines = [
@@ -58,6 +60,14 @@ export function renderTextReport(result, options = {}) {
58
60
  lines.push(` Message: ${finding.message}`);
59
61
  lines.push(` Impact: ${finding.impact}`);
60
62
  lines.push(` Suggested fix: ${finding.suggestedFix}`);
63
+ if (explain) {
64
+ const rule = findRuleDefinition(finding.id);
65
+ if (rule) {
66
+ lines.push(` Why: ${rule.why}`);
67
+ lines.push(` Fix detail: ${rule.fix}`);
68
+ lines.push(` Example: ${rule.example}`);
69
+ }
70
+ }
61
71
  }
62
72
  };
63
73
  appendSection("Failures", failures, glyphs.fail);
package/dist/run-cli.d.ts CHANGED
@@ -2,6 +2,7 @@ import { runCheck } from "./index.js";
2
2
  export interface CliIo {
3
3
  writeStdout(message: string): void;
4
4
  writeStderr(message: string): void;
5
+ readStdin?(prompt: string): Promise<string>;
5
6
  }
6
7
  export interface CliTerminalContext {
7
8
  stdoutIsTTY: boolean;
package/dist/run-cli.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { createInterface } from "node:readline/promises";
3
4
  import { fileURLToPath } from "node:url";
4
5
  import { discoverInstalledPlugins, filterInstalledPlugins } from "./core/discover-installed-plugins.js";
5
6
  import { appendValidationHistoryEntry, readValidationHistory, summarizeValidationHistory } from "./core/validation-history.js";
@@ -36,10 +37,22 @@ const defaultIo = {
36
37
  },
37
38
  writeStderr(message) {
38
39
  process.stderr.write(`${message}\n`);
40
+ },
41
+ async readStdin(prompt) {
42
+ const readline = createInterface({
43
+ input: process.stdin,
44
+ output: process.stdout
45
+ });
46
+ try {
47
+ return await readline.question(prompt);
48
+ }
49
+ finally {
50
+ readline.close();
51
+ }
39
52
  }
40
53
  };
41
54
  function printUsage(io) {
42
- io.writeStderr("Usage: codex-plugin-doctor check <path|--installed> [filter] [--json|--markdown|--badge-json|--badge-markdown] [--output <path>] [--history <path>] [--runtime] [--verbose-runtime] [--no-animations] [--ascii]\n codex-plugin-doctor compat <path> [--client <client>] [--json] [--scorecard] [--output <path>] [--install-preview|--apply --backup]\n codex-plugin-doctor fix <path> (--dry-run|--apply --backup)\n codex-plugin-doctor history <history.jsonl> [--json] [--fail-on-regression]\n codex-plugin-doctor doctor\n codex-plugin-doctor init-ci [path]\n codex-plugin-doctor self-test\n codex-plugin-doctor list --installed\n codex-plugin-doctor explain <finding-id>\n codex-plugin-doctor --version");
55
+ io.writeStderr("Usage: codex-plugin-doctor check <path|--installed> [filter] [--json|--markdown|--badge-json|--badge-markdown] [--output <path>] [--history <path>] [--runtime] [--verbose-runtime] [--explain] [--no-animations] [--ascii]\n codex-plugin-doctor compat <path> [--all|--client <client>] [--json] [--scorecard] [--output <path>] [--install-preview|--apply --backup]\n codex-plugin-doctor fix <path> (--dry-run|--interactive --backup|--apply --backup)\n codex-plugin-doctor history <history.jsonl> [--json] [--fail-on-regression]\n codex-plugin-doctor doctor\n codex-plugin-doctor init-ci [path]\n codex-plugin-doctor self-test\n codex-plugin-doctor list --installed\n codex-plugin-doctor explain <finding-id>\n codex-plugin-doctor --version\n\nFirst run:\n codex-plugin-doctor doctor\n codex-plugin-doctor self-test\n codex-plugin-doctor init my-plugin\n codex-plugin-doctor check . --runtime --explain");
43
56
  }
44
57
  function renderInstalledPlugins(plugins) {
45
58
  const lines = [
@@ -212,19 +225,24 @@ export async function runCli(args, io = defaultIo, options = {}) {
212
225
  }
213
226
  if (command === "fix") {
214
227
  if (!maybePath || maybePath.startsWith("--")) {
215
- io.writeStderr("Missing target path. Usage: codex-plugin-doctor fix <path> (--dry-run|--apply --backup)");
228
+ io.writeStderr("Missing target path. Usage: codex-plugin-doctor fix <path> (--dry-run|--interactive --backup|--apply --backup)");
216
229
  return 2;
217
230
  }
218
231
  const dryRun = remainingArgs.includes("--dry-run");
219
232
  const apply = remainingArgs.includes("--apply");
233
+ const interactive = remainingArgs.includes("--interactive");
220
234
  const backup = remainingArgs.includes("--backup");
221
235
  const jsonOutput = remainingArgs.includes("--json");
222
- if (apply && !backup) {
223
- io.writeStderr("Fix apply requires --backup.");
236
+ if ((apply || interactive) && !backup) {
237
+ io.writeStderr("Fix mode requires --backup.");
238
+ return 2;
239
+ }
240
+ if ([dryRun, apply, interactive].filter(Boolean).length !== 1) {
241
+ io.writeStderr("Choose exactly one fix mode: --dry-run, --interactive --backup, or --apply --backup.");
224
242
  return 2;
225
243
  }
226
- if (dryRun === apply) {
227
- io.writeStderr("Choose exactly one fix mode: --dry-run or --apply --backup.");
244
+ if (interactive && jsonOutput) {
245
+ io.writeStderr("Interactive fix mode does not support --json.");
228
246
  return 2;
229
247
  }
230
248
  if (dryRun) {
@@ -234,6 +252,21 @@ export async function runCli(args, io = defaultIo, options = {}) {
234
252
  : renderFixPlan(plan, "dry-run"));
235
253
  return 0;
236
254
  }
255
+ if (interactive) {
256
+ const plan = await buildFixPlan(maybePath);
257
+ io.writeStdout([
258
+ renderFixPlan(plan, "interactive"),
259
+ "",
260
+ "Type yes to apply these fixes with a backup. Anything else cancels."
261
+ ].join("\n"));
262
+ const answer = (await io.readStdin?.("Apply fixes? ") ?? "").trim().toLowerCase();
263
+ if (answer !== "yes") {
264
+ io.writeStdout("Fix cancelled. No files changed.");
265
+ return 0;
266
+ }
267
+ io.writeStdout(renderApplyFixResult(await applyFixPlan(maybePath)));
268
+ return 0;
269
+ }
237
270
  const result = await applyFixPlan(maybePath);
238
271
  io.writeStdout(jsonOutput
239
272
  ? renderFixPlanJsonReport(result.plan, {
@@ -254,6 +287,7 @@ export async function runCli(args, io = defaultIo, options = {}) {
254
287
  const installPreview = compatFlags.includes("--install-preview");
255
288
  const applyInstall = compatFlags.includes("--apply");
256
289
  const backupInstall = compatFlags.includes("--backup");
290
+ const allClients = compatFlags.includes("--all");
257
291
  const clientIndex = compatFlags.indexOf("--client");
258
292
  const clientFilter = clientIndex === -1 ? null : compatFlags[clientIndex + 1];
259
293
  const outputIndex = compatFlags.indexOf("--output");
@@ -262,6 +296,10 @@ export async function runCli(args, io = defaultIo, options = {}) {
262
296
  io.writeStderr("Missing client after --client.");
263
297
  return 2;
264
298
  }
299
+ if (allClients && clientFilter) {
300
+ io.writeStderr("Use either --all or --client, not both.");
301
+ return 2;
302
+ }
265
303
  if (outputIndex !== -1 && (!outputPath || outputPath.startsWith("--"))) {
266
304
  io.writeStderr("Missing path after --output.");
267
305
  return 2;
@@ -378,6 +416,7 @@ export async function runCli(args, io = defaultIo, options = {}) {
378
416
  const sarifOutput = normalizedFlags.includes("--sarif");
379
417
  const runtimeProbeEnabled = normalizedFlags.includes("--runtime");
380
418
  const verboseRuntime = normalizedFlags.includes("--verbose-runtime");
419
+ const explainFindings = normalizedFlags.includes("--explain");
381
420
  const noAnimations = normalizedFlags.includes("--no-animations");
382
421
  const asciiMode = normalizedFlags.includes("--ascii");
383
422
  const installedSummary = normalizedFlags.includes("--all-summary");
@@ -460,7 +499,10 @@ export async function runCli(args, io = defaultIo, options = {}) {
460
499
  ? buildMarkdownReport(item.result, { runtimeProbeEnabled: effectiveRuntimeProbeEnabled })
461
500
  : jsonOutput
462
501
  ? renderJsonReport(item.result, { runtimeProbeEnabled: effectiveRuntimeProbeEnabled })
463
- : renderTextReport(item.result, { ascii: outputPolicy.style === "ascii" }))
502
+ : renderTextReport(item.result, {
503
+ ascii: outputPolicy.style === "ascii",
504
+ explain: explainFindings
505
+ }))
464
506
  .join("\n\n");
465
507
  if (outputPath) {
466
508
  await writeFile(outputPath, report, "utf8");
@@ -497,7 +539,10 @@ export async function runCli(args, io = defaultIo, options = {}) {
497
539
  ? renderBadgeJson(result)
498
540
  : badgeMarkdownOutput
499
541
  ? renderBadgeMarkdown(result)
500
- : renderTextReport(result, { ascii: outputPolicy.style === "ascii" });
542
+ : renderTextReport(result, {
543
+ ascii: outputPolicy.style === "ascii",
544
+ explain: explainFindings
545
+ });
501
546
  if (outputPath) {
502
547
  await writeFile(outputPath, report, "utf8");
503
548
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-plugin-doctor",
3
- "version": "0.8.0",
3
+ "version": "0.9.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",
@@ -26,6 +26,7 @@
26
26
  "generate-validation-artifacts": "node scripts/generate-validation-artifacts.mjs",
27
27
  "prepare-rc": "tsx scripts/prepare-release-candidate.ts",
28
28
  "prepare-release": "npm test && npm run build && npm pack --dry-run",
29
+ "release-check": "node scripts/release-check.mjs",
29
30
  "prepublishOnly": "npm test && npm run build",
30
31
  "test": "vitest run",
31
32
  "test:watch": "vitest"