@agentlighthouse/cli 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +13 -0
- package/dist/commands/baseline.d.ts +12 -0
- package/dist/commands/baseline.d.ts.map +1 -0
- package/dist/commands/baseline.js +55 -0
- package/dist/commands/compare.d.ts +226 -0
- package/dist/commands/compare.d.ts.map +1 -0
- package/dist/commands/compare.js +198 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +56 -0
- package/dist/commands/scan.d.ts +38 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +250 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +159 -0
- package/dist/pathing.d.ts +2 -0
- package/dist/pathing.d.ts.map +1 -0
- package/dist/pathing.js +7 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AgentLighthouse contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# @agentlighthouse/cli
|
|
2
|
+
|
|
3
|
+
Command-line interface for AgentLighthouse, a local-first agent-readiness scanner.
|
|
4
|
+
|
|
5
|
+
The package provides the `agentlighthouse` binary:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
agentlighthouse scan .
|
|
9
|
+
agentlighthouse baseline create . --output agentlighthouse-baseline.json
|
|
10
|
+
agentlighthouse scan . --baseline agentlighthouse-baseline.json --report-dir agentlighthouse-reports
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The package is prepared for public alpha packaging, but npm publishing is not part of this repository change. For source development, use the root workspace commands documented in the repository README.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ScanProfile } from "@agentlighthouse/core";
|
|
2
|
+
export interface BaselineCreateOptions {
|
|
3
|
+
output?: string;
|
|
4
|
+
profile?: ScanProfile;
|
|
5
|
+
}
|
|
6
|
+
export interface BaselineValidateOptions {
|
|
7
|
+
json?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function runBaselineCreateCommand(targetPath: string, options: BaselineCreateOptions): Promise<void>;
|
|
10
|
+
export declare function runBaselineValidateCommand(baselinePath: string, options?: BaselineValidateOptions): Promise<void>;
|
|
11
|
+
export declare function runBaselineSummaryCommand(baselinePath: string): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=baseline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../src/commands/baseline.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAIzD,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,wBAAwB,CAC5C,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED,wBAAsB,0BAA0B,CAC9C,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAsB,yBAAyB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGnF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { renderJsonReport, scanProject } from "@agentlighthouse/core";
|
|
4
|
+
import { resolveFromInvocationCwd } from "../pathing.js";
|
|
5
|
+
import { readScanResultFile } from "./compare.js";
|
|
6
|
+
export async function runBaselineCreateCommand(targetPath, options) {
|
|
7
|
+
const output = options.output ?? "agentlighthouse-baseline.json";
|
|
8
|
+
const outputPath = resolveFromInvocationCwd(output);
|
|
9
|
+
const result = await scanProject(resolveFromInvocationCwd(targetPath), {
|
|
10
|
+
profile: options.profile
|
|
11
|
+
});
|
|
12
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
13
|
+
await writeFile(outputPath, renderJsonReport(result), "utf8");
|
|
14
|
+
process.stdout.write([
|
|
15
|
+
"AgentLighthouse baseline created.",
|
|
16
|
+
`Output: ${output}`,
|
|
17
|
+
`Score: ${result.score}/100`,
|
|
18
|
+
`Confidence: ${result.scoreConfidence} (${result.scoreConfidenceScore}/100)`,
|
|
19
|
+
`Coverage: ${result.coverage.coveragePercent}%`,
|
|
20
|
+
`Profile: ${result.profile}`,
|
|
21
|
+
"",
|
|
22
|
+
"A baseline is a normal scan-result JSON file. Commit or update it intentionally when the team accepts the current agent-readiness state."
|
|
23
|
+
].join("\n") + "\n");
|
|
24
|
+
}
|
|
25
|
+
export async function runBaselineValidateCommand(baselinePath, options = {}) {
|
|
26
|
+
void options;
|
|
27
|
+
const result = await readScanResultFile(baselinePath);
|
|
28
|
+
process.stdout.write(`${baselineSummary(result, baselinePath)}\n`);
|
|
29
|
+
}
|
|
30
|
+
export async function runBaselineSummaryCommand(baselinePath) {
|
|
31
|
+
const result = await readScanResultFile(baselinePath);
|
|
32
|
+
process.stdout.write(`${baselineSummary(result, baselinePath)}\n`);
|
|
33
|
+
}
|
|
34
|
+
function baselineSummary(result, baselinePath) {
|
|
35
|
+
const severityCounts = result.findings.reduce((counts, finding) => {
|
|
36
|
+
counts[finding.severity] = (counts[finding.severity] ?? 0) + 1;
|
|
37
|
+
return counts;
|
|
38
|
+
}, {});
|
|
39
|
+
return [
|
|
40
|
+
`AgentLighthouse baseline: ${baselinePath}`,
|
|
41
|
+
`Scan ID: ${result.scanId}`,
|
|
42
|
+
`Project: ${result.projectName ?? result.detectedProject.name ?? result.scannedPath}`,
|
|
43
|
+
`Score: ${result.score}/100`,
|
|
44
|
+
`Confidence: ${result.scoreConfidence} (${result.scoreConfidenceScore}/100)`,
|
|
45
|
+
`Coverage: ${result.coverage.coveragePercent}%`,
|
|
46
|
+
`Profile: ${result.profile}`,
|
|
47
|
+
`Completed: ${result.completedAt}`,
|
|
48
|
+
`AgentLighthouse version: ${result.agentLighthouseVersion}`,
|
|
49
|
+
`Scoring model: ${result.scoringModelVersion}`,
|
|
50
|
+
`Findings: ${result.findings.length}`,
|
|
51
|
+
`Severity counts: ${Object.entries(severityCounts)
|
|
52
|
+
.map(([severity, count]) => `${severity}=${count}`)
|
|
53
|
+
.join(", ")}`
|
|
54
|
+
].join("\n");
|
|
55
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { ChangedFile, ComparisonResult, Severity } from "@agentlighthouse/core";
|
|
2
|
+
export type CompareFormat = "text" | "json" | "markdown" | "pr-summary";
|
|
3
|
+
export interface CompareCommandOptions {
|
|
4
|
+
baseline?: string;
|
|
5
|
+
current?: string;
|
|
6
|
+
format?: CompareFormat;
|
|
7
|
+
output?: string;
|
|
8
|
+
failOnRegression?: boolean;
|
|
9
|
+
failOnScoreDrop?: string;
|
|
10
|
+
failOnCoverageDrop?: string;
|
|
11
|
+
failOnConfidenceDrop?: string;
|
|
12
|
+
failOnNewSeverity?: Severity;
|
|
13
|
+
failOnNewCritical?: boolean;
|
|
14
|
+
failOnNewHigh?: boolean;
|
|
15
|
+
changedFiles?: string;
|
|
16
|
+
gitBase?: string;
|
|
17
|
+
gitHead?: string;
|
|
18
|
+
failOnNewChangedSeverity?: Severity;
|
|
19
|
+
failOnNewChangedCritical?: boolean;
|
|
20
|
+
failOnNewChangedHigh?: boolean;
|
|
21
|
+
failOnPrRegression?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare function runCompareCommand(options: CompareCommandOptions): Promise<void>;
|
|
24
|
+
export declare function readScanResultFile(filePath: string): Promise<{
|
|
25
|
+
projectName: string;
|
|
26
|
+
ignoredPaths: string[];
|
|
27
|
+
warnings: string[];
|
|
28
|
+
errors: string[];
|
|
29
|
+
scanStats: {
|
|
30
|
+
filesScanned: number;
|
|
31
|
+
textFilesRead: number;
|
|
32
|
+
bytesRead: number;
|
|
33
|
+
docsMarkdownFileCount: number;
|
|
34
|
+
openApiFileCount: number;
|
|
35
|
+
benchmarkFileCount: number;
|
|
36
|
+
findingCount: number;
|
|
37
|
+
};
|
|
38
|
+
score: number;
|
|
39
|
+
summary: string;
|
|
40
|
+
signals: {
|
|
41
|
+
rootPath: string;
|
|
42
|
+
projectName: string;
|
|
43
|
+
scannedFiles: string[];
|
|
44
|
+
artifacts: Record<string, {
|
|
45
|
+
path: string;
|
|
46
|
+
exists: boolean;
|
|
47
|
+
kind: "file" | "directory" | "missing";
|
|
48
|
+
sizeBytes?: number | undefined;
|
|
49
|
+
contentPreview?: string | undefined;
|
|
50
|
+
}>;
|
|
51
|
+
docsMarkdownFiles: string[];
|
|
52
|
+
openApiFiles: string[];
|
|
53
|
+
mcpFiles: string[];
|
|
54
|
+
configFiles: string[];
|
|
55
|
+
benchmarkFiles: string[];
|
|
56
|
+
ignoredPaths: string[];
|
|
57
|
+
warnings: string[];
|
|
58
|
+
errors: string[];
|
|
59
|
+
scanStats: {
|
|
60
|
+
filesScanned: number;
|
|
61
|
+
textFilesRead: number;
|
|
62
|
+
bytesRead: number;
|
|
63
|
+
docsMarkdownFileCount: number;
|
|
64
|
+
openApiFileCount: number;
|
|
65
|
+
benchmarkFileCount: number;
|
|
66
|
+
};
|
|
67
|
+
textByPath: Record<string, string>;
|
|
68
|
+
packageJson?: {
|
|
69
|
+
path: string;
|
|
70
|
+
scripts: Record<string, string>;
|
|
71
|
+
dependencies: string[];
|
|
72
|
+
devDependencies: string[];
|
|
73
|
+
name?: string | undefined;
|
|
74
|
+
packageManager?: string | undefined;
|
|
75
|
+
} | undefined;
|
|
76
|
+
};
|
|
77
|
+
durationMs: number;
|
|
78
|
+
scanId: string;
|
|
79
|
+
scannedPath: string;
|
|
80
|
+
startedAt: string;
|
|
81
|
+
completedAt: string;
|
|
82
|
+
agentLighthouseVersion: string;
|
|
83
|
+
profile: "default" | "devtool" | "api" | "mcp" | "docs" | "library" | "internal";
|
|
84
|
+
scoringModelVersion: string;
|
|
85
|
+
rawScore: number;
|
|
86
|
+
scoreConfidence: "high" | "medium" | "low";
|
|
87
|
+
scoreConfidenceScore: number;
|
|
88
|
+
coverage: {
|
|
89
|
+
evaluatedChecks: number;
|
|
90
|
+
skippedChecks: number;
|
|
91
|
+
notApplicableChecks: number;
|
|
92
|
+
notEvaluatedChecks: number;
|
|
93
|
+
evaluatedCategories: ("agent_instructions" | "documentation" | "api_schema" | "mcp_tools" | "examples" | "setup_and_tests" | "security_and_privacy" | "task_benchmarks" | "repo_structure" | "freshness_and_consistency")[];
|
|
94
|
+
missingCategories: ("agent_instructions" | "documentation" | "api_schema" | "mcp_tools" | "examples" | "setup_and_tests" | "security_and_privacy" | "task_benchmarks" | "repo_structure" | "freshness_and_consistency")[];
|
|
95
|
+
coveragePercent: number;
|
|
96
|
+
};
|
|
97
|
+
scoringCaps: {
|
|
98
|
+
id: string;
|
|
99
|
+
maxScore: number;
|
|
100
|
+
reason: string;
|
|
101
|
+
}[];
|
|
102
|
+
scoreInterpretation: {
|
|
103
|
+
agentReadinessScore: number;
|
|
104
|
+
humanReadableProjectSignals: {
|
|
105
|
+
score: number;
|
|
106
|
+
summary: string;
|
|
107
|
+
signals: string[];
|
|
108
|
+
};
|
|
109
|
+
agentSpecificContextLayer: {
|
|
110
|
+
score: number;
|
|
111
|
+
summary: string;
|
|
112
|
+
signals: string[];
|
|
113
|
+
};
|
|
114
|
+
verifiability: {
|
|
115
|
+
score: number;
|
|
116
|
+
summary: string;
|
|
117
|
+
signals: string[];
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
subscores: {
|
|
121
|
+
id: string;
|
|
122
|
+
label: string;
|
|
123
|
+
score: number;
|
|
124
|
+
findingsCount: number;
|
|
125
|
+
}[];
|
|
126
|
+
findings: {
|
|
127
|
+
id: string;
|
|
128
|
+
ruleId: string;
|
|
129
|
+
title: string;
|
|
130
|
+
severity: "critical" | "high" | "medium" | "low" | "info";
|
|
131
|
+
category: "agent_instructions" | "documentation" | "api_schema" | "mcp_tools" | "examples" | "setup_and_tests" | "security_and_privacy" | "task_benchmarks" | "repo_structure" | "freshness_and_consistency";
|
|
132
|
+
description: string;
|
|
133
|
+
evidence: string[];
|
|
134
|
+
recommendation: string;
|
|
135
|
+
suggestedFixType: "create_file" | "update_file" | "add_section" | "add_script" | "add_example" | "review_manually" | "none";
|
|
136
|
+
locationKey?: string | undefined;
|
|
137
|
+
subject?: string | undefined;
|
|
138
|
+
fingerprint?: string | undefined;
|
|
139
|
+
identityParts?: string[] | undefined;
|
|
140
|
+
affectedFile?: string | undefined;
|
|
141
|
+
location?: {
|
|
142
|
+
file: string;
|
|
143
|
+
sourceKind: "mcp" | "unknown" | "markdown" | "openapi" | "task" | "config" | "project";
|
|
144
|
+
symbol?: string | undefined;
|
|
145
|
+
startLine?: number | undefined;
|
|
146
|
+
startColumn?: number | undefined;
|
|
147
|
+
endLine?: number | undefined;
|
|
148
|
+
endColumn?: number | undefined;
|
|
149
|
+
locationKey?: string | undefined;
|
|
150
|
+
subject?: string | undefined;
|
|
151
|
+
} | undefined;
|
|
152
|
+
agentFailureMode?: string | undefined;
|
|
153
|
+
fixExample?: string | undefined;
|
|
154
|
+
docsLinks?: string[] | undefined;
|
|
155
|
+
}[];
|
|
156
|
+
recommendations: string[];
|
|
157
|
+
apiAnalysis: {
|
|
158
|
+
specFiles: string[];
|
|
159
|
+
operationCount: number;
|
|
160
|
+
operationsWithExamples: number;
|
|
161
|
+
operationsMissingDescriptions: number;
|
|
162
|
+
destructiveOperations: string[];
|
|
163
|
+
authSchemes: string[];
|
|
164
|
+
weakOperations: string[];
|
|
165
|
+
highRiskOperations: string[];
|
|
166
|
+
};
|
|
167
|
+
mcpAnalysis: {
|
|
168
|
+
detected: boolean;
|
|
169
|
+
files: string[];
|
|
170
|
+
toolCount: number;
|
|
171
|
+
toolsWithSchemas: number;
|
|
172
|
+
toolsWithExamples: number;
|
|
173
|
+
ambiguousTools: string[];
|
|
174
|
+
destructiveTools: string[];
|
|
175
|
+
privacySensitiveTools: string[];
|
|
176
|
+
weakTools: string[];
|
|
177
|
+
};
|
|
178
|
+
commandProbes: {
|
|
179
|
+
passed: number;
|
|
180
|
+
failed: number;
|
|
181
|
+
skipped: number;
|
|
182
|
+
enabled: boolean;
|
|
183
|
+
attempted: number;
|
|
184
|
+
timedOut: number;
|
|
185
|
+
results: {
|
|
186
|
+
status: "passed" | "failed" | "timed_out" | "skipped";
|
|
187
|
+
command: string;
|
|
188
|
+
script: string;
|
|
189
|
+
exitCode: number | null;
|
|
190
|
+
durationMs: number;
|
|
191
|
+
reason?: string | undefined;
|
|
192
|
+
stdoutExcerpt?: string | undefined;
|
|
193
|
+
stderrExcerpt?: string | undefined;
|
|
194
|
+
}[];
|
|
195
|
+
};
|
|
196
|
+
detectedProject: {
|
|
197
|
+
type: "unknown" | "node_typescript" | "node_javascript" | "python" | "rust" | "go" | "docs_only" | "openapi_project" | "mcp_project";
|
|
198
|
+
evidence: string[];
|
|
199
|
+
name: string;
|
|
200
|
+
packageManager: "unknown" | "go" | "pnpm" | "npm" | "yarn" | "bun" | "pip" | "poetry" | "cargo";
|
|
201
|
+
confidence: number;
|
|
202
|
+
frameworks: string[];
|
|
203
|
+
};
|
|
204
|
+
detectedArtifacts: {
|
|
205
|
+
path: string;
|
|
206
|
+
exists: boolean;
|
|
207
|
+
kind: "file" | "directory" | "missing";
|
|
208
|
+
role: string;
|
|
209
|
+
quality: "unknown" | "missing" | "thin" | "partial" | "strong";
|
|
210
|
+
notes: string[];
|
|
211
|
+
}[];
|
|
212
|
+
projectPath?: string | undefined;
|
|
213
|
+
scannedAt?: string | undefined;
|
|
214
|
+
recommendedActions?: string[] | undefined;
|
|
215
|
+
}>;
|
|
216
|
+
export declare function readChangedFiles(options: Pick<CompareCommandOptions, "changedFiles" | "gitBase" | "gitHead">): Promise<ChangedFile[] | undefined>;
|
|
217
|
+
export declare function renderComparisonResult(result: ComparisonResult, format: CompareFormat, options: {
|
|
218
|
+
status: "passed" | "failed";
|
|
219
|
+
reasons: string[];
|
|
220
|
+
reportPaths: string[];
|
|
221
|
+
}): string;
|
|
222
|
+
export declare function evaluateComparisonGates(result: ComparisonResult, options: CompareCommandOptions): {
|
|
223
|
+
failed: boolean;
|
|
224
|
+
reasons: string[];
|
|
225
|
+
};
|
|
226
|
+
//# sourceMappingURL=compare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.d.ts","sourceRoot":"","sources":["../../src/commands/compare.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGrF,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,YAAY,CAAC;AAIxE,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,iBAAiB,CAAC,EAAE,QAAQ,CAAC;IAC7B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wBAAwB,CAAC,EAAE,QAAQ,CAAC;IACpC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqCrF;AAED,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;qBAmN450C,CAAC;0BAAgD,CAAC;;;;;;;;;;;;;;;;;;;mBAAslB,CAAC;;;;;gBAAsK,CAAC;0BAAgD,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAAi0E,CAAC;eAAqC,CAAC;mBAAyC,CAAC;qBAA2C,CAAC;oBAA4C,CAAC;gBAAsC,CAAC;;;kBAAoJ,CAAC;qBAA2C,CAAC;uBAA6C,CAAC;mBAAyC,CAAC;qBAA2C,CAAC;uBAA6C,CAAC;mBAAyC,CAAC;;wBAAqE,CAAC;kBAAwC,CAAC;iBAAuC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAA2kC,CAAC;yBAA+C,CAAC;yBAA+C,CAAC;;;;;;;;;;;;;;;;;;;;;;GA9Kvi/C;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,IAAI,CAAC,qBAAqB,EAAE,cAAc,GAAG,SAAS,GAAG,SAAS,CAAC,GAC3E,OAAO,CAAC,WAAW,EAAE,GAAG,SAAS,CAAC,CAgDpC;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE;IAAE,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAA;CAAE,GACjF,MAAM,CAWR;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,qBAAqB,GAC7B;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAwFxC"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { compareScanResults, parseChangedFilesText, parseGitNameStatus, renderComparisonCliReport, renderComparisonJsonReport, renderComparisonMarkdownReport, renderComparisonPrSummaryReport, scanResultSchema, severityRank } from "@agentlighthouse/core";
|
|
6
|
+
import { resolveFromInvocationCwd } from "../pathing.js";
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
export async function runCompareCommand(options) {
|
|
9
|
+
if (!options.baseline) {
|
|
10
|
+
throw new Error("Missing --baseline <file>.");
|
|
11
|
+
}
|
|
12
|
+
if (!options.current) {
|
|
13
|
+
throw new Error("Missing --current <file>.");
|
|
14
|
+
}
|
|
15
|
+
const format = options.format ?? "text";
|
|
16
|
+
if (!["text", "json", "markdown", "pr-summary"].includes(format)) {
|
|
17
|
+
throw new Error(`Unsupported format "${format}". Use text, json, markdown, or pr-summary.`);
|
|
18
|
+
}
|
|
19
|
+
const baseline = await readScanResultFile(options.baseline);
|
|
20
|
+
const current = await readScanResultFile(options.current);
|
|
21
|
+
const changedFiles = await readChangedFiles(options);
|
|
22
|
+
const comparison = compareScanResults(baseline, current, { changedFiles });
|
|
23
|
+
const gateResult = evaluateComparisonGates(comparison, options);
|
|
24
|
+
const outputOption = options.output;
|
|
25
|
+
const outputPath = outputOption ? resolveFromInvocationCwd(outputOption) : undefined;
|
|
26
|
+
const rendered = renderComparisonResult(comparison, format, {
|
|
27
|
+
status: gateResult.failed ? "failed" : "passed",
|
|
28
|
+
reasons: gateResult.reasons,
|
|
29
|
+
reportPaths: outputOption ? [outputOption] : []
|
|
30
|
+
});
|
|
31
|
+
if (outputPath) {
|
|
32
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
33
|
+
await writeFile(outputPath, rendered, "utf8");
|
|
34
|
+
}
|
|
35
|
+
process.stdout.write(`${rendered}\n`);
|
|
36
|
+
if (gateResult.failed) {
|
|
37
|
+
for (const reason of gateResult.reasons) {
|
|
38
|
+
process.stderr.write(`${reason}\n`);
|
|
39
|
+
}
|
|
40
|
+
process.exitCode = 1;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export async function readScanResultFile(filePath) {
|
|
44
|
+
const resolved = resolveFromInvocationCwd(filePath);
|
|
45
|
+
let raw;
|
|
46
|
+
try {
|
|
47
|
+
raw = await readFile(resolved, "utf8");
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
51
|
+
throw new Error(`Could not read scan result JSON at ${filePath}. Expected a file produced by: agentlighthouse scan . --format json --output ${filePath}. ${message}`);
|
|
52
|
+
}
|
|
53
|
+
let parsed;
|
|
54
|
+
try {
|
|
55
|
+
parsed = JSON.parse(raw);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
59
|
+
throw new Error(`Invalid JSON in ${filePath}. Expected AgentLighthouse scan-result JSON. Recreate it with: agentlighthouse scan . --format json --output ${filePath}. ${message}`);
|
|
60
|
+
}
|
|
61
|
+
if (parsed && typeof parsed === "object" && "comparisonId" in parsed && !("scanId" in parsed)) {
|
|
62
|
+
throw new Error(`${filePath} is a comparison report, not a scan-result JSON file. Use files produced by: agentlighthouse scan . --format json --output current.json`);
|
|
63
|
+
}
|
|
64
|
+
const result = scanResultSchema.safeParse(parsed);
|
|
65
|
+
if (!result.success) {
|
|
66
|
+
throw new Error(`${filePath} is not a valid AgentLighthouse scan-result JSON file. Expected fields include scanId, score, coverage, findings, and scoringModelVersion. Recreate it with: agentlighthouse scan . --format json --output ${filePath}`);
|
|
67
|
+
}
|
|
68
|
+
if (result.data.scoringModelVersion !== "0.1.0") {
|
|
69
|
+
throw new Error(`${filePath} uses unsupported scoring model ${result.data.scoringModelVersion}. This CLI supports scoring model 0.1.0. Regenerate the file with this CLI version.`);
|
|
70
|
+
}
|
|
71
|
+
return result.data;
|
|
72
|
+
}
|
|
73
|
+
export async function readChangedFiles(options) {
|
|
74
|
+
const hasExplicit = Boolean(options.changedFiles);
|
|
75
|
+
const hasGit = Boolean(options.gitBase || options.gitHead);
|
|
76
|
+
if (hasExplicit && hasGit) {
|
|
77
|
+
throw new Error("Use either --changed-files or --git-base/--git-head, not both.");
|
|
78
|
+
}
|
|
79
|
+
if (options.changedFiles) {
|
|
80
|
+
const resolved = resolveFromInvocationCwd(options.changedFiles);
|
|
81
|
+
try {
|
|
82
|
+
return parseChangedFilesText(await readFile(resolved, "utf8"), "explicit");
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
86
|
+
throw new Error(`Could not read changed-files list at ${options.changedFiles}. Create one with: git diff --name-status origin/main...HEAD > changed-files.txt. ${message}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (options.gitBase || options.gitHead) {
|
|
90
|
+
if (!options.gitBase || !options.gitHead) {
|
|
91
|
+
throw new Error("Both --git-base and --git-head are required for git changed-file detection.");
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const { stdout } = await execFileAsync("git", ["diff", "--name-status", "-z", `${options.gitBase}...${options.gitHead}`], {
|
|
95
|
+
cwd: process.cwd(),
|
|
96
|
+
encoding: "utf8",
|
|
97
|
+
maxBuffer: 1024 * 1024
|
|
98
|
+
});
|
|
99
|
+
const files = parseGitNameStatus(stdout, "git");
|
|
100
|
+
if (files.length === 0) {
|
|
101
|
+
process.stderr.write(`Git diff ${options.gitBase}...${options.gitHead} returned no changed files; PR impact sections will be empty.\n`);
|
|
102
|
+
}
|
|
103
|
+
return files;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
107
|
+
throw new Error(`Unable to read changed files from git refs ${options.gitBase}...${options.gitHead}: ${message}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
export function renderComparisonResult(result, format, options) {
|
|
113
|
+
if (format === "json") {
|
|
114
|
+
return renderComparisonJsonReport(result);
|
|
115
|
+
}
|
|
116
|
+
if (format === "markdown") {
|
|
117
|
+
return renderComparisonMarkdownReport(result, options);
|
|
118
|
+
}
|
|
119
|
+
if (format === "pr-summary") {
|
|
120
|
+
return renderComparisonPrSummaryReport(result, options);
|
|
121
|
+
}
|
|
122
|
+
return renderComparisonCliReport(result);
|
|
123
|
+
}
|
|
124
|
+
export function evaluateComparisonGates(result, options) {
|
|
125
|
+
const reasons = [];
|
|
126
|
+
const scoreDrop = parseOptionalNumber(options.failOnScoreDrop, "fail-on-score-drop");
|
|
127
|
+
const coverageDrop = parseOptionalNumber(options.failOnCoverageDrop, "fail-on-coverage-drop");
|
|
128
|
+
const confidenceDrop = parseOptionalNumber(options.failOnConfidenceDrop, "fail-on-confidence-drop");
|
|
129
|
+
const newSeverity = options.failOnNewCritical
|
|
130
|
+
? "critical"
|
|
131
|
+
: options.failOnNewHigh
|
|
132
|
+
? "high"
|
|
133
|
+
: options.failOnNewSeverity;
|
|
134
|
+
const newChangedSeverity = options.failOnNewChangedCritical
|
|
135
|
+
? "critical"
|
|
136
|
+
: options.failOnNewChangedHigh
|
|
137
|
+
? "high"
|
|
138
|
+
: options.failOnNewChangedSeverity;
|
|
139
|
+
if (options.failOnRegression && result.summary.regressionDetected) {
|
|
140
|
+
reasons.push(`AgentLighthouse comparison verdict is ${result.summary.verdict}.`);
|
|
141
|
+
}
|
|
142
|
+
if (scoreDrop !== undefined && result.deltas.scoreDelta <= -scoreDrop) {
|
|
143
|
+
reasons.push(`AgentLighthouse score dropped by ${Math.abs(result.deltas.scoreDelta)} point(s), meeting threshold ${scoreDrop}.`);
|
|
144
|
+
}
|
|
145
|
+
if (coverageDrop !== undefined && result.deltas.coverageDelta <= -coverageDrop) {
|
|
146
|
+
reasons.push(`AgentLighthouse coverage dropped by ${Math.abs(result.deltas.coverageDelta)} point(s), meeting threshold ${coverageDrop}.`);
|
|
147
|
+
}
|
|
148
|
+
if (confidenceDrop !== undefined && result.deltas.confidenceDelta <= -confidenceDrop) {
|
|
149
|
+
reasons.push(`AgentLighthouse confidence score dropped by ${Math.abs(result.deltas.confidenceDelta)} point(s), meeting threshold ${confidenceDrop}.`);
|
|
150
|
+
}
|
|
151
|
+
if (newSeverity) {
|
|
152
|
+
if (!["critical", "high", "medium", "low", "info"].includes(newSeverity)) {
|
|
153
|
+
throw new Error(`Unsupported fail-on-new-severity "${newSeverity}".`);
|
|
154
|
+
}
|
|
155
|
+
const threshold = severityRank(newSeverity);
|
|
156
|
+
const newFindings = result.findings.new.filter((finding) => severityRank(finding.severity) >= threshold);
|
|
157
|
+
if (newFindings.length > 0) {
|
|
158
|
+
reasons.push(`AgentLighthouse found ${newFindings.length} new finding(s) at or above ${newSeverity} severity.`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (newChangedSeverity) {
|
|
162
|
+
if (!["critical", "high", "medium", "low", "info"].includes(newChangedSeverity)) {
|
|
163
|
+
throw new Error(`Unsupported fail-on-new-changed-severity "${newChangedSeverity}".`);
|
|
164
|
+
}
|
|
165
|
+
if (!result.prImpact) {
|
|
166
|
+
reasons.push("Changed-file gate requested, but no changed-file information was supplied. Use --changed-files or --git-base/--git-head.");
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
const threshold = severityRank(newChangedSeverity);
|
|
170
|
+
const changedFindings = result.prImpact.newFindingsOnChangedFiles.filter((finding) => severityRank(finding.severity) >= threshold);
|
|
171
|
+
if (changedFindings.length > 0) {
|
|
172
|
+
reasons.push(`AgentLighthouse found ${changedFindings.length} new finding(s) at or above ${newChangedSeverity} severity on changed files.`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (options.failOnPrRegression) {
|
|
177
|
+
if (!result.prImpact) {
|
|
178
|
+
reasons.push("PR regression gate requested, but no changed-file information was supplied. Use --changed-files or --git-base/--git-head.");
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
const changedHighRisk = result.prImpact.newFindingsOnChangedFiles.filter((finding) => severityRank(finding.severity) >= severityRank("high"));
|
|
182
|
+
if (result.deltas.scoreDelta < 0 || changedHighRisk.length > 0) {
|
|
183
|
+
reasons.push(`AgentLighthouse PR regression detected: score delta ${result.deltas.scoreDelta}, ${changedHighRisk.length} new high/critical finding(s) on changed files.`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return { failed: reasons.length > 0, reasons };
|
|
188
|
+
}
|
|
189
|
+
function parseOptionalNumber(value, optionName) {
|
|
190
|
+
if (value === undefined) {
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
const parsed = Number.parseInt(value, 10);
|
|
194
|
+
if (Number.isNaN(parsed)) {
|
|
195
|
+
throw new Error(`Invalid ${optionName} value "${value}".`);
|
|
196
|
+
}
|
|
197
|
+
return parsed;
|
|
198
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAkDf"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { access, mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { generateStarterArtifacts } from "@agentlighthouse/core";
|
|
4
|
+
import { resolveFromInvocationCwd } from "../pathing.js";
|
|
5
|
+
export async function runInitCommand(targetPath, options) {
|
|
6
|
+
const rootPath = resolveFromInvocationCwd(targetPath);
|
|
7
|
+
const artifacts = await generateStarterArtifacts(rootPath);
|
|
8
|
+
const created = [];
|
|
9
|
+
const skipped = [];
|
|
10
|
+
const wouldCreate = [];
|
|
11
|
+
const overwritten = [];
|
|
12
|
+
for (const artifact of artifacts) {
|
|
13
|
+
const absolutePath = path.join(rootPath, artifact.path);
|
|
14
|
+
const exists = await fileExists(absolutePath);
|
|
15
|
+
if (options.dryRun) {
|
|
16
|
+
wouldCreate.push(`${artifact.path}${exists && options.force ? " (overwrite)" : exists ? " (skip existing)" : ""}`);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (exists && !options.force) {
|
|
20
|
+
skipped.push(artifact.path);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
await mkdir(path.dirname(absolutePath), { recursive: true });
|
|
24
|
+
await writeFile(absolutePath, artifact.content, "utf8");
|
|
25
|
+
if (exists) {
|
|
26
|
+
overwritten.push(artifact.path);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
created.push(artifact.path);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (options.dryRun) {
|
|
33
|
+
process.stdout.write(`AgentLighthouse init dry run for ${rootPath}\n${wouldCreate.map((file) => `- ${file}`).join("\n")}\n`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
process.stdout.write(`AgentLighthouse init completed for ${rootPath}\n`);
|
|
37
|
+
if (created.length > 0) {
|
|
38
|
+
process.stdout.write(`Created:\n${created.map((file) => `- ${file}`).join("\n")}\n`);
|
|
39
|
+
}
|
|
40
|
+
if (overwritten.length > 0) {
|
|
41
|
+
process.stdout.write(`Overwritten:\n${overwritten.map((file) => `- ${file}`).join("\n")}\n`);
|
|
42
|
+
}
|
|
43
|
+
if (skipped.length > 0) {
|
|
44
|
+
process.stdout.write(`Skipped existing files:\n${skipped.map((file) => `- ${file}`).join("\n")}\n`);
|
|
45
|
+
process.stdout.write("Use --force to overwrite existing starter artifacts.\n");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function fileExists(filePath) {
|
|
49
|
+
try {
|
|
50
|
+
await access(filePath);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ScanProfile, ScoreConfidenceLevel, Severity } from "@agentlighthouse/core";
|
|
2
|
+
import type { CompareFormat } from "./compare.js";
|
|
3
|
+
export type ScanFormat = "text" | "json" | "markdown" | "sarif" | "pr-summary";
|
|
4
|
+
export interface ScanCommandOptions {
|
|
5
|
+
json?: boolean;
|
|
6
|
+
format?: ScanFormat;
|
|
7
|
+
profile?: ScanProfile;
|
|
8
|
+
probe?: string[];
|
|
9
|
+
runProbes?: boolean;
|
|
10
|
+
output?: string;
|
|
11
|
+
failUnder?: string;
|
|
12
|
+
failOnSeverity?: Severity;
|
|
13
|
+
minConfidence?: ScoreConfidenceLevel;
|
|
14
|
+
githubStepSummary?: boolean;
|
|
15
|
+
include?: string[];
|
|
16
|
+
exclude?: string[];
|
|
17
|
+
color?: boolean;
|
|
18
|
+
baseline?: string;
|
|
19
|
+
comparisonOutput?: string;
|
|
20
|
+
comparisonFormat?: CompareFormat;
|
|
21
|
+
changedFiles?: string;
|
|
22
|
+
gitBase?: string;
|
|
23
|
+
gitHead?: string;
|
|
24
|
+
failOnRegression?: boolean;
|
|
25
|
+
failOnScoreDrop?: string;
|
|
26
|
+
failOnCoverageDrop?: string;
|
|
27
|
+
failOnConfidenceDrop?: string;
|
|
28
|
+
failOnNewSeverity?: Severity;
|
|
29
|
+
failOnNewCritical?: boolean;
|
|
30
|
+
failOnNewHigh?: boolean;
|
|
31
|
+
failOnNewChangedSeverity?: Severity;
|
|
32
|
+
failOnNewChangedCritical?: boolean;
|
|
33
|
+
failOnNewChangedHigh?: boolean;
|
|
34
|
+
failOnPrRegression?: boolean;
|
|
35
|
+
reportDir?: string;
|
|
36
|
+
}
|
|
37
|
+
export declare function runScanCommand(targetPath: string, options: ScanCommandOptions): Promise<void>;
|
|
38
|
+
//# sourceMappingURL=scan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEzF,OAAO,KAAK,EAAyB,aAAa,EAAE,MAAM,cAAc,CAAC;AAQzE,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,YAAY,CAAC;AAE/E,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,QAAQ,CAAC;IAC1B,aAAa,CAAC,EAAE,oBAAoB,CAAC;IACrC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,aAAa,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,iBAAiB,CAAC,EAAE,QAAQ,CAAC;IAC7B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,wBAAwB,CAAC,EAAE,QAAQ,CAAC;IACpC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAwIf"}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { appendFile, mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { compareScanResults, confidenceRank, renderCliReport, renderGithubStepSummary, renderJsonReport, renderMarkdownReport, renderPrSummaryReport, renderSarifReport, scanProject, severityRank } from "@agentlighthouse/core";
|
|
4
|
+
import { resolveFromInvocationCwd } from "../pathing.js";
|
|
5
|
+
import { evaluateComparisonGates, readChangedFiles, readScanResultFile, renderComparisonResult } from "./compare.js";
|
|
6
|
+
export async function runScanCommand(targetPath, options) {
|
|
7
|
+
const scanPath = resolveFromInvocationCwd(targetPath);
|
|
8
|
+
const format = options.json ? "json" : (options.format ?? "text");
|
|
9
|
+
if (!["text", "json", "markdown", "sarif", "pr-summary"].includes(format)) {
|
|
10
|
+
throw new Error(`Unsupported format "${format}". Use text, json, markdown, sarif, or pr-summary.`);
|
|
11
|
+
}
|
|
12
|
+
if (options.comparisonFormat &&
|
|
13
|
+
!["text", "json", "markdown", "pr-summary"].includes(options.comparisonFormat)) {
|
|
14
|
+
throw new Error(`Unsupported comparison-format "${options.comparisonFormat}". Use text, json, markdown, or pr-summary.`);
|
|
15
|
+
}
|
|
16
|
+
if (!options.baseline && hasComparisonOnlyOptions(options)) {
|
|
17
|
+
throw new Error("Comparison options require --baseline <file>. Example: agentlighthouse scan . --baseline agentlighthouse-baseline.json --comparison-output agentlighthouse-delta.md");
|
|
18
|
+
}
|
|
19
|
+
if (options.profile &&
|
|
20
|
+
!["default", "devtool", "api", "mcp", "docs", "library", "internal"].includes(options.profile)) {
|
|
21
|
+
throw new Error(`Unsupported profile "${options.profile}".`);
|
|
22
|
+
}
|
|
23
|
+
if (options.failOnSeverity &&
|
|
24
|
+
!["critical", "high", "medium", "low", "info"].includes(options.failOnSeverity)) {
|
|
25
|
+
throw new Error(`Unsupported fail-on-severity "${options.failOnSeverity}".`);
|
|
26
|
+
}
|
|
27
|
+
if (options.minConfidence && !["low", "medium", "high"].includes(options.minConfidence)) {
|
|
28
|
+
throw new Error(`Unsupported min-confidence "${options.minConfidence}".`);
|
|
29
|
+
}
|
|
30
|
+
const probes = new Set(options.probe ?? []);
|
|
31
|
+
if ([...probes].some((probe) => probe !== "commands")) {
|
|
32
|
+
throw new Error(`Unsupported probe. Use --probe commands.`);
|
|
33
|
+
}
|
|
34
|
+
const result = await scanProject(scanPath, {
|
|
35
|
+
include: options.include ?? [],
|
|
36
|
+
exclude: options.exclude ?? [],
|
|
37
|
+
profile: options.profile,
|
|
38
|
+
probes: {
|
|
39
|
+
commands: options.runProbes === true || probes.has("commands")
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
const failUnder = options.failUnder ? Number.parseInt(options.failUnder, 10) : undefined;
|
|
43
|
+
if (options.failUnder && Number.isNaN(failUnder)) {
|
|
44
|
+
throw new Error(`Invalid fail-under score "${options.failUnder}".`);
|
|
45
|
+
}
|
|
46
|
+
const gateResult = evaluateGates(result, {
|
|
47
|
+
failUnder,
|
|
48
|
+
failOnSeverity: options.failOnSeverity,
|
|
49
|
+
minConfidence: options.minConfidence
|
|
50
|
+
});
|
|
51
|
+
const outputOption = options.output;
|
|
52
|
+
const outputPath = outputOption ? resolveFromInvocationCwd(outputOption) : undefined;
|
|
53
|
+
const reportPaths = outputOption ? [outputOption] : [];
|
|
54
|
+
const bundlePaths = [];
|
|
55
|
+
if (options.reportDir) {
|
|
56
|
+
bundlePaths.push(...(await writeReportBundle(options.reportDir, result)));
|
|
57
|
+
}
|
|
58
|
+
const comparison = options.baseline
|
|
59
|
+
? compareScanResults(await readScanResultFile(options.baseline), result, {
|
|
60
|
+
changedFiles: await readChangedFiles(options)
|
|
61
|
+
})
|
|
62
|
+
: undefined;
|
|
63
|
+
const comparisonGateResult = comparison
|
|
64
|
+
? evaluateComparisonGates(comparison, comparisonOptions(options))
|
|
65
|
+
: { failed: false, reasons: [] };
|
|
66
|
+
const allReasons = [...gateResult.reasons, ...comparisonGateResult.reasons];
|
|
67
|
+
const failed = gateResult.failed || comparisonGateResult.failed;
|
|
68
|
+
const comparisonOutputOption = options.comparisonOutput;
|
|
69
|
+
const comparisonOutputPath = comparisonOutputOption
|
|
70
|
+
? resolveFromInvocationCwd(comparisonOutputOption)
|
|
71
|
+
: undefined;
|
|
72
|
+
const comparisonFormat = options.comparisonFormat ?? "pr-summary";
|
|
73
|
+
const rendered = renderResult(result, format, options.color ?? true, {
|
|
74
|
+
status: failed ? "failed" : "passed",
|
|
75
|
+
reasons: allReasons,
|
|
76
|
+
reportPaths: [...reportPaths, ...bundlePaths]
|
|
77
|
+
});
|
|
78
|
+
if (outputPath) {
|
|
79
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
80
|
+
await writeFile(outputPath, rendered, "utf8");
|
|
81
|
+
}
|
|
82
|
+
if (comparison && options.reportDir) {
|
|
83
|
+
bundlePaths.push(...(await writeComparisonBundle(options.reportDir, comparison, {
|
|
84
|
+
status: failed ? "failed" : "passed",
|
|
85
|
+
reasons: comparisonGateResult.reasons
|
|
86
|
+
})));
|
|
87
|
+
}
|
|
88
|
+
if (comparison && comparisonOutputPath) {
|
|
89
|
+
const comparisonRendered = renderComparisonResult(comparison, comparisonFormat, {
|
|
90
|
+
status: failed ? "failed" : "passed",
|
|
91
|
+
reasons: comparisonGateResult.reasons,
|
|
92
|
+
reportPaths: comparisonOutputOption ? [comparisonOutputOption] : []
|
|
93
|
+
});
|
|
94
|
+
await mkdir(path.dirname(comparisonOutputPath), { recursive: true });
|
|
95
|
+
await writeFile(comparisonOutputPath, comparisonRendered, "utf8");
|
|
96
|
+
}
|
|
97
|
+
process.stdout.write(`${rendered}\n`);
|
|
98
|
+
if (comparison) {
|
|
99
|
+
const comparisonRendered = renderComparisonResult(comparison, comparisonFormat, {
|
|
100
|
+
status: failed ? "failed" : "passed",
|
|
101
|
+
reasons: comparisonGateResult.reasons,
|
|
102
|
+
reportPaths: [
|
|
103
|
+
...(comparisonOutputOption ? [comparisonOutputOption] : []),
|
|
104
|
+
...bundlePaths.filter((file) => file.includes("comparison"))
|
|
105
|
+
]
|
|
106
|
+
});
|
|
107
|
+
process.stdout.write(`${comparisonRendered}\n`);
|
|
108
|
+
}
|
|
109
|
+
if (options.reportDir) {
|
|
110
|
+
process.stdout.write(`AgentLighthouse report bundle written to ${options.reportDir}\n`);
|
|
111
|
+
}
|
|
112
|
+
if (options.githubStepSummary) {
|
|
113
|
+
await writeGithubStepSummary(result, {
|
|
114
|
+
status: failed ? "failed" : "passed",
|
|
115
|
+
reasons: allReasons,
|
|
116
|
+
reportPaths: [...reportPaths, ...bundlePaths]
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (failed) {
|
|
120
|
+
for (const reason of allReasons) {
|
|
121
|
+
process.stderr.write(`${reason}\n`);
|
|
122
|
+
}
|
|
123
|
+
process.exitCode = 1;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function hasComparisonOnlyOptions(options) {
|
|
127
|
+
return Boolean(options.comparisonOutput ||
|
|
128
|
+
options.changedFiles ||
|
|
129
|
+
options.gitBase ||
|
|
130
|
+
options.gitHead ||
|
|
131
|
+
options.failOnRegression ||
|
|
132
|
+
options.failOnScoreDrop ||
|
|
133
|
+
options.failOnCoverageDrop ||
|
|
134
|
+
options.failOnConfidenceDrop ||
|
|
135
|
+
options.failOnNewSeverity ||
|
|
136
|
+
options.failOnNewCritical ||
|
|
137
|
+
options.failOnNewHigh ||
|
|
138
|
+
options.failOnNewChangedSeverity ||
|
|
139
|
+
options.failOnNewChangedCritical ||
|
|
140
|
+
options.failOnNewChangedHigh ||
|
|
141
|
+
options.failOnPrRegression);
|
|
142
|
+
}
|
|
143
|
+
function comparisonOptions(options) {
|
|
144
|
+
return {
|
|
145
|
+
failOnRegression: options.failOnRegression,
|
|
146
|
+
failOnScoreDrop: options.failOnScoreDrop,
|
|
147
|
+
failOnCoverageDrop: options.failOnCoverageDrop,
|
|
148
|
+
failOnConfidenceDrop: options.failOnConfidenceDrop,
|
|
149
|
+
failOnNewSeverity: options.failOnNewSeverity,
|
|
150
|
+
failOnNewCritical: options.failOnNewCritical,
|
|
151
|
+
failOnNewHigh: options.failOnNewHigh,
|
|
152
|
+
failOnNewChangedSeverity: options.failOnNewChangedSeverity,
|
|
153
|
+
failOnNewChangedCritical: options.failOnNewChangedCritical,
|
|
154
|
+
failOnNewChangedHigh: options.failOnNewChangedHigh,
|
|
155
|
+
failOnPrRegression: options.failOnPrRegression
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
async function writeReportBundle(reportDir, result) {
|
|
159
|
+
const resolved = resolveFromInvocationCwd(reportDir);
|
|
160
|
+
await mkdir(resolved, { recursive: true });
|
|
161
|
+
const files = [
|
|
162
|
+
["scan.json", renderJsonReport(result)],
|
|
163
|
+
["scan.md", renderMarkdownReport(result)],
|
|
164
|
+
["scan.sarif", renderSarifReport(result)],
|
|
165
|
+
[
|
|
166
|
+
"pr-summary.md",
|
|
167
|
+
renderPrSummaryReport(result, {
|
|
168
|
+
reportPaths: [
|
|
169
|
+
path.posix.join(reportDir, "scan.json"),
|
|
170
|
+
path.posix.join(reportDir, "scan.md"),
|
|
171
|
+
path.posix.join(reportDir, "scan.sarif")
|
|
172
|
+
]
|
|
173
|
+
})
|
|
174
|
+
]
|
|
175
|
+
];
|
|
176
|
+
for (const [fileName, contents] of files) {
|
|
177
|
+
await writeFile(path.join(resolved, fileName), contents, "utf8");
|
|
178
|
+
}
|
|
179
|
+
return files.map(([fileName]) => path.posix.join(reportDir, fileName));
|
|
180
|
+
}
|
|
181
|
+
async function writeComparisonBundle(reportDir, comparison, options) {
|
|
182
|
+
const resolved = resolveFromInvocationCwd(reportDir);
|
|
183
|
+
await mkdir(resolved, { recursive: true });
|
|
184
|
+
const files = [
|
|
185
|
+
[
|
|
186
|
+
"comparison.json",
|
|
187
|
+
renderComparisonResult(comparison, "json", bundleComparisonOptions(options))
|
|
188
|
+
],
|
|
189
|
+
[
|
|
190
|
+
"comparison.md",
|
|
191
|
+
renderComparisonResult(comparison, "markdown", bundleComparisonOptions(options))
|
|
192
|
+
],
|
|
193
|
+
[
|
|
194
|
+
"comparison-pr-summary.md",
|
|
195
|
+
renderComparisonResult(comparison, "pr-summary", bundleComparisonOptions(options))
|
|
196
|
+
]
|
|
197
|
+
];
|
|
198
|
+
for (const [fileName, contents] of files) {
|
|
199
|
+
await writeFile(path.join(resolved, fileName), contents, "utf8");
|
|
200
|
+
}
|
|
201
|
+
return files.map(([fileName]) => path.posix.join(reportDir, fileName));
|
|
202
|
+
}
|
|
203
|
+
function bundleComparisonOptions(options) {
|
|
204
|
+
return {
|
|
205
|
+
status: options.status,
|
|
206
|
+
reasons: options.reasons,
|
|
207
|
+
reportPaths: []
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function renderResult(result, format, color, options) {
|
|
211
|
+
if (format === "json") {
|
|
212
|
+
return renderJsonReport(result);
|
|
213
|
+
}
|
|
214
|
+
if (format === "markdown") {
|
|
215
|
+
return renderMarkdownReport(result);
|
|
216
|
+
}
|
|
217
|
+
if (format === "sarif") {
|
|
218
|
+
return renderSarifReport(result);
|
|
219
|
+
}
|
|
220
|
+
if (format === "pr-summary") {
|
|
221
|
+
return renderPrSummaryReport(result, options);
|
|
222
|
+
}
|
|
223
|
+
return renderCliReport(result, color);
|
|
224
|
+
}
|
|
225
|
+
function evaluateGates(result, options) {
|
|
226
|
+
const reasons = [];
|
|
227
|
+
if (options.failUnder !== undefined && result.score < options.failUnder) {
|
|
228
|
+
reasons.push(`AgentLighthouse score ${result.score} is below fail-under threshold ${options.failUnder}.`);
|
|
229
|
+
}
|
|
230
|
+
if (options.failOnSeverity) {
|
|
231
|
+
const threshold = severityRank(options.failOnSeverity);
|
|
232
|
+
const blocking = result.findings.filter((finding) => severityRank(finding.severity) >= threshold);
|
|
233
|
+
if (blocking.length > 0) {
|
|
234
|
+
reasons.push(`AgentLighthouse found ${blocking.length} finding(s) at or above ${options.failOnSeverity} severity.`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (options.minConfidence &&
|
|
238
|
+
confidenceRank(result.scoreConfidence) < confidenceRank(options.minConfidence)) {
|
|
239
|
+
reasons.push(`AgentLighthouse confidence ${result.scoreConfidence} is below minimum ${options.minConfidence}.`);
|
|
240
|
+
}
|
|
241
|
+
return { failed: reasons.length > 0, reasons };
|
|
242
|
+
}
|
|
243
|
+
async function writeGithubStepSummary(result, options) {
|
|
244
|
+
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
245
|
+
if (!summaryPath) {
|
|
246
|
+
process.stderr.write("GITHUB_STEP_SUMMARY is not set; skipping GitHub step summary output.\n");
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
await appendFile(summaryPath, `${renderGithubStepSummary(result, options)}\n`, "utf8");
|
|
250
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { runBaselineCreateCommand, runBaselineSummaryCommand, runBaselineValidateCommand } from "./commands/baseline.js";
|
|
4
|
+
import { runCompareCommand } from "./commands/compare.js";
|
|
5
|
+
import { runInitCommand } from "./commands/init.js";
|
|
6
|
+
import { runScanCommand } from "./commands/scan.js";
|
|
7
|
+
const program = new Command();
|
|
8
|
+
program
|
|
9
|
+
.name("agentlighthouse")
|
|
10
|
+
.description("Lighthouse for AI agents: scan projects for agent-readiness.")
|
|
11
|
+
.version("0.1.0-alpha.0");
|
|
12
|
+
program
|
|
13
|
+
.command("scan")
|
|
14
|
+
.argument("[path]", "Project path to scan", ".")
|
|
15
|
+
.option("--json", "Print JSON output")
|
|
16
|
+
.option("--format <format>", "Output format: text, json, markdown, sarif, or pr-summary", "text")
|
|
17
|
+
.option("--profile <profile>", "Scan profile: default, devtool, api, mcp, docs, library, or internal")
|
|
18
|
+
.option("--probe <probe...>", "Opt-in probes to run, currently: commands")
|
|
19
|
+
.option("--run-probes", "Run all safe opt-in probes")
|
|
20
|
+
.option("--output <file>", "Write report output to a file")
|
|
21
|
+
.option("--report-dir <dir>", "Write scan and comparison report bundle files to a directory")
|
|
22
|
+
.option("--baseline <file>", "Compare this scan against a baseline scan-result JSON")
|
|
23
|
+
.option("--comparison-format <format>", "Comparison output format when --baseline is used: text, json, markdown, or pr-summary", "pr-summary")
|
|
24
|
+
.option("--comparison-output <file>", "Write comparison output when --baseline is used")
|
|
25
|
+
.option("--changed-files <file>", "Classify baseline comparison using a changed-files list")
|
|
26
|
+
.option("--git-base <ref>", "Read changed files from git diff using this base ref")
|
|
27
|
+
.option("--git-head <ref>", "Read changed files from git diff using this head ref")
|
|
28
|
+
.option("--fail-under <score>", "Exit with code 1 when score is below the threshold")
|
|
29
|
+
.option("--fail-on-severity <severity>", "Exit with code 1 when any finding is at or above severity: critical, high, medium, low, or info")
|
|
30
|
+
.option("--min-confidence <confidence>", "Exit with code 1 when score confidence is below: low, medium, or high")
|
|
31
|
+
.option("--github-step-summary", "Append a concise Markdown summary to GITHUB_STEP_SUMMARY")
|
|
32
|
+
.option("--fail-on-regression", "With --baseline, exit when comparison regresses")
|
|
33
|
+
.option("--fail-on-score-drop <points>", "With --baseline, exit when score drops by points")
|
|
34
|
+
.option("--fail-on-coverage-drop <points>", "With --baseline, exit when coverage drops by points")
|
|
35
|
+
.option("--fail-on-confidence-drop <points>", "With --baseline, exit when confidence score drops by points")
|
|
36
|
+
.option("--fail-on-new-severity <severity>", "With --baseline, exit when any new finding is at or above severity")
|
|
37
|
+
.option("--fail-on-new-critical", "With --baseline, exit when a new critical finding appears")
|
|
38
|
+
.option("--fail-on-new-high", "With --baseline, exit when a new high/critical finding appears")
|
|
39
|
+
.option("--fail-on-new-changed-severity <severity>", "With --baseline and changed files, exit when new changed-file findings meet severity")
|
|
40
|
+
.option("--fail-on-new-changed-critical", "With --baseline and changed files, exit when a new critical changed-file finding appears")
|
|
41
|
+
.option("--fail-on-new-changed-high", "With --baseline and changed files, exit when a new high/critical changed-file finding appears")
|
|
42
|
+
.option("--fail-on-pr-regression", "With --baseline and changed files, exit on score drop or new high/critical changed-file findings")
|
|
43
|
+
.option("--include <glob...>", "Only include paths containing these patterns")
|
|
44
|
+
.option("--exclude <glob...>", "Exclude paths matching these patterns")
|
|
45
|
+
.option("--no-color", "Disable terminal color")
|
|
46
|
+
.description("Scan a local project directory.")
|
|
47
|
+
.addHelpText("after", `
|
|
48
|
+
|
|
49
|
+
Examples:
|
|
50
|
+
agentlighthouse scan .
|
|
51
|
+
agentlighthouse scan . --format markdown --output agentlighthouse-report.md
|
|
52
|
+
agentlighthouse scan . --report-dir agentlighthouse-reports
|
|
53
|
+
agentlighthouse scan . --baseline agentlighthouse-baseline.json --comparison-output agentlighthouse-delta.md
|
|
54
|
+
agentlighthouse scan . --baseline agentlighthouse-baseline.json --git-base origin/main --git-head HEAD --fail-on-pr-regression
|
|
55
|
+
`)
|
|
56
|
+
.action((targetPath, options) => {
|
|
57
|
+
runScanCommand(targetPath, options).catch(handleError);
|
|
58
|
+
});
|
|
59
|
+
program
|
|
60
|
+
.command("compare")
|
|
61
|
+
.requiredOption("--baseline <file>", "Baseline scan result JSON")
|
|
62
|
+
.requiredOption("--current <file>", "Current scan result JSON")
|
|
63
|
+
.option("--format <format>", "Output format: text, json, markdown, or pr-summary", "text")
|
|
64
|
+
.option("--output <file>", "Write comparison report output to a file")
|
|
65
|
+
.option("--fail-on-regression", "Exit with code 1 when any regression is detected")
|
|
66
|
+
.option("--fail-on-score-drop <points>", "Exit with code 1 when score drops by at least points")
|
|
67
|
+
.option("--fail-on-coverage-drop <points>", "Exit with code 1 when coverage drops by at least points")
|
|
68
|
+
.option("--fail-on-confidence-drop <points>", "Exit with code 1 when confidence score drops by at least points")
|
|
69
|
+
.option("--fail-on-new-severity <severity>", "Exit with code 1 when any new finding is at or above severity")
|
|
70
|
+
.option("--fail-on-new-critical", "Exit with code 1 when a new critical finding appears")
|
|
71
|
+
.option("--fail-on-new-high", "Exit with code 1 when a new high or critical finding appears")
|
|
72
|
+
.option("--changed-files <file>", "Classify comparison findings using an explicit changed-files list")
|
|
73
|
+
.option("--git-base <ref>", "Read changed files from git diff using this base ref")
|
|
74
|
+
.option("--git-head <ref>", "Read changed files from git diff using this head ref")
|
|
75
|
+
.option("--fail-on-new-changed-severity <severity>", "Exit with code 1 when a new finding at or above severity appears on changed files")
|
|
76
|
+
.option("--fail-on-new-changed-critical", "Exit with code 1 when a new critical finding appears on changed files")
|
|
77
|
+
.option("--fail-on-new-changed-high", "Exit with code 1 when a new high or critical finding appears on changed files")
|
|
78
|
+
.option("--fail-on-pr-regression", "Exit with code 1 when score drops or new high/critical findings appear on changed files")
|
|
79
|
+
.description("Compare two saved AgentLighthouse JSON scan reports.")
|
|
80
|
+
.addHelpText("after", `
|
|
81
|
+
|
|
82
|
+
Examples:
|
|
83
|
+
agentlighthouse compare --baseline baseline.json --current current.json
|
|
84
|
+
agentlighthouse compare --baseline baseline.json --current current.json --format markdown --output delta.md
|
|
85
|
+
agentlighthouse compare --baseline baseline.json --current current.json --changed-files changed-files.txt --fail-on-new-changed-high
|
|
86
|
+
`)
|
|
87
|
+
.action((options) => {
|
|
88
|
+
runCompareCommand(options).catch(handleError);
|
|
89
|
+
});
|
|
90
|
+
program
|
|
91
|
+
.command("init")
|
|
92
|
+
.argument("[path]", "Project path where starter artifacts should be created", ".")
|
|
93
|
+
.option("--dry-run", "Show what would be generated without writing files")
|
|
94
|
+
.option("--force", "Overwrite existing starter artifacts")
|
|
95
|
+
.option("--yes", "Assume yes for non-destructive generation")
|
|
96
|
+
.description("Generate safe starter agent-readiness artifacts without overwriting by default.")
|
|
97
|
+
.addHelpText("after", `
|
|
98
|
+
|
|
99
|
+
Examples:
|
|
100
|
+
agentlighthouse init .
|
|
101
|
+
agentlighthouse init . --dry-run
|
|
102
|
+
`)
|
|
103
|
+
.action((targetPath, options) => {
|
|
104
|
+
runInitCommand(targetPath, options).catch(handleError);
|
|
105
|
+
});
|
|
106
|
+
const baseline = program
|
|
107
|
+
.command("baseline")
|
|
108
|
+
.description("Create, validate, and summarize baselines.");
|
|
109
|
+
baseline
|
|
110
|
+
.command("create")
|
|
111
|
+
.argument("[path]", "Project path to scan for the baseline", ".")
|
|
112
|
+
.option("--output <file>", "Baseline output path", "agentlighthouse-baseline.json")
|
|
113
|
+
.option("--profile <profile>", "Scan profile: default, devtool, api, mcp, docs, library, or internal")
|
|
114
|
+
.description("Create a baseline scan-result JSON file.")
|
|
115
|
+
.addHelpText("after", `
|
|
116
|
+
|
|
117
|
+
Examples:
|
|
118
|
+
agentlighthouse baseline create .
|
|
119
|
+
agentlighthouse baseline create . --output agentlighthouse-baseline.json
|
|
120
|
+
`)
|
|
121
|
+
.action((targetPath, options) => {
|
|
122
|
+
runBaselineCreateCommand(targetPath, options).catch(handleError);
|
|
123
|
+
});
|
|
124
|
+
baseline
|
|
125
|
+
.command("validate")
|
|
126
|
+
.argument("<file>", "Baseline scan-result JSON file")
|
|
127
|
+
.description("Validate that a baseline file is an AgentLighthouse scan-result JSON.")
|
|
128
|
+
.addHelpText("after", `
|
|
129
|
+
|
|
130
|
+
Examples:
|
|
131
|
+
agentlighthouse baseline validate agentlighthouse-baseline.json
|
|
132
|
+
`)
|
|
133
|
+
.action((baselinePath, options) => {
|
|
134
|
+
runBaselineValidateCommand(baselinePath, options).catch(handleError);
|
|
135
|
+
});
|
|
136
|
+
baseline
|
|
137
|
+
.command("summary")
|
|
138
|
+
.argument("<file>", "Baseline scan-result JSON file")
|
|
139
|
+
.description("Print score, confidence, coverage, profile, and finding counts for a baseline.")
|
|
140
|
+
.addHelpText("after", `
|
|
141
|
+
|
|
142
|
+
Examples:
|
|
143
|
+
agentlighthouse baseline summary agentlighthouse-baseline.json
|
|
144
|
+
`)
|
|
145
|
+
.action((baselinePath) => {
|
|
146
|
+
runBaselineSummaryCommand(baselinePath).catch(handleError);
|
|
147
|
+
});
|
|
148
|
+
program
|
|
149
|
+
.command("version")
|
|
150
|
+
.description("Print the AgentLighthouse CLI version.")
|
|
151
|
+
.action(() => {
|
|
152
|
+
process.stdout.write(`${program.version()}\n`);
|
|
153
|
+
});
|
|
154
|
+
program.parse();
|
|
155
|
+
function handleError(error) {
|
|
156
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
157
|
+
process.stderr.write(`AgentLighthouse error: ${message}\n`);
|
|
158
|
+
process.exitCode = 1;
|
|
159
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathing.d.ts","sourceRoot":"","sources":["../src/pathing.ts"],"names":[],"mappings":"AAEA,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAKnE"}
|
package/dist/pathing.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentlighthouse/cli",
|
|
3
|
+
"version": "0.1.0-alpha.0",
|
|
4
|
+
"description": "AgentLighthouse command-line interface.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/PainDeMie64/agentlighthouse.git",
|
|
9
|
+
"directory": "packages/cli"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/PainDeMie64/agentlighthouse#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/PainDeMie64/agentlighthouse/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"agent-readiness",
|
|
17
|
+
"cli",
|
|
18
|
+
"ci",
|
|
19
|
+
"sarif",
|
|
20
|
+
"github-actions",
|
|
21
|
+
"openapi",
|
|
22
|
+
"mcp"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"!dist/__tests__",
|
|
29
|
+
"!dist/.tsbuildinfo",
|
|
30
|
+
"package.json",
|
|
31
|
+
"README.md",
|
|
32
|
+
"LICENSE"
|
|
33
|
+
],
|
|
34
|
+
"bin": {
|
|
35
|
+
"agentlighthouse": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"main": "./dist/index.js",
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"exports": {
|
|
40
|
+
".": {
|
|
41
|
+
"types": "./dist/index.d.ts",
|
|
42
|
+
"import": "./dist/index.js"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsc -p tsconfig.json",
|
|
50
|
+
"dev": "tsx src/index.ts",
|
|
51
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
52
|
+
"test": "vitest run"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@agentlighthouse/core": "workspace:^",
|
|
56
|
+
"commander": "^14.0.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/node": "^22.0.0",
|
|
60
|
+
"tsx": "^4.0.0",
|
|
61
|
+
"typescript": "^5.0.0",
|
|
62
|
+
"vitest": "^3.0.0"
|
|
63
|
+
}
|
|
64
|
+
}
|