codex-plugin-doctor 1.46.1 → 1.48.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 +27 -14
- package/dist/core/attestation.d.ts +1 -0
- package/dist/core/attestation.js +1 -1
- package/dist/core/corpus-quality-metrics.d.ts +125 -0
- package/dist/core/corpus-quality-metrics.js +433 -0
- package/dist/core/external-validation-corpus.d.ts +88 -0
- package/dist/core/external-validation-corpus.js +271 -0
- package/dist/core/init-git-hooks.js +43 -4
- package/dist/core/output-contract.js +25 -2
- package/dist/core/release-check.d.ts +2 -1
- package/dist/core/release-check.js +2 -1
- package/dist/core/release-evidence.d.ts +5 -2
- package/dist/core/release-evidence.js +12 -2
- package/dist/core/runtime-plan.d.ts +5 -1
- package/dist/core/runtime-plan.js +13 -1
- package/dist/core/runtime-policy.d.ts +5 -1
- package/dist/core/runtime-policy.js +3 -2
- package/dist/core/runtime-probe.d.ts +2 -1
- package/dist/core/runtime-probe.js +100 -18
- package/dist/core/runtime-sandbox.d.ts +21 -0
- package/dist/core/runtime-sandbox.js +68 -0
- package/dist/core/validate-plugin.js +19 -8
- package/dist/core/validation-corpus.d.ts +4 -2
- package/dist/core/validation-corpus.js +12 -1
- package/dist/domain/types.d.ts +11 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/reporting/render-json-report.js +3 -0
- package/dist/reporting/render-markdown-report.js +3 -0
- package/dist/reporting/render-text-report.js +3 -0
- package/dist/run-cli.d.ts +2 -0
- package/dist/run-cli.js +214 -19
- package/package.json +1 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
1
|
+
import { execFile, spawn } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { readFile, stat } from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import readline from "node:readline";
|
|
6
|
+
import { buildRuntimeLaunch, DOCKER_RUNTIME_STARTUP_TIMEOUT_MS, RuntimeSandboxError } from "./runtime-sandbox.js";
|
|
5
7
|
import { formatRequestTranscript as formatRequestTranscriptForLog, formatResponseTranscript as formatResponseTranscriptForLog } from "./runtime-transcript.js";
|
|
6
8
|
const MCP_PROTOCOL_VERSION = "2025-11-25";
|
|
7
9
|
const PROMPT_PROBE_PLACEHOLDER = "codex-plugin-doctor-probe";
|
|
@@ -9,6 +11,19 @@ const METHOD_NOT_FOUND = -32601;
|
|
|
9
11
|
const MAX_TOOL_CALL_CONTENT_LENGTH = 4096;
|
|
10
12
|
const MAX_RESOURCE_READ_CONTENT_LENGTH = 4096;
|
|
11
13
|
const MAX_PROMPT_GET_CONTENT_LENGTH = 4096;
|
|
14
|
+
const DOCKER_CLEANUP_TIMEOUT_MS = 5_000;
|
|
15
|
+
function buildNativeRuntimeLaunch(input) {
|
|
16
|
+
return {
|
|
17
|
+
...input,
|
|
18
|
+
containerName: null,
|
|
19
|
+
evidence: {
|
|
20
|
+
backend: "native",
|
|
21
|
+
image: null,
|
|
22
|
+
network: "host",
|
|
23
|
+
packageMount: "host"
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
12
27
|
function createRuntimeScorecard() {
|
|
13
28
|
return {
|
|
14
29
|
initialize: "skipped",
|
|
@@ -72,6 +87,21 @@ function withRuntimeEvidence(finding, serverName) {
|
|
|
72
87
|
}
|
|
73
88
|
};
|
|
74
89
|
}
|
|
90
|
+
function cleanupDockerContainer(containerName) {
|
|
91
|
+
return new Promise((resolve) => {
|
|
92
|
+
execFile("docker", ["rm", "-f", containerName], {
|
|
93
|
+
encoding: "utf8",
|
|
94
|
+
timeout: DOCKER_CLEANUP_TIMEOUT_MS,
|
|
95
|
+
windowsHide: true
|
|
96
|
+
}, (error, _stdout, stderr) => {
|
|
97
|
+
if (!error || /no such container/i.test(stderr)) {
|
|
98
|
+
resolve(null);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
resolve(buildFailure("plugin.runtime.sandbox.cleanup_failed", "The Docker runtime container could not be removed after probing.", "A leftover runtime container weakens cleanup guarantees and may continue consuming local resources.", "Verify the Docker daemon is available, then remove the codex-doctor container before retrying."));
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
75
105
|
function isPlainObject(value) {
|
|
76
106
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
77
107
|
}
|
|
@@ -605,16 +635,42 @@ function collectOversizedPromptGetWarnings(message, evidence) {
|
|
|
605
635
|
];
|
|
606
636
|
}
|
|
607
637
|
async function probeCommandServer(input) {
|
|
608
|
-
const { serverName, command, args, cwd, startupTimeoutMs, transcript } = input;
|
|
638
|
+
const { serverName, packageRoot, command, args, cwd, startupTimeoutMs, sandbox, transcript } = input;
|
|
639
|
+
let launch;
|
|
640
|
+
try {
|
|
641
|
+
launch = sandbox === "docker"
|
|
642
|
+
? buildRuntimeLaunch({
|
|
643
|
+
sandbox,
|
|
644
|
+
packageRoot,
|
|
645
|
+
command,
|
|
646
|
+
args,
|
|
647
|
+
cwd,
|
|
648
|
+
containerName: `codex-doctor-${randomUUID()}`
|
|
649
|
+
})
|
|
650
|
+
: buildNativeRuntimeLaunch({ command, args, cwd });
|
|
651
|
+
}
|
|
652
|
+
catch (error) {
|
|
653
|
+
const scorecard = createRuntimeScorecard();
|
|
654
|
+
scorecard.initialize = "fail";
|
|
655
|
+
return {
|
|
656
|
+
findings: [
|
|
657
|
+
withRuntimeEvidence(buildFailure("plugin.runtime.startup.failed", `The MCP server \`${serverName}\` could not be started in the requested runtime backend.`, "The configured stdio server is not supported by the selected runtime backend, so runtime validation cannot proceed.", error instanceof RuntimeSandboxError
|
|
658
|
+
? error.message
|
|
659
|
+
: "Verify the runtime command and selected sandbox before retrying."), serverName)
|
|
660
|
+
],
|
|
661
|
+
scorecard
|
|
662
|
+
};
|
|
663
|
+
}
|
|
609
664
|
return new Promise((resolve) => {
|
|
610
665
|
const scorecard = createRuntimeScorecard();
|
|
611
666
|
const warnings = [];
|
|
612
667
|
let settled = false;
|
|
668
|
+
let runtimeExecuted = false;
|
|
613
669
|
let stderrPreview = "";
|
|
614
670
|
let finalizeRequested = false;
|
|
615
671
|
let nextRequestId = 1;
|
|
616
|
-
const child = spawn(command, args, {
|
|
617
|
-
cwd,
|
|
672
|
+
const child = spawn(launch.command, launch.args, {
|
|
673
|
+
cwd: launch.cwd,
|
|
618
674
|
stdio: ["pipe", "pipe", "pipe"]
|
|
619
675
|
});
|
|
620
676
|
const stdoutReader = readline.createInterface({
|
|
@@ -637,21 +693,33 @@ async function probeCommandServer(input) {
|
|
|
637
693
|
child.stdin.end();
|
|
638
694
|
child.kill("SIGTERM");
|
|
639
695
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
696
|
+
void (async () => {
|
|
697
|
+
const cleanupFinding = launch.containerName && runtimeExecuted
|
|
698
|
+
? await cleanupDockerContainer(launch.containerName)
|
|
699
|
+
: null;
|
|
700
|
+
resolve({
|
|
701
|
+
findings: [
|
|
702
|
+
...warnings.map((warning) => withRuntimeEvidence(warning, serverName)),
|
|
703
|
+
...(finding ? [withRuntimeEvidence(finding, serverName)] : []),
|
|
704
|
+
...(cleanupFinding
|
|
705
|
+
? [withRuntimeEvidence(cleanupFinding, serverName)]
|
|
706
|
+
: [])
|
|
707
|
+
],
|
|
708
|
+
scorecard,
|
|
709
|
+
...(runtimeExecuted ? { execution: launch.evidence } : {})
|
|
710
|
+
});
|
|
711
|
+
})();
|
|
647
712
|
};
|
|
648
713
|
const sendRequest = (method, params, timeoutFinding) => new Promise((requestResolve, requestReject) => {
|
|
649
714
|
const id = nextRequestId++;
|
|
650
715
|
transcript?.(formatRequestTranscriptForLog(method, params));
|
|
716
|
+
const requestTimeoutMs = launch.evidence.backend === "docker" && method === "initialize"
|
|
717
|
+
? DOCKER_RUNTIME_STARTUP_TIMEOUT_MS
|
|
718
|
+
: startupTimeoutMs;
|
|
651
719
|
const timer = setTimeout(() => {
|
|
652
720
|
pendingRequests.delete(id);
|
|
653
721
|
requestReject(timeoutFinding);
|
|
654
|
-
},
|
|
722
|
+
}, requestTimeoutMs);
|
|
655
723
|
pendingRequests.set(id, {
|
|
656
724
|
method,
|
|
657
725
|
resolve: requestResolve,
|
|
@@ -701,6 +769,9 @@ async function probeCommandServer(input) {
|
|
|
701
769
|
}
|
|
702
770
|
stderrPreview += chunk.toString();
|
|
703
771
|
});
|
|
772
|
+
child.on("spawn", () => {
|
|
773
|
+
runtimeExecuted = true;
|
|
774
|
+
});
|
|
704
775
|
stdoutReader.on("line", (line) => {
|
|
705
776
|
if (settled) {
|
|
706
777
|
return;
|
|
@@ -731,7 +802,9 @@ async function probeCommandServer(input) {
|
|
|
731
802
|
});
|
|
732
803
|
child.on("error", () => {
|
|
733
804
|
scorecard.initialize = "fail";
|
|
734
|
-
settle(buildFailure("plugin.runtime.startup.failed", `The MCP server \`${serverName}\` could not be started.`, "The configured stdio server could not be launched, so runtime validation cannot proceed.",
|
|
805
|
+
settle(buildFailure("plugin.runtime.startup.failed", `The MCP server \`${serverName}\` could not be started.`, "The configured stdio server could not be launched, so runtime validation cannot proceed.", launch.evidence.backend === "docker"
|
|
806
|
+
? "Verify Docker is installed and the Docker daemon is available."
|
|
807
|
+
: `Verify the command \`${command}\` is installed and executable from \`${cwd}\`.`));
|
|
735
808
|
});
|
|
736
809
|
child.on("exit", () => {
|
|
737
810
|
if (settled || finalizeRequested) {
|
|
@@ -740,9 +813,11 @@ async function probeCommandServer(input) {
|
|
|
740
813
|
if (scorecard.initialize === "skipped") {
|
|
741
814
|
scorecard.initialize = "fail";
|
|
742
815
|
}
|
|
743
|
-
settle(buildFailure("plugin.runtime.exited_early", `The MCP server \`${serverName}\` exited before the startup probe completed.`, "A server that exits immediately is unlikely to remain available for Codex during normal use.", stderrPreview.trim().length > 0
|
|
816
|
+
settle(buildFailure("plugin.runtime.exited_early", `The MCP server \`${serverName}\` exited before the startup probe completed.`, "A server that exits immediately is unlikely to remain available for Codex during normal use.", launch.evidence.backend !== "docker" && stderrPreview.trim().length > 0
|
|
744
817
|
? `Inspect the startup error output: ${stderrPreview.trim()}`
|
|
745
|
-
:
|
|
818
|
+
: launch.evidence.backend === "docker"
|
|
819
|
+
? "Verify Docker can start the pinned runtime image and keep the server process running."
|
|
820
|
+
: `Keep the \`${serverName}\` process running after startup and inspect its command or arguments.`));
|
|
746
821
|
});
|
|
747
822
|
const runProtocolProbe = async () => {
|
|
748
823
|
const initializeResponse = await sendRequest("initialize", {
|
|
@@ -917,9 +992,11 @@ async function probeCommandServer(input) {
|
|
|
917
992
|
if (scorecard.initialize === "skipped") {
|
|
918
993
|
scorecard.initialize = "fail";
|
|
919
994
|
}
|
|
920
|
-
settle(buildFailure("plugin.runtime.protocol.unhandled", `The MCP server \`${serverName}\` triggered an unexpected protocol probe failure.`, "Unexpected probe failures reduce confidence in runtime validation results.", stderrPreview.trim().length > 0
|
|
995
|
+
settle(buildFailure("plugin.runtime.protocol.unhandled", `The MCP server \`${serverName}\` triggered an unexpected protocol probe failure.`, "Unexpected probe failures reduce confidence in runtime validation results.", launch.evidence.backend !== "docker" && stderrPreview.trim().length > 0
|
|
921
996
|
? `Inspect stderr for details: ${stderrPreview.trim()}`
|
|
922
|
-
:
|
|
997
|
+
: launch.evidence.backend === "docker"
|
|
998
|
+
? "Inspect Docker availability and the server protocol implementation without exposing raw runtime output."
|
|
999
|
+
: "Inspect the server output and protocol implementation for unexpected runtime errors."));
|
|
923
1000
|
});
|
|
924
1001
|
});
|
|
925
1002
|
}
|
|
@@ -934,6 +1011,7 @@ export async function probeRuntime(discoveredPackage, options = {}) {
|
|
|
934
1011
|
}
|
|
935
1012
|
const findings = [];
|
|
936
1013
|
let scorecard = createRuntimeScorecard();
|
|
1014
|
+
let execution;
|
|
937
1015
|
for (const [serverName, config] of Object.entries(servers)) {
|
|
938
1016
|
if (!isPlainObject(config)) {
|
|
939
1017
|
continue;
|
|
@@ -950,19 +1028,23 @@ export async function probeRuntime(discoveredPackage, options = {}) {
|
|
|
950
1028
|
: discoveredPackage.rootPath;
|
|
951
1029
|
const result = await probeCommandServer({
|
|
952
1030
|
serverName,
|
|
1031
|
+
packageRoot: discoveredPackage.rootPath,
|
|
953
1032
|
command,
|
|
954
1033
|
args,
|
|
955
1034
|
cwd,
|
|
956
1035
|
startupTimeoutMs,
|
|
1036
|
+
sandbox: options.sandbox,
|
|
957
1037
|
transcript: options.transcript
|
|
958
1038
|
});
|
|
959
1039
|
scorecard = result.scorecard;
|
|
1040
|
+
execution = result.execution ?? execution;
|
|
960
1041
|
if (result.findings.length > 0) {
|
|
961
1042
|
findings.push(...result.findings);
|
|
962
1043
|
}
|
|
963
1044
|
}
|
|
964
1045
|
return {
|
|
965
1046
|
findings,
|
|
966
|
-
scorecard
|
|
1047
|
+
scorecard,
|
|
1048
|
+
...(execution ? { execution } : {})
|
|
967
1049
|
};
|
|
968
1050
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { RuntimeExecutionEvidence, RuntimeSandboxMode } from "../domain/types.js";
|
|
2
|
+
export interface RuntimeLaunchInput {
|
|
3
|
+
sandbox: RuntimeSandboxMode;
|
|
4
|
+
packageRoot: string;
|
|
5
|
+
cwd: string;
|
|
6
|
+
command: string;
|
|
7
|
+
args: string[];
|
|
8
|
+
containerName: string;
|
|
9
|
+
}
|
|
10
|
+
export interface RuntimeLaunchSpec {
|
|
11
|
+
command: string;
|
|
12
|
+
args: string[];
|
|
13
|
+
cwd: string;
|
|
14
|
+
containerName: string;
|
|
15
|
+
evidence: RuntimeExecutionEvidence;
|
|
16
|
+
}
|
|
17
|
+
export declare class RuntimeSandboxError extends Error {
|
|
18
|
+
}
|
|
19
|
+
export declare const DOCKER_RUNTIME_STARTUP_TIMEOUT_MS = 60000;
|
|
20
|
+
export declare const DOCKER_RUNTIME_IMAGE = "node:22-bookworm-slim@sha256:6c74791e557ce11fc957704f6d4fe134a7bc8d6f5ca4403205b2966bd488f6b3";
|
|
21
|
+
export declare function buildRuntimeLaunch(input: RuntimeLaunchInput): RuntimeLaunchSpec;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
export class RuntimeSandboxError extends Error {
|
|
3
|
+
}
|
|
4
|
+
export const DOCKER_RUNTIME_STARTUP_TIMEOUT_MS = 60_000;
|
|
5
|
+
export const DOCKER_RUNTIME_IMAGE = "node:22-bookworm-slim@sha256:6c74791e557ce11fc957704f6d4fe134a7bc8d6f5ca4403205b2966bd488f6b3";
|
|
6
|
+
export function buildRuntimeLaunch(input) {
|
|
7
|
+
if (input.sandbox !== "docker") {
|
|
8
|
+
throw new RuntimeSandboxError("Unsupported sandbox mode.");
|
|
9
|
+
}
|
|
10
|
+
if (!/^(node|node\.exe)$/i.test(path.basename(input.command))) {
|
|
11
|
+
throw new RuntimeSandboxError("Docker sandbox supports Node.js stdio commands only.");
|
|
12
|
+
}
|
|
13
|
+
const relativeCwd = path.relative(input.packageRoot, input.cwd);
|
|
14
|
+
if (relativeCwd === ".." ||
|
|
15
|
+
relativeCwd.startsWith(`..${path.sep}`) ||
|
|
16
|
+
path.isAbsolute(relativeCwd)) {
|
|
17
|
+
throw new RuntimeSandboxError("Runtime cwd escapes the package root.");
|
|
18
|
+
}
|
|
19
|
+
if (input.packageRoot.includes(",")) {
|
|
20
|
+
throw new RuntimeSandboxError("Docker sandbox does not support commas in package paths.");
|
|
21
|
+
}
|
|
22
|
+
const workdir = relativeCwd
|
|
23
|
+
? `/workspace/${relativeCwd.split(path.sep).join("/")}`
|
|
24
|
+
: "/workspace";
|
|
25
|
+
return {
|
|
26
|
+
command: "docker",
|
|
27
|
+
args: [
|
|
28
|
+
"run",
|
|
29
|
+
"--rm",
|
|
30
|
+
"-i",
|
|
31
|
+
"--init",
|
|
32
|
+
"--name",
|
|
33
|
+
input.containerName,
|
|
34
|
+
"--network",
|
|
35
|
+
"none",
|
|
36
|
+
"--read-only",
|
|
37
|
+
"--cap-drop",
|
|
38
|
+
"ALL",
|
|
39
|
+
"--security-opt",
|
|
40
|
+
"no-new-privileges",
|
|
41
|
+
"--user",
|
|
42
|
+
"65532:65532",
|
|
43
|
+
"--pids-limit",
|
|
44
|
+
"64",
|
|
45
|
+
"--memory",
|
|
46
|
+
"256m",
|
|
47
|
+
"--cpus",
|
|
48
|
+
"1",
|
|
49
|
+
"--tmpfs",
|
|
50
|
+
"/tmp:rw,noexec,nosuid,size=16m",
|
|
51
|
+
"--mount",
|
|
52
|
+
`type=bind,source=${input.packageRoot},target=/workspace,readonly`,
|
|
53
|
+
"--workdir",
|
|
54
|
+
workdir,
|
|
55
|
+
DOCKER_RUNTIME_IMAGE,
|
|
56
|
+
"node",
|
|
57
|
+
...input.args
|
|
58
|
+
],
|
|
59
|
+
cwd: input.packageRoot,
|
|
60
|
+
containerName: input.containerName,
|
|
61
|
+
evidence: {
|
|
62
|
+
backend: "docker",
|
|
63
|
+
image: DOCKER_RUNTIME_IMAGE,
|
|
64
|
+
network: "none",
|
|
65
|
+
packageMount: "read_only"
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -404,18 +404,23 @@ export async function validatePlugin(targetPath, options = {}) {
|
|
|
404
404
|
findings: withFindingFingerprints([await buildMissingManifestFailure(rootPath)], rootPath)
|
|
405
405
|
};
|
|
406
406
|
}
|
|
407
|
-
const
|
|
407
|
+
const staticFindings = [
|
|
408
|
+
...validateRequiredManifestFields(discoveredPackage),
|
|
409
|
+
...(await validateSkillsDirectory(discoveredPackage)),
|
|
410
|
+
...(await validateSkillDefinitions(discoveredPackage)),
|
|
411
|
+
...(await validateMcpConfig(discoveredPackage))
|
|
412
|
+
];
|
|
413
|
+
const staticFailed = staticFindings.some((finding) => finding.severity === "fail");
|
|
414
|
+
const runtimeResult = options.runtime && !staticFailed
|
|
408
415
|
? await probeRuntime(discoveredPackage, {
|
|
409
416
|
startupTimeoutMs: options.runtimeStartupTimeoutMs,
|
|
417
|
+
sandbox: options.runtimeSandbox,
|
|
410
418
|
transcript: options.runtimeTranscript
|
|
411
419
|
})
|
|
412
420
|
: null;
|
|
413
421
|
const findings = [
|
|
414
|
-
...
|
|
415
|
-
...(
|
|
416
|
-
...(await validateSkillDefinitions(discoveredPackage)),
|
|
417
|
-
...(await validateMcpConfig(discoveredPackage)),
|
|
418
|
-
...(runtimeResult ? runtimeResult.findings : [])
|
|
422
|
+
...staticFindings,
|
|
423
|
+
...(runtimeResult?.findings ?? [])
|
|
419
424
|
];
|
|
420
425
|
const fingerprintedFindings = withFindingFingerprints(findings, discoveredPackage.rootPath);
|
|
421
426
|
const hasFailures = fingerprintedFindings.some((finding) => finding.severity === "fail");
|
|
@@ -426,7 +431,10 @@ export async function validatePlugin(targetPath, options = {}) {
|
|
|
426
431
|
status: "pass",
|
|
427
432
|
exitCode: 0,
|
|
428
433
|
findings: [],
|
|
429
|
-
...(runtimeResult ? { runtimeScorecard: runtimeResult.scorecard } : {})
|
|
434
|
+
...(runtimeResult ? { runtimeScorecard: runtimeResult.scorecard } : {}),
|
|
435
|
+
...(runtimeResult?.execution
|
|
436
|
+
? { runtimeExecution: runtimeResult.execution }
|
|
437
|
+
: {})
|
|
430
438
|
};
|
|
431
439
|
}
|
|
432
440
|
return {
|
|
@@ -434,6 +442,9 @@ export async function validatePlugin(targetPath, options = {}) {
|
|
|
434
442
|
status: hasFailures ? "fail" : "warn",
|
|
435
443
|
exitCode: hasFailures ? 1 : 0,
|
|
436
444
|
findings: fingerprintedFindings,
|
|
437
|
-
...(runtimeResult ? { runtimeScorecard: runtimeResult.scorecard } : {})
|
|
445
|
+
...(runtimeResult ? { runtimeScorecard: runtimeResult.scorecard } : {}),
|
|
446
|
+
...(runtimeResult?.execution
|
|
447
|
+
? { runtimeExecution: runtimeResult.execution }
|
|
448
|
+
: {})
|
|
438
449
|
};
|
|
439
450
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type PackageAnalysisOptions } from "./package-analysis.js";
|
|
2
|
+
import type { ExternalValidationCorpusReport } from "./external-validation-corpus.js";
|
|
2
3
|
type ValidationStatus = "pass" | "warn" | "fail";
|
|
3
4
|
export interface ValidationCorpusCaseDefinition {
|
|
4
5
|
id: string;
|
|
@@ -36,6 +37,7 @@ export interface ValidationCorpusCaseResult {
|
|
|
36
37
|
export interface DoctorValidationCorpusReport {
|
|
37
38
|
schemaVersion: "1.0.0";
|
|
38
39
|
kind: "doctor.validation.corpus";
|
|
40
|
+
corpusType?: "bundled";
|
|
39
41
|
generatedAt: string;
|
|
40
42
|
version: string;
|
|
41
43
|
summary: {
|
|
@@ -51,8 +53,8 @@ export interface BuildDoctorValidationCorpusOptions {
|
|
|
51
53
|
environment?: PackageAnalysisOptions["environment"];
|
|
52
54
|
}
|
|
53
55
|
export declare function buildDoctorValidationCorpusReport(options?: BuildDoctorValidationCorpusOptions): Promise<DoctorValidationCorpusReport>;
|
|
54
|
-
export declare function renderDoctorValidationCorpusJson(report: DoctorValidationCorpusReport): string;
|
|
55
|
-
export declare function renderDoctorValidationCorpusReport(report: DoctorValidationCorpusReport, options?: {
|
|
56
|
+
export declare function renderDoctorValidationCorpusJson(report: DoctorValidationCorpusReport | ExternalValidationCorpusReport): string;
|
|
57
|
+
export declare function renderDoctorValidationCorpusReport(report: DoctorValidationCorpusReport | ExternalValidationCorpusReport, options?: {
|
|
56
58
|
outputPath?: string | null;
|
|
57
59
|
}): string;
|
|
58
60
|
export {};
|
|
@@ -163,8 +163,19 @@ export function renderDoctorValidationCorpusReport(report, options = {}) {
|
|
|
163
163
|
}
|
|
164
164
|
lines.push("", "Cases", "-----");
|
|
165
165
|
for (const caseResult of report.cases) {
|
|
166
|
+
const actual = caseResult.actual;
|
|
167
|
+
let status;
|
|
168
|
+
let findingCount;
|
|
169
|
+
if ("status" in actual) {
|
|
170
|
+
status = actual.status;
|
|
171
|
+
findingCount = actual.findings.length;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
status = actual.validationStatus;
|
|
175
|
+
findingCount = actual.findingIds.length;
|
|
176
|
+
}
|
|
166
177
|
lines.push(`${caseResult.id}: ${caseResult.expectationMatched ? "PASS" : "FAIL"} ` +
|
|
167
|
-
`(${
|
|
178
|
+
`(${status.toUpperCase()}, ${findingCount} findings)`);
|
|
168
179
|
}
|
|
169
180
|
return lines.join("\n");
|
|
170
181
|
}
|
package/dist/domain/types.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export interface CheckResult {
|
|
|
30
30
|
suppressedFindings?: SuppressedFinding[];
|
|
31
31
|
suppressionSummary?: SuppressionSummary;
|
|
32
32
|
runtimeScorecard?: RuntimeScorecard;
|
|
33
|
+
runtimeExecution?: RuntimeExecutionEvidence;
|
|
33
34
|
baselinedFindings?: Finding[];
|
|
34
35
|
baselineSummary?: BaselineSummary;
|
|
35
36
|
}
|
|
@@ -42,6 +43,7 @@ export interface CheckOptions {
|
|
|
42
43
|
runtime?: boolean;
|
|
43
44
|
runtimeTranscript?: (line: string) => void;
|
|
44
45
|
runtimeStartupTimeoutMs?: number;
|
|
46
|
+
runtimeSandbox?: RuntimeSandboxMode;
|
|
45
47
|
}
|
|
46
48
|
export interface PluginManifest {
|
|
47
49
|
name?: string;
|
|
@@ -61,6 +63,7 @@ export interface JsonReportSummary {
|
|
|
61
63
|
exitCode: 0 | 1;
|
|
62
64
|
runtimeProbeEnabled: boolean;
|
|
63
65
|
runtimeScorecard?: RuntimeScorecard;
|
|
66
|
+
runtimeExecution?: RuntimeExecutionEvidence;
|
|
64
67
|
findingCounts: {
|
|
65
68
|
fail: number;
|
|
66
69
|
warn: number;
|
|
@@ -91,4 +94,12 @@ export interface RuntimeScorecard {
|
|
|
91
94
|
export interface RuntimeProbeResult {
|
|
92
95
|
findings: Finding[];
|
|
93
96
|
scorecard: RuntimeScorecard;
|
|
97
|
+
execution?: RuntimeExecutionEvidence;
|
|
98
|
+
}
|
|
99
|
+
export type RuntimeSandboxMode = "docker";
|
|
100
|
+
export interface RuntimeExecutionEvidence {
|
|
101
|
+
backend: "native" | "docker";
|
|
102
|
+
image: string | null;
|
|
103
|
+
network: "host" | "none";
|
|
104
|
+
packageMount: "host" | "read_only";
|
|
94
105
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export { buildDoctorSnapshot, renderDoctorSnapshot, renderDoctorSnapshotJson, ty
|
|
|
10
10
|
export { buildDoctorRecommendations, renderDoctorRecommendations, renderDoctorRecommendationsJson, type DoctorRecommendationAction, type DoctorRecommendationsReport } from "./core/doctor-recommendations.js";
|
|
11
11
|
export { buildDoctorExportBundle, renderDoctorExportBundle, renderDoctorExportBundleJson, type DoctorExportBundle } from "./core/doctor-export-bundle.js";
|
|
12
12
|
export { buildDoctorAttestation, renderDoctorAttestation, renderDoctorAttestationJson, renderDoctorAttestationVerification, renderDoctorAttestationVerificationJson, verifyDoctorAttestation, type DoctorAttestation, type DoctorAttestationVerificationCheck, type DoctorAttestationVerificationReport, type Digest, type PackageFingerprint } from "./core/attestation.js";
|
|
13
|
+
export { buildExternalValidationCorpusReport, ExternalCorpusManifestError, loadExternalCorpusManifest, type BuildExternalValidationCorpusOptions, type ExternalCorpusCaseResult, type ExternalCorpusClassificationCounts, type ExternalCorpusFindingReview, type ExternalCorpusManifest, type ExternalCorpusMode, type ExternalCorpusProfile, type ExternalCorpusSourceType, type ExternalValidationCorpusReport, type FindingReviewClassification } from "./core/external-validation-corpus.js";
|
|
14
|
+
export { buildCorpusQualityMetricsReport, CorpusMetricsManifestError, loadCorpusMetricsManifest, reconcileCorpusMetricFindings, renderCorpusQualityMetricsJson, renderCorpusQualityMetricsMarkdown, renderCorpusQualityMetricsText, type BuildCorpusMetricsOptions, type CorpusMetricCounts, type CorpusMetricFinding, type CorpusMetricFindingReview, type CorpusMetricReconciliation, type CorpusMetricsManifest, type CorpusMetricSource, type CorpusMetricThresholdCheck, type CorpusMetricThresholds, type CorpusMetricValues, type CorpusQualityMetricsReport, type CorpusQualityMetricsTargetResult } from "./core/corpus-quality-metrics.js";
|
|
13
15
|
export { buildDoctorOutputContract, renderDoctorOutputContract, renderDoctorOutputContractJson, type DoctorOutputContract, type OutputContractRule, type OutputContractSchema } from "./core/output-contract.js";
|
|
14
16
|
export { buildDoctorValidationCorpusReport, renderDoctorValidationCorpusJson, renderDoctorValidationCorpusReport, type BuildDoctorValidationCorpusOptions, type DoctorValidationCorpusReport, type ValidationCorpusCaseDefinition, type ValidationCorpusCaseResult } from "./core/validation-corpus.js";
|
|
15
17
|
export { buildDoctorExportBundleFromAnalysis, buildDoctorRecommendationsFromAnalysis, buildPackageAnalysis, type PackageAnalysis, type PackageAnalysisOptions, type PackageAnalysisStage, type PackageAnalysisTiming } from "./core/package-analysis.js";
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,8 @@ export { buildDoctorSnapshot, renderDoctorSnapshot, renderDoctorSnapshotJson } f
|
|
|
10
10
|
export { buildDoctorRecommendations, renderDoctorRecommendations, renderDoctorRecommendationsJson } from "./core/doctor-recommendations.js";
|
|
11
11
|
export { buildDoctorExportBundle, renderDoctorExportBundle, renderDoctorExportBundleJson } from "./core/doctor-export-bundle.js";
|
|
12
12
|
export { buildDoctorAttestation, renderDoctorAttestation, renderDoctorAttestationJson, renderDoctorAttestationVerification, renderDoctorAttestationVerificationJson, verifyDoctorAttestation } from "./core/attestation.js";
|
|
13
|
+
export { buildExternalValidationCorpusReport, ExternalCorpusManifestError, loadExternalCorpusManifest } from "./core/external-validation-corpus.js";
|
|
14
|
+
export { buildCorpusQualityMetricsReport, CorpusMetricsManifestError, loadCorpusMetricsManifest, reconcileCorpusMetricFindings, renderCorpusQualityMetricsJson, renderCorpusQualityMetricsMarkdown, renderCorpusQualityMetricsText } from "./core/corpus-quality-metrics.js";
|
|
13
15
|
export { buildDoctorOutputContract, renderDoctorOutputContract, renderDoctorOutputContractJson } from "./core/output-contract.js";
|
|
14
16
|
export { buildDoctorValidationCorpusReport, renderDoctorValidationCorpusJson, renderDoctorValidationCorpusReport } from "./core/validation-corpus.js";
|
|
15
17
|
export { buildDoctorExportBundleFromAnalysis, buildDoctorRecommendationsFromAnalysis, buildPackageAnalysis } from "./core/package-analysis.js";
|
|
@@ -12,6 +12,9 @@ export function buildJsonReport(result, options) {
|
|
|
12
12
|
...(result.runtimeScorecard
|
|
13
13
|
? { runtimeScorecard: result.runtimeScorecard }
|
|
14
14
|
: {}),
|
|
15
|
+
...(result.runtimeExecution
|
|
16
|
+
? { runtimeExecution: result.runtimeExecution }
|
|
17
|
+
: {}),
|
|
15
18
|
findingCounts: {
|
|
16
19
|
fail: failCount,
|
|
17
20
|
warn: warnCount,
|
|
@@ -23,6 +23,9 @@ export function buildMarkdownReport(result, options) {
|
|
|
23
23
|
if (result.baselineSummary) {
|
|
24
24
|
lines.push(`| Baseline New | ${result.baselineSummary.new} |`, `| Baseline Matched | ${result.baselineSummary.matched} |`, `| Baseline Resolved | ${result.baselineSummary.resolved} |`);
|
|
25
25
|
}
|
|
26
|
+
if (result.runtimeExecution) {
|
|
27
|
+
lines.push("", `Runtime backend: ${result.runtimeExecution.backend.toUpperCase()}`, `Runtime isolation: network=${result.runtimeExecution.network}, package=${result.runtimeExecution.packageMount}`);
|
|
28
|
+
}
|
|
26
29
|
if (result.findings.length === 0 && !result.suppressedFindings?.length) {
|
|
27
30
|
lines.push("", result.baselineSummary ? "No new findings." : "No findings.");
|
|
28
31
|
return lines.join("\n");
|
|
@@ -42,6 +42,9 @@ export function renderTextReport(result, options = {}) {
|
|
|
42
42
|
if (result.baselineSummary) {
|
|
43
43
|
lines.push(`Baseline: ${result.baselineSummary.new} new, ${result.baselineSummary.matched} matched, ${result.baselineSummary.resolved} resolved`);
|
|
44
44
|
}
|
|
45
|
+
if (result.runtimeExecution) {
|
|
46
|
+
lines.push(`Runtime backend: ${result.runtimeExecution.backend.toUpperCase()}`, `Runtime isolation: network=${result.runtimeExecution.network}, package=${result.runtimeExecution.packageMount}`);
|
|
47
|
+
}
|
|
45
48
|
if (result.findings.length === 0 && !result.suppressedFindings?.length) {
|
|
46
49
|
if (result.runtimeScorecard) {
|
|
47
50
|
lines.push("", "Runtime Scorecard", "----------------");
|
package/dist/run-cli.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildCorpusQualityMetricsReport } from "./core/corpus-quality-metrics.js";
|
|
1
2
|
import { runCheck } from "./index.js";
|
|
2
3
|
import { writeRawDoctorConfig } from "./index.js";
|
|
3
4
|
export interface CliIo {
|
|
@@ -18,5 +19,6 @@ export interface RunCliOptions {
|
|
|
18
19
|
now?: () => Date;
|
|
19
20
|
releaseAssetUploadImpl?: (args: string[]) => Promise<void>;
|
|
20
21
|
resolveLatestVersion?: () => Promise<string>;
|
|
22
|
+
buildCorpusMetricsReportImpl?: typeof buildCorpusQualityMetricsReport;
|
|
21
23
|
}
|
|
22
24
|
export declare function runCli(args: string[], io?: CliIo, options?: RunCliOptions): Promise<number>;
|