codex-plugin-doctor 1.15.0 → 1.17.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 +2 -2
- package/dist/core/dep-audit.d.ts +17 -0
- package/dist/core/dep-audit.js +169 -0
- package/dist/core/init-git-hooks.d.ts +8 -0
- package/dist/core/init-git-hooks.js +77 -0
- package/dist/core/review-bundle.d.ts +3 -1
- package/dist/core/review-bundle.js +4 -1
- package/dist/core/watch-plugin.d.ts +13 -0
- package/dist/core/watch-plugin.js +87 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/run-cli.js +73 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -358,9 +358,9 @@ jobs:
|
|
|
358
358
|
runs-on: ubuntu-latest
|
|
359
359
|
steps:
|
|
360
360
|
- uses: actions/checkout@v5
|
|
361
|
-
- uses: Esquetta/CodexPluginDoctor@v1.
|
|
361
|
+
- uses: Esquetta/CodexPluginDoctor@v1.17.0
|
|
362
362
|
with:
|
|
363
|
-
version: "1.
|
|
363
|
+
version: "1.17.0"
|
|
364
364
|
path: .
|
|
365
365
|
runtime: "true"
|
|
366
366
|
policy: codex-publish
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface DepAuditVulnerability {
|
|
2
|
+
name: string;
|
|
3
|
+
severity: "critical" | "high" | "moderate" | "low";
|
|
4
|
+
isDirect: boolean;
|
|
5
|
+
fixAvailable: boolean;
|
|
6
|
+
via: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface DepAuditReport {
|
|
9
|
+
targetPath: string;
|
|
10
|
+
status: "pass" | "warn" | "fail";
|
|
11
|
+
vulnerabilities: DepAuditVulnerability[];
|
|
12
|
+
totalVulnerabilities: number;
|
|
13
|
+
auditJson: unknown;
|
|
14
|
+
}
|
|
15
|
+
export declare function buildDepAudit(targetPath: string): Promise<DepAuditReport>;
|
|
16
|
+
export declare function renderDepAudit(report: DepAuditReport): string;
|
|
17
|
+
export declare function renderDepAuditJson(report: DepAuditReport): string;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { readFile, stat } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
function resolvePackageJson(targetPath) {
|
|
5
|
+
return path.resolve(targetPath, "package.json");
|
|
6
|
+
}
|
|
7
|
+
async function fileExists(filePath) {
|
|
8
|
+
try {
|
|
9
|
+
const details = await stat(filePath);
|
|
10
|
+
return details.isFile();
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function runNpmAudit(cwd) {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
execFile("npm", ["audit", "--json"], { cwd, shell: process.platform === "win32", timeout: 120_000 }, (error, stdout, stderr) => {
|
|
19
|
+
const parsed = (() => {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(stdout);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
})();
|
|
27
|
+
if (parsed) {
|
|
28
|
+
resolve(parsed);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
reject(new Error(stderr.trim() || error?.message || "npm audit failed"));
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function extractVulnerabilities(auditJson) {
|
|
37
|
+
const result = [];
|
|
38
|
+
if (!auditJson || typeof auditJson !== "object") {
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
const data = auditJson;
|
|
42
|
+
const vulns = data.vulnerabilities;
|
|
43
|
+
if (!vulns) {
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
for (const [name, entry] of Object.entries(vulns)) {
|
|
47
|
+
if (!entry || typeof entry !== "object") {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const vuln = entry;
|
|
51
|
+
const severity = vuln.severity;
|
|
52
|
+
const isDirect = Boolean(vuln.isDirect);
|
|
53
|
+
const fixAvailable = Boolean(vuln.fixAvailable);
|
|
54
|
+
const via = Array.isArray(vuln.via) ? vuln.via.map((v) => (typeof v === "string" ? v : "unknown")) : [];
|
|
55
|
+
if (severity === "critical" || severity === "high" || severity === "moderate" || severity === "low") {
|
|
56
|
+
result.push({
|
|
57
|
+
name,
|
|
58
|
+
severity,
|
|
59
|
+
isDirect,
|
|
60
|
+
fixAvailable,
|
|
61
|
+
via
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
function computeStatus(vulnerabilities) {
|
|
68
|
+
if (vulnerabilities.length === 0) {
|
|
69
|
+
return "pass";
|
|
70
|
+
}
|
|
71
|
+
const hasCritical = vulnerabilities.some((v) => v.severity === "critical");
|
|
72
|
+
const hasHigh = vulnerabilities.some((v) => v.severity === "high");
|
|
73
|
+
if (hasCritical) {
|
|
74
|
+
return "fail";
|
|
75
|
+
}
|
|
76
|
+
if (hasHigh) {
|
|
77
|
+
return "fail";
|
|
78
|
+
}
|
|
79
|
+
return "warn";
|
|
80
|
+
}
|
|
81
|
+
export async function buildDepAudit(targetPath) {
|
|
82
|
+
const resolvedPath = path.resolve(targetPath);
|
|
83
|
+
const packageJsonPath = resolvePackageJson(resolvedPath);
|
|
84
|
+
if (!(await fileExists(packageJsonPath))) {
|
|
85
|
+
return {
|
|
86
|
+
targetPath: resolvedPath,
|
|
87
|
+
status: "pass",
|
|
88
|
+
vulnerabilities: [],
|
|
89
|
+
totalVulnerabilities: 0,
|
|
90
|
+
auditJson: null
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
let pkg;
|
|
94
|
+
try {
|
|
95
|
+
pkg = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return {
|
|
99
|
+
targetPath: resolvedPath,
|
|
100
|
+
status: "warn",
|
|
101
|
+
vulnerabilities: [],
|
|
102
|
+
totalVulnerabilities: 0,
|
|
103
|
+
auditJson: { error: "Failed to parse package.json" }
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const hasDeps = pkg.dependencies || pkg.devDependencies;
|
|
107
|
+
if (!hasDeps) {
|
|
108
|
+
return {
|
|
109
|
+
targetPath: resolvedPath,
|
|
110
|
+
status: "pass",
|
|
111
|
+
vulnerabilities: [],
|
|
112
|
+
totalVulnerabilities: 0,
|
|
113
|
+
auditJson: { message: "No runtime dependencies to audit" }
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
let auditJson;
|
|
117
|
+
try {
|
|
118
|
+
auditJson = await runNpmAudit(resolvedPath);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
return {
|
|
122
|
+
targetPath: resolvedPath,
|
|
123
|
+
status: "warn",
|
|
124
|
+
vulnerabilities: [],
|
|
125
|
+
totalVulnerabilities: 0,
|
|
126
|
+
auditJson: { error: error.message }
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const vulnerabilities = extractVulnerabilities(auditJson);
|
|
130
|
+
const status = computeStatus(vulnerabilities);
|
|
131
|
+
return {
|
|
132
|
+
targetPath: resolvedPath,
|
|
133
|
+
status,
|
|
134
|
+
vulnerabilities,
|
|
135
|
+
totalVulnerabilities: vulnerabilities.length,
|
|
136
|
+
auditJson
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
export function renderDepAudit(report) {
|
|
140
|
+
const lines = [
|
|
141
|
+
"Dependency Vulnerability Audit",
|
|
142
|
+
"=============================",
|
|
143
|
+
`Path: ${report.targetPath}`,
|
|
144
|
+
`Status: ${report.status.toUpperCase()}`,
|
|
145
|
+
`Vulnerabilities: ${report.totalVulnerabilities}`,
|
|
146
|
+
""
|
|
147
|
+
];
|
|
148
|
+
if (report.vulnerabilities.length === 0) {
|
|
149
|
+
lines.push("No known vulnerabilities found.");
|
|
150
|
+
return lines.join("\n");
|
|
151
|
+
}
|
|
152
|
+
for (const vuln of report.vulnerabilities) {
|
|
153
|
+
const tag = vuln.severity === "critical" ? "CRITICAL" :
|
|
154
|
+
vuln.severity === "high" ? "HIGH" :
|
|
155
|
+
vuln.severity === "moderate" ? "MODERATE" : "LOW";
|
|
156
|
+
lines.push(`${tag.padEnd(10)} ${vuln.name}`, ` Direct: ${vuln.isDirect ? "yes" : "no"}`, ` Fix available: ${vuln.fixAvailable ? "yes" : "no"}`, vuln.via.length > 0 ? ` Via: ${vuln.via.join(", ")}` : "", "");
|
|
157
|
+
}
|
|
158
|
+
return lines.join("\n");
|
|
159
|
+
}
|
|
160
|
+
export function renderDepAuditJson(report) {
|
|
161
|
+
return JSON.stringify({
|
|
162
|
+
schemaVersion: "1.0.0",
|
|
163
|
+
targetPath: report.targetPath,
|
|
164
|
+
status: report.status,
|
|
165
|
+
totalVulnerabilities: report.totalVulnerabilities,
|
|
166
|
+
vulnerabilities: report.vulnerabilities,
|
|
167
|
+
audit: report.auditJson
|
|
168
|
+
}, null, 2);
|
|
169
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { chmod, mkdir, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const preCommitHook = [
|
|
4
|
+
"#!/usr/bin/env sh",
|
|
5
|
+
"# Generated by codex-plugin-doctor init git-hooks",
|
|
6
|
+
"# Validates the plugin package before every commit.",
|
|
7
|
+
"",
|
|
8
|
+
"echo \"Codex Plugin Doctor: running pre-commit validation...\"",
|
|
9
|
+
"",
|
|
10
|
+
"npx --yes codex-plugin-doctor check . --profile ci",
|
|
11
|
+
"",
|
|
12
|
+
"if [ $? -ne 0 ]; then",
|
|
13
|
+
" echo \"\"",
|
|
14
|
+
" echo \"Plugin validation failed. Commit blocked.\"",
|
|
15
|
+
" echo \"Run 'codex-plugin-doctor check .' to see full diagnostics.\"",
|
|
16
|
+
" exit 1",
|
|
17
|
+
"fi",
|
|
18
|
+
""
|
|
19
|
+
].join("\n");
|
|
20
|
+
const prePushHook = [
|
|
21
|
+
"#!/usr/bin/env sh",
|
|
22
|
+
"# Generated by codex-plugin-doctor init git-hooks",
|
|
23
|
+
"# Validates the plugin package before every push.",
|
|
24
|
+
"",
|
|
25
|
+
"echo \"Codex Plugin Doctor: running pre-push validation...\"",
|
|
26
|
+
"",
|
|
27
|
+
"npx --yes codex-plugin-doctor check . --profile ci --runtime",
|
|
28
|
+
"",
|
|
29
|
+
"if [ $? -ne 0 ]; then",
|
|
30
|
+
" echo \"\"",
|
|
31
|
+
" echo \"Plugin validation failed. Push blocked.\"",
|
|
32
|
+
" echo \"Run 'codex-plugin-doctor check . --runtime' to see full diagnostics.\"",
|
|
33
|
+
" exit 1",
|
|
34
|
+
"fi",
|
|
35
|
+
""
|
|
36
|
+
].join("\n");
|
|
37
|
+
async function fileExists(filePath) {
|
|
38
|
+
try {
|
|
39
|
+
await stat(filePath);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function writeHook(hookPath, content, force) {
|
|
47
|
+
const exists = await fileExists(hookPath);
|
|
48
|
+
if (exists && !force) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
await writeFile(hookPath, content, "utf8");
|
|
52
|
+
if (process.platform !== "win32") {
|
|
53
|
+
await chmod(hookPath, 0o755);
|
|
54
|
+
}
|
|
55
|
+
return exists;
|
|
56
|
+
}
|
|
57
|
+
export async function initGitHooks(targetPath, options = {}) {
|
|
58
|
+
const rootPath = path.resolve(targetPath);
|
|
59
|
+
const hooksDir = path.join(rootPath, ".git", "hooks");
|
|
60
|
+
const force = options.force ?? false;
|
|
61
|
+
await mkdir(hooksDir, { recursive: true });
|
|
62
|
+
const hookPaths = [];
|
|
63
|
+
const preExisting = [];
|
|
64
|
+
const preCommitPath = path.join(hooksDir, "pre-commit");
|
|
65
|
+
const prePushPath = path.join(hooksDir, "pre-push");
|
|
66
|
+
const wasPreCommitExisting = await writeHook(preCommitPath, preCommitHook, force);
|
|
67
|
+
hookPaths.push(preCommitPath);
|
|
68
|
+
if (wasPreCommitExisting) {
|
|
69
|
+
preExisting.push(preCommitPath);
|
|
70
|
+
}
|
|
71
|
+
const wasPrePushExisting = await writeHook(prePushPath, prePushHook, force);
|
|
72
|
+
hookPaths.push(prePushPath);
|
|
73
|
+
if (wasPrePushExisting) {
|
|
74
|
+
preExisting.push(prePushPath);
|
|
75
|
+
}
|
|
76
|
+
return { rootPath, hookPaths, preExisting };
|
|
77
|
+
}
|
|
@@ -125,7 +125,9 @@ export declare function verifyDoctorReviewBundle(bundleDirectory: string, option
|
|
|
125
125
|
targetPath: string;
|
|
126
126
|
}): Promise<DoctorReviewBundleVerificationReport>;
|
|
127
127
|
export declare function renderDoctorReviewBundleVerificationJson(report: DoctorReviewBundleVerificationReport): string;
|
|
128
|
-
export declare function renderDoctorReviewBundleVerification(report: DoctorReviewBundleVerificationReport
|
|
128
|
+
export declare function renderDoctorReviewBundleVerification(report: DoctorReviewBundleVerificationReport, options?: {
|
|
129
|
+
failuresOnly?: boolean;
|
|
130
|
+
}): string;
|
|
129
131
|
export declare function renderDoctorReviewBundleDiffJson(report: DoctorReviewBundleDiffReport): string;
|
|
130
132
|
export declare function renderDoctorReviewBundleDiff(report: DoctorReviewBundleDiffReport): string;
|
|
131
133
|
export declare function renderDoctorReviewBundle(bundle: DoctorReviewBundle): string;
|
|
@@ -730,7 +730,7 @@ export async function verifyDoctorReviewBundle(bundleDirectory, options) {
|
|
|
730
730
|
export function renderDoctorReviewBundleVerificationJson(report) {
|
|
731
731
|
return JSON.stringify(report, null, 2);
|
|
732
732
|
}
|
|
733
|
-
export function renderDoctorReviewBundleVerification(report) {
|
|
733
|
+
export function renderDoctorReviewBundleVerification(report, options = {}) {
|
|
734
734
|
const lines = [
|
|
735
735
|
"Doctor Review Bundle Verification",
|
|
736
736
|
"=================================",
|
|
@@ -757,6 +757,9 @@ export function renderDoctorReviewBundleVerification(report) {
|
|
|
757
757
|
lines.push(` ${check.message}`);
|
|
758
758
|
}
|
|
759
759
|
}
|
|
760
|
+
if (options.failuresOnly) {
|
|
761
|
+
return lines.join("\n");
|
|
762
|
+
}
|
|
760
763
|
lines.push("", "Checks", "------");
|
|
761
764
|
for (const check of report.checks) {
|
|
762
765
|
lines.push(`${check.status === "pass" ? "PASS" : "FAIL"} ${check.id}`);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface WatchPluginOptions {
|
|
2
|
+
targetPath: string;
|
|
3
|
+
debounceMs?: number;
|
|
4
|
+
runtime?: boolean;
|
|
5
|
+
jsonOutput?: boolean;
|
|
6
|
+
outputPath?: string | null;
|
|
7
|
+
}
|
|
8
|
+
export interface WatchPluginResult {
|
|
9
|
+
targetPath: string;
|
|
10
|
+
validations: number;
|
|
11
|
+
failures: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function watchPlugin(options: WatchPluginOptions): Promise<WatchPluginResult>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { watch } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { validatePlugin } from "./validate-plugin.js";
|
|
4
|
+
function renderWatchValidation(result, iteration, jsonOutput) {
|
|
5
|
+
if (jsonOutput) {
|
|
6
|
+
const report = {
|
|
7
|
+
schemaVersion: "1.0.0",
|
|
8
|
+
iteration,
|
|
9
|
+
timestamp: new Date().toISOString(),
|
|
10
|
+
targetPath: result.targetPath,
|
|
11
|
+
status: result.status,
|
|
12
|
+
findingsCount: result.findings.length,
|
|
13
|
+
findings: result.findings
|
|
14
|
+
};
|
|
15
|
+
return JSON.stringify(report, null, 2);
|
|
16
|
+
}
|
|
17
|
+
const icon = result.status === "pass" ? "PASS" : "FAIL";
|
|
18
|
+
return [
|
|
19
|
+
`[#${String(iteration).padStart(3, "0")}] ${icon} ${result.targetPath}`,
|
|
20
|
+
` findings: ${result.findings.length} (${result.status})`,
|
|
21
|
+
result.findings.length > 0
|
|
22
|
+
? result.findings.map((f) => ` ${f.severity === "fail" ? "FAIL" : "WARN"} ${f.id}: ${f.message}`).join("\n")
|
|
23
|
+
: ""
|
|
24
|
+
].filter(Boolean).join("\n");
|
|
25
|
+
}
|
|
26
|
+
export async function watchPlugin(options) {
|
|
27
|
+
const { targetPath, debounceMs = 300, runtime = false, jsonOutput = false, outputPath = null } = options;
|
|
28
|
+
const resolvedPath = path.resolve(targetPath);
|
|
29
|
+
let validations = 0;
|
|
30
|
+
let failures = 0;
|
|
31
|
+
let timer = null;
|
|
32
|
+
let pending = false;
|
|
33
|
+
const runValidation = async (iteration) => {
|
|
34
|
+
const result = await validatePlugin(resolvedPath, { runtime });
|
|
35
|
+
validations += 1;
|
|
36
|
+
if (result.status !== "pass") {
|
|
37
|
+
failures += 1;
|
|
38
|
+
}
|
|
39
|
+
const output = renderWatchValidation(result, iteration, jsonOutput);
|
|
40
|
+
if (outputPath) {
|
|
41
|
+
const { writeFile } = await import("node:fs/promises");
|
|
42
|
+
await writeFile(outputPath, output, "utf8");
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
process.stdout.write(`${output}\n\n`);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
return new Promise((resolve, _reject) => {
|
|
49
|
+
let iteration = 0;
|
|
50
|
+
const schedule = () => {
|
|
51
|
+
if (timer !== null) {
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
}
|
|
54
|
+
if (pending) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
pending = true;
|
|
58
|
+
timer = setTimeout(async () => {
|
|
59
|
+
pending = false;
|
|
60
|
+
timer = null;
|
|
61
|
+
iteration += 1;
|
|
62
|
+
await runValidation(iteration);
|
|
63
|
+
}, debounceMs);
|
|
64
|
+
};
|
|
65
|
+
const watcher = watch(resolvedPath, { recursive: true }, (_eventType, filename) => {
|
|
66
|
+
if (!filename || filename.includes("node_modules") || filename.startsWith(".git")) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
schedule();
|
|
70
|
+
});
|
|
71
|
+
watcher.on("error", (error) => {
|
|
72
|
+
process.stderr.write(`Watch error: ${error.message}\n`);
|
|
73
|
+
});
|
|
74
|
+
const handleExit = () => {
|
|
75
|
+
if (timer !== null) {
|
|
76
|
+
clearTimeout(timer);
|
|
77
|
+
}
|
|
78
|
+
watcher.close();
|
|
79
|
+
resolve({ targetPath: resolvedPath, validations, failures });
|
|
80
|
+
};
|
|
81
|
+
process.on("SIGINT", handleExit);
|
|
82
|
+
process.on("SIGTERM", handleExit);
|
|
83
|
+
process.on("SIGHUP", handleExit);
|
|
84
|
+
process.stdout.write(`Watching ${resolvedPath} for changes (debounce: ${debounceMs}ms, runtime: ${runtime ? "enabled" : "disabled"})...\nPress Ctrl+C to stop.\n\n`);
|
|
85
|
+
schedule();
|
|
86
|
+
});
|
|
87
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -19,4 +19,7 @@ export { buildDoctorInspectorReport, renderDoctorInspectorReport, renderDoctorIn
|
|
|
19
19
|
export { buildEcosystemAudit, renderEcosystemAudit, renderEcosystemAuditJson, type EcosystemAuditReport } from "./audit/ecosystem-audit.js";
|
|
20
20
|
export { applyPolicyToDoctorConfig, applyPolicyToSecurityAudit, parsePolicyPack, policyEnablesRuntime, policyFailsOnWarnings, policyPackNames, type PolicyPackName } from "./policy/policy-packs.js";
|
|
21
21
|
export { buildGenericMcpDoctor, renderGenericMcpDoctor, renderGenericMcpDoctorJson, type GenericMcpDoctorReport } from "./mcp/generic-mcp-doctor.js";
|
|
22
|
+
export { watchPlugin, type WatchPluginOptions, type WatchPluginResult } from "./core/watch-plugin.js";
|
|
23
|
+
export { buildDepAudit, renderDepAudit, renderDepAuditJson, type DepAuditReport, type DepAuditVulnerability } from "./core/dep-audit.js";
|
|
24
|
+
export { initGitHooks, type InitGitHooksResult } from "./core/init-git-hooks.js";
|
|
22
25
|
export declare function runCheck(targetPath: string, options?: CheckOptions): Promise<CheckResult>;
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,9 @@ export { buildDoctorInspectorReport, renderDoctorInspectorReport, renderDoctorIn
|
|
|
19
19
|
export { buildEcosystemAudit, renderEcosystemAudit, renderEcosystemAuditJson } from "./audit/ecosystem-audit.js";
|
|
20
20
|
export { applyPolicyToDoctorConfig, applyPolicyToSecurityAudit, parsePolicyPack, policyEnablesRuntime, policyFailsOnWarnings, policyPackNames } from "./policy/policy-packs.js";
|
|
21
21
|
export { buildGenericMcpDoctor, renderGenericMcpDoctor, renderGenericMcpDoctorJson } from "./mcp/generic-mcp-doctor.js";
|
|
22
|
+
export { watchPlugin } from "./core/watch-plugin.js";
|
|
23
|
+
export { buildDepAudit, renderDepAudit, renderDepAuditJson } from "./core/dep-audit.js";
|
|
24
|
+
export { initGitHooks } from "./core/init-git-hooks.js";
|
|
22
25
|
export async function runCheck(targetPath, options = {}) {
|
|
23
26
|
return validatePlugin(targetPath, options);
|
|
24
27
|
}
|
package/dist/run-cli.js
CHANGED
|
@@ -30,6 +30,9 @@ import { buildDoctorInspectorReport, renderDoctorInspectorReport, renderDoctorIn
|
|
|
30
30
|
import { applyFixPlan, buildFixPlan, renderApplyFixResult, renderFixPlanJsonReport, renderFixPlan } from "./core/fix-plan.js";
|
|
31
31
|
import { renderClientDoctor, renderEnvironmentDoctor, renderEnvironmentDoctorJson } from "./core/environment-doctor.js";
|
|
32
32
|
import { initCiWorkflow } from "./core/init-ci.js";
|
|
33
|
+
import { watchPlugin } from "./core/watch-plugin.js";
|
|
34
|
+
import { buildDepAudit, renderDepAudit, renderDepAuditJson } from "./core/dep-audit.js";
|
|
35
|
+
import { initGitHooks } from "./core/init-git-hooks.js";
|
|
33
36
|
import { initPluginPackage, initPluginTemplates, isInitPluginTemplate } from "./core/init-plugin.js";
|
|
34
37
|
import { runCheck } from "./index.js";
|
|
35
38
|
import { buildGenericMcpDoctor, renderGenericMcpDoctor, renderGenericMcpDoctorJson } from "./mcp/generic-mcp-doctor.js";
|
|
@@ -73,7 +76,7 @@ const defaultIo = {
|
|
|
73
76
|
}
|
|
74
77
|
};
|
|
75
78
|
function printUsage(io) {
|
|
76
|
-
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>]|review-bundle <path> --output <dir> --sign-key-env NAME [--json] [--allow-dirty] [--allow-untagged]|review-bundle verify <bundle-dir> --target <path> --sign-key-env NAME [--json] [--output <path>]|review-bundle diff --before <dir> --after <dir> [--json]|attest <path> [--sign-key-env NAME]|attest verify <attestation.json> --target <path> --sign-key-env NAME|release-evidence <path> --sign-key-env NAME [--allow-dirty] [--allow-untagged] [--require-runtime-approval --runtime-approval-digest <digest>]|release-evidence verify <evidence.json> --target <path> --sign-key-env NAME|release-evidence asset <path> --tag <tag> --output <evidence.json> --sign-key-env NAME [--upload]|mcp <path>|inspector <path>|diff --before <path> --after <path>|recommend <path>|trust <path>|perf <path> [--max-total-ms <ms>] [--max-stage-ms stage=ms]|export --bundle <path>|snapshot|clients|--json|--update-check]\n codex-plugin-doctor init [path] [--template skill-only|mcp-stdio|mcp-http|full-runtime]\n codex-plugin-doctor init-ci [path]\n codex-plugin-doctor 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");
|
|
79
|
+
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 audit deps <path> [--json] [--output <path>]\n codex-plugin-doctor mcp <path> [--json] [--output <path>]\n codex-plugin-doctor security <path> [--policy security] [--json|--scorecard]\n codex-plugin-doctor compat <path> [--all|--client <client>] [--json] [--scorecard] [--output <path>] [--install-preview|--apply --backup]\n codex-plugin-doctor fix <path> (--dry-run|--interactive --backup|--apply --backup)\n codex-plugin-doctor history <history.jsonl> [--json] [--fail-on-regression]\n codex-plugin-doctor watch <path> [--runtime] [--json] [--output <path>] [--debounce-ms <ms>]\n codex-plugin-doctor doctor [npm <package>|contract|corpus|runtime-plan <path> [--json|--markdown] [--output <path>]|runtime-policy <path> [--json] [--output <path>]|review-bundle <path> --output <dir> --sign-key-env NAME [--json] [--allow-dirty] [--allow-untagged]|review-bundle verify <bundle-dir> --target <path> --sign-key-env NAME [--json] [--output <path>] [--failures-only]|review-bundle diff --before <dir> --after <dir> [--json]|attest <path> [--sign-key-env NAME]|attest verify <attestation.json> --target <path> --sign-key-env NAME|release-evidence <path> --sign-key-env NAME [--allow-dirty] [--allow-untagged] [--require-runtime-approval --runtime-approval-digest <digest>]|release-evidence verify <evidence.json> --target <path> --sign-key-env NAME|release-evidence asset <path> --tag <tag> --output <evidence.json> --sign-key-env NAME [--upload]|mcp <path>|inspector <path>|diff --before <path> --after <path>|recommend <path>|trust <path>|perf <path> [--max-total-ms <ms>] [--max-stage-ms stage=ms]|export --bundle <path>|snapshot|clients|--json|--update-check]\n codex-plugin-doctor init [path] [--template skill-only|mcp-stdio|mcp-http|full-runtime]\n codex-plugin-doctor init-ci [path]\n codex-plugin-doctor init-git-hooks [path] [--force]\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");
|
|
77
80
|
}
|
|
78
81
|
const performanceStageNames = new Set([
|
|
79
82
|
"validation",
|
|
@@ -449,6 +452,7 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
449
452
|
: null;
|
|
450
453
|
const verifyFlags = bundleDirectory ? remainingArgs.slice(2) : remainingArgs.slice(1);
|
|
451
454
|
const jsonOutput = verifyFlags.includes("--json");
|
|
455
|
+
const failuresOnly = verifyFlags.includes("--failures-only");
|
|
452
456
|
const outputIndex = verifyFlags.indexOf("--output");
|
|
453
457
|
const outputPath = outputIndex === -1 ? null : verifyFlags[outputIndex + 1];
|
|
454
458
|
const targetIndex = verifyFlags.indexOf("--target");
|
|
@@ -490,7 +494,7 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
490
494
|
});
|
|
491
495
|
const renderedReport = jsonOutput
|
|
492
496
|
? renderDoctorReviewBundleVerificationJson(report)
|
|
493
|
-
: renderDoctorReviewBundleVerification(report);
|
|
497
|
+
: renderDoctorReviewBundleVerification(report, { failuresOnly });
|
|
494
498
|
if (outputPath) {
|
|
495
499
|
await writeFile(outputPath, renderedReport, "utf8");
|
|
496
500
|
}
|
|
@@ -1218,6 +1222,50 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
1218
1222
|
].join("\n"));
|
|
1219
1223
|
return 0;
|
|
1220
1224
|
}
|
|
1225
|
+
if (command === "init-git-hooks") {
|
|
1226
|
+
const targetPath = maybePath && !maybePath.startsWith("--") ? maybePath : ".";
|
|
1227
|
+
const initFlags = maybePath && maybePath.startsWith("--")
|
|
1228
|
+
? [maybePath, ...remainingArgs]
|
|
1229
|
+
: remainingArgs;
|
|
1230
|
+
const force = initFlags.includes("--force");
|
|
1231
|
+
const result = await initGitHooks(targetPath, { force });
|
|
1232
|
+
const overwritten = result.preExisting.length > 0
|
|
1233
|
+
? `\nOverwritten existing hooks: ${result.preExisting.join(", ")}`
|
|
1234
|
+
: "";
|
|
1235
|
+
io.writeStdout([
|
|
1236
|
+
"Initialized Codex Plugin Doctor git hooks",
|
|
1237
|
+
`Root: ${result.rootPath}`,
|
|
1238
|
+
`Hooks: ${result.hookPaths.join(", ")}`,
|
|
1239
|
+
overwritten
|
|
1240
|
+
].filter(Boolean).join("\n"));
|
|
1241
|
+
return 0;
|
|
1242
|
+
}
|
|
1243
|
+
if (command === "watch") {
|
|
1244
|
+
const targetPath = maybePath && !maybePath.startsWith("--") ? maybePath : ".";
|
|
1245
|
+
const watchFlags = maybePath && maybePath.startsWith("--")
|
|
1246
|
+
? [maybePath, ...remainingArgs]
|
|
1247
|
+
: remainingArgs;
|
|
1248
|
+
const runtime = watchFlags.includes("--runtime");
|
|
1249
|
+
const jsonOutput = watchFlags.includes("--json");
|
|
1250
|
+
const outputIndex = watchFlags.indexOf("--output");
|
|
1251
|
+
const outputPath = outputIndex === -1 ? null : watchFlags[outputIndex + 1];
|
|
1252
|
+
const debounceIndex = watchFlags.indexOf("--debounce-ms");
|
|
1253
|
+
const debounceRaw = debounceIndex === -1 ? null : watchFlags[debounceIndex + 1];
|
|
1254
|
+
const debounceMs = debounceRaw ? Number(debounceRaw) || 300 : 300;
|
|
1255
|
+
if (outputIndex !== -1 && (!outputPath || outputPath.startsWith("--"))) {
|
|
1256
|
+
io.writeStderr("Missing path after --output.");
|
|
1257
|
+
return 2;
|
|
1258
|
+
}
|
|
1259
|
+
const result = await watchPlugin({
|
|
1260
|
+
targetPath,
|
|
1261
|
+
debounceMs,
|
|
1262
|
+
runtime,
|
|
1263
|
+
jsonOutput,
|
|
1264
|
+
outputPath
|
|
1265
|
+
});
|
|
1266
|
+
io.writeStdout(`\nStopped watching ${result.targetPath}: ${result.validations} validations, ${result.failures} failures.`);
|
|
1267
|
+
return result.failures > 0 ? 1 : 0;
|
|
1268
|
+
}
|
|
1221
1269
|
if (command === "fix") {
|
|
1222
1270
|
if (!maybePath || maybePath.startsWith("--")) {
|
|
1223
1271
|
io.writeStderr("Missing target path. Usage: codex-plugin-doctor fix <path> (--dry-run|--interactive --backup|--apply --backup)");
|
|
@@ -1323,10 +1371,32 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
1323
1371
|
return report.exitCode;
|
|
1324
1372
|
}
|
|
1325
1373
|
if (command === "audit") {
|
|
1374
|
+
if (maybePath === "deps") {
|
|
1375
|
+
const targetPath = remainingArgs[0] && !remainingArgs[0].startsWith("--") ? remainingArgs[0] : ".";
|
|
1376
|
+
const depsFlags = remainingArgs[0] && remainingArgs[0].startsWith("--")
|
|
1377
|
+
? remainingArgs
|
|
1378
|
+
: remainingArgs.slice(1);
|
|
1379
|
+
const jsonOutput = depsFlags.includes("--json");
|
|
1380
|
+
const outputIndex = depsFlags.indexOf("--output");
|
|
1381
|
+
const outputPath = outputIndex === -1 ? null : depsFlags[outputIndex + 1];
|
|
1382
|
+
if (outputIndex !== -1 && (!outputPath || outputPath.startsWith("--"))) {
|
|
1383
|
+
io.writeStderr("Missing path after --output.");
|
|
1384
|
+
return 2;
|
|
1385
|
+
}
|
|
1386
|
+
const report = await buildDepAudit(targetPath);
|
|
1387
|
+
const renderedReport = jsonOutput
|
|
1388
|
+
? renderDepAuditJson(report)
|
|
1389
|
+
: renderDepAudit(report);
|
|
1390
|
+
if (outputPath) {
|
|
1391
|
+
await writeFile(outputPath, renderedReport, "utf8");
|
|
1392
|
+
}
|
|
1393
|
+
io.writeStdout(renderedReport);
|
|
1394
|
+
return report.status === "fail" ? 1 : 0;
|
|
1395
|
+
}
|
|
1326
1396
|
const auditFlags = maybePath ? [maybePath, ...remainingArgs] : remainingArgs;
|
|
1327
1397
|
const installed = auditFlags.includes("--installed");
|
|
1328
1398
|
if (!installed) {
|
|
1329
|
-
io.writeStderr("Usage: codex-plugin-doctor audit --installed [filter] [--security] [--compat] [--json] [--output <path>] [--cache] [--changed]");
|
|
1399
|
+
io.writeStderr("Usage: codex-plugin-doctor audit --installed [filter] [--security] [--compat] [--json] [--output <path>] [--cache] [--changed]\n codex-plugin-doctor audit deps <path> [--json] [--output <path>]");
|
|
1330
1400
|
return 2;
|
|
1331
1401
|
}
|
|
1332
1402
|
const installedIndex = auditFlags.indexOf("--installed");
|
package/package.json
CHANGED