@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.
Files changed (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +16 -0
  3. package/dist/analyzers/mcp.d.ts +8 -0
  4. package/dist/analyzers/mcp.d.ts.map +1 -0
  5. package/dist/analyzers/mcp.js +214 -0
  6. package/dist/analyzers/openapi.d.ts +7 -0
  7. package/dist/analyzers/openapi.d.ts.map +1 -0
  8. package/dist/analyzers/openapi.js +344 -0
  9. package/dist/analyzers/readiness.d.ts +8 -0
  10. package/dist/analyzers/readiness.d.ts.map +1 -0
  11. package/dist/analyzers/readiness.js +766 -0
  12. package/dist/analyzers/tasks.d.ts +3 -0
  13. package/dist/analyzers/tasks.d.ts.map +1 -0
  14. package/dist/analyzers/tasks.js +140 -0
  15. package/dist/changes/files.d.ts +5 -0
  16. package/dist/changes/files.d.ts.map +1 -0
  17. package/dist/changes/files.js +71 -0
  18. package/dist/comparison/compare.d.ts +14 -0
  19. package/dist/comparison/compare.d.ts.map +1 -0
  20. package/dist/comparison/compare.js +323 -0
  21. package/dist/config/profile.d.ts +16 -0
  22. package/dist/config/profile.d.ts.map +1 -0
  23. package/dist/config/profile.js +47 -0
  24. package/dist/detection/project.d.ts +4 -0
  25. package/dist/detection/project.d.ts.map +1 -0
  26. package/dist/detection/project.js +225 -0
  27. package/dist/findings/helpers.d.ts +36 -0
  28. package/dist/findings/helpers.d.ts.map +1 -0
  29. package/dist/findings/helpers.js +115 -0
  30. package/dist/findings/locations.d.ts +4 -0
  31. package/dist/findings/locations.d.ts.map +1 -0
  32. package/dist/findings/locations.js +117 -0
  33. package/dist/generators/artifacts.d.ts +6 -0
  34. package/dist/generators/artifacts.d.ts.map +1 -0
  35. package/dist/generators/artifacts.js +255 -0
  36. package/dist/index.d.ts +486 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +451 -0
  39. package/dist/probes/commands.d.ts +7 -0
  40. package/dist/probes/commands.d.ts.map +1 -0
  41. package/dist/probes/commands.js +198 -0
  42. package/dist/reporters/cli.d.ts +4 -0
  43. package/dist/reporters/cli.d.ts.map +1 -0
  44. package/dist/reporters/cli.js +42 -0
  45. package/dist/reporters/comparison.d.ts +13 -0
  46. package/dist/reporters/comparison.d.ts.map +1 -0
  47. package/dist/reporters/comparison.js +227 -0
  48. package/dist/reporters/github-summary.d.ts +4 -0
  49. package/dist/reporters/github-summary.d.ts.map +1 -0
  50. package/dist/reporters/github-summary.js +4 -0
  51. package/dist/reporters/json.d.ts +3 -0
  52. package/dist/reporters/json.d.ts.map +1 -0
  53. package/dist/reporters/json.js +3 -0
  54. package/dist/reporters/markdown.d.ts +3 -0
  55. package/dist/reporters/markdown.d.ts.map +1 -0
  56. package/dist/reporters/markdown.js +146 -0
  57. package/dist/reporters/pr-summary.d.ts +8 -0
  58. package/dist/reporters/pr-summary.d.ts.map +1 -0
  59. package/dist/reporters/pr-summary.js +38 -0
  60. package/dist/reporters/sarif.d.ts +3 -0
  61. package/dist/reporters/sarif.d.ts.map +1 -0
  62. package/dist/reporters/sarif.js +119 -0
  63. package/dist/reporters/shared.d.ts +8 -0
  64. package/dist/reporters/shared.d.ts.map +1 -0
  65. package/dist/reporters/shared.js +26 -0
  66. package/dist/scanners/filesystem.d.ts +6 -0
  67. package/dist/scanners/filesystem.d.ts.map +1 -0
  68. package/dist/scanners/filesystem.js +231 -0
  69. package/dist/schemas/types.d.ts +6652 -0
  70. package/dist/schemas/types.d.ts.map +1 -0
  71. package/dist/schemas/types.js +383 -0
  72. package/dist/scoring/calibration.d.ts +18 -0
  73. package/dist/scoring/calibration.d.ts.map +1 -0
  74. package/dist/scoring/calibration.js +231 -0
  75. package/dist/scoring/model.d.ts +21 -0
  76. package/dist/scoring/model.d.ts.map +1 -0
  77. package/dist/scoring/model.js +109 -0
  78. 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,4 @@
1
+ import type { ScanResult } from "../schemas/types.js";
2
+ import { type PrSummaryOptions } from "./pr-summary.js";
3
+ export declare function renderGithubStepSummary(result: ScanResult, options?: PrSummaryOptions): string;
4
+ //# sourceMappingURL=github-summary.d.ts.map
@@ -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,4 @@
1
+ import { renderPrSummaryReport } from "./pr-summary.js";
2
+ export function renderGithubStepSummary(result, options = {}) {
3
+ return renderPrSummaryReport(result, options);
4
+ }
@@ -0,0 +1,3 @@
1
+ import type { ScanResult } from "../schemas/types.js";
2
+ export declare function renderJsonReport(result: ScanResult): string;
3
+ //# sourceMappingURL=json.d.ts.map
@@ -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,3 @@
1
+ export function renderJsonReport(result) {
2
+ return JSON.stringify(result, null, 2);
3
+ }
@@ -0,0 +1,3 @@
1
+ import type { ScanResult } from "../schemas/types.js";
2
+ export declare function renderMarkdownReport(result: ScanResult): string;
3
+ //# sourceMappingURL=markdown.d.ts.map
@@ -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,3 @@
1
+ import type { ScanResult } from "../schemas/types.js";
2
+ export declare function renderSarifReport(result: ScanResult): string;
3
+ //# sourceMappingURL=sarif.d.ts.map
@@ -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"}