@agentlighthouse/core 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 +16 -0
- package/dist/analyzers/mcp.d.ts +8 -0
- package/dist/analyzers/mcp.d.ts.map +1 -0
- package/dist/analyzers/mcp.js +214 -0
- package/dist/analyzers/openapi.d.ts +7 -0
- package/dist/analyzers/openapi.d.ts.map +1 -0
- package/dist/analyzers/openapi.js +344 -0
- package/dist/analyzers/readiness.d.ts +8 -0
- package/dist/analyzers/readiness.d.ts.map +1 -0
- package/dist/analyzers/readiness.js +766 -0
- package/dist/analyzers/tasks.d.ts +3 -0
- package/dist/analyzers/tasks.d.ts.map +1 -0
- package/dist/analyzers/tasks.js +140 -0
- package/dist/changes/files.d.ts +5 -0
- package/dist/changes/files.d.ts.map +1 -0
- package/dist/changes/files.js +71 -0
- package/dist/comparison/compare.d.ts +14 -0
- package/dist/comparison/compare.d.ts.map +1 -0
- package/dist/comparison/compare.js +323 -0
- package/dist/config/profile.d.ts +16 -0
- package/dist/config/profile.d.ts.map +1 -0
- package/dist/config/profile.js +47 -0
- package/dist/detection/project.d.ts +4 -0
- package/dist/detection/project.d.ts.map +1 -0
- package/dist/detection/project.js +225 -0
- package/dist/findings/helpers.d.ts +36 -0
- package/dist/findings/helpers.d.ts.map +1 -0
- package/dist/findings/helpers.js +115 -0
- package/dist/findings/locations.d.ts +4 -0
- package/dist/findings/locations.d.ts.map +1 -0
- package/dist/findings/locations.js +117 -0
- package/dist/generators/artifacts.d.ts +6 -0
- package/dist/generators/artifacts.d.ts.map +1 -0
- package/dist/generators/artifacts.js +255 -0
- package/dist/index.d.ts +486 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +451 -0
- package/dist/probes/commands.d.ts +7 -0
- package/dist/probes/commands.d.ts.map +1 -0
- package/dist/probes/commands.js +198 -0
- package/dist/reporters/cli.d.ts +4 -0
- package/dist/reporters/cli.d.ts.map +1 -0
- package/dist/reporters/cli.js +42 -0
- package/dist/reporters/comparison.d.ts +13 -0
- package/dist/reporters/comparison.d.ts.map +1 -0
- package/dist/reporters/comparison.js +227 -0
- package/dist/reporters/github-summary.d.ts +4 -0
- package/dist/reporters/github-summary.d.ts.map +1 -0
- package/dist/reporters/github-summary.js +4 -0
- package/dist/reporters/json.d.ts +3 -0
- package/dist/reporters/json.d.ts.map +1 -0
- package/dist/reporters/json.js +3 -0
- package/dist/reporters/markdown.d.ts +3 -0
- package/dist/reporters/markdown.d.ts.map +1 -0
- package/dist/reporters/markdown.js +146 -0
- package/dist/reporters/pr-summary.d.ts +8 -0
- package/dist/reporters/pr-summary.d.ts.map +1 -0
- package/dist/reporters/pr-summary.js +38 -0
- package/dist/reporters/sarif.d.ts +3 -0
- package/dist/reporters/sarif.d.ts.map +1 -0
- package/dist/reporters/sarif.js +119 -0
- package/dist/reporters/shared.d.ts +8 -0
- package/dist/reporters/shared.d.ts.map +1 -0
- package/dist/reporters/shared.js +26 -0
- package/dist/scanners/filesystem.d.ts +6 -0
- package/dist/scanners/filesystem.d.ts.map +1 -0
- package/dist/scanners/filesystem.js +231 -0
- package/dist/schemas/types.d.ts +6652 -0
- package/dist/schemas/types.d.ts.map +1 -0
- package/dist/schemas/types.js +383 -0
- package/dist/scoring/calibration.d.ts +18 -0
- package/dist/scoring/calibration.d.ts.map +1 -0
- package/dist/scoring/calibration.js +231 -0
- package/dist/scoring/model.d.ts +21 -0
- package/dist/scoring/model.d.ts.map +1 -0
- package/dist/scoring/model.js +109 -0
- package/package.json +58 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { severityOrder, titleCase } from "./shared.js";
|
|
2
|
+
export { renderMarkdownReport } from "./markdown.js";
|
|
3
|
+
export function renderCliReport(result, color = false) {
|
|
4
|
+
const lines = [];
|
|
5
|
+
lines.push(`AgentLighthouse Score: ${result.score}/100`);
|
|
6
|
+
lines.push(`Confidence: ${titleCase(result.scoreConfidence)} (${result.scoreConfidenceScore}/100)`);
|
|
7
|
+
lines.push(`Coverage: ${result.coverage.coveragePercent}%`);
|
|
8
|
+
lines.push(`Project: ${result.detectedProject.name} (${result.detectedProject.type})`);
|
|
9
|
+
lines.push(`Human signals: ${result.scoreInterpretation.humanReadableProjectSignals.score}/100 | Agent context: ${result.scoreInterpretation.agentSpecificContextLayer.score}/100 | Verifiability: ${result.scoreInterpretation.verifiability.score}/100`);
|
|
10
|
+
lines.push("");
|
|
11
|
+
lines.push("Subscores:");
|
|
12
|
+
for (const subscore of result.subscores) {
|
|
13
|
+
lines.push(`- ${subscore.label}: ${subscore.score}/100`);
|
|
14
|
+
}
|
|
15
|
+
lines.push("");
|
|
16
|
+
lines.push(result.summary);
|
|
17
|
+
lines.push("");
|
|
18
|
+
for (const severity of severityOrder) {
|
|
19
|
+
const findings = result.findings.filter((finding) => finding.severity === severity);
|
|
20
|
+
if (findings.length === 0) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
lines.push(`${titleCase(severity)} severity:`);
|
|
24
|
+
for (const finding of findings) {
|
|
25
|
+
const affected = finding.affectedFile ? ` (${finding.affectedFile})` : "";
|
|
26
|
+
lines.push(`- ${finding.title}${affected}`);
|
|
27
|
+
lines.push(` ${finding.recommendation}`);
|
|
28
|
+
if (finding.agentFailureMode) {
|
|
29
|
+
lines.push(` Agent failure mode: ${finding.agentFailureMode}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
lines.push("");
|
|
33
|
+
}
|
|
34
|
+
if (result.recommendations.length > 0) {
|
|
35
|
+
lines.push("Recommended next actions:");
|
|
36
|
+
result.recommendations.forEach((action, index) => {
|
|
37
|
+
lines.push(`${index + 1}. ${action}`);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const report = lines.join("\n").trimEnd();
|
|
41
|
+
return color ? report : report;
|
|
42
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ComparisonResult } from "../schemas/types.js";
|
|
2
|
+
export declare function renderComparisonJsonReport(result: ComparisonResult): string;
|
|
3
|
+
export declare function renderComparisonCliReport(result: ComparisonResult): string;
|
|
4
|
+
export declare function renderComparisonMarkdownReport(result: ComparisonResult, options?: {
|
|
5
|
+
status?: "passed" | "failed";
|
|
6
|
+
reasons?: string[];
|
|
7
|
+
}): string;
|
|
8
|
+
export declare function renderComparisonPrSummaryReport(result: ComparisonResult, options?: {
|
|
9
|
+
status?: "passed" | "failed";
|
|
10
|
+
reasons?: string[];
|
|
11
|
+
reportPaths?: string[];
|
|
12
|
+
}): string;
|
|
13
|
+
//# sourceMappingURL=comparison.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comparison.d.ts","sourceRoot":"","sources":["../../src/reporters/comparison.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAqB,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG/E,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAE3E;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAmC1E;AAED,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,gBAAgB,EACxB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GACjE,MAAM,CAgER;AAED,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,gBAAgB,EACxB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GACzF,MAAM,CAyCR"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { severityOrder, severityRank, titleCase } from "./shared.js";
|
|
2
|
+
export function renderComparisonJsonReport(result) {
|
|
3
|
+
return JSON.stringify(result, null, 2);
|
|
4
|
+
}
|
|
5
|
+
export function renderComparisonCliReport(result) {
|
|
6
|
+
const lines = [];
|
|
7
|
+
lines.push(`AgentLighthouse PR Delta: ${titleCase(result.summary.verdict)}`);
|
|
8
|
+
lines.push(`Score: ${result.baseline.score} -> ${result.current.score} (${formatDelta(result.deltas.scoreDelta)})`);
|
|
9
|
+
lines.push(`Confidence: ${result.baseline.confidence} -> ${result.current.confidence} (${formatDelta(result.deltas.confidenceDelta)})`);
|
|
10
|
+
lines.push(`Coverage: ${result.baseline.coverage}% -> ${result.current.coverage}% (${formatDelta(result.deltas.coverageDelta)}%)`);
|
|
11
|
+
lines.push("");
|
|
12
|
+
lines.push(`New findings: ${result.findings.new.length}`);
|
|
13
|
+
lines.push(`Resolved findings: ${result.findings.resolved.length}`);
|
|
14
|
+
lines.push(`Worsened findings: ${result.findings.worsened.length}`);
|
|
15
|
+
lines.push(`Improved findings: ${result.findings.improved.length}`);
|
|
16
|
+
lines.push(`Unchanged findings: ${result.findings.unchanged.length}`);
|
|
17
|
+
if (result.summary.recommendedActions.length > 0) {
|
|
18
|
+
lines.push("");
|
|
19
|
+
lines.push("Recommended actions:");
|
|
20
|
+
result.summary.recommendedActions.forEach((action, index) => {
|
|
21
|
+
lines.push(`${index + 1}. ${action}`);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (result.summary.caveats.length > 0) {
|
|
25
|
+
lines.push("");
|
|
26
|
+
lines.push("Caveats:");
|
|
27
|
+
result.summary.caveats.forEach((caveat) => lines.push(`- ${caveat}`));
|
|
28
|
+
}
|
|
29
|
+
if (result.prImpact) {
|
|
30
|
+
lines.push("");
|
|
31
|
+
lines.push(`PR impact: ${result.prImpact.summary}`);
|
|
32
|
+
}
|
|
33
|
+
return lines.join("\n");
|
|
34
|
+
}
|
|
35
|
+
export function renderComparisonMarkdownReport(result, options = {}) {
|
|
36
|
+
return `# AgentLighthouse Comparison Report
|
|
37
|
+
|
|
38
|
+
Verdict: **${titleCase(result.summary.verdict)}**
|
|
39
|
+
|
|
40
|
+
AgentLighthouse compares **agent-readiness**, not general software quality. A score regression means agents have less usable context, weaker verifiability, or new semantic risk under the selected profile.
|
|
41
|
+
|
|
42
|
+
## Delta Summary
|
|
43
|
+
|
|
44
|
+
| Metric | Baseline | Current | Delta |
|
|
45
|
+
| --- | ---: | ---: | ---: |
|
|
46
|
+
| Score | ${result.baseline.score} | ${result.current.score} | ${formatDelta(result.deltas.scoreDelta)} |
|
|
47
|
+
| Confidence score | ${result.baseline.confidenceScore} | ${result.current.confidenceScore} | ${formatDelta(result.deltas.confidenceDelta)} |
|
|
48
|
+
| Coverage | ${result.baseline.coverage}% | ${result.current.coverage}% | ${formatDelta(result.deltas.coverageDelta)}% |
|
|
49
|
+
| Findings | ${totalFindings(result, "baseline")} | ${totalFindings(result, "current")} | ${formatDelta(result.deltas.findingCountDelta)} |
|
|
50
|
+
|
|
51
|
+
${renderPrImpactMarkdown(result)}
|
|
52
|
+
|
|
53
|
+
## Gate Status
|
|
54
|
+
|
|
55
|
+
${renderGateStatus(options)}
|
|
56
|
+
|
|
57
|
+
## Severity Count Deltas
|
|
58
|
+
|
|
59
|
+
| Severity | Delta |
|
|
60
|
+
| --- | ---: |
|
|
61
|
+
${severityOrder.map((severity) => `| ${severity} | ${formatDelta(result.deltas.severityCountDeltas[severity] ?? 0)} |`).join("\n")}
|
|
62
|
+
|
|
63
|
+
## New Findings
|
|
64
|
+
|
|
65
|
+
${renderFindingList(result.findings.new)}
|
|
66
|
+
|
|
67
|
+
## Resolved Findings
|
|
68
|
+
|
|
69
|
+
${renderFindingList(result.findings.resolved)}
|
|
70
|
+
|
|
71
|
+
## Worsened Findings
|
|
72
|
+
|
|
73
|
+
${renderFindingList(result.findings.worsened)}
|
|
74
|
+
|
|
75
|
+
## Improved Findings
|
|
76
|
+
|
|
77
|
+
${renderFindingList(result.findings.improved)}
|
|
78
|
+
|
|
79
|
+
## Unchanged
|
|
80
|
+
|
|
81
|
+
${result.findings.unchanged.length} finding(s) remained unchanged.
|
|
82
|
+
|
|
83
|
+
## Recommended Actions
|
|
84
|
+
|
|
85
|
+
${result.summary.recommendedActions.map((action, index) => `${index + 1}. ${action}`).join("\n") || "No comparison-specific actions."}
|
|
86
|
+
|
|
87
|
+
## Caveats
|
|
88
|
+
|
|
89
|
+
${result.summary.caveats.map((caveat) => `- ${caveat}`).join("\n") || "No caveats."}
|
|
90
|
+
|
|
91
|
+
## Metadata
|
|
92
|
+
|
|
93
|
+
- Baseline scan: \`${result.baseline.scanId}\` (${result.baseline.completedAt})
|
|
94
|
+
- Current scan: \`${result.current.scanId}\` (${result.current.completedAt})
|
|
95
|
+
- Baseline profile: \`${result.baseline.profile}\`
|
|
96
|
+
- Current profile: \`${result.current.profile}\`
|
|
97
|
+
- Comparison model: \`${result.metadata.comparisonModelVersion}\`
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
100
|
+
export function renderComparisonPrSummaryReport(result, options = {}) {
|
|
101
|
+
const newHighRisk = result.findings.new.filter((finding) => severityRank(finding.severity) >= severityRank("high"));
|
|
102
|
+
const impact = result.prImpact;
|
|
103
|
+
return `## AgentLighthouse PR Delta: ${titleCase(result.summary.verdict)}
|
|
104
|
+
|
|
105
|
+
**Status:** ${options.status === "failed" ? "Failed" : "Passed"}
|
|
106
|
+
|
|
107
|
+
Score: **${result.baseline.score} -> ${result.current.score}** (${formatDelta(result.deltas.scoreDelta)})
|
|
108
|
+
Confidence: **${result.baseline.confidence} -> ${result.current.confidence}** (${formatDelta(result.deltas.confidenceDelta)})
|
|
109
|
+
Coverage: **${result.baseline.coverage}% -> ${result.current.coverage}%** (${formatDelta(result.deltas.coverageDelta)}%)
|
|
110
|
+
${impact ? `Changed files analyzed: **${impact.changedFileCount}**\n` : ""}
|
|
111
|
+
|
|
112
|
+
${options.reasons && options.reasons.length > 0 ? `### Gate Result\n\n${options.reasons.map((reason) => `- ${reason}`).join("\n")}\n` : ""}
|
|
113
|
+
${impact ? `### New Findings On Changed Files\n\n${renderFindingList(impact.newFindingsOnChangedFiles.slice(0, 5))}\n\n### Resolved Findings On Changed Files\n\n${renderFindingList(impact.resolvedFindingsOnChangedFiles.slice(0, 5))}\n\n### New Global Findings\n\n${renderFindingList(impact.globalNewFindings.slice(0, 5))}\n` : ""}
|
|
114
|
+
### New High-Severity Findings
|
|
115
|
+
|
|
116
|
+
${renderFindingList(newHighRisk.slice(0, 5))}
|
|
117
|
+
|
|
118
|
+
### Top New Findings
|
|
119
|
+
|
|
120
|
+
${renderFindingList(result.findings.new.slice(0, 5))}
|
|
121
|
+
|
|
122
|
+
### Top Resolved Findings
|
|
123
|
+
|
|
124
|
+
${renderFindingList(result.findings.resolved.slice(0, 5))}
|
|
125
|
+
|
|
126
|
+
### Recommended Actions
|
|
127
|
+
|
|
128
|
+
${result.summary.recommendedActions
|
|
129
|
+
.slice(0, 5)
|
|
130
|
+
.map((action, index) => `${index + 1}. ${action}`)
|
|
131
|
+
.join("\n") || "No comparison-specific actions."}
|
|
132
|
+
|
|
133
|
+
### Reports
|
|
134
|
+
|
|
135
|
+
${options.reportPaths?.map((reportPath) => `- ${reportPath}`).join("\n") || "No comparison report artifact path was provided."}
|
|
136
|
+
`;
|
|
137
|
+
}
|
|
138
|
+
function renderPrImpactMarkdown(result) {
|
|
139
|
+
const impact = result.prImpact;
|
|
140
|
+
if (!impact) {
|
|
141
|
+
return "";
|
|
142
|
+
}
|
|
143
|
+
return `## PR Impact Summary
|
|
144
|
+
|
|
145
|
+
${impact.summary}
|
|
146
|
+
|
|
147
|
+
| Bucket | Count |
|
|
148
|
+
| --- | ---: |
|
|
149
|
+
| Changed files | ${impact.changedFileCount} |
|
|
150
|
+
| New findings on changed files | ${impact.newFindingsOnChangedFiles.length} |
|
|
151
|
+
| Resolved findings on changed files | ${impact.resolvedFindingsOnChangedFiles.length} |
|
|
152
|
+
| New global findings | ${impact.globalNewFindings.length} |
|
|
153
|
+
| Unknown-location findings | ${impact.unknownLocationFindings.length} |
|
|
154
|
+
| Unrelated existing findings | ${impact.unrelatedExistingFindings.length} |
|
|
155
|
+
|
|
156
|
+
## Changed Files
|
|
157
|
+
|
|
158
|
+
${impact.changedFiles.map((file) => `- ${file.status}: ${file.oldPath ? `${file.oldPath} -> ` : ""}${file.path}`).join("\n") || "None."}
|
|
159
|
+
|
|
160
|
+
## New Findings On Changed Files
|
|
161
|
+
|
|
162
|
+
${renderFindingList(impact.newFindingsOnChangedFiles)}
|
|
163
|
+
|
|
164
|
+
## Resolved Findings On Changed Files
|
|
165
|
+
|
|
166
|
+
${renderFindingList(impact.resolvedFindingsOnChangedFiles)}
|
|
167
|
+
|
|
168
|
+
## New Global Findings
|
|
169
|
+
|
|
170
|
+
${renderFindingList(impact.globalNewFindings)}
|
|
171
|
+
|
|
172
|
+
## Unknown-Location Findings
|
|
173
|
+
|
|
174
|
+
${renderFindingList(impact.unknownLocationFindings)}
|
|
175
|
+
|
|
176
|
+
## Unrelated Existing Findings Summary
|
|
177
|
+
|
|
178
|
+
${impact.unrelatedExistingFindings.length} unchanged finding(s) are outside the changed-file set.`;
|
|
179
|
+
}
|
|
180
|
+
function renderGateStatus(options) {
|
|
181
|
+
if (!options.status) {
|
|
182
|
+
return "No comparison gate status was provided.";
|
|
183
|
+
}
|
|
184
|
+
const status = options.status === "failed" ? "Failed" : "Passed";
|
|
185
|
+
const reasons = options.reasons && options.reasons.length > 0
|
|
186
|
+
? `\n\n${options.reasons.map((reason) => `- ${reason}`).join("\n")}`
|
|
187
|
+
: "";
|
|
188
|
+
return `**${status}**${reasons}`;
|
|
189
|
+
}
|
|
190
|
+
function renderFindingList(findings) {
|
|
191
|
+
if (findings.length === 0) {
|
|
192
|
+
return "None.";
|
|
193
|
+
}
|
|
194
|
+
return findings
|
|
195
|
+
.map((finding, index) => {
|
|
196
|
+
const severityChange = finding.previousSeverity && finding.currentSeverity
|
|
197
|
+
? ` (${finding.previousSeverity} -> ${finding.currentSeverity})`
|
|
198
|
+
: "";
|
|
199
|
+
return `${index + 1}. **${finding.severity}** \`${finding.ruleId}\`: ${finding.title}${severityChange}${formatFindingLocation(finding)}`;
|
|
200
|
+
})
|
|
201
|
+
.join("\n");
|
|
202
|
+
}
|
|
203
|
+
function formatFindingLocation(finding) {
|
|
204
|
+
const file = finding.location?.file ?? finding.affectedFile;
|
|
205
|
+
if (!file)
|
|
206
|
+
return "";
|
|
207
|
+
const line = finding.location?.startLine ? `:${finding.location.startLine}` : "";
|
|
208
|
+
const subject = finding.subject ?? finding.location?.subject;
|
|
209
|
+
return ` (${file}${line}${subject ? `, ${subject}` : ""})`;
|
|
210
|
+
}
|
|
211
|
+
function formatDelta(value) {
|
|
212
|
+
if (value > 0)
|
|
213
|
+
return `+${value}`;
|
|
214
|
+
return String(value);
|
|
215
|
+
}
|
|
216
|
+
function totalFindings(result, side) {
|
|
217
|
+
if (side === "baseline") {
|
|
218
|
+
return (result.findings.resolved.length +
|
|
219
|
+
result.findings.unchanged.length +
|
|
220
|
+
result.findings.worsened.length +
|
|
221
|
+
result.findings.improved.length);
|
|
222
|
+
}
|
|
223
|
+
return (result.findings.new.length +
|
|
224
|
+
result.findings.unchanged.length +
|
|
225
|
+
result.findings.worsened.length +
|
|
226
|
+
result.findings.improved.length);
|
|
227
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-summary.d.ts","sourceRoot":"","sources":["../../src/reporters/github-summary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAyB,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAE/E,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE,gBAAqB,GAC7B,MAAM,CAER"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/reporters/json.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAE3D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/reporters/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAGtD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAyJ/D"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { severityOrder, titleCase, topActionableFindings } from "./shared.js";
|
|
2
|
+
export function renderMarkdownReport(result) {
|
|
3
|
+
const topFindings = topActionableFindings(result.findings, 5)
|
|
4
|
+
.map((finding) => `- **${finding.severity}**: ${finding.title}${finding.affectedFile ? ` (${finding.affectedFile})` : ""}`)
|
|
5
|
+
.join("\n");
|
|
6
|
+
const groupedFindings = severityOrder
|
|
7
|
+
.map((severity) => {
|
|
8
|
+
const findings = result.findings.filter((finding) => finding.severity === severity);
|
|
9
|
+
if (findings.length === 0) {
|
|
10
|
+
return "";
|
|
11
|
+
}
|
|
12
|
+
return `### ${titleCase(severity)}
|
|
13
|
+
|
|
14
|
+
${findings
|
|
15
|
+
.map((finding) => `#### ${finding.title}
|
|
16
|
+
|
|
17
|
+
- Rule ID: \`${finding.ruleId}\`
|
|
18
|
+
- Severity: ${finding.severity}
|
|
19
|
+
- Category: ${finding.category}
|
|
20
|
+
- Affected file: ${finding.affectedFile ?? "n/a"}
|
|
21
|
+
- Recommendation: ${finding.recommendation}
|
|
22
|
+
- Agent failure mode: ${finding.agentFailureMode ?? "n/a"}
|
|
23
|
+
- Fix example: ${finding.fixExample ?? "n/a"}
|
|
24
|
+
- Evidence: ${finding.evidence.join("; ")}
|
|
25
|
+
`)
|
|
26
|
+
.join("\n")}`;
|
|
27
|
+
})
|
|
28
|
+
.filter(Boolean)
|
|
29
|
+
.join("\n\n");
|
|
30
|
+
const artifactTable = result.detectedArtifacts
|
|
31
|
+
.map((artifact) => `| ${artifact.path} | ${artifact.exists ? "yes" : "no"} | ${artifact.kind} | ${artifact.quality} | ${artifact.role} |`)
|
|
32
|
+
.join("\n");
|
|
33
|
+
return `# AgentLighthouse Report: ${result.projectName}
|
|
34
|
+
|
|
35
|
+
AgentLighthouse measures **agent-readiness**, not general software quality. A mature human-friendly project can score lower when it lacks agent-specific context, verifiable workflows, or machine-readable API/tool guidance.
|
|
36
|
+
|
|
37
|
+
Score: **${result.score}/100**
|
|
38
|
+
|
|
39
|
+
${result.summary}
|
|
40
|
+
|
|
41
|
+
Confidence: **${titleCase(result.scoreConfidence)}** (${result.scoreConfidenceScore}/100)
|
|
42
|
+
|
|
43
|
+
Coverage: **${result.coverage.coveragePercent}%**
|
|
44
|
+
|
|
45
|
+
## Project Detection
|
|
46
|
+
|
|
47
|
+
- Type: \`${result.detectedProject.type}\`
|
|
48
|
+
- Profile: \`${result.profile}\`
|
|
49
|
+
- Confidence: ${Math.round(result.detectedProject.confidence * 100)}%
|
|
50
|
+
- Package manager: \`${result.detectedProject.packageManager}\`
|
|
51
|
+
- Frameworks: ${result.detectedProject.frameworks.length > 0 ? result.detectedProject.frameworks.join(", ") : "none detected"}
|
|
52
|
+
- Evidence: ${result.detectedProject.evidence.join("; ")}
|
|
53
|
+
|
|
54
|
+
## Score Interpretation
|
|
55
|
+
|
|
56
|
+
- Agent-Readiness Score: ${result.scoreInterpretation.agentReadinessScore}/100
|
|
57
|
+
- Human-readable project signals: ${result.scoreInterpretation.humanReadableProjectSignals.score}/100 - ${result.scoreInterpretation.humanReadableProjectSignals.signals.join(", ") || "none detected"}
|
|
58
|
+
- Agent-specific context layer: ${result.scoreInterpretation.agentSpecificContextLayer.score}/100 - ${result.scoreInterpretation.agentSpecificContextLayer.signals.join(", ") || "none detected"}
|
|
59
|
+
- Verifiability: ${result.scoreInterpretation.verifiability.score}/100 - ${result.scoreInterpretation.verifiability.signals.join(", ") || "none detected"}
|
|
60
|
+
|
|
61
|
+
## CI Interpretation
|
|
62
|
+
|
|
63
|
+
- This score should be used as an agent-readiness gate, not as a judgment of overall engineering quality.
|
|
64
|
+
- Low scores usually mean agents need more context files, clearer examples, verifiable commands, or safer API/MCP descriptions.
|
|
65
|
+
- Command execution probes are opt-in; static analysis remains the default.
|
|
66
|
+
|
|
67
|
+
## Subscores
|
|
68
|
+
|
|
69
|
+
${result.subscores.map((subscore) => `- ${subscore.label}: ${subscore.score}/100`).join("\n")}
|
|
70
|
+
|
|
71
|
+
## API Analysis
|
|
72
|
+
|
|
73
|
+
- Spec files: ${result.apiAnalysis.specFiles.join(", ") || "none"}
|
|
74
|
+
- Operations: ${result.apiAnalysis.operationCount}
|
|
75
|
+
- Operations with examples: ${result.apiAnalysis.operationsWithExamples}
|
|
76
|
+
- Operations missing descriptions: ${result.apiAnalysis.operationsMissingDescriptions}
|
|
77
|
+
- Auth schemes: ${result.apiAnalysis.authSchemes.join(", ") || "none"}
|
|
78
|
+
- Destructive operations: ${result.apiAnalysis.destructiveOperations.join("; ") || "none"}
|
|
79
|
+
- Weak operations: ${result.apiAnalysis.weakOperations.slice(0, 8).join("; ") || "none"}
|
|
80
|
+
|
|
81
|
+
## MCP Analysis
|
|
82
|
+
|
|
83
|
+
- Detected: ${result.mcpAnalysis.detected ? "yes" : "no"}
|
|
84
|
+
- Files: ${result.mcpAnalysis.files.join(", ") || "none"}
|
|
85
|
+
- Tools: ${result.mcpAnalysis.toolCount}
|
|
86
|
+
- Tools with schemas: ${result.mcpAnalysis.toolsWithSchemas}
|
|
87
|
+
- Tools with examples: ${result.mcpAnalysis.toolsWithExamples}
|
|
88
|
+
- Ambiguous tools: ${result.mcpAnalysis.ambiguousTools.join("; ") || "none"}
|
|
89
|
+
- Destructive tools: ${result.mcpAnalysis.destructiveTools.join("; ") || "none"}
|
|
90
|
+
- Weak tools: ${result.mcpAnalysis.weakTools.slice(0, 8).join("; ") || "none"}
|
|
91
|
+
|
|
92
|
+
## Command Probes
|
|
93
|
+
|
|
94
|
+
- Enabled: ${result.commandProbes.enabled ? "yes" : "no"}
|
|
95
|
+
- Attempted: ${result.commandProbes.attempted}
|
|
96
|
+
- Passed: ${result.commandProbes.passed}
|
|
97
|
+
- Failed: ${result.commandProbes.failed}
|
|
98
|
+
- Timed out: ${result.commandProbes.timedOut}
|
|
99
|
+
- Skipped: ${result.commandProbes.skipped}
|
|
100
|
+
|
|
101
|
+
## Coverage
|
|
102
|
+
|
|
103
|
+
- Evaluated checks: ${result.coverage.evaluatedChecks}
|
|
104
|
+
- Skipped checks: ${result.coverage.skippedChecks}
|
|
105
|
+
- Not applicable checks: ${result.coverage.notApplicableChecks}
|
|
106
|
+
- Not evaluated checks: ${result.coverage.notEvaluatedChecks}
|
|
107
|
+
- Evaluated categories: ${result.coverage.evaluatedCategories.join(", ") || "none"}
|
|
108
|
+
- Missing categories: ${result.coverage.missingCategories.join(", ") || "none"}
|
|
109
|
+
|
|
110
|
+
## Scoring Caps
|
|
111
|
+
|
|
112
|
+
${result.scoringCaps.map((cap) => `- ${cap.id}: max ${cap.maxScore}. ${cap.reason}`).join("\n") || "No scoring caps applied."}
|
|
113
|
+
|
|
114
|
+
## Top Findings
|
|
115
|
+
|
|
116
|
+
${topFindings || "No non-informational findings."}
|
|
117
|
+
|
|
118
|
+
## Recommended Actions
|
|
119
|
+
|
|
120
|
+
${result.recommendations.map((action, index) => `${index + 1}. ${action}`).join("\n") || "No prioritized actions."}
|
|
121
|
+
|
|
122
|
+
## Detected Artifacts
|
|
123
|
+
|
|
124
|
+
| Path | Exists | Kind | Quality | Role |
|
|
125
|
+
| --- | --- | --- | --- | --- |
|
|
126
|
+
${artifactTable}
|
|
127
|
+
|
|
128
|
+
## Scan Metadata
|
|
129
|
+
|
|
130
|
+
- Scan ID: \`${result.scanId}\`
|
|
131
|
+
- AgentLighthouse version: \`${result.agentLighthouseVersion}\`
|
|
132
|
+
- Scoring model: \`${result.scoringModelVersion}\`
|
|
133
|
+
- Started: ${result.startedAt}
|
|
134
|
+
- Completed: ${result.completedAt}
|
|
135
|
+
- Duration: ${result.durationMs}ms
|
|
136
|
+
- Files scanned: ${result.scanStats.filesScanned}
|
|
137
|
+
- Text files read: ${result.scanStats.textFilesRead}
|
|
138
|
+
- Ignored paths observed: ${result.ignoredPaths.length}
|
|
139
|
+
- Warnings: ${result.warnings.length}
|
|
140
|
+
- Errors: ${result.errors.length}
|
|
141
|
+
|
|
142
|
+
## Findings
|
|
143
|
+
|
|
144
|
+
${groupedFindings || "No findings."}
|
|
145
|
+
`;
|
|
146
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ScanResult } from "../schemas/types.js";
|
|
2
|
+
export interface PrSummaryOptions {
|
|
3
|
+
status?: "passed" | "failed";
|
|
4
|
+
reasons?: string[];
|
|
5
|
+
reportPaths?: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function renderPrSummaryReport(result: ScanResult, options?: PrSummaryOptions): string;
|
|
8
|
+
//# sourceMappingURL=pr-summary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pr-summary.d.ts","sourceRoot":"","sources":["../../src/reporters/pr-summary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAGtD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAsChG"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { titleCase, topActionableFindings } from "./shared.js";
|
|
2
|
+
export function renderPrSummaryReport(result, options = {}) {
|
|
3
|
+
const status = options.status ?? "passed";
|
|
4
|
+
const findings = topActionableFindings(result.findings, 5);
|
|
5
|
+
const reportPaths = options.reportPaths ?? [];
|
|
6
|
+
return `## AgentLighthouse PR Summary
|
|
7
|
+
|
|
8
|
+
**Status:** ${status === "passed" ? "Passed" : "Failed"}
|
|
9
|
+
|
|
10
|
+
AgentLighthouse measures **agent-readiness**, not general software quality. It checks whether AI coding agents have enough context, examples, API/tool semantics, and verifiable workflows to use this project safely.
|
|
11
|
+
|
|
12
|
+
- Score: **${result.score}/100**
|
|
13
|
+
- Confidence: **${titleCase(result.scoreConfidence)}** (${result.scoreConfidenceScore}/100)
|
|
14
|
+
- Coverage: **${result.coverage.coveragePercent}%**
|
|
15
|
+
- Profile: \`${result.profile}\`
|
|
16
|
+
- Project type: \`${result.detectedProject.type}\`
|
|
17
|
+
|
|
18
|
+
${options.reasons && options.reasons.length > 0 ? `### Gate Result\n\n${options.reasons.map((reason) => `- ${reason}`).join("\n")}\n` : ""}
|
|
19
|
+
### Top Findings
|
|
20
|
+
|
|
21
|
+
${findings.map((finding) => `- **${finding.severity}** \`${finding.ruleId}\`: ${finding.title}${finding.affectedFile ? ` (${finding.affectedFile})` : ""}`).join("\n") || "No non-informational findings."}
|
|
22
|
+
|
|
23
|
+
### Recommended Next Actions
|
|
24
|
+
|
|
25
|
+
${result.recommendations
|
|
26
|
+
.slice(0, 5)
|
|
27
|
+
.map((action, index) => `${index + 1}. ${action}`)
|
|
28
|
+
.join("\n") || "No prioritized actions."}
|
|
29
|
+
|
|
30
|
+
### Reports
|
|
31
|
+
|
|
32
|
+
${reportPaths.map((reportPath) => `- ${reportPath}`).join("\n") || "No external report artifact path was provided."}
|
|
33
|
+
|
|
34
|
+
### What Changed
|
|
35
|
+
|
|
36
|
+
Diff comparison is planned for a future baseline mode. For now, compare this summary with the saved baseline report or previous CI artifact.
|
|
37
|
+
`;
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sarif.d.ts","sourceRoot":"","sources":["../../src/reporters/sarif.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,UAAU,EAAY,MAAM,qBAAqB,CAAC;AAmEzE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAoB5D"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
export function renderSarifReport(result) {
|
|
2
|
+
const rules = uniqueRules(result.findings);
|
|
3
|
+
const sarif = {
|
|
4
|
+
version: "2.1.0",
|
|
5
|
+
$schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
|
6
|
+
runs: [
|
|
7
|
+
{
|
|
8
|
+
tool: {
|
|
9
|
+
driver: {
|
|
10
|
+
name: "AgentLighthouse",
|
|
11
|
+
semanticVersion: result.agentLighthouseVersion,
|
|
12
|
+
informationUri: "https://github.com/PainDeMie64/agentlighthouse",
|
|
13
|
+
rules
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
results: result.findings.map((finding) => findingToResult(finding, result))
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
};
|
|
20
|
+
return JSON.stringify(sarif, null, 2);
|
|
21
|
+
}
|
|
22
|
+
function uniqueRules(findings) {
|
|
23
|
+
const byRule = new Map();
|
|
24
|
+
for (const finding of findings) {
|
|
25
|
+
if (!byRule.has(finding.ruleId)) {
|
|
26
|
+
byRule.set(finding.ruleId, finding);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return [...byRule.values()].map((finding) => ({
|
|
30
|
+
id: finding.ruleId,
|
|
31
|
+
name: finding.title,
|
|
32
|
+
shortDescription: { text: finding.title },
|
|
33
|
+
fullDescription: { text: finding.description },
|
|
34
|
+
help: {
|
|
35
|
+
text: finding.recommendation,
|
|
36
|
+
markdown: [
|
|
37
|
+
finding.recommendation,
|
|
38
|
+
finding.agentFailureMode ? `Agent failure mode: ${finding.agentFailureMode}` : undefined,
|
|
39
|
+
finding.fixExample ? `Fix example: ${finding.fixExample}` : undefined
|
|
40
|
+
]
|
|
41
|
+
.filter((line) => Boolean(line))
|
|
42
|
+
.join("\n\n")
|
|
43
|
+
},
|
|
44
|
+
defaultConfiguration: { level: severityToSarifLevel(finding.severity) },
|
|
45
|
+
properties: {
|
|
46
|
+
category: finding.category,
|
|
47
|
+
severity: finding.severity,
|
|
48
|
+
tags: ["agent-readiness", finding.category, finding.severity]
|
|
49
|
+
}
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
function findingToResult(finding, result) {
|
|
53
|
+
return {
|
|
54
|
+
ruleId: finding.ruleId,
|
|
55
|
+
level: severityToSarifLevel(finding.severity),
|
|
56
|
+
message: {
|
|
57
|
+
text: `${finding.title}: ${finding.recommendation}`
|
|
58
|
+
},
|
|
59
|
+
...(finding.location?.file || finding.affectedFile
|
|
60
|
+
? {
|
|
61
|
+
locations: [
|
|
62
|
+
{
|
|
63
|
+
physicalLocation: {
|
|
64
|
+
artifactLocation: { uri: finding.location?.file ?? finding.affectedFile },
|
|
65
|
+
region: sarifRegion(finding)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
: {}),
|
|
71
|
+
partialFingerprints: {
|
|
72
|
+
agentLighthouseFinding: stableFingerprint(finding)
|
|
73
|
+
},
|
|
74
|
+
properties: {
|
|
75
|
+
category: finding.category,
|
|
76
|
+
severity: finding.severity,
|
|
77
|
+
...(finding.agentFailureMode ? { agentFailureMode: finding.agentFailureMode } : {}),
|
|
78
|
+
recommendation: finding.recommendation,
|
|
79
|
+
...(finding.fixExample ? { fixExample: finding.fixExample } : {}),
|
|
80
|
+
confidence: result.scoreConfidence,
|
|
81
|
+
...(finding.subject || finding.location?.subject
|
|
82
|
+
? { subject: finding.subject ?? finding.location?.subject }
|
|
83
|
+
: {}),
|
|
84
|
+
...(finding.locationKey || finding.location?.locationKey
|
|
85
|
+
? { locationKey: finding.locationKey ?? finding.location?.locationKey }
|
|
86
|
+
: {}),
|
|
87
|
+
...(finding.location?.sourceKind ? { sourceKind: finding.location.sourceKind } : {})
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function sarifRegion(finding) {
|
|
92
|
+
return {
|
|
93
|
+
startLine: finding.location?.startLine ?? 1,
|
|
94
|
+
...(finding.location?.startColumn ? { startColumn: finding.location.startColumn } : {}),
|
|
95
|
+
...(finding.location?.endLine ? { endLine: finding.location.endLine } : {}),
|
|
96
|
+
...(finding.location?.endColumn ? { endColumn: finding.location.endColumn } : {})
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function severityToSarifLevel(severity) {
|
|
100
|
+
if (severity === "critical" || severity === "high") {
|
|
101
|
+
return "error";
|
|
102
|
+
}
|
|
103
|
+
if (severity === "medium") {
|
|
104
|
+
return "warning";
|
|
105
|
+
}
|
|
106
|
+
return "note";
|
|
107
|
+
}
|
|
108
|
+
function stableFingerprint(finding) {
|
|
109
|
+
if (finding.fingerprint) {
|
|
110
|
+
return finding.fingerprint;
|
|
111
|
+
}
|
|
112
|
+
const input = `${finding.ruleId}:${finding.affectedFile ?? ""}:${finding.evidence.join("|")}`;
|
|
113
|
+
let hash = 2166136261;
|
|
114
|
+
for (const character of input) {
|
|
115
|
+
hash ^= character.charCodeAt(0);
|
|
116
|
+
hash = Math.imul(hash, 16777619);
|
|
117
|
+
}
|
|
118
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
119
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Finding, Severity } from "../schemas/types.js";
|
|
2
|
+
export declare const severityOrder: Severity[];
|
|
3
|
+
export declare function titleCase(value: string): string;
|
|
4
|
+
export declare function topActionableFindings(findings: Finding[], limit: number): Finding[];
|
|
5
|
+
export declare function findingLocation(finding: Finding): string;
|
|
6
|
+
export declare function severityRank(severity: Severity): number;
|
|
7
|
+
export declare function confidenceRank(confidence: "low" | "medium" | "high"): number;
|
|
8
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/reporters/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE7D,eAAO,MAAM,aAAa,EAAE,QAAQ,EAAkD,CAAC;AAEvF,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE,CAEnF;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAExD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAQvD;AAED,wBAAgB,cAAc,CAAC,UAAU,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAM5E"}
|