codex-plugin-doctor 1.2.0 → 1.4.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 +6 -3
- package/dist/core/output-contract.js +6 -0
- package/dist/core/runtime-plan.d.ts +1 -0
- package/dist/core/runtime-plan.js +61 -0
- package/dist/core/runtime-policy.d.ts +34 -0
- package/dist/core/runtime-policy.js +167 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/run-cli.js +38 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -215,6 +215,9 @@ codex-plugin-doctor doctor perf . --json --output perf.json
|
|
|
215
215
|
codex-plugin-doctor doctor perf . --max-total-ms 2500 --max-stage-ms validation=500
|
|
216
216
|
codex-plugin-doctor doctor runtime-plan .
|
|
217
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
|
|
218
221
|
codex-plugin-doctor doctor mcp .
|
|
219
222
|
codex-plugin-doctor doctor mcp . --json --output mcp-healthcheck.json
|
|
220
223
|
codex-plugin-doctor doctor export --bundle .
|
|
@@ -286,7 +289,7 @@ codex-plugin-doctor check . --json --runtime --verbose-runtime
|
|
|
286
289
|
|
|
287
290
|
`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`.
|
|
288
291
|
|
|
289
|
-
`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. 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. `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.
|
|
292
|
+
`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. 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. `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.
|
|
290
293
|
|
|
291
294
|
`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.
|
|
292
295
|
|
|
@@ -352,9 +355,9 @@ jobs:
|
|
|
352
355
|
runs-on: ubuntu-latest
|
|
353
356
|
steps:
|
|
354
357
|
- uses: actions/checkout@v5
|
|
355
|
-
- uses: Esquetta/CodexPluginDoctor@v1.
|
|
358
|
+
- uses: Esquetta/CodexPluginDoctor@v1.4.0
|
|
356
359
|
with:
|
|
357
|
-
version: "1.
|
|
360
|
+
version: "1.4.0"
|
|
358
361
|
path: .
|
|
359
362
|
runtime: "true"
|
|
360
363
|
policy: codex-publish
|
|
@@ -77,6 +77,12 @@ const publicSchemaDefinitions = [
|
|
|
77
77
|
outputKind: "doctor.runtime.plan",
|
|
78
78
|
required: ["schemaVersion", "kind", "generatedAt", "version", "targetPath", "status", "exitCode", "runtimeExecution", "digest", "summary", "servers", "findings"]
|
|
79
79
|
},
|
|
80
|
+
{
|
|
81
|
+
id: "doctor.runtime.policy.json",
|
|
82
|
+
command: "codex-plugin-doctor doctor runtime-policy <path> --json",
|
|
83
|
+
outputKind: "doctor.runtime.policy",
|
|
84
|
+
required: ["schemaVersion", "kind", "generatedAt", "version", "targetPath", "status", "exitCode", "runtimeExecution", "planDigest", "recommendation", "summary", "servers"]
|
|
85
|
+
},
|
|
80
86
|
{
|
|
81
87
|
id: "doctor.export.bundle.json",
|
|
82
88
|
command: "codex-plugin-doctor doctor export --bundle <path> --json",
|
|
@@ -47,5 +47,6 @@ export declare function evaluateRuntimeApproval(plan: DoctorRuntimePlan, options
|
|
|
47
47
|
}): RuntimeApprovalReport;
|
|
48
48
|
export declare function runtimeApprovalPassed(approval: RuntimeApprovalReport): boolean;
|
|
49
49
|
export declare function renderDoctorRuntimePlanJson(plan: DoctorRuntimePlan): string;
|
|
50
|
+
export declare function renderDoctorRuntimePlanMarkdown(plan: DoctorRuntimePlan): string;
|
|
50
51
|
export declare function renderDoctorRuntimePlan(plan: DoctorRuntimePlan): string;
|
|
51
52
|
export {};
|
|
@@ -203,6 +203,67 @@ export function runtimeApprovalPassed(approval) {
|
|
|
203
203
|
export function renderDoctorRuntimePlanJson(plan) {
|
|
204
204
|
return JSON.stringify(plan, null, 2);
|
|
205
205
|
}
|
|
206
|
+
function markdownList(items, emptyValue) {
|
|
207
|
+
if (items.length === 0) {
|
|
208
|
+
return `- ${emptyValue}`;
|
|
209
|
+
}
|
|
210
|
+
return items.map((item) => `- ${item}`).join("\n");
|
|
211
|
+
}
|
|
212
|
+
function markdownEscape(value) {
|
|
213
|
+
return value.replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
214
|
+
}
|
|
215
|
+
export function renderDoctorRuntimePlanMarkdown(plan) {
|
|
216
|
+
const lines = [
|
|
217
|
+
"# Doctor Runtime Review Plan",
|
|
218
|
+
"",
|
|
219
|
+
"This artifact records the intended MCP runtime probe boundary before any package-local server is started.",
|
|
220
|
+
"",
|
|
221
|
+
"## Summary",
|
|
222
|
+
"",
|
|
223
|
+
`- Target: \`${plan.targetPath}\``,
|
|
224
|
+
`- Status: **${plan.status.toUpperCase()}**`,
|
|
225
|
+
`- Runtime execution: \`${plan.runtimeExecution}\``,
|
|
226
|
+
`- Approval digest: \`${plan.digest}\``,
|
|
227
|
+
`- Servers: ${plan.summary.serverCount}`,
|
|
228
|
+
`- Executable servers: ${plan.summary.executableServerCount}`,
|
|
229
|
+
`- High-risk servers: ${plan.summary.highRiskServerCount}`,
|
|
230
|
+
`- Findings: ${plan.summary.findings.fail} fail, ${plan.summary.findings.warn} warn, ${plan.summary.findings.total} total`,
|
|
231
|
+
"",
|
|
232
|
+
"## Execution Boundary",
|
|
233
|
+
"",
|
|
234
|
+
"- This plan is non-executing.",
|
|
235
|
+
"- Runtime probes require explicit operator approval before local MCP servers are started.",
|
|
236
|
+
"- The approval digest changes when command, args, cwd, probe methods, risk reasons, or findings change.",
|
|
237
|
+
"- Runtime approval is a review gate, not an OS, VM, or container sandbox.",
|
|
238
|
+
"",
|
|
239
|
+
"## Review Checklist",
|
|
240
|
+
"",
|
|
241
|
+
"- Confirm every command, argument, and working directory is expected.",
|
|
242
|
+
"- Confirm remote URLs and network expectations are acceptable for the task.",
|
|
243
|
+
"- Confirm high-risk findings are resolved or intentionally accepted before runtime probing.",
|
|
244
|
+
"- Use the approval digest with `check --runtime --require-runtime-approval --runtime-approval-digest <digest>`.",
|
|
245
|
+
"- Preserve this artifact with release evidence when runtime execution is part of the release gate."
|
|
246
|
+
];
|
|
247
|
+
if (plan.servers.length === 0) {
|
|
248
|
+
lines.push("", "## Servers", "", "No MCP runtime servers found.");
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
lines.push("", "## Servers", "", "| Risk | Name | Transport | Command or URL | Cwd |", "| --- | --- | --- | --- | --- |");
|
|
252
|
+
for (const server of plan.servers) {
|
|
253
|
+
lines.push(`| ${server.riskLevel.toUpperCase()} | ${markdownEscape(server.name)} | ${server.transport} | ${markdownEscape(server.command ?? server.url ?? "not executable by runtime probe")} | ${markdownEscape(server.cwd ?? "n/a")} |`);
|
|
254
|
+
}
|
|
255
|
+
for (const server of plan.servers) {
|
|
256
|
+
lines.push("", `### ${server.name}`, "", "**Probe methods**", "", markdownList(server.probeMethods, "none"), "", "**Risk reasons**", "", markdownList(server.riskReasons, "none"));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (plan.findings.length > 0) {
|
|
260
|
+
lines.push("", "## Findings", "");
|
|
261
|
+
for (const finding of plan.findings) {
|
|
262
|
+
lines.push(`- **${finding.severity.toUpperCase()}** \`${finding.id}\`: ${finding.message}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return lines.join("\n");
|
|
266
|
+
}
|
|
206
267
|
export function renderDoctorRuntimePlan(plan) {
|
|
207
268
|
const lines = [
|
|
208
269
|
"Doctor Runtime Plan",
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type DoctorRuntimePlan, type RuntimePlanServer } from "./runtime-plan.js";
|
|
2
|
+
export type RuntimePolicyDecision = "allow" | "review" | "sandbox_recommended" | "deny";
|
|
3
|
+
export interface RuntimePolicyRecommendation {
|
|
4
|
+
decision: RuntimePolicyDecision;
|
|
5
|
+
reason: string;
|
|
6
|
+
actions: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface DoctorRuntimePolicyReport {
|
|
9
|
+
schemaVersion: "1.0.0";
|
|
10
|
+
kind: "doctor.runtime.policy";
|
|
11
|
+
generatedAt: string;
|
|
12
|
+
version: string;
|
|
13
|
+
targetPath: string;
|
|
14
|
+
status: "pass" | "warn" | "fail";
|
|
15
|
+
exitCode: 0 | 1;
|
|
16
|
+
runtimeExecution: "not_started";
|
|
17
|
+
planDigest: string;
|
|
18
|
+
recommendation: RuntimePolicyRecommendation;
|
|
19
|
+
summary: {
|
|
20
|
+
serverCount: number;
|
|
21
|
+
executableServerCount: number;
|
|
22
|
+
highRiskServerCount: number;
|
|
23
|
+
findingCounts: DoctorRuntimePlan["summary"]["findings"];
|
|
24
|
+
};
|
|
25
|
+
servers: Array<{
|
|
26
|
+
name: string;
|
|
27
|
+
decision: RuntimePolicyDecision;
|
|
28
|
+
riskLevel: RuntimePlanServer["riskLevel"];
|
|
29
|
+
reasons: string[];
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
export declare function buildDoctorRuntimePolicyReport(targetPath: string, generatedAt?: string): Promise<DoctorRuntimePolicyReport>;
|
|
33
|
+
export declare function renderDoctorRuntimePolicyJson(report: DoctorRuntimePolicyReport): string;
|
|
34
|
+
export declare function renderDoctorRuntimePolicy(report: DoctorRuntimePolicyReport): string;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { packageVersion } from "../version.js";
|
|
2
|
+
import { buildDoctorRuntimePlan } from "./runtime-plan.js";
|
|
3
|
+
const denyFindingIds = new Set([
|
|
4
|
+
"plugin.security.encoded_command",
|
|
5
|
+
"plugin.security.remote_pipe_install",
|
|
6
|
+
"plugin.security.cwd_outside_root",
|
|
7
|
+
"plugin.security.hard_coded_secret",
|
|
8
|
+
"plugin.security.prompt_injection_text",
|
|
9
|
+
"plugin.security.path_traversal"
|
|
10
|
+
]);
|
|
11
|
+
function unique(values) {
|
|
12
|
+
return [...new Set(values)];
|
|
13
|
+
}
|
|
14
|
+
function serverDecision(server) {
|
|
15
|
+
if (server.riskReasons.some((reason) => denyFindingIds.has(reason))) {
|
|
16
|
+
return "deny";
|
|
17
|
+
}
|
|
18
|
+
if (server.riskLevel === "high") {
|
|
19
|
+
return "sandbox_recommended";
|
|
20
|
+
}
|
|
21
|
+
if (server.riskLevel === "medium" ||
|
|
22
|
+
server.riskReasons.includes("runtime.executes_local_command") ||
|
|
23
|
+
server.riskReasons.includes("runtime.connects_remote_server")) {
|
|
24
|
+
return "review";
|
|
25
|
+
}
|
|
26
|
+
return "allow";
|
|
27
|
+
}
|
|
28
|
+
function strongestDecision(decisions) {
|
|
29
|
+
if (decisions.includes("deny")) {
|
|
30
|
+
return "deny";
|
|
31
|
+
}
|
|
32
|
+
if (decisions.includes("sandbox_recommended")) {
|
|
33
|
+
return "sandbox_recommended";
|
|
34
|
+
}
|
|
35
|
+
if (decisions.includes("review")) {
|
|
36
|
+
return "review";
|
|
37
|
+
}
|
|
38
|
+
return "allow";
|
|
39
|
+
}
|
|
40
|
+
function buildRecommendation(plan, decision, reasons) {
|
|
41
|
+
if (plan.summary.executableServerCount === 0) {
|
|
42
|
+
return {
|
|
43
|
+
decision: "allow",
|
|
44
|
+
reason: "No executable local MCP runtime servers were found.",
|
|
45
|
+
actions: [
|
|
46
|
+
"Use static validation and security checks; runtime probing is not needed for this package."
|
|
47
|
+
]
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (decision === "deny") {
|
|
51
|
+
return {
|
|
52
|
+
decision,
|
|
53
|
+
reason: "One or more runtime servers include high-confidence unsafe execution signals.",
|
|
54
|
+
actions: [
|
|
55
|
+
"Do not start runtime probes until deny-level findings are resolved.",
|
|
56
|
+
"Remove encoded commands, remote pipe-to-shell startup, cwd escapes, hard-coded secrets, or prompt-injection text.",
|
|
57
|
+
"Regenerate `doctor runtime-plan` after fixes and review the new approval digest."
|
|
58
|
+
]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (decision === "sandbox_recommended") {
|
|
62
|
+
return {
|
|
63
|
+
decision,
|
|
64
|
+
reason: "Runtime execution has high-risk findings or failed security checks that need isolation before probing.",
|
|
65
|
+
actions: [
|
|
66
|
+
"Prefer an isolated container, VM, or managed sandbox before starting this MCP server.",
|
|
67
|
+
"Keep network egress allowlists explicit and minimal.",
|
|
68
|
+
"Attach the runtime plan digest and review artifact to the release or CI evidence."
|
|
69
|
+
]
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (decision === "review") {
|
|
73
|
+
return {
|
|
74
|
+
decision,
|
|
75
|
+
reason: reasons.length > 0
|
|
76
|
+
? `Runtime probing requires human review: ${unique(reasons).join(", ")}.`
|
|
77
|
+
: "Runtime probing starts local or remote MCP server surfaces and should be explicitly reviewed.",
|
|
78
|
+
actions: [
|
|
79
|
+
"Review command, args, cwd, URL, probe methods, and risk reasons before execution.",
|
|
80
|
+
"Approve the exact plan digest with `check --runtime --require-runtime-approval --runtime-approval-digest <digest>`.",
|
|
81
|
+
"Use `doctor runtime-plan --markdown` when the approval needs to be preserved with release evidence."
|
|
82
|
+
]
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
decision,
|
|
87
|
+
reason: "Runtime plan is clean and contains only low-risk executable server entries.",
|
|
88
|
+
actions: [
|
|
89
|
+
"Runtime probes can run after normal operator approval.",
|
|
90
|
+
"Keep the runtime plan digest with CI or release evidence when reproducibility matters."
|
|
91
|
+
]
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export async function buildDoctorRuntimePolicyReport(targetPath, generatedAt = new Date().toISOString()) {
|
|
95
|
+
const plan = await buildDoctorRuntimePlan(targetPath, generatedAt);
|
|
96
|
+
const servers = plan.servers.map((server) => ({
|
|
97
|
+
name: server.name,
|
|
98
|
+
decision: serverDecision(server),
|
|
99
|
+
riskLevel: server.riskLevel,
|
|
100
|
+
reasons: server.riskReasons
|
|
101
|
+
}));
|
|
102
|
+
const findingReasons = plan.findings.map((finding) => finding.id);
|
|
103
|
+
const decision = plan.summary.findings.fail > 0 && servers.length === 0
|
|
104
|
+
? "deny"
|
|
105
|
+
: strongestDecision(servers.map((server) => server.decision));
|
|
106
|
+
const recommendation = buildRecommendation(plan, decision, unique([...findingReasons, ...servers.flatMap((server) => server.reasons)]));
|
|
107
|
+
const status = recommendation.decision === "deny"
|
|
108
|
+
? "fail"
|
|
109
|
+
: recommendation.decision === "allow"
|
|
110
|
+
? "pass"
|
|
111
|
+
: "warn";
|
|
112
|
+
return {
|
|
113
|
+
schemaVersion: "1.0.0",
|
|
114
|
+
kind: "doctor.runtime.policy",
|
|
115
|
+
generatedAt,
|
|
116
|
+
version: packageVersion,
|
|
117
|
+
targetPath: plan.targetPath,
|
|
118
|
+
status,
|
|
119
|
+
exitCode: status === "fail" ? 1 : 0,
|
|
120
|
+
runtimeExecution: "not_started",
|
|
121
|
+
planDigest: plan.digest,
|
|
122
|
+
recommendation,
|
|
123
|
+
summary: {
|
|
124
|
+
serverCount: plan.summary.serverCount,
|
|
125
|
+
executableServerCount: plan.summary.executableServerCount,
|
|
126
|
+
highRiskServerCount: plan.summary.highRiskServerCount,
|
|
127
|
+
findingCounts: plan.summary.findings
|
|
128
|
+
},
|
|
129
|
+
servers
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
export function renderDoctorRuntimePolicyJson(report) {
|
|
133
|
+
return JSON.stringify(report, null, 2);
|
|
134
|
+
}
|
|
135
|
+
export function renderDoctorRuntimePolicy(report) {
|
|
136
|
+
const lines = [
|
|
137
|
+
"Doctor Runtime Policy",
|
|
138
|
+
"=====================",
|
|
139
|
+
`Target: ${report.targetPath}`,
|
|
140
|
+
`Status: ${report.status.toUpperCase()}`,
|
|
141
|
+
`Decision: ${report.recommendation.decision.toUpperCase()}`,
|
|
142
|
+
`Runtime execution: ${report.runtimeExecution}`,
|
|
143
|
+
`Plan digest: ${report.planDigest}`,
|
|
144
|
+
`Servers: ${report.summary.serverCount}`,
|
|
145
|
+
`Executable servers: ${report.summary.executableServerCount}`,
|
|
146
|
+
`High-risk servers: ${report.summary.highRiskServerCount}`,
|
|
147
|
+
"",
|
|
148
|
+
"Reason",
|
|
149
|
+
"------",
|
|
150
|
+
report.recommendation.reason,
|
|
151
|
+
"",
|
|
152
|
+
"Actions",
|
|
153
|
+
"-------"
|
|
154
|
+
];
|
|
155
|
+
for (const action of report.recommendation.actions) {
|
|
156
|
+
lines.push(`- ${action}`);
|
|
157
|
+
}
|
|
158
|
+
if (report.servers.length > 0) {
|
|
159
|
+
lines.push("", "Servers", "-------");
|
|
160
|
+
for (const server of report.servers) {
|
|
161
|
+
lines.push(`${server.decision.toUpperCase()} ${server.name}`);
|
|
162
|
+
lines.push(` Risk: ${server.riskLevel}`);
|
|
163
|
+
lines.push(` Reasons: ${server.reasons.length > 0 ? server.reasons.join(", ") : "none"}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return lines.join("\n");
|
|
167
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,8 @@ export { buildDoctorOutputContract, renderDoctorOutputContract, renderDoctorOutp
|
|
|
9
9
|
export { buildDoctorValidationCorpusReport, renderDoctorValidationCorpusJson, renderDoctorValidationCorpusReport, type BuildDoctorValidationCorpusOptions, type DoctorValidationCorpusReport, type ValidationCorpusCaseDefinition, type ValidationCorpusCaseResult } from "./core/validation-corpus.js";
|
|
10
10
|
export { buildDoctorExportBundleFromAnalysis, buildDoctorRecommendationsFromAnalysis, buildPackageAnalysis, type PackageAnalysis, type PackageAnalysisOptions, type PackageAnalysisStage, type PackageAnalysisTiming } from "./core/package-analysis.js";
|
|
11
11
|
export { buildDoctorPerformanceReport, renderDoctorPerformanceReport, renderDoctorPerformanceReportJson, type BuildDoctorPerformanceReportOptions, type DoctorPerformanceReport, type DoctorPerformanceStage, type DoctorPerformanceStageName } from "./core/performance-report.js";
|
|
12
|
-
export { buildDoctorRuntimePlan, evaluateRuntimeApproval, renderDoctorRuntimePlan, renderDoctorRuntimePlanJson, runtimeApprovalPassed, type DoctorRuntimePlan, type RuntimeApprovalReport, type RuntimePlanServer } from "./core/runtime-plan.js";
|
|
12
|
+
export { buildDoctorRuntimePlan, evaluateRuntimeApproval, renderDoctorRuntimePlan, renderDoctorRuntimePlanMarkdown, renderDoctorRuntimePlanJson, runtimeApprovalPassed, type DoctorRuntimePlan, type RuntimeApprovalReport, type RuntimePlanServer } from "./core/runtime-plan.js";
|
|
13
|
+
export { buildDoctorRuntimePolicyReport, renderDoctorRuntimePolicy, renderDoctorRuntimePolicyJson, type DoctorRuntimePolicyReport, type RuntimePolicyDecision, type RuntimePolicyRecommendation } from "./core/runtime-policy.js";
|
|
13
14
|
export { buildDoctorReleaseEvidenceAssetReport, buildDoctorReleaseEvidenceReport, renderDoctorReleaseEvidenceAsset, renderDoctorReleaseEvidenceAssetJson, renderDoctorReleaseEvidence, renderDoctorReleaseEvidenceJson, renderDoctorReleaseEvidenceVerification, renderDoctorReleaseEvidenceVerificationJson, verifyDoctorReleaseEvidence, type BuildDoctorReleaseEvidenceOptions, type DoctorReleaseEvidenceAssetReport, type DoctorReleaseEvidenceGitMetadata, type DoctorReleaseEvidencePackageMetadata, type DoctorReleaseEvidenceReport, type DoctorReleaseEvidenceVerificationReport } from "./core/release-evidence.js";
|
|
14
15
|
export { buildDoctorNpmPackageReport, renderDoctorNpmPackageReport, renderDoctorNpmPackageReportJson, type BuildDoctorNpmPackageReportOptions, type DoctorNpmPackageReport } from "./core/npm-package-doctor.js";
|
|
15
16
|
export { buildDoctorRiskDiffReport, renderDoctorRiskDiffReport, renderDoctorRiskDiffReportJson, type BuildDoctorRiskDiffReportOptions, type DoctorRiskDiffReport, type RiskDiffFinding, type RiskFindingCategory } from "./core/risk-diff.js";
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,8 @@ export { buildDoctorOutputContract, renderDoctorOutputContract, renderDoctorOutp
|
|
|
9
9
|
export { buildDoctorValidationCorpusReport, renderDoctorValidationCorpusJson, renderDoctorValidationCorpusReport } from "./core/validation-corpus.js";
|
|
10
10
|
export { buildDoctorExportBundleFromAnalysis, buildDoctorRecommendationsFromAnalysis, buildPackageAnalysis } from "./core/package-analysis.js";
|
|
11
11
|
export { buildDoctorPerformanceReport, renderDoctorPerformanceReport, renderDoctorPerformanceReportJson } from "./core/performance-report.js";
|
|
12
|
-
export { buildDoctorRuntimePlan, evaluateRuntimeApproval, renderDoctorRuntimePlan, renderDoctorRuntimePlanJson, runtimeApprovalPassed } from "./core/runtime-plan.js";
|
|
12
|
+
export { buildDoctorRuntimePlan, evaluateRuntimeApproval, renderDoctorRuntimePlan, renderDoctorRuntimePlanMarkdown, renderDoctorRuntimePlanJson, runtimeApprovalPassed } from "./core/runtime-plan.js";
|
|
13
|
+
export { buildDoctorRuntimePolicyReport, renderDoctorRuntimePolicy, renderDoctorRuntimePolicyJson } from "./core/runtime-policy.js";
|
|
13
14
|
export { buildDoctorReleaseEvidenceAssetReport, buildDoctorReleaseEvidenceReport, renderDoctorReleaseEvidenceAsset, renderDoctorReleaseEvidenceAssetJson, renderDoctorReleaseEvidence, renderDoctorReleaseEvidenceJson, renderDoctorReleaseEvidenceVerification, renderDoctorReleaseEvidenceVerificationJson, verifyDoctorReleaseEvidence } from "./core/release-evidence.js";
|
|
14
15
|
export { buildDoctorNpmPackageReport, renderDoctorNpmPackageReport, renderDoctorNpmPackageReportJson } from "./core/npm-package-doctor.js";
|
|
15
16
|
export { buildDoctorRiskDiffReport, renderDoctorRiskDiffReport, renderDoctorRiskDiffReportJson } from "./core/risk-diff.js";
|
package/dist/run-cli.js
CHANGED
|
@@ -20,7 +20,8 @@ import { buildDoctorAttestation, renderDoctorAttestation, renderDoctorAttestatio
|
|
|
20
20
|
import { buildDoctorOutputContract, renderDoctorOutputContract, renderDoctorOutputContractJson } from "./core/output-contract.js";
|
|
21
21
|
import { buildDoctorValidationCorpusReport, renderDoctorValidationCorpusJson, renderDoctorValidationCorpusReport } from "./core/validation-corpus.js";
|
|
22
22
|
import { buildDoctorPerformanceReport, renderDoctorPerformanceReport, renderDoctorPerformanceReportJson } from "./core/performance-report.js";
|
|
23
|
-
import { buildDoctorRuntimePlan, evaluateRuntimeApproval, renderDoctorRuntimePlan, renderDoctorRuntimePlanJson, runtimeApprovalPassed } from "./core/runtime-plan.js";
|
|
23
|
+
import { buildDoctorRuntimePlan, evaluateRuntimeApproval, renderDoctorRuntimePlan, renderDoctorRuntimePlanMarkdown, renderDoctorRuntimePlanJson, runtimeApprovalPassed } from "./core/runtime-plan.js";
|
|
24
|
+
import { buildDoctorRuntimePolicyReport, renderDoctorRuntimePolicy, renderDoctorRuntimePolicyJson } from "./core/runtime-policy.js";
|
|
24
25
|
import { buildDoctorReleaseEvidenceAssetReport, buildDoctorReleaseEvidenceReport, renderDoctorReleaseEvidenceAsset, renderDoctorReleaseEvidenceAssetJson, renderDoctorReleaseEvidence, renderDoctorReleaseEvidenceJson, renderDoctorReleaseEvidenceVerification, renderDoctorReleaseEvidenceVerificationJson, verifyDoctorReleaseEvidence } from "./core/release-evidence.js";
|
|
25
26
|
import { buildDoctorNpmPackageReport, renderDoctorNpmPackageReport, renderDoctorNpmPackageReportJson } from "./core/npm-package-doctor.js";
|
|
26
27
|
import { buildDoctorRiskDiffReport, renderDoctorRiskDiffReport, renderDoctorRiskDiffReportJson } from "./core/risk-diff.js";
|
|
@@ -71,7 +72,7 @@ const defaultIo = {
|
|
|
71
72
|
}
|
|
72
73
|
};
|
|
73
74
|
function printUsage(io) {
|
|
74
|
-
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]\n codex-plugin-doctor audit --installed [filter] [--policy codex-publish|mcp-strict|security] [--security] [--compat] [--json] [--output <path>] [--cache] [--changed]\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 fix <path> (--dry-run|--interactive --backup|--apply --backup)\n codex-plugin-doctor history <history.jsonl> [--json] [--fail-on-regression]\n codex-plugin-doctor doctor [npm <package>|contract|corpus|runtime-plan <path
|
|
75
|
+
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]\n codex-plugin-doctor audit --installed [filter] [--policy codex-publish|mcp-strict|security] [--security] [--compat] [--json] [--output <path>] [--cache] [--changed]\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 fix <path> (--dry-run|--interactive --backup|--apply --backup)\n codex-plugin-doctor history <history.jsonl> [--json] [--fail-on-regression]\n codex-plugin-doctor doctor [npm <package>|contract|corpus|runtime-plan <path> [--json|--markdown] [--output <path>]|runtime-policy <path> [--json] [--output <path>]|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 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");
|
|
75
76
|
}
|
|
76
77
|
const performanceStageNames = new Set([
|
|
77
78
|
"validation",
|
|
@@ -358,6 +359,7 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
358
359
|
: null;
|
|
359
360
|
const runtimePlanFlags = targetPath ? remainingArgs.slice(1) : remainingArgs;
|
|
360
361
|
const jsonOutput = runtimePlanFlags.includes("--json");
|
|
362
|
+
const markdownOutput = runtimePlanFlags.includes("--markdown");
|
|
361
363
|
const outputIndex = runtimePlanFlags.indexOf("--output");
|
|
362
364
|
const outputPath = outputIndex === -1 ? null : runtimePlanFlags[outputIndex + 1];
|
|
363
365
|
if (!targetPath) {
|
|
@@ -368,16 +370,48 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
368
370
|
io.writeStderr("Missing path after --output.");
|
|
369
371
|
return 2;
|
|
370
372
|
}
|
|
373
|
+
if (jsonOutput && markdownOutput) {
|
|
374
|
+
io.writeStderr("Use either --json or --markdown, not both.");
|
|
375
|
+
return 2;
|
|
376
|
+
}
|
|
371
377
|
const plan = await buildDoctorRuntimePlan(targetPath);
|
|
372
378
|
const renderedPlan = jsonOutput
|
|
373
379
|
? renderDoctorRuntimePlanJson(plan)
|
|
374
|
-
:
|
|
380
|
+
: markdownOutput
|
|
381
|
+
? renderDoctorRuntimePlanMarkdown(plan)
|
|
382
|
+
: renderDoctorRuntimePlan(plan);
|
|
375
383
|
if (outputPath) {
|
|
376
|
-
await writeFile(outputPath, renderDoctorRuntimePlanJson(plan), "utf8");
|
|
384
|
+
await writeFile(outputPath, markdownOutput ? renderDoctorRuntimePlanMarkdown(plan) : renderDoctorRuntimePlanJson(plan), "utf8");
|
|
377
385
|
}
|
|
378
386
|
io.writeStdout(renderedPlan);
|
|
379
387
|
return plan.exitCode;
|
|
380
388
|
}
|
|
389
|
+
if (maybePath === "runtime-policy") {
|
|
390
|
+
const targetPath = remainingArgs[0] && !remainingArgs[0].startsWith("--")
|
|
391
|
+
? remainingArgs[0]
|
|
392
|
+
: null;
|
|
393
|
+
const runtimePolicyFlags = targetPath ? remainingArgs.slice(1) : remainingArgs;
|
|
394
|
+
const jsonOutput = runtimePolicyFlags.includes("--json");
|
|
395
|
+
const outputIndex = runtimePolicyFlags.indexOf("--output");
|
|
396
|
+
const outputPath = outputIndex === -1 ? null : runtimePolicyFlags[outputIndex + 1];
|
|
397
|
+
if (!targetPath) {
|
|
398
|
+
io.writeStderr("Missing target path for runtime policy.");
|
|
399
|
+
return 2;
|
|
400
|
+
}
|
|
401
|
+
if (outputIndex !== -1 && (!outputPath || outputPath.startsWith("--"))) {
|
|
402
|
+
io.writeStderr("Missing path after --output.");
|
|
403
|
+
return 2;
|
|
404
|
+
}
|
|
405
|
+
const report = await buildDoctorRuntimePolicyReport(targetPath);
|
|
406
|
+
const renderedReport = jsonOutput
|
|
407
|
+
? renderDoctorRuntimePolicyJson(report)
|
|
408
|
+
: renderDoctorRuntimePolicy(report);
|
|
409
|
+
if (outputPath) {
|
|
410
|
+
await writeFile(outputPath, renderedReport, "utf8");
|
|
411
|
+
}
|
|
412
|
+
io.writeStdout(renderedReport);
|
|
413
|
+
return report.exitCode;
|
|
414
|
+
}
|
|
381
415
|
if (maybePath === "release-evidence") {
|
|
382
416
|
if (remainingArgs[0] === "asset") {
|
|
383
417
|
const targetPath = remainingArgs[1] && !remainingArgs[1].startsWith("--")
|
package/package.json
CHANGED