codex-plugin-doctor 0.5.0 → 0.6.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
|
@@ -65,8 +65,9 @@ Output formats:
|
|
|
65
65
|
- human text output
|
|
66
66
|
- JSON reports
|
|
67
67
|
- Markdown reports
|
|
68
|
-
- Shields-compatible badge JSON and static badge Markdown
|
|
69
|
-
-
|
|
68
|
+
- Shields-compatible badge JSON and static badge Markdown
|
|
69
|
+
- validation history JSONL and trend summaries
|
|
70
|
+
- `--output` file writing
|
|
70
71
|
- CI summary and artifact generation
|
|
71
72
|
|
|
72
73
|
## Quick Start
|
|
@@ -184,10 +185,14 @@ codex-plugin-doctor check . --badge-markdown
|
|
|
184
185
|
codex-plugin-doctor check . --sarif --output results.sarif
|
|
185
186
|
codex-plugin-doctor check . --ascii
|
|
186
187
|
codex-plugin-doctor check . --no-animations
|
|
187
|
-
codex-plugin-doctor check . --runtime
|
|
188
|
-
codex-plugin-doctor check . --config .codex-doctor.json
|
|
189
|
-
codex-plugin-doctor check . --
|
|
190
|
-
|
|
188
|
+
codex-plugin-doctor check . --runtime
|
|
189
|
+
codex-plugin-doctor check . --config .codex-doctor.json
|
|
190
|
+
codex-plugin-doctor check . --history validation-history.jsonl
|
|
191
|
+
codex-plugin-doctor history validation-history.jsonl
|
|
192
|
+
codex-plugin-doctor history validation-history.jsonl --json
|
|
193
|
+
codex-plugin-doctor history validation-history.jsonl --fail-on-regression
|
|
194
|
+
codex-plugin-doctor check . --json --runtime --verbose-runtime
|
|
195
|
+
```
|
|
191
196
|
|
|
192
197
|
`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`.
|
|
193
198
|
|
|
@@ -197,7 +202,9 @@ codex-plugin-doctor check . --json --runtime --verbose-runtime
|
|
|
197
202
|
|
|
198
203
|
`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`.
|
|
199
204
|
|
|
200
|
-
`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`.
|
|
205
|
+
`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`.
|
|
206
|
+
|
|
207
|
+
`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.
|
|
201
208
|
|
|
202
209
|
Optional local policy file:
|
|
203
210
|
|
|
@@ -232,11 +239,11 @@ jobs:
|
|
|
232
239
|
runs-on: ubuntu-latest
|
|
233
240
|
steps:
|
|
234
241
|
- uses: actions/checkout@v4
|
|
235
|
-
- uses: Esquetta/CodexPluginDoctor@v0.
|
|
236
|
-
with:
|
|
237
|
-
version: "0.
|
|
238
|
-
path: .
|
|
239
|
-
runtime: "false"
|
|
242
|
+
- uses: Esquetta/CodexPluginDoctor@v0.6.0
|
|
243
|
+
with:
|
|
244
|
+
version: "0.6.0"
|
|
245
|
+
path: .
|
|
246
|
+
runtime: "false"
|
|
240
247
|
```
|
|
241
248
|
|
|
242
249
|
For runtime probing, SARIF output, installed plugin cache checks, and pinned release examples, see [GitHub Action Usage](./docs/engineering/github-action-usage.md).
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { CheckResult } from "../domain/types.js";
|
|
2
|
+
export interface ValidationHistoryEntry {
|
|
3
|
+
schemaVersion: "1.0.0";
|
|
4
|
+
generatedAt: string;
|
|
5
|
+
targetPath: string;
|
|
6
|
+
status: CheckResult["status"];
|
|
7
|
+
runtimeProbeEnabled: boolean;
|
|
8
|
+
findingCounts: {
|
|
9
|
+
fail: number;
|
|
10
|
+
warn: number;
|
|
11
|
+
total: number;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export interface ValidationHistorySummary {
|
|
15
|
+
schemaVersion: "1.0.0";
|
|
16
|
+
runs: number;
|
|
17
|
+
latest: ValidationHistoryEntry;
|
|
18
|
+
previous: ValidationHistoryEntry | null;
|
|
19
|
+
delta: {
|
|
20
|
+
fail: number;
|
|
21
|
+
warn: number;
|
|
22
|
+
total: number;
|
|
23
|
+
};
|
|
24
|
+
regression: boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare function buildValidationHistoryEntry(result: CheckResult, options: {
|
|
27
|
+
runtimeProbeEnabled: boolean;
|
|
28
|
+
}): ValidationHistoryEntry;
|
|
29
|
+
export declare function appendValidationHistoryEntry(historyPath: string, result: CheckResult, options: {
|
|
30
|
+
runtimeProbeEnabled: boolean;
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
export declare function readValidationHistory(historyPath: string): Promise<ValidationHistoryEntry[]>;
|
|
33
|
+
export declare function summarizeValidationHistory(entries: ValidationHistoryEntry[]): ValidationHistorySummary;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const statusRank = {
|
|
4
|
+
pass: 0,
|
|
5
|
+
warn: 1,
|
|
6
|
+
fail: 2
|
|
7
|
+
};
|
|
8
|
+
function countFindings(result) {
|
|
9
|
+
return {
|
|
10
|
+
fail: result.findings.filter((finding) => finding.severity === "fail").length,
|
|
11
|
+
warn: result.findings.filter((finding) => finding.severity === "warn").length,
|
|
12
|
+
total: result.findings.length
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function buildValidationHistoryEntry(result, options) {
|
|
16
|
+
return {
|
|
17
|
+
schemaVersion: "1.0.0",
|
|
18
|
+
generatedAt: new Date().toISOString(),
|
|
19
|
+
targetPath: result.targetPath,
|
|
20
|
+
status: result.status,
|
|
21
|
+
runtimeProbeEnabled: options.runtimeProbeEnabled,
|
|
22
|
+
findingCounts: countFindings(result)
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export async function appendValidationHistoryEntry(historyPath, result, options) {
|
|
26
|
+
const absoluteHistoryPath = path.resolve(historyPath);
|
|
27
|
+
await mkdir(path.dirname(absoluteHistoryPath), { recursive: true });
|
|
28
|
+
await writeFile(absoluteHistoryPath, `${JSON.stringify(buildValidationHistoryEntry(result, options))}\n`, { encoding: "utf8", flag: "a" });
|
|
29
|
+
}
|
|
30
|
+
export async function readValidationHistory(historyPath) {
|
|
31
|
+
const content = await readFile(path.resolve(historyPath), "utf8");
|
|
32
|
+
return content
|
|
33
|
+
.split(/\r?\n/)
|
|
34
|
+
.map((line) => line.trim())
|
|
35
|
+
.filter(Boolean)
|
|
36
|
+
.map((line) => JSON.parse(line));
|
|
37
|
+
}
|
|
38
|
+
export function summarizeValidationHistory(entries) {
|
|
39
|
+
if (entries.length === 0) {
|
|
40
|
+
throw new Error("No validation history entries found.");
|
|
41
|
+
}
|
|
42
|
+
const latest = entries[entries.length - 1];
|
|
43
|
+
const previous = entries.length > 1 ? entries[entries.length - 2] : null;
|
|
44
|
+
const delta = previous
|
|
45
|
+
? {
|
|
46
|
+
fail: latest.findingCounts.fail - previous.findingCounts.fail,
|
|
47
|
+
warn: latest.findingCounts.warn - previous.findingCounts.warn,
|
|
48
|
+
total: latest.findingCounts.total - previous.findingCounts.total
|
|
49
|
+
}
|
|
50
|
+
: { fail: 0, warn: 0, total: 0 };
|
|
51
|
+
const regression = previous
|
|
52
|
+
? statusRank[latest.status] > statusRank[previous.status]
|
|
53
|
+
|| delta.fail > 0
|
|
54
|
+
|| delta.warn > 0
|
|
55
|
+
: false;
|
|
56
|
+
return {
|
|
57
|
+
schemaVersion: "1.0.0",
|
|
58
|
+
runs: entries.length,
|
|
59
|
+
latest,
|
|
60
|
+
previous,
|
|
61
|
+
delta,
|
|
62
|
+
regression
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { summarizeValidationHistory } from "../core/validation-history.js";
|
|
2
|
+
function formatDelta(value) {
|
|
3
|
+
return value > 0 ? `+${value}` : String(value);
|
|
4
|
+
}
|
|
5
|
+
export function renderHistorySummary(entries) {
|
|
6
|
+
const summary = summarizeValidationHistory(entries);
|
|
7
|
+
const { latest, previous } = summary;
|
|
8
|
+
const lines = [
|
|
9
|
+
"Validation History",
|
|
10
|
+
"==================",
|
|
11
|
+
`Runs: ${summary.runs}`,
|
|
12
|
+
`Latest: ${latest.status.toUpperCase()}`,
|
|
13
|
+
`Target: ${latest.targetPath}`,
|
|
14
|
+
`Generated: ${latest.generatedAt}`,
|
|
15
|
+
`Fail findings: ${latest.findingCounts.fail}`,
|
|
16
|
+
`Warn findings: ${latest.findingCounts.warn}`,
|
|
17
|
+
`Regression: ${summary.regression ? "YES" : "NO"}`
|
|
18
|
+
];
|
|
19
|
+
if (previous) {
|
|
20
|
+
lines.push("", `Previous: ${previous.status.toUpperCase()}`, `Fail findings: ${formatDelta(summary.delta.fail)}`, `Warn findings: ${formatDelta(summary.delta.warn)}`);
|
|
21
|
+
}
|
|
22
|
+
return lines.join("\n");
|
|
23
|
+
}
|
package/dist/run-cli.js
CHANGED
|
@@ -2,6 +2,7 @@ import { writeFile } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { discoverInstalledPlugins, filterInstalledPlugins } from "./core/discover-installed-plugins.js";
|
|
5
|
+
import { appendValidationHistoryEntry, readValidationHistory, summarizeValidationHistory } from "./core/validation-history.js";
|
|
5
6
|
import { buildCompatibilityMatrix, matrixExitCode } from "./compatibility/compatibility-matrix.js";
|
|
6
7
|
import { applyInstallPreview, renderApplyInstallResult } from "./compatibility/apply-install-preview.js";
|
|
7
8
|
import { buildClaudeDesktopInstallPreview, renderClaudeDesktopInstallPreview } from "./compatibility/claude-desktop-install-preview.js";
|
|
@@ -13,6 +14,7 @@ import { renderInstalledSummary } from "./reporting/render-installed-summary.js"
|
|
|
13
14
|
import { renderBadgeJson, renderBadgeMarkdown } from "./reporting/render-badge-report.js";
|
|
14
15
|
import { renderCompatibilityScorecard } from "./reporting/render-compatibility-scorecard.js";
|
|
15
16
|
import { renderCompatibilityReport } from "./reporting/render-compatibility-report.js";
|
|
17
|
+
import { renderHistorySummary } from "./reporting/render-history-summary.js";
|
|
16
18
|
import { renderJsonReport } from "./reporting/render-json-report.js";
|
|
17
19
|
import { buildMarkdownReport } from "./reporting/render-markdown-report.js";
|
|
18
20
|
import { renderRuleExplanation } from "./reporting/render-rule-explanation.js";
|
|
@@ -32,7 +34,7 @@ const defaultIo = {
|
|
|
32
34
|
}
|
|
33
35
|
};
|
|
34
36
|
function printUsage(io) {
|
|
35
|
-
io.writeStderr("Usage: codex-plugin-doctor check <path|--installed> [filter] [--json|--markdown|--badge-json|--badge-markdown] [--output <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 self-test\n codex-plugin-doctor list --installed\n codex-plugin-doctor explain <finding-id>\n codex-plugin-doctor --version");
|
|
37
|
+
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 history <history.jsonl> [--json] [--fail-on-regression]\n codex-plugin-doctor self-test\n codex-plugin-doctor list --installed\n codex-plugin-doctor explain <finding-id>\n codex-plugin-doctor --version");
|
|
36
38
|
}
|
|
37
39
|
function renderInstalledPlugins(plugins) {
|
|
38
40
|
const lines = [
|
|
@@ -118,6 +120,31 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
118
120
|
io.writeStdout(renderRuleExplanation(rule));
|
|
119
121
|
return 0;
|
|
120
122
|
}
|
|
123
|
+
if (command === "history") {
|
|
124
|
+
if (!maybePath || maybePath.startsWith("--")) {
|
|
125
|
+
io.writeStderr("Missing history path. Usage: codex-plugin-doctor history <history.jsonl> [--json] [--fail-on-regression]");
|
|
126
|
+
return 2;
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const entries = await readValidationHistory(maybePath);
|
|
130
|
+
const summary = summarizeValidationHistory(entries);
|
|
131
|
+
const jsonOutput = remainingArgs.includes("--json");
|
|
132
|
+
const failOnRegression = remainingArgs.includes("--fail-on-regression");
|
|
133
|
+
io.writeStdout(jsonOutput
|
|
134
|
+
? JSON.stringify(summary, null, 2)
|
|
135
|
+
: renderHistorySummary(entries));
|
|
136
|
+
if (failOnRegression && summary.regression) {
|
|
137
|
+
io.writeStderr("Validation history regression detected.");
|
|
138
|
+
return 1;
|
|
139
|
+
}
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
const message = error instanceof Error ? error.message : "Unable to read validation history.";
|
|
144
|
+
io.writeStderr(message);
|
|
145
|
+
return 1;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
121
148
|
if (command === "self-test" || command === "demo") {
|
|
122
149
|
const targetPath = resolveBundledSelfTestTarget();
|
|
123
150
|
const runCheckImpl = options.runCheckImpl ?? runCheck;
|
|
@@ -260,6 +287,8 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
260
287
|
const outputPath = outputIndex === -1 ? null : normalizedFlags[outputIndex + 1];
|
|
261
288
|
const configIndex = normalizedFlags.indexOf("--config");
|
|
262
289
|
const configPath = configIndex === -1 ? null : normalizedFlags[configIndex + 1];
|
|
290
|
+
const historyIndex = normalizedFlags.indexOf("--history");
|
|
291
|
+
const historyPath = historyIndex === -1 ? null : normalizedFlags[historyIndex + 1];
|
|
263
292
|
if (outputIndex !== -1 && (!outputPath || outputPath.startsWith("--"))) {
|
|
264
293
|
io.writeStderr("Missing path after --output.");
|
|
265
294
|
return 2;
|
|
@@ -268,10 +297,18 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
268
297
|
io.writeStderr("Missing path after --config.");
|
|
269
298
|
return 2;
|
|
270
299
|
}
|
|
300
|
+
if (historyIndex !== -1 && (!historyPath || historyPath.startsWith("--"))) {
|
|
301
|
+
io.writeStderr("Missing path after --history.");
|
|
302
|
+
return 2;
|
|
303
|
+
}
|
|
271
304
|
if (checkInstalled && (badgeJsonOutput || badgeMarkdownOutput)) {
|
|
272
305
|
io.writeStderr("Badge output requires a single package target.");
|
|
273
306
|
return 2;
|
|
274
307
|
}
|
|
308
|
+
if (checkInstalled && historyPath) {
|
|
309
|
+
io.writeStderr("History output requires a single package target.");
|
|
310
|
+
return 2;
|
|
311
|
+
}
|
|
275
312
|
const outputPolicy = determineOutputPolicy({
|
|
276
313
|
jsonOutput: jsonOutput || badgeJsonOutput,
|
|
277
314
|
markdownOutput: markdownOutput || badgeMarkdownOutput,
|
|
@@ -354,6 +391,9 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
354
391
|
if (outputPath) {
|
|
355
392
|
await writeFile(outputPath, report, "utf8");
|
|
356
393
|
}
|
|
394
|
+
if (historyPath) {
|
|
395
|
+
await appendValidationHistoryEntry(historyPath, result, { runtimeProbeEnabled });
|
|
396
|
+
}
|
|
357
397
|
io.writeStdout(report);
|
|
358
398
|
return result.exitCode;
|
|
359
399
|
}
|
package/package.json
CHANGED