claude-crap 0.1.2 → 0.3.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/CHANGELOG.md +68 -0
- package/README.md +44 -23
- package/dist/index.js +142 -1
- package/dist/index.js.map +1 -1
- package/dist/scanner/auto-scan.d.ts +57 -0
- package/dist/scanner/auto-scan.d.ts.map +1 -0
- package/dist/scanner/auto-scan.js +138 -0
- package/dist/scanner/auto-scan.js.map +1 -0
- package/dist/scanner/bootstrap.d.ts +89 -0
- package/dist/scanner/bootstrap.d.ts.map +1 -0
- package/dist/scanner/bootstrap.js +278 -0
- package/dist/scanner/bootstrap.js.map +1 -0
- package/dist/scanner/detector.d.ts +53 -0
- package/dist/scanner/detector.d.ts.map +1 -0
- package/dist/scanner/detector.js +173 -0
- package/dist/scanner/detector.js.map +1 -0
- package/dist/scanner/index.d.ts +23 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +23 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/scanner/runner.d.ts +59 -0
- package/dist/scanner/runner.d.ts.map +1 -0
- package/dist/scanner/runner.js +159 -0
- package/dist/scanner/runner.js.map +1 -0
- package/dist/schemas/tool-schemas.d.ts +23 -0
- package/dist/schemas/tool-schemas.d.ts.map +1 -1
- package/dist/schemas/tool-schemas.js +23 -0
- package/dist/schemas/tool-schemas.js.map +1 -1
- package/package.json +5 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/bundle/mcp-server.mjs +732 -0
- package/plugin/bundle/mcp-server.mjs.map +4 -4
- package/plugin/package.json +1 -1
- package/src/index.ts +176 -0
- package/src/scanner/auto-scan.ts +212 -0
- package/src/scanner/bootstrap.ts +383 -0
- package/src/scanner/detector.ts +224 -0
- package/src/scanner/index.ts +30 -0
- package/src/scanner/runner.ts +212 -0
- package/src/schemas/tool-schemas.ts +27 -0
- package/src/tests/auto-scan.test.ts +137 -0
- package/src/tests/integration/mcp-server.integration.test.ts +3 -1
- package/src/tests/scanner-bootstrap.test.ts +186 -0
- package/src/tests/scanner-detector.test.ts +181 -0
- package/src/tests/scanner-runner.test.ts +63 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestrator: detect available scanners, run them, and ingest results.
|
|
3
|
+
*
|
|
4
|
+
* This module ties together the detector, runner, and adapter pipeline
|
|
5
|
+
* into a single `autoScan()` function that:
|
|
6
|
+
*
|
|
7
|
+
* 1. Probes the workspace for available scanners
|
|
8
|
+
* 2. Executes detected scanners in parallel
|
|
9
|
+
* 3. Routes each scanner's output through its adapter
|
|
10
|
+
* 4. Ingests the normalized SARIF into the store
|
|
11
|
+
*
|
|
12
|
+
* The function is designed to be called:
|
|
13
|
+
* - At MCP server boot (fire-and-forget, non-blocking)
|
|
14
|
+
* - On demand via the `auto_scan` MCP tool
|
|
15
|
+
*
|
|
16
|
+
* Failures in individual scanners are logged and skipped — a broken
|
|
17
|
+
* Semgrep install should not prevent ESLint from running.
|
|
18
|
+
*
|
|
19
|
+
* @module scanner/auto-scan
|
|
20
|
+
*/
|
|
21
|
+
import { detectScanners } from "./detector.js";
|
|
22
|
+
import { runScanner } from "./runner.js";
|
|
23
|
+
import { adaptScannerOutput } from "../adapters/index.js";
|
|
24
|
+
// ── Orchestrator ───────────────────────────────────────────────────
|
|
25
|
+
/**
|
|
26
|
+
* Ingest a single scanner's raw output through its adapter and into
|
|
27
|
+
* the SARIF store. Returns the number of accepted findings.
|
|
28
|
+
*/
|
|
29
|
+
function ingestScannerRun(scanner, rawOutput, sarifStore) {
|
|
30
|
+
// Parse the raw output — adapters accept string or object
|
|
31
|
+
let parsed;
|
|
32
|
+
try {
|
|
33
|
+
parsed = JSON.parse(rawOutput);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Semgrep outputs SARIF as a string, others are JSON.
|
|
37
|
+
// If parsing fails, pass the raw string to the adapter.
|
|
38
|
+
parsed = rawOutput;
|
|
39
|
+
}
|
|
40
|
+
const adapted = adaptScannerOutput(scanner, parsed);
|
|
41
|
+
const stats = sarifStore.ingestRun(adapted.document, adapted.sourceTool);
|
|
42
|
+
return { accepted: stats.accepted };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Auto-detect, run, and ingest all available scanners.
|
|
46
|
+
*
|
|
47
|
+
* @param workspaceRoot Absolute path to the project root.
|
|
48
|
+
* @param sarifStore Live SARIF store to ingest findings into.
|
|
49
|
+
* @param logger Pino logger for progress and error reporting.
|
|
50
|
+
* @returns Summary of what was detected, run, and ingested.
|
|
51
|
+
*/
|
|
52
|
+
export async function autoScan(workspaceRoot, sarifStore, logger) {
|
|
53
|
+
const start = Date.now();
|
|
54
|
+
// 1. Detect available scanners
|
|
55
|
+
const detected = await detectScanners(workspaceRoot);
|
|
56
|
+
const available = detected.filter((d) => d.available);
|
|
57
|
+
logger.info({
|
|
58
|
+
detected: detected.map((d) => `${d.scanner}:${d.available}`),
|
|
59
|
+
available: available.length,
|
|
60
|
+
}, "auto-scan: detection complete");
|
|
61
|
+
if (available.length === 0) {
|
|
62
|
+
return {
|
|
63
|
+
detected,
|
|
64
|
+
results: [],
|
|
65
|
+
totalFindings: 0,
|
|
66
|
+
totalDurationMs: Date.now() - start,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// 2. Run all available scanners in parallel
|
|
70
|
+
const runResults = await Promise.allSettled(available.map((d) => runScanner(d.scanner, workspaceRoot)));
|
|
71
|
+
// 3. Ingest results
|
|
72
|
+
const results = [];
|
|
73
|
+
let totalFindings = 0;
|
|
74
|
+
let persistNeeded = false;
|
|
75
|
+
for (let i = 0; i < available.length; i++) {
|
|
76
|
+
const detection = available[i];
|
|
77
|
+
const settled = runResults[i];
|
|
78
|
+
if (settled.status === "rejected") {
|
|
79
|
+
const error = String(settled.reason);
|
|
80
|
+
logger.warn({ scanner: detection.scanner, error }, "auto-scan: scanner execution rejected");
|
|
81
|
+
results.push({
|
|
82
|
+
scanner: detection.scanner,
|
|
83
|
+
success: false,
|
|
84
|
+
findingsIngested: 0,
|
|
85
|
+
durationMs: 0,
|
|
86
|
+
error,
|
|
87
|
+
});
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const runResult = settled.value;
|
|
91
|
+
if (!runResult.success) {
|
|
92
|
+
logger.warn({ scanner: runResult.scanner, error: runResult.error }, "auto-scan: scanner returned failure");
|
|
93
|
+
results.push({
|
|
94
|
+
scanner: runResult.scanner,
|
|
95
|
+
success: false,
|
|
96
|
+
findingsIngested: 0,
|
|
97
|
+
durationMs: runResult.durationMs,
|
|
98
|
+
error: runResult.error ?? "unknown error",
|
|
99
|
+
});
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
// Ingest through adapter pipeline
|
|
103
|
+
try {
|
|
104
|
+
const { accepted } = ingestScannerRun(runResult.scanner, runResult.rawOutput, sarifStore);
|
|
105
|
+
totalFindings += accepted;
|
|
106
|
+
persistNeeded = true;
|
|
107
|
+
logger.info({ scanner: runResult.scanner, accepted, durationMs: runResult.durationMs }, "auto-scan: scanner ingested");
|
|
108
|
+
results.push({
|
|
109
|
+
scanner: runResult.scanner,
|
|
110
|
+
success: true,
|
|
111
|
+
findingsIngested: accepted,
|
|
112
|
+
durationMs: runResult.durationMs,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
const error = err.message;
|
|
117
|
+
logger.warn({ scanner: runResult.scanner, error }, "auto-scan: adapter/ingestion failed");
|
|
118
|
+
results.push({
|
|
119
|
+
scanner: runResult.scanner,
|
|
120
|
+
success: false,
|
|
121
|
+
findingsIngested: 0,
|
|
122
|
+
durationMs: runResult.durationMs,
|
|
123
|
+
error,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// 4. Persist consolidated SARIF if anything was ingested
|
|
128
|
+
if (persistNeeded) {
|
|
129
|
+
await sarifStore.persist();
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
detected,
|
|
133
|
+
results,
|
|
134
|
+
totalFindings,
|
|
135
|
+
totalDurationMs: Date.now() - start,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=auto-scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-scan.js","sourceRoot":"","sources":["../../src/scanner/auto-scan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAAE,cAAc,EAAyB,MAAM,eAAe,CAAC;AACtE,OAAO,EAAE,UAAU,EAAyB,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAqB,MAAM,sBAAsB,CAAC;AA8B7E,sEAAsE;AAEtE;;;GAGG;AACH,SAAS,gBAAgB,CACvB,OAAqB,EACrB,SAAiB,EACjB,UAAsB;IAEtB,0DAA0D;IAC1D,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,sDAAsD;QACtD,wDAAwD;QACxD,MAAM,GAAG,SAAS,CAAC;IACrB,CAAC;IAED,MAAM,OAAO,GAAG,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACpD,MAAM,KAAK,GAAG,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IACzE,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;AACtC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,aAAqB,EACrB,UAAsB,EACtB,MAAc;IAEd,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEzB,+BAA+B;IAC/B,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,aAAa,CAAC,CAAC;IACrD,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAEtD,MAAM,CAAC,IAAI,CACT;QACE,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;QAC5D,SAAS,EAAE,SAAS,CAAC,MAAM;KAC5B,EACD,+BAA+B,CAChC,CAAC;IAEF,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO;YACL,QAAQ;YACR,OAAO,EAAE,EAAE;YACX,aAAa,EAAE,CAAC;YAChB,eAAe,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;SACpC,CAAC;IACJ,CAAC;IAED,4CAA4C;IAC5C,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,UAAU,CACzC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAC3D,CAAC;IAEF,oBAAoB;IACpB,MAAM,OAAO,GAAoB,EAAE,CAAC;IACpC,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,IAAI,aAAa,GAAG,KAAK,CAAC;IAE1B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1C,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAE,CAAC;QAChC,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,CAAE,CAAC;QAE/B,IAAI,OAAO,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YAClC,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YACrC,MAAM,CAAC,IAAI,CACT,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,EACrC,uCAAuC,CACxC,CAAC;YACF,OAAO,CAAC,IAAI,CAAC;gBACX,OAAO,EAAE,SAAS,CAAC,OAAO;gBAC1B,OAAO,EAAE,KAAK;gBACd,gBAAgB,EAAE,CAAC;gBACnB,UAAU,EAAE,CAAC;gBACb,KAAK;aACN,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,MAAM,SAAS,GAAqB,OAAO,CAAC,KAAK,CAAC;QAElD,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CACT,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,EACtD,qCAAqC,CACtC,CAAC;YACF,OAAO,CAAC,IAAI,CAAC;gBACX,OAAO,EAAE,SAAS,CAAC,OAAO;gBAC1B,OAAO,EAAE,KAAK;gBACd,gBAAgB,EAAE,CAAC;gBACnB,UAAU,EAAE,SAAS,CAAC,UAAU;gBAChC,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,eAAe;aAC1C,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,kCAAkC;QAClC,IAAI,CAAC;YACH,MAAM,EAAE,QAAQ,EAAE,GAAG,gBAAgB,CACnC,SAAS,CAAC,OAAO,EACjB,SAAS,CAAC,SAAS,EACnB,UAAU,CACX,CAAC;YACF,aAAa,IAAI,QAAQ,CAAC;YAC1B,aAAa,GAAG,IAAI,CAAC;YAErB,MAAM,CAAC,IAAI,CACT,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,CAAC,UAAU,EAAE,EAC1E,6BAA6B,CAC9B,CAAC;YAEF,OAAO,CAAC,IAAI,CAAC;gBACX,OAAO,EAAE,SAAS,CAAC,OAAO;gBAC1B,OAAO,EAAE,IAAI;gBACb,gBAAgB,EAAE,QAAQ;gBAC1B,UAAU,EAAE,SAAS,CAAC,UAAU;aACjC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,KAAK,GAAI,GAAa,CAAC,OAAO,CAAC;YACrC,MAAM,CAAC,IAAI,CACT,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,EACrC,qCAAqC,CACtC,CAAC;YACF,OAAO,CAAC,IAAI,CAAC;gBACX,OAAO,EAAE,SAAS,CAAC,OAAO;gBAC1B,OAAO,EAAE,KAAK;gBACd,gBAAgB,EAAE,CAAC;gBACnB,UAAU,EAAE,SAAS,CAAC,UAAU;gBAChC,KAAK;aACN,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,yDAAyD;IACzD,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC;IAC7B,CAAC;IAED,OAAO;QACL,QAAQ;QACR,OAAO;QACP,aAAa;QACb,eAAe,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;KACpC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootstrap a scanner for projects that don't have one configured.
|
|
3
|
+
*
|
|
4
|
+
* Detects the project type from workspace signals (package.json,
|
|
5
|
+
* tsconfig.json, pyproject.toml, pom.xml, *.csproj, etc.), installs
|
|
6
|
+
* the appropriate scanner, creates a minimal config file, and runs
|
|
7
|
+
* `autoScan()` to verify and ingest findings immediately.
|
|
8
|
+
*
|
|
9
|
+
* Coverage maps to the five languages the tree-sitter engine supports:
|
|
10
|
+
*
|
|
11
|
+
* - JavaScript / TypeScript → ESLint (npm install + flat config)
|
|
12
|
+
* - Python → Bandit (install instructions only — virtualenv boundary)
|
|
13
|
+
* - Java → Semgrep (install instructions)
|
|
14
|
+
* - C# → Semgrep (install instructions)
|
|
15
|
+
* - Unknown → Semgrep (polyglot fallback)
|
|
16
|
+
*
|
|
17
|
+
* For JS/TS projects the tool runs `npm install --save-dev` and writes
|
|
18
|
+
* an `eslint.config.mjs`. For all other languages it returns manual
|
|
19
|
+
* install instructions rather than executing package managers whose
|
|
20
|
+
* environment assumptions may not hold.
|
|
21
|
+
*
|
|
22
|
+
* @module scanner/bootstrap
|
|
23
|
+
*/
|
|
24
|
+
import type { Logger } from "pino";
|
|
25
|
+
import { type AutoScanResult } from "./auto-scan.js";
|
|
26
|
+
import type { SarifStore } from "../sarif/sarif-store.js";
|
|
27
|
+
/**
|
|
28
|
+
* Detected project type, aligned with tree-sitter supported languages.
|
|
29
|
+
*/
|
|
30
|
+
export type ProjectType = "javascript" | "typescript" | "python" | "java" | "csharp" | "unknown";
|
|
31
|
+
/**
|
|
32
|
+
* A single step in the bootstrap process.
|
|
33
|
+
*/
|
|
34
|
+
export interface BootstrapStep {
|
|
35
|
+
/** What was attempted (e.g. "install eslint", "create eslint.config.mjs"). */
|
|
36
|
+
action: string;
|
|
37
|
+
/** Whether the step completed successfully. */
|
|
38
|
+
success: boolean;
|
|
39
|
+
/** Human-readable detail (command output, error message, or instruction). */
|
|
40
|
+
detail: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Complete result of a bootstrap_scanner invocation.
|
|
44
|
+
*/
|
|
45
|
+
export interface BootstrapResult {
|
|
46
|
+
/** Detected project type based on workspace signals. */
|
|
47
|
+
projectType: ProjectType;
|
|
48
|
+
/** Whether a scanner was already configured (detected by detector.ts). */
|
|
49
|
+
alreadyConfigured: boolean;
|
|
50
|
+
/** Which scanners were already available, if any. */
|
|
51
|
+
existingScanners: string[];
|
|
52
|
+
/** Steps executed (or instructions returned) during bootstrap. */
|
|
53
|
+
steps: BootstrapStep[];
|
|
54
|
+
/** The auto-scan result after installation (null if skipped). */
|
|
55
|
+
autoScanResult: AutoScanResult | null;
|
|
56
|
+
/** Whether the overall bootstrap succeeded. */
|
|
57
|
+
success: boolean;
|
|
58
|
+
/** Summary message suitable for display. */
|
|
59
|
+
summary: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Detect the project type from workspace signals.
|
|
63
|
+
*
|
|
64
|
+
* Checks in priority order: TypeScript, JavaScript, Python, Java,
|
|
65
|
+
* C#, then unknown. TypeScript wins over plain JavaScript because
|
|
66
|
+
* `tsconfig.json` implies a superset.
|
|
67
|
+
*/
|
|
68
|
+
export declare function detectProjectType(workspaceRoot: string): ProjectType;
|
|
69
|
+
/**
|
|
70
|
+
* Generate a minimal ESLint flat config (ESLint 9+).
|
|
71
|
+
*
|
|
72
|
+
* @param isTypeScript Include typescript-eslint when true.
|
|
73
|
+
* @returns The config file content as a string.
|
|
74
|
+
*/
|
|
75
|
+
export declare function generateEslintConfig(isTypeScript: boolean): string;
|
|
76
|
+
/**
|
|
77
|
+
* Bootstrap a scanner for the current workspace.
|
|
78
|
+
*
|
|
79
|
+
* 1. Check if a scanner is already configured (short-circuit if so)
|
|
80
|
+
* 2. Detect the project type
|
|
81
|
+
* 3. Install the recommended scanner (or return instructions)
|
|
82
|
+
* 4. Run auto_scan to verify and ingest findings
|
|
83
|
+
*
|
|
84
|
+
* @param workspaceRoot Absolute path to the project root.
|
|
85
|
+
* @param sarifStore Live SARIF store for auto-scan ingestion.
|
|
86
|
+
* @param logger Pino logger for progress reporting.
|
|
87
|
+
*/
|
|
88
|
+
export declare function bootstrapScanner(workspaceRoot: string, sarifStore: SarifStore, logger: Logger): Promise<BootstrapResult>;
|
|
89
|
+
//# sourceMappingURL=bootstrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../src/scanner/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAKH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAGnC,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAI1D;;GAEG;AACH,MAAM,MAAM,WAAW,GACnB,YAAY,GACZ,YAAY,GACZ,QAAQ,GACR,MAAM,GACN,QAAQ,GACR,SAAS,CAAC;AAEd;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,WAAW,EAAE,WAAW,CAAC;IACzB,0EAA0E;IAC1E,iBAAiB,EAAE,OAAO,CAAC;IAC3B,qDAAqD;IACrD,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,kEAAkE;IAClE,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,iEAAiE;IACjE,cAAc,EAAE,cAAc,GAAG,IAAI,CAAC;IACtC,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;CACjB;AAID;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,WAAW,CA+BpE;AAID;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,OAAO,GAAG,MAAM,CAwBlE;AAuHD;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CACpC,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,eAAe,CAAC,CA+F1B"}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootstrap a scanner for projects that don't have one configured.
|
|
3
|
+
*
|
|
4
|
+
* Detects the project type from workspace signals (package.json,
|
|
5
|
+
* tsconfig.json, pyproject.toml, pom.xml, *.csproj, etc.), installs
|
|
6
|
+
* the appropriate scanner, creates a minimal config file, and runs
|
|
7
|
+
* `autoScan()` to verify and ingest findings immediately.
|
|
8
|
+
*
|
|
9
|
+
* Coverage maps to the five languages the tree-sitter engine supports:
|
|
10
|
+
*
|
|
11
|
+
* - JavaScript / TypeScript → ESLint (npm install + flat config)
|
|
12
|
+
* - Python → Bandit (install instructions only — virtualenv boundary)
|
|
13
|
+
* - Java → Semgrep (install instructions)
|
|
14
|
+
* - C# → Semgrep (install instructions)
|
|
15
|
+
* - Unknown → Semgrep (polyglot fallback)
|
|
16
|
+
*
|
|
17
|
+
* For JS/TS projects the tool runs `npm install --save-dev` and writes
|
|
18
|
+
* an `eslint.config.mjs`. For all other languages it returns manual
|
|
19
|
+
* install instructions rather than executing package managers whose
|
|
20
|
+
* environment assumptions may not hold.
|
|
21
|
+
*
|
|
22
|
+
* @module scanner/bootstrap
|
|
23
|
+
*/
|
|
24
|
+
import { existsSync, writeFileSync, readdirSync } from "node:fs";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
import { execFile } from "node:child_process";
|
|
27
|
+
import { detectScanners } from "./detector.js";
|
|
28
|
+
import { autoScan } from "./auto-scan.js";
|
|
29
|
+
// ── Project type detection ─────────────────────────────────────────
|
|
30
|
+
/**
|
|
31
|
+
* Detect the project type from workspace signals.
|
|
32
|
+
*
|
|
33
|
+
* Checks in priority order: TypeScript, JavaScript, Python, Java,
|
|
34
|
+
* C#, then unknown. TypeScript wins over plain JavaScript because
|
|
35
|
+
* `tsconfig.json` implies a superset.
|
|
36
|
+
*/
|
|
37
|
+
export function detectProjectType(workspaceRoot) {
|
|
38
|
+
const has = (file) => existsSync(join(workspaceRoot, file));
|
|
39
|
+
// JS/TS detection — package.json is the anchor
|
|
40
|
+
if (has("package.json")) {
|
|
41
|
+
if (has("tsconfig.json"))
|
|
42
|
+
return "typescript";
|
|
43
|
+
return "javascript";
|
|
44
|
+
}
|
|
45
|
+
// Python detection
|
|
46
|
+
if (has("pyproject.toml") || has("setup.py") || has("requirements.txt")) {
|
|
47
|
+
return "python";
|
|
48
|
+
}
|
|
49
|
+
// Java detection
|
|
50
|
+
if (has("pom.xml") || has("build.gradle") || has("build.gradle.kts")) {
|
|
51
|
+
return "java";
|
|
52
|
+
}
|
|
53
|
+
// C# detection
|
|
54
|
+
if (has("Directory.Build.props"))
|
|
55
|
+
return "csharp";
|
|
56
|
+
try {
|
|
57
|
+
const entries = readdirSync(workspaceRoot);
|
|
58
|
+
if (entries.some((e) => e.endsWith(".csproj") || e.endsWith(".sln"))) {
|
|
59
|
+
return "csharp";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// readdirSync can fail on permissions — fall through
|
|
64
|
+
}
|
|
65
|
+
return "unknown";
|
|
66
|
+
}
|
|
67
|
+
// ── ESLint config generation ───────────────────────────────────────
|
|
68
|
+
/**
|
|
69
|
+
* Generate a minimal ESLint flat config (ESLint 9+).
|
|
70
|
+
*
|
|
71
|
+
* @param isTypeScript Include typescript-eslint when true.
|
|
72
|
+
* @returns The config file content as a string.
|
|
73
|
+
*/
|
|
74
|
+
export function generateEslintConfig(isTypeScript) {
|
|
75
|
+
if (isTypeScript) {
|
|
76
|
+
return `import js from "@eslint/js";
|
|
77
|
+
import tseslint from "typescript-eslint";
|
|
78
|
+
|
|
79
|
+
export default tseslint.config(
|
|
80
|
+
js.configs.recommended,
|
|
81
|
+
...tseslint.configs.recommended,
|
|
82
|
+
{
|
|
83
|
+
ignores: ["dist/", "node_modules/", "coverage/"],
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
`;
|
|
87
|
+
}
|
|
88
|
+
return `import js from "@eslint/js";
|
|
89
|
+
|
|
90
|
+
export default [
|
|
91
|
+
js.configs.recommended,
|
|
92
|
+
{
|
|
93
|
+
ignores: ["dist/", "node_modules/", "coverage/"],
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
`;
|
|
97
|
+
}
|
|
98
|
+
// ── Installation helpers ───────────────────────────────────────────
|
|
99
|
+
/**
|
|
100
|
+
* Run `npm install --save-dev` for the given packages.
|
|
101
|
+
*/
|
|
102
|
+
function npmInstall(workspaceRoot, packages) {
|
|
103
|
+
return new Promise((resolve) => {
|
|
104
|
+
execFile("npm", ["install", "--save-dev", ...packages], {
|
|
105
|
+
cwd: workspaceRoot,
|
|
106
|
+
timeout: 120_000,
|
|
107
|
+
env: { ...process.env, FORCE_COLOR: "0" },
|
|
108
|
+
}, (err, stdout, stderr) => {
|
|
109
|
+
if (err) {
|
|
110
|
+
resolve({
|
|
111
|
+
action: `npm install --save-dev ${packages.join(" ")}`,
|
|
112
|
+
success: false,
|
|
113
|
+
detail: stderr || err.message,
|
|
114
|
+
});
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
resolve({
|
|
118
|
+
action: `npm install --save-dev ${packages.join(" ")}`,
|
|
119
|
+
success: true,
|
|
120
|
+
detail: `installed ${packages.join(", ")}`,
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Write the ESLint config file to the workspace root.
|
|
127
|
+
* Returns failure if the file already exists.
|
|
128
|
+
*/
|
|
129
|
+
function writeEslintConfigFile(workspaceRoot, isTypeScript) {
|
|
130
|
+
const configPath = join(workspaceRoot, "eslint.config.mjs");
|
|
131
|
+
if (existsSync(configPath)) {
|
|
132
|
+
return {
|
|
133
|
+
action: "create eslint.config.mjs",
|
|
134
|
+
success: true,
|
|
135
|
+
detail: "eslint.config.mjs already exists — skipped",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
writeFileSync(configPath, generateEslintConfig(isTypeScript), "utf-8");
|
|
140
|
+
return {
|
|
141
|
+
action: "create eslint.config.mjs",
|
|
142
|
+
success: true,
|
|
143
|
+
detail: `created eslint.config.mjs (${isTypeScript ? "TypeScript" : "JavaScript"} template)`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
return {
|
|
148
|
+
action: "create eslint.config.mjs",
|
|
149
|
+
success: false,
|
|
150
|
+
detail: err.message,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function getRecommendation(projectType) {
|
|
155
|
+
switch (projectType) {
|
|
156
|
+
case "javascript":
|
|
157
|
+
case "typescript":
|
|
158
|
+
return {
|
|
159
|
+
scanner: "eslint",
|
|
160
|
+
canAutoInstall: true,
|
|
161
|
+
installInstructions: "npm install --save-dev eslint @eslint/js",
|
|
162
|
+
};
|
|
163
|
+
case "python":
|
|
164
|
+
return {
|
|
165
|
+
scanner: "bandit",
|
|
166
|
+
canAutoInstall: false,
|
|
167
|
+
installInstructions: "pip install bandit (or: pipx install bandit, poetry add --group dev bandit)",
|
|
168
|
+
};
|
|
169
|
+
case "java":
|
|
170
|
+
case "csharp":
|
|
171
|
+
return {
|
|
172
|
+
scanner: "semgrep",
|
|
173
|
+
canAutoInstall: false,
|
|
174
|
+
installInstructions: "brew install semgrep (or: pip install semgrep, pipx install semgrep)",
|
|
175
|
+
};
|
|
176
|
+
case "unknown":
|
|
177
|
+
return {
|
|
178
|
+
scanner: "semgrep",
|
|
179
|
+
canAutoInstall: false,
|
|
180
|
+
installInstructions: "brew install semgrep (or: pip install semgrep, pipx install semgrep)",
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// ── Main orchestrator ──────────────────────────────────────────────
|
|
185
|
+
/**
|
|
186
|
+
* Bootstrap a scanner for the current workspace.
|
|
187
|
+
*
|
|
188
|
+
* 1. Check if a scanner is already configured (short-circuit if so)
|
|
189
|
+
* 2. Detect the project type
|
|
190
|
+
* 3. Install the recommended scanner (or return instructions)
|
|
191
|
+
* 4. Run auto_scan to verify and ingest findings
|
|
192
|
+
*
|
|
193
|
+
* @param workspaceRoot Absolute path to the project root.
|
|
194
|
+
* @param sarifStore Live SARIF store for auto-scan ingestion.
|
|
195
|
+
* @param logger Pino logger for progress reporting.
|
|
196
|
+
*/
|
|
197
|
+
export async function bootstrapScanner(workspaceRoot, sarifStore, logger) {
|
|
198
|
+
// 1. Check existing scanners
|
|
199
|
+
const detections = await detectScanners(workspaceRoot);
|
|
200
|
+
const available = detections.filter((d) => d.available);
|
|
201
|
+
if (available.length > 0) {
|
|
202
|
+
const existingScanners = available.map((d) => d.scanner);
|
|
203
|
+
logger.info({ existingScanners }, "bootstrap: scanner(s) already configured — skipping");
|
|
204
|
+
return {
|
|
205
|
+
projectType: detectProjectType(workspaceRoot),
|
|
206
|
+
alreadyConfigured: true,
|
|
207
|
+
existingScanners,
|
|
208
|
+
steps: [],
|
|
209
|
+
autoScanResult: null,
|
|
210
|
+
success: true,
|
|
211
|
+
summary: `Scanner(s) already configured: ${existingScanners.join(", ")}. Run auto_scan to ingest findings.`,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
// 2. Detect project type
|
|
215
|
+
const projectType = detectProjectType(workspaceRoot);
|
|
216
|
+
const recommendation = getRecommendation(projectType);
|
|
217
|
+
const steps = [];
|
|
218
|
+
logger.info({ projectType, scanner: recommendation.scanner }, "bootstrap: detected project type");
|
|
219
|
+
// 3. Install scanner
|
|
220
|
+
if (recommendation.canAutoInstall) {
|
|
221
|
+
// JS/TS: auto-install ESLint
|
|
222
|
+
const isTypeScript = projectType === "typescript";
|
|
223
|
+
const packages = isTypeScript
|
|
224
|
+
? ["eslint", "@eslint/js", "typescript-eslint"]
|
|
225
|
+
: ["eslint", "@eslint/js"];
|
|
226
|
+
const installStep = await npmInstall(workspaceRoot, packages);
|
|
227
|
+
steps.push(installStep);
|
|
228
|
+
if (installStep.success) {
|
|
229
|
+
const configStep = writeEslintConfigFile(workspaceRoot, isTypeScript);
|
|
230
|
+
steps.push(configStep);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
// Python / Java / C# / Unknown: return instructions
|
|
235
|
+
steps.push({
|
|
236
|
+
action: `suggest ${recommendation.scanner} install`,
|
|
237
|
+
success: true,
|
|
238
|
+
detail: recommendation.installInstructions,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
// 4. Run auto_scan if installation succeeded
|
|
242
|
+
const installSucceeded = steps.every((s) => s.success);
|
|
243
|
+
let autoScanResult = null;
|
|
244
|
+
if (installSucceeded && recommendation.canAutoInstall) {
|
|
245
|
+
try {
|
|
246
|
+
autoScanResult = await autoScan(workspaceRoot, sarifStore, logger);
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
logger.warn({ err: err.message }, "bootstrap: auto_scan after install failed");
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// 5. Build result
|
|
253
|
+
const findings = autoScanResult?.totalFindings ?? 0;
|
|
254
|
+
const scannerInstalled = recommendation.canAutoInstall && installSucceeded;
|
|
255
|
+
let summary;
|
|
256
|
+
if (scannerInstalled && autoScanResult) {
|
|
257
|
+
summary = `Installed ${recommendation.scanner} for ${projectType} project. Auto-scan found ${findings} finding(s).`;
|
|
258
|
+
}
|
|
259
|
+
else if (scannerInstalled) {
|
|
260
|
+
summary = `Installed ${recommendation.scanner} for ${projectType} project. Auto-scan did not run.`;
|
|
261
|
+
}
|
|
262
|
+
else if (!recommendation.canAutoInstall) {
|
|
263
|
+
summary = `Detected ${projectType} project. Install ${recommendation.scanner} manually: ${recommendation.installInstructions}`;
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
summary = `Failed to install ${recommendation.scanner}. Check the error details in the steps.`;
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
projectType,
|
|
270
|
+
alreadyConfigured: false,
|
|
271
|
+
existingScanners: [],
|
|
272
|
+
steps,
|
|
273
|
+
autoScanResult,
|
|
274
|
+
success: installSucceeded,
|
|
275
|
+
summary,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
//# sourceMappingURL=bootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../../src/scanner/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAG9C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAuB,MAAM,gBAAgB,CAAC;AAgD/D,sEAAsE;AAEtE;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,aAAqB;IACrD,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC;IAEpE,+CAA+C;IAC/C,IAAI,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC;QACxB,IAAI,GAAG,CAAC,eAAe,CAAC;YAAE,OAAO,YAAY,CAAC;QAC9C,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,mBAAmB;IACnB,IAAI,GAAG,CAAC,gBAAgB,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,kBAAkB,CAAC,EAAE,CAAC;QACxE,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,iBAAiB;IACjB,IAAI,GAAG,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,cAAc,CAAC,IAAI,GAAG,CAAC,kBAAkB,CAAC,EAAE,CAAC;QACrE,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,eAAe;IACf,IAAI,GAAG,CAAC,uBAAuB,CAAC;QAAE,OAAO,QAAQ,CAAC;IAClD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,WAAW,CAAC,aAAa,CAAC,CAAC;QAC3C,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;YACrE,OAAO,QAAQ,CAAC;QAClB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,qDAAqD;IACvD,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,sEAAsE;AAEtE;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,YAAqB;IACxD,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO;;;;;;;;;;CAUV,CAAC;IACA,CAAC;IAED,OAAO;;;;;;;;CAQR,CAAC;AACF,CAAC;AAED,sEAAsE;AAEtE;;GAEG;AACH,SAAS,UAAU,CACjB,aAAqB,EACrB,QAAkB;IAElB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,QAAQ,CACN,KAAK,EACL,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,QAAQ,CAAC,EACtC;YACE,GAAG,EAAE,aAAa;YAClB,OAAO,EAAE,OAAO;YAChB,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE;SAC1C,EACD,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACtB,IAAI,GAAG,EAAE,CAAC;gBACR,OAAO,CAAC;oBACN,MAAM,EAAE,0BAA0B,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;oBACtD,OAAO,EAAE,KAAK;oBACd,MAAM,EAAE,MAAM,IAAK,GAAa,CAAC,OAAO;iBACzC,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YACD,OAAO,CAAC;gBACN,MAAM,EAAE,0BAA0B,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;gBACtD,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,aAAa,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;aAC3C,CAAC,CAAC;QACL,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,SAAS,qBAAqB,CAC5B,aAAqB,EACrB,YAAqB;IAErB,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,mBAAmB,CAAC,CAAC;IAC5D,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,OAAO;YACL,MAAM,EAAE,0BAA0B;YAClC,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,4CAA4C;SACrD,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,aAAa,CAAC,UAAU,EAAE,oBAAoB,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;QACvE,OAAO;YACL,MAAM,EAAE,0BAA0B;YAClC,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,8BAA8B,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY,YAAY;SAC7F,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,MAAM,EAAE,0BAA0B;YAClC,OAAO,EAAE,KAAK;YACd,MAAM,EAAG,GAAa,CAAC,OAAO;SAC/B,CAAC;IACJ,CAAC;AACH,CAAC;AAaD,SAAS,iBAAiB,CAAC,WAAwB;IACjD,QAAQ,WAAW,EAAE,CAAC;QACpB,KAAK,YAAY,CAAC;QAClB,KAAK,YAAY;YACf,OAAO;gBACL,OAAO,EAAE,QAAQ;gBACjB,cAAc,EAAE,IAAI;gBACpB,mBAAmB,EAAE,0CAA0C;aAChE,CAAC;QACJ,KAAK,QAAQ;YACX,OAAO;gBACL,OAAO,EAAE,QAAQ;gBACjB,cAAc,EAAE,KAAK;gBACrB,mBAAmB,EACjB,8EAA8E;aACjF,CAAC;QACJ,KAAK,MAAM,CAAC;QACZ,KAAK,QAAQ;YACX,OAAO;gBACL,OAAO,EAAE,SAAS;gBAClB,cAAc,EAAE,KAAK;gBACrB,mBAAmB,EACjB,uEAAuE;aAC1E,CAAC;QACJ,KAAK,SAAS;YACZ,OAAO;gBACL,OAAO,EAAE,SAAS;gBAClB,cAAc,EAAE,KAAK;gBACrB,mBAAmB,EACjB,uEAAuE;aAC1E,CAAC;IACN,CAAC;AACH,CAAC;AAED,sEAAsE;AAEtE;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,aAAqB,EACrB,UAAsB,EACtB,MAAc;IAEd,6BAA6B;IAC7B,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,CAAC,CAAC;IACvD,MAAM,SAAS,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAExD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,gBAAgB,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACzD,MAAM,CAAC,IAAI,CACT,EAAE,gBAAgB,EAAE,EACpB,qDAAqD,CACtD,CAAC;QACF,OAAO;YACL,WAAW,EAAE,iBAAiB,CAAC,aAAa,CAAC;YAC7C,iBAAiB,EAAE,IAAI;YACvB,gBAAgB;YAChB,KAAK,EAAE,EAAE;YACT,cAAc,EAAE,IAAI;YACpB,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,kCAAkC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,qCAAqC;SAC5G,CAAC;IACJ,CAAC;IAED,yBAAyB;IACzB,MAAM,WAAW,GAAG,iBAAiB,CAAC,aAAa,CAAC,CAAC;IACrD,MAAM,cAAc,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;IACtD,MAAM,KAAK,GAAoB,EAAE,CAAC;IAElC,MAAM,CAAC,IAAI,CACT,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,CAAC,OAAO,EAAE,EAChD,kCAAkC,CACnC,CAAC;IAEF,qBAAqB;IACrB,IAAI,cAAc,CAAC,cAAc,EAAE,CAAC;QAClC,6BAA6B;QAC7B,MAAM,YAAY,GAAG,WAAW,KAAK,YAAY,CAAC;QAClD,MAAM,QAAQ,GAAG,YAAY;YAC3B,CAAC,CAAC,CAAC,QAAQ,EAAE,YAAY,EAAE,mBAAmB,CAAC;YAC/C,CAAC,CAAC,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QAE7B,MAAM,WAAW,GAAG,MAAM,UAAU,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;QAC9D,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAExB,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;YACxB,MAAM,UAAU,GAAG,qBAAqB,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;YACtE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;SAAM,CAAC;QACN,oDAAoD;QACpD,KAAK,CAAC,IAAI,CAAC;YACT,MAAM,EAAE,WAAW,cAAc,CAAC,OAAO,UAAU;YACnD,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,cAAc,CAAC,mBAAmB;SAC3C,CAAC,CAAC;IACL,CAAC;IAED,6CAA6C;IAC7C,MAAM,gBAAgB,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACvD,IAAI,cAAc,GAA0B,IAAI,CAAC;IAEjD,IAAI,gBAAgB,IAAI,cAAc,CAAC,cAAc,EAAE,CAAC;QACtD,IAAI,CAAC;YACH,cAAc,GAAG,MAAM,QAAQ,CAAC,aAAa,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;QACrE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CACT,EAAE,GAAG,EAAG,GAAa,CAAC,OAAO,EAAE,EAC/B,2CAA2C,CAC5C,CAAC;QACJ,CAAC;IACH,CAAC;IAED,kBAAkB;IAClB,MAAM,QAAQ,GAAG,cAAc,EAAE,aAAa,IAAI,CAAC,CAAC;IACpD,MAAM,gBAAgB,GAAG,cAAc,CAAC,cAAc,IAAI,gBAAgB,CAAC;IAE3E,IAAI,OAAe,CAAC;IACpB,IAAI,gBAAgB,IAAI,cAAc,EAAE,CAAC;QACvC,OAAO,GAAG,aAAa,cAAc,CAAC,OAAO,QAAQ,WAAW,6BAA6B,QAAQ,cAAc,CAAC;IACtH,CAAC;SAAM,IAAI,gBAAgB,EAAE,CAAC;QAC5B,OAAO,GAAG,aAAa,cAAc,CAAC,OAAO,QAAQ,WAAW,kCAAkC,CAAC;IACrG,CAAC;SAAM,IAAI,CAAC,cAAc,CAAC,cAAc,EAAE,CAAC;QAC1C,OAAO,GAAG,YAAY,WAAW,qBAAqB,cAAc,CAAC,OAAO,cAAc,cAAc,CAAC,mBAAmB,EAAE,CAAC;IACjI,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,qBAAqB,cAAc,CAAC,OAAO,yCAAyC,CAAC;IACjG,CAAC;IAED,OAAO;QACL,WAAW;QACX,iBAAiB,EAAE,KAAK;QACxB,gBAAgB,EAAE,EAAE;QACpB,KAAK;QACL,cAAc;QACd,OAAO,EAAE,gBAAgB;QACzB,OAAO;KACR,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-detect which scanners are available in the current workspace.
|
|
3
|
+
*
|
|
4
|
+
* For each of the four supported scanners (ESLint, Semgrep, Bandit,
|
|
5
|
+
* Stryker) the detector probes three signal layers in order:
|
|
6
|
+
*
|
|
7
|
+
* 1. Config file existence (fastest — a single `fs.stat`)
|
|
8
|
+
* 2. Package.json dependency (for JS-ecosystem scanners)
|
|
9
|
+
* 3. Binary availability via `which` (slowest — spawns a child process)
|
|
10
|
+
*
|
|
11
|
+
* Detection short-circuits on the first hit, so a project that has an
|
|
12
|
+
* `eslint.config.mjs` will never shell out to `which eslint`.
|
|
13
|
+
*
|
|
14
|
+
* The module is side-effect-free beyond filesystem reads and one
|
|
15
|
+
* `child_process.execFile` per binary probe.
|
|
16
|
+
*
|
|
17
|
+
* @module scanner/detector
|
|
18
|
+
*/
|
|
19
|
+
import type { KnownScanner } from "../adapters/common.js";
|
|
20
|
+
/**
|
|
21
|
+
* Result of probing a single scanner's availability.
|
|
22
|
+
*/
|
|
23
|
+
export interface ScannerDetection {
|
|
24
|
+
/** Which scanner was probed. */
|
|
25
|
+
scanner: KnownScanner;
|
|
26
|
+
/** Whether the scanner is available and can be executed. */
|
|
27
|
+
available: boolean;
|
|
28
|
+
/** Human-readable reason for the verdict. */
|
|
29
|
+
reason: string;
|
|
30
|
+
/** Path to the config file that triggered detection, if any. */
|
|
31
|
+
configPath?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Config file globs and package.json keys per scanner. Order matters:
|
|
35
|
+
* the first matching config file short-circuits further probes.
|
|
36
|
+
*/
|
|
37
|
+
interface ScannerSignals {
|
|
38
|
+
configFiles: string[];
|
|
39
|
+
packageJsonKeys: string[];
|
|
40
|
+
binaryNames: string[];
|
|
41
|
+
}
|
|
42
|
+
declare const SCANNER_SIGNALS: Record<KnownScanner, ScannerSignals>;
|
|
43
|
+
/**
|
|
44
|
+
* Detect which of the four supported scanners are available in the
|
|
45
|
+
* given workspace. Probes config files, package.json, and binary
|
|
46
|
+
* availability in order, short-circuiting on first match.
|
|
47
|
+
*
|
|
48
|
+
* @param workspaceRoot Absolute path to the project root.
|
|
49
|
+
* @returns One {@link ScannerDetection} per known scanner.
|
|
50
|
+
*/
|
|
51
|
+
export declare function detectScanners(workspaceRoot: string): Promise<ScannerDetection[]>;
|
|
52
|
+
export { SCANNER_SIGNALS };
|
|
53
|
+
//# sourceMappingURL=detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../../src/scanner/detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAI1D;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,gCAAgC;IAChC,OAAO,EAAE,YAAY,CAAC;IACtB,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAID;;;GAGG;AACH,UAAU,cAAc;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,QAAA,MAAM,eAAe,EAAE,MAAM,CAAC,YAAY,EAAE,cAAc,CAgDzD,CAAC;AAgEF;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,gBAAgB,EAAE,CAAC,CA8C7B;AAGD,OAAO,EAAE,eAAe,EAAE,CAAC"}
|