@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
package/dist/index.js ADDED
@@ -0,0 +1,451 @@
1
+ import { analyzeMcp } from "./analyzers/mcp.js";
2
+ import { analyzeOpenApi } from "./analyzers/openapi.js";
3
+ import { ReadinessAnalyzer } from "./analyzers/readiness.js";
4
+ import { analyzeTaskBenchmarks } from "./analyzers/tasks.js";
5
+ import { compareScanResults } from "./comparison/compare.js";
6
+ import { resolveConfig, resolveProfile } from "./config/profile.js";
7
+ import { detectProject, detectedArtifacts } from "./detection/project.js";
8
+ import { enrichFindingLocations } from "./findings/locations.js";
9
+ import { StarterArtifactGenerator } from "./generators/artifacts.js";
10
+ import { runCommandProbes } from "./probes/commands.js";
11
+ import { LocalFilesystemScanner } from "./scanners/filesystem.js";
12
+ import { calibrateScore } from "./scoring/calibration.js";
13
+ import { TransparentScoringModel } from "./scoring/model.js";
14
+ export const agentLighthouseVersion = "0.1.0-alpha.0";
15
+ export * from "./schemas/types.js";
16
+ export * from "./analyzers/mcp.js";
17
+ export * from "./analyzers/openapi.js";
18
+ export * from "./analyzers/readiness.js";
19
+ export * from "./analyzers/tasks.js";
20
+ export * from "./changes/files.js";
21
+ export * from "./config/profile.js";
22
+ export * from "./comparison/compare.js";
23
+ export * from "./detection/project.js";
24
+ export * from "./findings/helpers.js";
25
+ export * from "./findings/locations.js";
26
+ export * from "./generators/artifacts.js";
27
+ export * from "./probes/commands.js";
28
+ export * from "./reporters/cli.js";
29
+ export * from "./reporters/comparison.js";
30
+ export * from "./reporters/github-summary.js";
31
+ export * from "./reporters/json.js";
32
+ export * from "./reporters/markdown.js";
33
+ export * from "./reporters/pr-summary.js";
34
+ export * from "./reporters/sarif.js";
35
+ export * from "./reporters/shared.js";
36
+ export * from "./scanners/filesystem.js";
37
+ export * from "./scoring/model.js";
38
+ export async function scanProject(projectPath, options = {}) {
39
+ const startedAt = new Date();
40
+ const scanner = new LocalFilesystemScanner();
41
+ const scoring = new TransparentScoringModel();
42
+ const warnings = [];
43
+ const errors = [];
44
+ let signals = await scanner.scan(projectPath, options);
45
+ const config = resolveConfig(signals);
46
+ const profile = resolveProfile(signals, options).profile;
47
+ const probes = {
48
+ ...config.probes,
49
+ ...options.probes,
50
+ allowedScripts: options.probes?.allowedScripts ?? config.probes?.allowedScripts
51
+ };
52
+ const analyzer = new ReadinessAnalyzer(profile);
53
+ const detectedProject = detectProject(signals);
54
+ const openApi = analyzeOpenApi(signals);
55
+ const mcp = analyzeMcp(signals);
56
+ const commandProbeRun = await runCommandProbes(signals, detectedProject, {
57
+ ...options,
58
+ probes
59
+ });
60
+ const findings = enrichFindingLocations(signals, [
61
+ ...analyzer.analyze(signals),
62
+ ...openApi.findings,
63
+ ...mcp.findings,
64
+ ...analyzeTaskBenchmarks(signals),
65
+ ...commandProbeRun.findings
66
+ ]);
67
+ const scored = scoring.score(findings, signals);
68
+ const artifacts = detectedArtifacts(signals);
69
+ const calibrated = calibrateScore({
70
+ rawScore: scored.score,
71
+ findings,
72
+ signals,
73
+ detectedProject,
74
+ detectedArtifacts: artifacts,
75
+ profile
76
+ });
77
+ const completedAt = new Date();
78
+ signals = {
79
+ ...signals,
80
+ warnings: [...signals.warnings, ...warnings],
81
+ errors: [...signals.errors, ...errors]
82
+ };
83
+ return {
84
+ scanId: createScanId(signals.rootPath, startedAt),
85
+ scannedPath: signals.rootPath,
86
+ startedAt: startedAt.toISOString(),
87
+ completedAt: completedAt.toISOString(),
88
+ durationMs: completedAt.getTime() - startedAt.getTime(),
89
+ agentLighthouseVersion,
90
+ profile,
91
+ ...scored,
92
+ ...calibrated,
93
+ projectName: signals.projectName,
94
+ findings,
95
+ scoreInterpretation: interpretScore({
96
+ score: calibrated.score,
97
+ signals,
98
+ apiAnalysis: openApi.analysis,
99
+ mcpAnalysis: mcp.analysis,
100
+ commandProbes: commandProbeRun.summary
101
+ }),
102
+ apiAnalysis: openApi.analysis,
103
+ mcpAnalysis: mcp.analysis,
104
+ commandProbes: commandProbeRun.summary,
105
+ detectedProject,
106
+ detectedArtifacts: artifacts,
107
+ scanStats: {
108
+ ...signals.scanStats,
109
+ findingCount: findings.length
110
+ },
111
+ ignoredPaths: signals.ignoredPaths,
112
+ warnings: signals.warnings,
113
+ errors: signals.errors,
114
+ projectPath: signals.rootPath,
115
+ scannedAt: completedAt.toISOString(),
116
+ recommendedActions: scored.recommendations,
117
+ signals
118
+ };
119
+ }
120
+ export async function generateStarterArtifacts(projectPath, options = {}) {
121
+ const scanner = new LocalFilesystemScanner();
122
+ const generator = new StarterArtifactGenerator();
123
+ const signals = await scanner.scan(projectPath, options);
124
+ return generator.generate(signals);
125
+ }
126
+ function createScanId(projectPath, startedAt) {
127
+ const input = `${projectPath}:${startedAt.toISOString()}`;
128
+ let hash = 0;
129
+ for (let index = 0; index < input.length; index += 1) {
130
+ hash = (hash * 31 + input.charCodeAt(index)) >>> 0;
131
+ }
132
+ return `scan_${hash.toString(16).padStart(8, "0")}`;
133
+ }
134
+ function interpretScore(input) {
135
+ const humanSignals = [
136
+ input.signals.artifacts["README.md"]?.exists ? "README present" : undefined,
137
+ input.signals.docsMarkdownFiles.length > 0
138
+ ? `${input.signals.docsMarkdownFiles.length} Markdown doc file(s)`
139
+ : undefined,
140
+ input.signals.packageJson ? "package metadata present" : undefined,
141
+ input.apiAnalysis.specFiles.length > 0 ? "OpenAPI spec present" : undefined,
142
+ input.mcpAnalysis.detected ? "MCP files or dependencies present" : undefined
143
+ ].filter((signal) => Boolean(signal));
144
+ const agentSignals = [
145
+ input.signals.artifacts["AGENTS.md"]?.exists ? "AGENTS.md present" : undefined,
146
+ input.signals.artifacts["CLAUDE.md"]?.exists ? "CLAUDE.md present" : undefined,
147
+ input.signals.artifacts["llms.txt"]?.exists ? "llms.txt present" : undefined,
148
+ input.signals.artifacts[".cursor/rules"]?.exists ? "Cursor rules present" : undefined,
149
+ input.signals.artifacts[".github/copilot-instructions.md"]?.exists
150
+ ? "Copilot instructions present"
151
+ : undefined,
152
+ input.signals.benchmarkFiles.length > 0 ? "agent task benchmark present" : undefined
153
+ ].filter((signal) => Boolean(signal));
154
+ const verifiabilitySignals = [
155
+ input.signals.packageJson?.scripts.test ? "test script declared" : undefined,
156
+ input.signals.packageJson?.scripts.lint ? "lint script declared" : undefined,
157
+ input.signals.packageJson?.scripts.typecheck ? "typecheck script declared" : undefined,
158
+ input.apiAnalysis.operationsWithExamples > 0
159
+ ? `${input.apiAnalysis.operationsWithExamples} API operation(s) have examples`
160
+ : undefined,
161
+ input.commandProbes.enabled
162
+ ? `${input.commandProbes.passed}/${input.commandProbes.attempted} command probes passed`
163
+ : "command probes not run"
164
+ ].filter((signal) => Boolean(signal));
165
+ return {
166
+ agentReadinessScore: input.score,
167
+ humanReadableProjectSignals: {
168
+ score: boundedScore(humanSignals.length * 20 + Math.min(input.apiAnalysis.operationCount, 5) * 3),
169
+ summary: "Human-readable project signals describe conventional repo, docs, examples, and API context.",
170
+ signals: humanSignals
171
+ },
172
+ agentSpecificContextLayer: {
173
+ score: boundedScore(agentSignals.length * 17),
174
+ summary: "Agent-specific context is the machine-readable layer that helps coding agents work safely.",
175
+ signals: agentSignals
176
+ },
177
+ verifiability: {
178
+ score: boundedScore(verifiabilitySignals.filter((signal) => signal !== "command probes not run").length * 20 +
179
+ (input.commandProbes.enabled ? 15 : 0)),
180
+ summary: "Verifiability measures whether setup, tests, examples, and workflows can be checked.",
181
+ signals: verifiabilitySignals
182
+ }
183
+ };
184
+ }
185
+ function boundedScore(value) {
186
+ return Math.max(0, Math.min(100, Math.round(value)));
187
+ }
188
+ export const sampleScanResult = {
189
+ scanId: "scan_demo",
190
+ scannedPath: "/sample/agent-ready-project",
191
+ startedAt: "2026-05-20T00:00:00.000Z",
192
+ completedAt: "2026-05-20T00:00:00.120Z",
193
+ durationMs: 120,
194
+ agentLighthouseVersion,
195
+ profile: "devtool",
196
+ projectName: "sample-agent-ready-project",
197
+ scoringModelVersion: "0.1.0",
198
+ score: 72,
199
+ rawScore: 82,
200
+ scoreConfidence: "medium",
201
+ scoreConfidenceScore: 76,
202
+ coverage: {
203
+ evaluatedChecks: 12,
204
+ skippedChecks: 0,
205
+ notApplicableChecks: 2,
206
+ notEvaluatedChecks: 3,
207
+ evaluatedCategories: [
208
+ "agent_instructions",
209
+ "documentation",
210
+ "setup_and_tests",
211
+ "security_and_privacy",
212
+ "freshness_and_consistency"
213
+ ],
214
+ missingCategories: ["examples", "task_benchmarks"],
215
+ coveragePercent: 80
216
+ },
217
+ scoringCaps: [
218
+ {
219
+ id: "cap.no-task-benchmarks",
220
+ maxScore: 90,
221
+ reason: "No realistic agent task benchmark file was found."
222
+ }
223
+ ],
224
+ scoreInterpretation: {
225
+ agentReadinessScore: 72,
226
+ humanReadableProjectSignals: {
227
+ score: 75,
228
+ summary: "Human-readable project signals describe conventional repo, docs, examples, and API context.",
229
+ signals: ["README present", "package metadata present", "OpenAPI spec present"]
230
+ },
231
+ agentSpecificContextLayer: {
232
+ score: 51,
233
+ summary: "Agent-specific context is the machine-readable layer that helps coding agents work safely.",
234
+ signals: ["AGENTS.md present", "agent task benchmark present"]
235
+ },
236
+ verifiability: {
237
+ score: 60,
238
+ summary: "Verifiability measures whether setup, tests, examples, and workflows can be checked.",
239
+ signals: ["test script declared", "command probes not run"]
240
+ }
241
+ },
242
+ summary: "Useful foundation, but 2 high-priority readiness issue(s) should be fixed.",
243
+ subscores: [
244
+ { id: "agent_instructions", label: "Agent Instructions", score: 78, findingsCount: 2 },
245
+ { id: "documentation", label: "Documentation", score: 82, findingsCount: 1 },
246
+ { id: "api_and_tooling", label: "API & Tooling", score: 65, findingsCount: 2 },
247
+ { id: "examples_and_tasks", label: "Examples & Tasks", score: 55, findingsCount: 1 },
248
+ { id: "security_and_privacy", label: "Security & Privacy", score: 80, findingsCount: 1 },
249
+ {
250
+ id: "freshness_and_consistency",
251
+ label: "Freshness & Consistency",
252
+ score: 90,
253
+ findingsCount: 0
254
+ }
255
+ ],
256
+ findings: [
257
+ {
258
+ id: "setup.missing-test-script",
259
+ ruleId: "setup.missing-test-script",
260
+ title: "No test script in package.json",
261
+ severity: "high",
262
+ category: "setup_and_tests",
263
+ description: "Agents cannot reliably run tests without a package script.",
264
+ evidence: ['package.json scripts does not include "test".'],
265
+ recommendation: "Add a package.json test script or document the equivalent command clearly.",
266
+ affectedFile: "package.json",
267
+ suggestedFixType: "add_script"
268
+ },
269
+ {
270
+ id: "TASK_BENCHMARK_MISSING",
271
+ ruleId: "TASK_BENCHMARK_MISSING",
272
+ title: "Missing agent task benchmark file",
273
+ severity: "medium",
274
+ category: "task_benchmarks",
275
+ description: "The project has no task benchmark describing realistic agent workflows.",
276
+ evidence: ["No benchmark file was found."],
277
+ recommendation: "Add a benchmark file with realistic developer tasks.",
278
+ affectedFile: "agentlighthouse.tasks.yaml",
279
+ suggestedFixType: "create_file"
280
+ },
281
+ {
282
+ id: "security.agent-secret-guidance-missing",
283
+ ruleId: "security.agent-secret-guidance-missing",
284
+ title: "Instructions do not tell agents how to handle secrets",
285
+ severity: "medium",
286
+ category: "security_and_privacy",
287
+ description: "Agent-facing instructions should state how to avoid exposing secrets or private data.",
288
+ evidence: ["No secret/privacy guidance was detected in AGENTS.md."],
289
+ recommendation: "Add a security section explaining secret handling and external LLM constraints.",
290
+ affectedFile: "AGENTS.md",
291
+ suggestedFixType: "add_section"
292
+ }
293
+ ],
294
+ recommendations: [
295
+ "Add or document setup, test, lint, and typecheck commands.",
296
+ "Add task benchmarks for the top developer workflows agents should complete.",
297
+ "Document secret-handling and privacy rules for agent workflows."
298
+ ],
299
+ apiAnalysis: {
300
+ specFiles: ["openapi.yaml"],
301
+ operationCount: 8,
302
+ operationsWithExamples: 4,
303
+ operationsMissingDescriptions: 2,
304
+ destructiveOperations: ["openapi.yaml: DELETE /v1/widgets/{id} (deleteWidget)"],
305
+ authSchemes: ["bearerAuth"],
306
+ weakOperations: ["openapi.yaml: POST /v1/widgets (createWidget)"],
307
+ highRiskOperations: ["openapi.yaml: DELETE /v1/widgets/{id} (deleteWidget)"]
308
+ },
309
+ mcpAnalysis: {
310
+ detected: false,
311
+ files: [],
312
+ toolCount: 0,
313
+ toolsWithSchemas: 0,
314
+ toolsWithExamples: 0,
315
+ ambiguousTools: [],
316
+ destructiveTools: [],
317
+ privacySensitiveTools: [],
318
+ weakTools: []
319
+ },
320
+ commandProbes: {
321
+ enabled: false,
322
+ attempted: 0,
323
+ skipped: 3,
324
+ passed: 0,
325
+ failed: 0,
326
+ timedOut: 0,
327
+ results: []
328
+ },
329
+ detectedProject: {
330
+ type: "node_typescript",
331
+ name: "sample-agent-ready-project",
332
+ confidence: 0.9,
333
+ evidence: ["package.json plus TypeScript config or source files detected."],
334
+ packageManager: "pnpm",
335
+ frameworks: ["TypeScript", "Vitest"]
336
+ },
337
+ detectedArtifacts: [
338
+ {
339
+ path: "AGENTS.md",
340
+ exists: true,
341
+ kind: "file",
342
+ role: "Primary coding-agent instructions",
343
+ quality: "partial",
344
+ notes: ["Includes setup commands.", "Missing security/privacy guidance."]
345
+ },
346
+ {
347
+ path: "llms.txt",
348
+ exists: false,
349
+ kind: "missing",
350
+ role: "LLM-readable project map",
351
+ quality: "missing",
352
+ notes: ["Artifact exists but no text content was available for quality checks."]
353
+ }
354
+ ],
355
+ scanStats: {
356
+ filesScanned: 42,
357
+ textFilesRead: 38,
358
+ bytesRead: 42000,
359
+ docsMarkdownFileCount: 3,
360
+ openApiFileCount: 0,
361
+ benchmarkFileCount: 0,
362
+ findingCount: 3
363
+ },
364
+ ignoredPaths: ["node_modules"],
365
+ errors: [],
366
+ warnings: [],
367
+ projectPath: "/sample/agent-ready-project",
368
+ scannedAt: "2026-05-20T00:00:00.120Z",
369
+ recommendedActions: [
370
+ "Add or document setup, test, lint, and typecheck commands.",
371
+ "Add task benchmarks for the top developer workflows agents should complete.",
372
+ "Document secret-handling and privacy rules for agent workflows."
373
+ ],
374
+ signals: {
375
+ rootPath: "/sample/agent-ready-project",
376
+ projectName: "sample-agent-ready-project",
377
+ scannedFiles: ["README.md", "package.json", "AGENTS.md"],
378
+ artifacts: {
379
+ "AGENTS.md": { path: "AGENTS.md", exists: true, kind: "file", sizeBytes: 600 },
380
+ "CLAUDE.md": { path: "CLAUDE.md", exists: false, kind: "missing" },
381
+ "llms.txt": { path: "llms.txt", exists: false, kind: "missing" },
382
+ "README.md": { path: "README.md", exists: true, kind: "file", sizeBytes: 2000 },
383
+ ".cursor/rules": { path: ".cursor/rules", exists: false, kind: "missing" },
384
+ ".github/copilot-instructions.md": {
385
+ path: ".github/copilot-instructions.md",
386
+ exists: false,
387
+ kind: "missing"
388
+ },
389
+ ".agentlighthouseignore": { path: ".agentlighthouseignore", exists: true, kind: "file" }
390
+ },
391
+ docsMarkdownFiles: ["docs/ARCHITECTURE.md"],
392
+ openApiFiles: [],
393
+ mcpFiles: [],
394
+ configFiles: ["package.json"],
395
+ benchmarkFiles: [],
396
+ ignoredPaths: ["node_modules"],
397
+ warnings: [],
398
+ errors: [],
399
+ scanStats: {
400
+ filesScanned: 42,
401
+ textFilesRead: 38,
402
+ bytesRead: 42000,
403
+ docsMarkdownFileCount: 1,
404
+ openApiFileCount: 0,
405
+ benchmarkFileCount: 0
406
+ },
407
+ packageJson: {
408
+ path: "package.json",
409
+ name: "sample-agent-ready-project",
410
+ packageManager: "pnpm@10.33.0",
411
+ scripts: { build: "tsc" },
412
+ dependencies: [],
413
+ devDependencies: []
414
+ },
415
+ textByPath: {}
416
+ }
417
+ };
418
+ export const sampleComparisonResult = compareScanResults({
419
+ ...sampleScanResult,
420
+ scanId: "scan_demo_baseline",
421
+ completedAt: "2026-05-19T00:00:00.120Z",
422
+ score: 63,
423
+ scoreConfidence: "low",
424
+ scoreConfidenceScore: 55,
425
+ coverage: {
426
+ ...sampleScanResult.coverage,
427
+ coveragePercent: 51
428
+ },
429
+ findings: [
430
+ ...sampleScanResult.findings,
431
+ {
432
+ ...sampleScanResult.findings[0],
433
+ id: "llms.missing",
434
+ ruleId: "llms.missing",
435
+ title: "Missing llms.txt",
436
+ severity: "medium",
437
+ category: "agent_instructions",
438
+ description: "The baseline lacked an LLM-readable project map.",
439
+ evidence: ["llms.txt was not found at the repository root."],
440
+ recommendation: "Create llms.txt with links to README, docs, architecture, and examples.",
441
+ affectedFile: "llms.txt",
442
+ suggestedFixType: "create_file"
443
+ }
444
+ ]
445
+ }, sampleScanResult, {
446
+ changedFiles: [
447
+ { path: "package.json", status: "modified", source: "explicit" },
448
+ { path: "AGENTS.md", status: "modified", source: "explicit" },
449
+ { path: "llms.txt", status: "added", source: "explicit" }
450
+ ]
451
+ });
@@ -0,0 +1,7 @@
1
+ import type { CommandProbeSummary, DetectedProject, Finding, ProjectSignals, ScanOptions } from "../schemas/types.js";
2
+ export interface CommandProbeRun {
3
+ summary: CommandProbeSummary;
4
+ findings: Finding[];
5
+ }
6
+ export declare function runCommandProbes(signals: ProjectSignals, detectedProject: DetectedProject, options?: ScanOptions): Promise<CommandProbeRun>;
7
+ //# sourceMappingURL=commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/probes/commands.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,mBAAmB,EACnB,eAAe,EACf,OAAO,EACP,cAAc,EACd,WAAW,EACZ,MAAM,qBAAqB,CAAC;AAM7B,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,mBAAmB,CAAC;IAC7B,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,cAAc,EACvB,eAAe,EAAE,eAAe,EAChC,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,CAsE1B"}
@@ -0,0 +1,198 @@
1
+ import { spawn } from "node:child_process";
2
+ import { finding } from "../findings/helpers.js";
3
+ const defaultAllowedScripts = ["test", "typecheck", "lint"];
4
+ const sensitivePattern = /(api[_-]?key|token|secret|authorization)\s*[:=]\s*["']?[A-Za-z0-9_.-]{8,}/gi;
5
+ export async function runCommandProbes(signals, detectedProject, options = {}) {
6
+ const probeOptions = options.probes;
7
+ if (!probeOptions?.commands) {
8
+ return {
9
+ summary: {
10
+ enabled: false,
11
+ attempted: 0,
12
+ skipped: plannedScripts(signals, probeOptions?.allowedScripts).length,
13
+ passed: 0,
14
+ failed: 0,
15
+ timedOut: 0,
16
+ results: []
17
+ },
18
+ findings: [
19
+ finding({
20
+ id: "COMMAND_VERIFICATION_SKIPPED",
21
+ title: "Command verification probes were skipped",
22
+ severity: "info",
23
+ category: "setup_and_tests",
24
+ description: "Static analysis is default; project commands are not executed unless explicitly enabled.",
25
+ evidence: ["Run with --probe commands or --run-probes to execute safe script probes."],
26
+ recommendation: "Use command probes in trusted local or CI environments when you want executable verification.",
27
+ agentFailureMode: "Without command probes, AgentLighthouse can tell agents what commands appear to exist, but not whether they currently pass.",
28
+ fixExample: "agentlighthouse scan . --probe commands",
29
+ suggestedFixType: "none"
30
+ })
31
+ ]
32
+ };
33
+ }
34
+ const scripts = plannedScripts(signals, probeOptions.allowedScripts);
35
+ const results = [];
36
+ for (const script of scripts) {
37
+ if (!signals.packageJson?.scripts[script]) {
38
+ results.push({
39
+ script,
40
+ command: commandForScript(detectedProject.packageManager, script),
41
+ status: "skipped",
42
+ exitCode: null,
43
+ durationMs: 0,
44
+ reason: "Script is not declared in package.json."
45
+ });
46
+ continue;
47
+ }
48
+ results.push(await runScript(signals.rootPath, commandForScript(detectedProject.packageManager, script), script, probeOptions.timeoutMs ?? 30_000));
49
+ }
50
+ const findings = findingsForResults(results);
51
+ return {
52
+ summary: {
53
+ enabled: true,
54
+ attempted: results.filter((result) => result.status !== "skipped").length,
55
+ skipped: results.filter((result) => result.status === "skipped").length,
56
+ passed: results.filter((result) => result.status === "passed").length,
57
+ failed: results.filter((result) => result.status === "failed").length,
58
+ timedOut: results.filter((result) => result.status === "timed_out").length,
59
+ results
60
+ },
61
+ findings
62
+ };
63
+ }
64
+ function plannedScripts(signals, allowedScripts) {
65
+ const allowed = allowedScripts && allowedScripts.length > 0 ? allowedScripts : defaultAllowedScripts;
66
+ void signals;
67
+ return [...new Set(allowed)].filter((script) => ["test", "typecheck", "lint", "build"].includes(script));
68
+ }
69
+ function commandForScript(packageManager, script) {
70
+ if (packageManager === "pnpm")
71
+ return `pnpm run ${script}`;
72
+ if (packageManager === "yarn")
73
+ return `yarn ${script}`;
74
+ if (packageManager === "bun")
75
+ return `bun run ${script}`;
76
+ return `npm run ${script}`;
77
+ }
78
+ async function runScript(cwd, command, script, timeoutMs) {
79
+ const startedAt = Date.now();
80
+ const [binary, ...args] = command.split(" ");
81
+ if (!binary) {
82
+ return {
83
+ script,
84
+ command,
85
+ status: "failed",
86
+ exitCode: null,
87
+ durationMs: 0,
88
+ reason: "Command could not be parsed."
89
+ };
90
+ }
91
+ return await new Promise((resolve) => {
92
+ const child = spawn(binary, args, { cwd, shell: false });
93
+ let stdout = "";
94
+ let stderr = "";
95
+ let timedOut = false;
96
+ const timeout = setTimeout(() => {
97
+ timedOut = true;
98
+ child.kill("SIGTERM");
99
+ }, timeoutMs);
100
+ child.stdout.on("data", (chunk) => {
101
+ stdout += chunk.toString("utf8");
102
+ });
103
+ child.stderr.on("data", (chunk) => {
104
+ stderr += chunk.toString("utf8");
105
+ });
106
+ child.on("error", (error) => {
107
+ clearTimeout(timeout);
108
+ resolve({
109
+ script,
110
+ command,
111
+ status: "failed",
112
+ exitCode: null,
113
+ durationMs: Date.now() - startedAt,
114
+ stderrExcerpt: sanitizeOutput(error.message)
115
+ });
116
+ });
117
+ child.on("close", (code) => {
118
+ clearTimeout(timeout);
119
+ resolve({
120
+ script,
121
+ command,
122
+ status: timedOut ? "timed_out" : code === 0 ? "passed" : "failed",
123
+ exitCode: code,
124
+ durationMs: Date.now() - startedAt,
125
+ stdoutExcerpt: sanitizeOutput(stdout),
126
+ stderrExcerpt: sanitizeOutput(stderr)
127
+ });
128
+ });
129
+ });
130
+ }
131
+ function findingsForResults(results) {
132
+ const findings = [];
133
+ const missing = results.filter((result) => result.status === "skipped");
134
+ const failed = results.filter((result) => result.status === "failed");
135
+ const timedOut = results.filter((result) => result.status === "timed_out");
136
+ const redacted = results.filter((result) => [result.stdoutExcerpt, result.stderrExcerpt].some((excerpt) => excerpt?.includes("[redacted]")));
137
+ if (missing.length > 0) {
138
+ findings.push(finding({
139
+ id: "COMMAND_DECLARED_BUT_MISSING",
140
+ title: "Expected verification commands are missing",
141
+ severity: "medium",
142
+ category: "setup_and_tests",
143
+ description: "One or more expected safe scripts were not declared in package.json.",
144
+ evidence: missing.map((result) => result.script),
145
+ recommendation: "Add missing scripts or remove them from probe configuration.",
146
+ agentFailureMode: "A coding agent may be told to verify changes with a command that does not exist.",
147
+ fixExample: "Add scripts.test, scripts.typecheck, and scripts.lint where applicable.",
148
+ affectedFile: "package.json",
149
+ suggestedFixType: "add_script"
150
+ }));
151
+ }
152
+ if (failed.length > 0) {
153
+ findings.push(finding({
154
+ id: "COMMAND_PROBE_FAILED",
155
+ title: "Command probe failed",
156
+ severity: "high",
157
+ category: "setup_and_tests",
158
+ description: "A safe opt-in command probe exited unsuccessfully.",
159
+ evidence: failed.map((result) => `${result.command} exited ${result.exitCode}`),
160
+ recommendation: "Fix the failing command or document known prerequisites.",
161
+ agentFailureMode: "A coding agent may follow documented verification steps and hit unexplained failures.",
162
+ fixExample: "Make the script pass locally or document setup required before running it.",
163
+ suggestedFixType: "review_manually"
164
+ }));
165
+ }
166
+ if (timedOut.length > 0) {
167
+ findings.push(finding({
168
+ id: "COMMAND_PROBE_TIMEOUT",
169
+ title: "Command probe timed out",
170
+ severity: "medium",
171
+ category: "setup_and_tests",
172
+ description: "A safe opt-in command probe exceeded the configured timeout.",
173
+ evidence: timedOut.map((result) => `${result.command} timed out after ${result.durationMs}ms`),
174
+ recommendation: "Adjust probe timeout or split long-running checks into faster scripts.",
175
+ agentFailureMode: "A coding agent may not know whether verification hung or simply needs more time.",
176
+ fixExample: "Expose a fast smoke test script for agent and CI verification.",
177
+ suggestedFixType: "review_manually"
178
+ }));
179
+ }
180
+ if (redacted.length > 0) {
181
+ findings.push(finding({
182
+ id: "COMMAND_OUTPUT_REDACTED",
183
+ title: "Command probe output was redacted",
184
+ severity: "info",
185
+ category: "security_and_privacy",
186
+ description: "Probe output contained secret-like text and was redacted in the scan result.",
187
+ evidence: redacted.map((result) => result.command),
188
+ recommendation: "Ensure scripts do not print secrets or credentials.",
189
+ agentFailureMode: "A coding agent reviewing logs could accidentally expose sensitive values.",
190
+ fixExample: "Mask tokens in script output and use placeholders in examples.",
191
+ suggestedFixType: "review_manually"
192
+ }));
193
+ }
194
+ return findings;
195
+ }
196
+ function sanitizeOutput(value) {
197
+ return value.replace(sensitivePattern, "$1=[redacted]").slice(0, 1200);
198
+ }
@@ -0,0 +1,4 @@
1
+ import type { ScanResult } from "../schemas/types.js";
2
+ export { renderMarkdownReport } from "./markdown.js";
3
+ export declare function renderCliReport(result: ScanResult, color?: boolean): string;
4
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/reporters/cli.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAGtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAErD,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,UAAQ,GAAG,MAAM,CA2CzE"}