@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,26 @@
|
|
|
1
|
+
export const severityOrder = ["critical", "high", "medium", "low", "info"];
|
|
2
|
+
export function titleCase(value) {
|
|
3
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
4
|
+
}
|
|
5
|
+
export function topActionableFindings(findings, limit) {
|
|
6
|
+
return findings.filter((finding) => finding.severity !== "info").slice(0, limit);
|
|
7
|
+
}
|
|
8
|
+
export function findingLocation(finding) {
|
|
9
|
+
return finding.affectedFile ? ` (${finding.affectedFile})` : "";
|
|
10
|
+
}
|
|
11
|
+
export function severityRank(severity) {
|
|
12
|
+
return {
|
|
13
|
+
info: 0,
|
|
14
|
+
low: 1,
|
|
15
|
+
medium: 2,
|
|
16
|
+
high: 3,
|
|
17
|
+
critical: 4
|
|
18
|
+
}[severity];
|
|
19
|
+
}
|
|
20
|
+
export function confidenceRank(confidence) {
|
|
21
|
+
return {
|
|
22
|
+
low: 0,
|
|
23
|
+
medium: 1,
|
|
24
|
+
high: 2
|
|
25
|
+
}[confidence];
|
|
26
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ProjectSignals, ScanOptions, Scanner } from "../schemas/types.js";
|
|
2
|
+
export declare class LocalFilesystemScanner implements Scanner {
|
|
3
|
+
readonly id = "local-filesystem";
|
|
4
|
+
scan(projectPath: string, options?: ScanOptions): Promise<ProjectSignals>;
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=filesystem.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filesystem.d.ts","sourceRoot":"","sources":["../../src/scanners/filesystem.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGV,cAAc,EACd,WAAW,EACX,OAAO,EACR,MAAM,qBAAqB,CAAC;AA0C7B,qBAAa,sBAAuB,YAAW,OAAO;IACpD,QAAQ,CAAC,EAAE,sBAAsB;IAE3B,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,cAAc,CAAC;CA+EpF"}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const defaultIgnores = [
|
|
4
|
+
".git",
|
|
5
|
+
"node_modules",
|
|
6
|
+
"dist",
|
|
7
|
+
"build",
|
|
8
|
+
"coverage",
|
|
9
|
+
".next",
|
|
10
|
+
".turbo",
|
|
11
|
+
".vercel",
|
|
12
|
+
".tmp",
|
|
13
|
+
"vendor",
|
|
14
|
+
".DS_Store"
|
|
15
|
+
];
|
|
16
|
+
const textExtensions = new Set([
|
|
17
|
+
".md",
|
|
18
|
+
".mdx",
|
|
19
|
+
".txt",
|
|
20
|
+
".json",
|
|
21
|
+
".yaml",
|
|
22
|
+
".yml",
|
|
23
|
+
".ts",
|
|
24
|
+
".tsx",
|
|
25
|
+
".js",
|
|
26
|
+
".jsx",
|
|
27
|
+
".mjs",
|
|
28
|
+
".cjs",
|
|
29
|
+
".toml"
|
|
30
|
+
]);
|
|
31
|
+
const artifactPaths = [
|
|
32
|
+
"AGENTS.md",
|
|
33
|
+
"CLAUDE.md",
|
|
34
|
+
"llms.txt",
|
|
35
|
+
"README.md",
|
|
36
|
+
".cursor/rules",
|
|
37
|
+
".github/copilot-instructions.md",
|
|
38
|
+
".agentlighthouseignore"
|
|
39
|
+
];
|
|
40
|
+
export class LocalFilesystemScanner {
|
|
41
|
+
id = "local-filesystem";
|
|
42
|
+
async scan(projectPath, options = {}) {
|
|
43
|
+
const rootPath = path.resolve(projectPath);
|
|
44
|
+
const maxFileSizeBytes = options.maxFileSizeBytes ?? 512_000;
|
|
45
|
+
const ignoreRules = await readIgnoreRules(rootPath, options.exclude ?? []);
|
|
46
|
+
const ignoredPaths = [];
|
|
47
|
+
const scannedFiles = await walk(rootPath, rootPath, ignoreRules, options.include ?? [], ignoredPaths);
|
|
48
|
+
const textByPath = {};
|
|
49
|
+
const artifacts = {};
|
|
50
|
+
let bytesRead = 0;
|
|
51
|
+
for (const relativePath of scannedFiles) {
|
|
52
|
+
const absolutePath = path.join(rootPath, relativePath);
|
|
53
|
+
const stat = await fs.stat(absolutePath);
|
|
54
|
+
if (stat.size <= maxFileSizeBytes && isTextFile(relativePath)) {
|
|
55
|
+
textByPath[relativePath] = await fs.readFile(absolutePath, "utf8");
|
|
56
|
+
bytesRead += stat.size;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const artifactPath of artifactPaths) {
|
|
60
|
+
const absolutePath = path.join(rootPath, artifactPath);
|
|
61
|
+
artifacts[artifactPath] = await artifactSignal(absolutePath, artifactPath, textByPath);
|
|
62
|
+
}
|
|
63
|
+
const docsMarkdownFiles = scannedFiles.filter((file) => file.startsWith("docs/") && [".md", ".mdx"].includes(path.extname(file)));
|
|
64
|
+
const openApiFiles = scannedFiles.filter(isOpenApiFile);
|
|
65
|
+
const mcpFiles = scannedFiles.filter((file) => /(^|\/)mcp\.json$/i.test(file) ||
|
|
66
|
+
/(^|\/)mcp-server\.[jt]sx?$/i.test(file) ||
|
|
67
|
+
/(^|\/)mcp-server\.example\.txt$/i.test(file));
|
|
68
|
+
const benchmarkFiles = scannedFiles.filter((file) => [
|
|
69
|
+
"agentlighthouse.tasks.yaml",
|
|
70
|
+
"agentlighthouse.tasks.yml",
|
|
71
|
+
"benchmarks/agent-tasks.yaml",
|
|
72
|
+
"benchmarks/agent-tasks.yml",
|
|
73
|
+
".agentlighthouse/tasks.yaml",
|
|
74
|
+
".agentlighthouse/tasks.yml"
|
|
75
|
+
].includes(file));
|
|
76
|
+
const configFiles = scannedFiles.filter(isConfigFile);
|
|
77
|
+
const packageJson = parsePackageJson(textByPath["package.json"]);
|
|
78
|
+
const projectName = packageJson?.name ?? path.basename(rootPath);
|
|
79
|
+
return {
|
|
80
|
+
rootPath,
|
|
81
|
+
projectName,
|
|
82
|
+
scannedFiles,
|
|
83
|
+
artifacts,
|
|
84
|
+
docsMarkdownFiles,
|
|
85
|
+
openApiFiles,
|
|
86
|
+
mcpFiles: [...new Set([...mcpFiles, ...mcpPackageSignals(packageJson)])],
|
|
87
|
+
configFiles,
|
|
88
|
+
benchmarkFiles,
|
|
89
|
+
ignoredPaths: ignoredPaths.sort(),
|
|
90
|
+
warnings: [],
|
|
91
|
+
errors: [],
|
|
92
|
+
scanStats: {
|
|
93
|
+
filesScanned: scannedFiles.length,
|
|
94
|
+
textFilesRead: Object.keys(textByPath).length,
|
|
95
|
+
bytesRead,
|
|
96
|
+
docsMarkdownFileCount: docsMarkdownFiles.length,
|
|
97
|
+
openApiFileCount: openApiFiles.length,
|
|
98
|
+
benchmarkFileCount: benchmarkFiles.length
|
|
99
|
+
},
|
|
100
|
+
packageJson,
|
|
101
|
+
textByPath
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function artifactSignal(absolutePath, relativePath, textByPath) {
|
|
106
|
+
try {
|
|
107
|
+
const stat = await fs.stat(absolutePath);
|
|
108
|
+
return {
|
|
109
|
+
path: relativePath,
|
|
110
|
+
exists: true,
|
|
111
|
+
kind: stat.isDirectory() ? "directory" : "file",
|
|
112
|
+
sizeBytes: stat.size,
|
|
113
|
+
contentPreview: textByPath[relativePath]?.slice(0, 500)
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return { path: relativePath, exists: false, kind: "missing" };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function readIgnoreRules(rootPath, extraExcludes) {
|
|
121
|
+
const rules = [...defaultIgnores, ...extraExcludes];
|
|
122
|
+
for (const ignoreFile of [".gitignore", ".agentlighthouseignore"]) {
|
|
123
|
+
try {
|
|
124
|
+
const content = await fs.readFile(path.join(rootPath, ignoreFile), "utf8");
|
|
125
|
+
rules.push(...content
|
|
126
|
+
.split(/\r?\n/)
|
|
127
|
+
.map((line) => line.trim())
|
|
128
|
+
.filter((line) => line && !line.startsWith("#")));
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// Missing ignore files are expected in many target repos.
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return rules;
|
|
135
|
+
}
|
|
136
|
+
async function walk(rootPath, currentPath, ignoreRules, includes, ignoredPaths) {
|
|
137
|
+
const entries = await fs.readdir(currentPath, { withFileTypes: true });
|
|
138
|
+
const files = [];
|
|
139
|
+
for (const entry of entries) {
|
|
140
|
+
const absolutePath = path.join(currentPath, entry.name);
|
|
141
|
+
const relativePath = normalizePath(path.relative(rootPath, absolutePath));
|
|
142
|
+
if (shouldIgnore(relativePath, entry.name, ignoreRules)) {
|
|
143
|
+
if (ignoredPaths.length < 500) {
|
|
144
|
+
ignoredPaths.push(relativePath);
|
|
145
|
+
}
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (entry.isDirectory()) {
|
|
149
|
+
files.push(...(await walk(rootPath, absolutePath, ignoreRules, includes, ignoredPaths)));
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (entry.isFile() && matchesInclude(relativePath, includes)) {
|
|
153
|
+
files.push(relativePath);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return files.sort();
|
|
157
|
+
}
|
|
158
|
+
function normalizePath(filePath) {
|
|
159
|
+
return filePath.split(path.sep).join("/");
|
|
160
|
+
}
|
|
161
|
+
function shouldIgnore(relativePath, basename, rules) {
|
|
162
|
+
return rules.some((rawRule) => {
|
|
163
|
+
const rule = rawRule.replace(/^\//, "").replace(/\/$/, "");
|
|
164
|
+
if (!rule || rule.startsWith("!")) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
if (rule.includes("*")) {
|
|
168
|
+
const pattern = new RegExp(`^${escapeRegex(rule).replaceAll("\\*", ".*")}$`);
|
|
169
|
+
return pattern.test(relativePath) || pattern.test(basename);
|
|
170
|
+
}
|
|
171
|
+
return relativePath === rule || relativePath.startsWith(`${rule}/`) || basename === rule;
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function matchesInclude(relativePath, includes) {
|
|
175
|
+
if (includes.length === 0) {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
return includes.some((include) => relativePath.includes(include.replaceAll("*", "")));
|
|
179
|
+
}
|
|
180
|
+
function escapeRegex(value) {
|
|
181
|
+
return value.replace(/[|\\{}()[\]^$+?.*]/g, "\\$&");
|
|
182
|
+
}
|
|
183
|
+
function isTextFile(file) {
|
|
184
|
+
return (textExtensions.has(path.extname(file)) || ["AGENTS.md", "CLAUDE.md", "llms.txt"].includes(file));
|
|
185
|
+
}
|
|
186
|
+
function isOpenApiFile(file) {
|
|
187
|
+
const lower = file.toLowerCase();
|
|
188
|
+
return (lower.endsWith("openapi.yaml") ||
|
|
189
|
+
lower.endsWith("openapi.yml") ||
|
|
190
|
+
lower.endsWith("openapi.json") ||
|
|
191
|
+
lower.endsWith("swagger.yaml") ||
|
|
192
|
+
lower.endsWith("swagger.yml") ||
|
|
193
|
+
lower.endsWith("swagger.json"));
|
|
194
|
+
}
|
|
195
|
+
function isConfigFile(file) {
|
|
196
|
+
return [
|
|
197
|
+
"package.json",
|
|
198
|
+
"tsconfig.json",
|
|
199
|
+
"tsconfig.base.json",
|
|
200
|
+
"eslint.config.mjs",
|
|
201
|
+
"next.config.ts",
|
|
202
|
+
"vite.config.ts",
|
|
203
|
+
"vitest.config.ts",
|
|
204
|
+
"agentlighthouse.config.json"
|
|
205
|
+
].includes(file);
|
|
206
|
+
}
|
|
207
|
+
function parsePackageJson(content) {
|
|
208
|
+
if (!content) {
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const parsed = JSON.parse(content);
|
|
213
|
+
return {
|
|
214
|
+
path: "package.json",
|
|
215
|
+
name: parsed.name,
|
|
216
|
+
packageManager: parsed.packageManager,
|
|
217
|
+
scripts: parsed.scripts ?? {},
|
|
218
|
+
dependencies: Object.keys(parsed.dependencies ?? {}),
|
|
219
|
+
devDependencies: Object.keys(parsed.devDependencies ?? {})
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function mcpPackageSignals(packageJson) {
|
|
227
|
+
if (!packageJson) {
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
return [...packageJson.dependencies, ...packageJson.devDependencies].filter((dependency) => dependency.toLowerCase().includes("mcp"));
|
|
231
|
+
}
|