edsger 0.55.4 → 0.56.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.
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Authoritative tool catalog for the quality-benchmark phase.
3
+ *
4
+ * Every tool the LLM is permitted to probe / install / execute is listed
5
+ * here. Commands are templates with placeholders resolved at runtime by
6
+ * `tool-runner.ts`:
7
+ * %REPO_ROOT% - absolute path to the repo checkout
8
+ * %PKG_MANAGER% - detected JS package manager (npm/pnpm/yarn)
9
+ * %SCAN_DIR% - per-run scratch dir for tool outputs
10
+ *
11
+ * Keep this in sync with `rubric.md` § "Tool Catalog". The LLM receives
12
+ * the rubric text in its system prompt, and the runtime enforces the
13
+ * catalog by refusing to execute commands not registered here.
14
+ */
15
+ import type { ToolCatalogEntry } from './types';
16
+ export declare const TOOL_CATALOG: readonly ToolCatalogEntry[];
17
+ /** Index for O(1) lookups by tool id. */
18
+ export declare const TOOL_CATALOG_BY_ID: ReadonlyMap<string, ToolCatalogEntry>;
19
+ /** All tool ids the LLM is allowed to invoke. */
20
+ export declare const ALL_TOOL_IDS: readonly string[];
21
+ /** All install commands — used to whitelist Bash invocations during Phase 2.5. */
22
+ export declare const ALL_INSTALL_COMMANDS: readonly string[];
23
+ /**
24
+ * Pick the catalog entries relevant to a detected context. This is advisory:
25
+ * the runtime still allows any catalog entry to run, but most callers should
26
+ * narrow first to avoid wasted probing.
27
+ */
28
+ export declare function selectToolsForContext(input: {
29
+ languages: readonly string[];
30
+ package_managers?: readonly string[];
31
+ frameworks?: readonly string[];
32
+ files_present?: readonly string[];
33
+ }): ToolCatalogEntry[];
@@ -0,0 +1,597 @@
1
+ /**
2
+ * Authoritative tool catalog for the quality-benchmark phase.
3
+ *
4
+ * Every tool the LLM is permitted to probe / install / execute is listed
5
+ * here. Commands are templates with placeholders resolved at runtime by
6
+ * `tool-runner.ts`:
7
+ * %REPO_ROOT% - absolute path to the repo checkout
8
+ * %PKG_MANAGER% - detected JS package manager (npm/pnpm/yarn)
9
+ * %SCAN_DIR% - per-run scratch dir for tool outputs
10
+ *
11
+ * Keep this in sync with `rubric.md` § "Tool Catalog". The LLM receives
12
+ * the rubric text in its system prompt, and the runtime enforces the
13
+ * catalog by refusing to execute commands not registered here.
14
+ */
15
+ // ---------------------------------------------------------------------------
16
+ // JavaScript / TypeScript
17
+ // ---------------------------------------------------------------------------
18
+ const eslint = {
19
+ id: 'eslint',
20
+ label: 'ESLint',
21
+ category: 'lint',
22
+ applies_to: ['js', 'ts'],
23
+ probe: 'npx --no eslint --version',
24
+ install: null, // project-local only
25
+ install_prereq: 'npx',
26
+ command: 'npx --no eslint . --format json --ext .js,.jsx,.ts,.tsx --no-error-on-unmatched-pattern',
27
+ timeout_minutes: 5,
28
+ parser: 'eslint',
29
+ subscores: ['code_quality.lint'],
30
+ tolerate_nonzero_exit: true,
31
+ requires: {
32
+ file_present: [
33
+ '.eslintrc.js',
34
+ '.eslintrc.cjs',
35
+ '.eslintrc.json',
36
+ '.eslintrc.yml',
37
+ 'eslint.config.js',
38
+ 'eslint.config.mjs',
39
+ ],
40
+ },
41
+ };
42
+ const tscTypecheck = {
43
+ id: 'tsc-typecheck',
44
+ label: 'TypeScript typecheck',
45
+ category: 'typecheck',
46
+ applies_to: ['ts'],
47
+ probe: 'npx --no tsc --version',
48
+ install: null,
49
+ install_prereq: 'npx',
50
+ command: 'npx --no tsc --noEmit --pretty false',
51
+ timeout_minutes: 5,
52
+ parser: 'tsc',
53
+ subscores: ['code_quality.lint'],
54
+ tolerate_nonzero_exit: true,
55
+ requires: { file_present: ['tsconfig.json'] },
56
+ };
57
+ const jscpd = {
58
+ id: 'jscpd',
59
+ label: 'jscpd (duplication)',
60
+ category: 'duplication',
61
+ applies_to: ['js', 'ts', 'py', 'java', 'go', 'cs', 'ruby'],
62
+ probe: 'command -v jscpd',
63
+ install: null, // always use npx
64
+ install_prereq: 'npx',
65
+ command: 'npx --yes jscpd %REPO_ROOT% --reporters json --output %SCAN_DIR%/jscpd --min-lines 5 --min-tokens 50 --silent',
66
+ timeout_minutes: 10,
67
+ parser: 'jscpd',
68
+ subscores: ['code_quality.duplication'],
69
+ tolerate_nonzero_exit: true,
70
+ };
71
+ const madge = {
72
+ id: 'madge',
73
+ label: 'madge (circular deps)',
74
+ category: 'cycles',
75
+ applies_to: ['js', 'ts'],
76
+ probe: 'command -v madge',
77
+ install: null,
78
+ install_prereq: 'npx',
79
+ command: 'npx --yes madge --circular --json --extensions js,jsx,ts,tsx %REPO_ROOT%',
80
+ timeout_minutes: 3,
81
+ parser: 'madge',
82
+ subscores: ['architecture.circular_deps'],
83
+ tolerate_nonzero_exit: true,
84
+ };
85
+ const depcheck = {
86
+ id: 'depcheck',
87
+ label: 'depcheck (unused deps)',
88
+ category: 'dep-unused',
89
+ applies_to: ['js', 'ts'],
90
+ probe: 'command -v depcheck',
91
+ install: null,
92
+ install_prereq: 'npx',
93
+ command: 'npx --yes depcheck --json',
94
+ timeout_minutes: 3,
95
+ parser: 'depcheck',
96
+ subscores: ['dependency_health.unused'],
97
+ tolerate_nonzero_exit: true,
98
+ };
99
+ const npmAudit = {
100
+ id: 'npm-audit',
101
+ label: 'npm audit',
102
+ category: 'dep-vuln',
103
+ applies_to: ['js', 'ts'],
104
+ probe: 'command -v npm',
105
+ install: null,
106
+ install_prereq: null,
107
+ command: 'npm audit --json',
108
+ timeout_minutes: 3,
109
+ parser: 'npm-audit',
110
+ subscores: ['security.dep_vulns'],
111
+ tolerate_nonzero_exit: true,
112
+ requires: { package_manager: ['npm'] },
113
+ };
114
+ const pnpmAudit = {
115
+ id: 'pnpm-audit',
116
+ label: 'pnpm audit',
117
+ category: 'dep-vuln',
118
+ applies_to: ['js', 'ts'],
119
+ probe: 'command -v pnpm',
120
+ install: null,
121
+ install_prereq: null,
122
+ command: 'pnpm audit --json',
123
+ timeout_minutes: 3,
124
+ parser: 'pnpm-audit',
125
+ subscores: ['security.dep_vulns'],
126
+ tolerate_nonzero_exit: true,
127
+ requires: { package_manager: ['pnpm'] },
128
+ };
129
+ const yarnAudit = {
130
+ id: 'yarn-audit',
131
+ label: 'yarn audit',
132
+ category: 'dep-vuln',
133
+ applies_to: ['js', 'ts'],
134
+ probe: 'command -v yarn',
135
+ install: null,
136
+ install_prereq: null,
137
+ command: 'yarn npm audit --json',
138
+ timeout_minutes: 3,
139
+ parser: 'yarn-audit',
140
+ subscores: ['security.dep_vulns'],
141
+ tolerate_nonzero_exit: true,
142
+ requires: { package_manager: ['yarn'] },
143
+ };
144
+ const npmOutdated = {
145
+ id: 'npm-outdated',
146
+ label: 'npm outdated',
147
+ category: 'dep-outdated',
148
+ applies_to: ['js', 'ts'],
149
+ probe: 'command -v npm',
150
+ install: null,
151
+ install_prereq: null,
152
+ command: 'npm outdated --json',
153
+ timeout_minutes: 3,
154
+ parser: 'npm-outdated',
155
+ subscores: ['dependency_health.freshness'],
156
+ tolerate_nonzero_exit: true,
157
+ requires: { package_manager: ['npm'] },
158
+ };
159
+ const licenseChecker = {
160
+ id: 'license-checker',
161
+ label: 'license-checker',
162
+ category: 'dep-license',
163
+ applies_to: ['js', 'ts'],
164
+ probe: 'command -v license-checker',
165
+ install: null,
166
+ install_prereq: 'npx',
167
+ command: 'npx --yes license-checker --json --production',
168
+ timeout_minutes: 3,
169
+ parser: 'license-checker',
170
+ subscores: ['dependency_health.license'],
171
+ };
172
+ // ---------------------------------------------------------------------------
173
+ // Python
174
+ // ---------------------------------------------------------------------------
175
+ const ruff = {
176
+ id: 'ruff',
177
+ label: 'ruff',
178
+ category: 'lint',
179
+ applies_to: ['py'],
180
+ probe: 'command -v ruff',
181
+ install: 'pipx install "ruff>=0.4,<1.0"',
182
+ install_prereq: 'pipx',
183
+ command: 'ruff check %REPO_ROOT% --output-format json --no-cache',
184
+ timeout_minutes: 3,
185
+ parser: 'ruff',
186
+ subscores: ['code_quality.lint'],
187
+ tolerate_nonzero_exit: true,
188
+ };
189
+ const mypy = {
190
+ id: 'mypy',
191
+ label: 'mypy',
192
+ category: 'typecheck',
193
+ applies_to: ['py'],
194
+ probe: 'command -v mypy',
195
+ install: 'pipx install "mypy>=1.8,<2.0"',
196
+ install_prereq: 'pipx',
197
+ command: 'mypy %REPO_ROOT% --no-error-summary --show-error-codes --no-color-output',
198
+ timeout_minutes: 5,
199
+ parser: 'mypy',
200
+ subscores: ['code_quality.lint'],
201
+ tolerate_nonzero_exit: true,
202
+ };
203
+ const bandit = {
204
+ id: 'bandit',
205
+ label: 'bandit (Python SAST)',
206
+ category: 'sast',
207
+ applies_to: ['py'],
208
+ probe: 'command -v bandit',
209
+ install: 'pipx install "bandit>=1.7,<2.0"',
210
+ install_prereq: 'pipx',
211
+ command: 'bandit -r %REPO_ROOT% -f json -q',
212
+ timeout_minutes: 5,
213
+ parser: 'bandit',
214
+ subscores: ['security.sast'],
215
+ tolerate_nonzero_exit: true,
216
+ };
217
+ const pipAudit = {
218
+ id: 'pip-audit',
219
+ label: 'pip-audit',
220
+ category: 'dep-vuln',
221
+ applies_to: ['py'],
222
+ probe: 'command -v pip-audit',
223
+ install: 'pipx install "pip-audit>=2.7,<3.0"',
224
+ install_prereq: 'pipx',
225
+ command: 'pip-audit --format json',
226
+ timeout_minutes: 5,
227
+ parser: 'pip-audit',
228
+ subscores: ['security.dep_vulns'],
229
+ tolerate_nonzero_exit: true,
230
+ };
231
+ const radon = {
232
+ id: 'radon',
233
+ label: 'radon (Python complexity)',
234
+ category: 'complexity',
235
+ applies_to: ['py'],
236
+ probe: 'command -v radon',
237
+ install: 'pipx install "radon>=6.0,<7.0"',
238
+ install_prereq: 'pipx',
239
+ command: 'radon cc -j %REPO_ROOT%',
240
+ timeout_minutes: 3,
241
+ parser: 'radon',
242
+ subscores: ['code_quality.complexity'],
243
+ };
244
+ const vulture = {
245
+ id: 'vulture',
246
+ label: 'vulture (Python dead code)',
247
+ category: 'dead-code',
248
+ applies_to: ['py'],
249
+ probe: 'command -v vulture',
250
+ install: 'pipx install "vulture>=2.10,<3.0"',
251
+ install_prereq: 'pipx',
252
+ command: 'vulture %REPO_ROOT% --min-confidence 80',
253
+ timeout_minutes: 3,
254
+ parser: 'vulture',
255
+ subscores: ['code_quality.dead_code'],
256
+ tolerate_nonzero_exit: true,
257
+ };
258
+ // ---------------------------------------------------------------------------
259
+ // Go
260
+ // ---------------------------------------------------------------------------
261
+ const golangciLint = {
262
+ id: 'golangci-lint',
263
+ label: 'golangci-lint',
264
+ category: 'lint',
265
+ applies_to: ['go'],
266
+ probe: 'command -v golangci-lint',
267
+ install: 'go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.60.3',
268
+ install_prereq: 'go',
269
+ command: 'golangci-lint run --out-format json ./...',
270
+ timeout_minutes: 10,
271
+ parser: 'golangci-lint',
272
+ subscores: ['code_quality.lint'],
273
+ tolerate_nonzero_exit: true,
274
+ };
275
+ const gosec = {
276
+ id: 'gosec',
277
+ label: 'gosec',
278
+ category: 'sast',
279
+ applies_to: ['go'],
280
+ probe: 'command -v gosec',
281
+ install: 'go install github.com/securego/gosec/v2/cmd/gosec@v2.21.4',
282
+ install_prereq: 'go',
283
+ command: 'gosec -fmt json -quiet ./...',
284
+ timeout_minutes: 5,
285
+ parser: 'gosec',
286
+ subscores: ['security.sast'],
287
+ tolerate_nonzero_exit: true,
288
+ };
289
+ const govulncheck = {
290
+ id: 'govulncheck',
291
+ label: 'govulncheck',
292
+ category: 'dep-vuln',
293
+ applies_to: ['go'],
294
+ probe: 'command -v govulncheck',
295
+ install: 'go install golang.org/x/vuln/cmd/govulncheck@latest',
296
+ install_prereq: 'go',
297
+ command: 'govulncheck -json ./...',
298
+ timeout_minutes: 5,
299
+ parser: 'govulncheck',
300
+ subscores: ['security.dep_vulns'],
301
+ tolerate_nonzero_exit: true,
302
+ };
303
+ const gocyclo = {
304
+ id: 'gocyclo',
305
+ label: 'gocyclo',
306
+ category: 'complexity',
307
+ applies_to: ['go'],
308
+ probe: 'command -v gocyclo',
309
+ install: 'go install github.com/fzipp/gocyclo/cmd/gocyclo@latest',
310
+ install_prereq: 'go',
311
+ command: 'gocyclo -over 15 -json %REPO_ROOT%',
312
+ timeout_minutes: 3,
313
+ parser: 'gocyclo',
314
+ subscores: ['code_quality.complexity'],
315
+ tolerate_nonzero_exit: true,
316
+ };
317
+ const goModOutdated = {
318
+ id: 'go-mod-outdated',
319
+ label: 'go list -m -u',
320
+ category: 'dep-outdated',
321
+ applies_to: ['go'],
322
+ probe: 'command -v go',
323
+ install: null,
324
+ install_prereq: null,
325
+ command: 'go list -m -u -mod=mod -json all',
326
+ timeout_minutes: 3,
327
+ parser: 'go-mod-outdated',
328
+ subscores: ['dependency_health.freshness'],
329
+ tolerate_nonzero_exit: true,
330
+ };
331
+ // ---------------------------------------------------------------------------
332
+ // Rust
333
+ // ---------------------------------------------------------------------------
334
+ const clippy = {
335
+ id: 'clippy',
336
+ label: 'cargo clippy',
337
+ category: 'lint',
338
+ applies_to: ['rust'],
339
+ probe: 'command -v cargo',
340
+ install: null,
341
+ install_prereq: null,
342
+ command: 'cargo clippy --message-format json --all-targets -- -D warnings',
343
+ timeout_minutes: 10,
344
+ parser: 'clippy',
345
+ subscores: ['code_quality.lint'],
346
+ tolerate_nonzero_exit: true,
347
+ };
348
+ const cargoAudit = {
349
+ id: 'cargo-audit',
350
+ label: 'cargo audit',
351
+ category: 'dep-vuln',
352
+ applies_to: ['rust'],
353
+ probe: 'command -v cargo-audit',
354
+ install: 'cargo install --locked cargo-audit',
355
+ install_prereq: 'cargo',
356
+ command: 'cargo audit --json',
357
+ timeout_minutes: 5,
358
+ parser: 'cargo-audit',
359
+ subscores: ['security.dep_vulns'],
360
+ tolerate_nonzero_exit: true,
361
+ };
362
+ const cargoDeny = {
363
+ id: 'cargo-deny',
364
+ label: 'cargo deny',
365
+ category: 'dep-license',
366
+ applies_to: ['rust'],
367
+ probe: 'command -v cargo-deny',
368
+ install: 'cargo install --locked cargo-deny',
369
+ install_prereq: 'cargo',
370
+ command: 'cargo deny check --format json',
371
+ timeout_minutes: 5,
372
+ parser: 'cargo-deny',
373
+ subscores: ['dependency_health.license', 'security.dep_vulns'],
374
+ tolerate_nonzero_exit: true,
375
+ };
376
+ const cargoOutdated = {
377
+ id: 'cargo-outdated',
378
+ label: 'cargo outdated',
379
+ category: 'dep-outdated',
380
+ applies_to: ['rust'],
381
+ probe: 'command -v cargo-outdated',
382
+ install: 'cargo install --locked cargo-outdated',
383
+ install_prereq: 'cargo',
384
+ command: 'cargo outdated --format json',
385
+ timeout_minutes: 5,
386
+ parser: 'cargo-outdated',
387
+ subscores: ['dependency_health.freshness'],
388
+ tolerate_nonzero_exit: true,
389
+ };
390
+ // ---------------------------------------------------------------------------
391
+ // Ruby
392
+ // ---------------------------------------------------------------------------
393
+ const rubocop = {
394
+ id: 'rubocop',
395
+ label: 'RuboCop',
396
+ category: 'lint',
397
+ applies_to: ['ruby'],
398
+ probe: 'command -v rubocop',
399
+ install: 'gem install --user-install rubocop --version "~> 1.65"',
400
+ install_prereq: 'gem',
401
+ command: 'rubocop --format json',
402
+ timeout_minutes: 5,
403
+ parser: 'rubocop',
404
+ subscores: ['code_quality.lint'],
405
+ tolerate_nonzero_exit: true,
406
+ };
407
+ const brakeman = {
408
+ id: 'brakeman',
409
+ label: 'Brakeman (Rails SAST)',
410
+ category: 'sast',
411
+ applies_to: ['ruby'],
412
+ probe: 'command -v brakeman',
413
+ install: 'gem install --user-install brakeman',
414
+ install_prereq: 'gem',
415
+ command: 'brakeman -f json -q --no-progress',
416
+ timeout_minutes: 10,
417
+ parser: 'brakeman',
418
+ subscores: ['security.sast'],
419
+ tolerate_nonzero_exit: true,
420
+ requires: { framework: ['rails'] },
421
+ };
422
+ const bundlerAudit = {
423
+ id: 'bundler-audit',
424
+ label: 'bundler-audit',
425
+ category: 'dep-vuln',
426
+ applies_to: ['ruby'],
427
+ probe: 'command -v bundle-audit',
428
+ install: 'gem install --user-install bundler-audit',
429
+ install_prereq: 'gem',
430
+ command: 'bundle-audit check --format json',
431
+ timeout_minutes: 5,
432
+ parser: 'bundler-audit',
433
+ subscores: ['security.dep_vulns'],
434
+ tolerate_nonzero_exit: true,
435
+ };
436
+ // ---------------------------------------------------------------------------
437
+ // Multi-language / polyglot
438
+ // ---------------------------------------------------------------------------
439
+ const semgrep = {
440
+ id: 'semgrep',
441
+ label: 'Semgrep',
442
+ category: 'sast',
443
+ applies_to: ['js', 'ts', 'py', 'go', 'rust', 'java', 'ruby', 'c', 'cpp'],
444
+ probe: 'command -v semgrep',
445
+ install: 'pipx install "semgrep>=1.40,<2.0"',
446
+ install_prereq: 'pipx',
447
+ command: 'semgrep --config auto --json --quiet --metrics off --timeout 300',
448
+ timeout_minutes: 15,
449
+ parser: 'semgrep',
450
+ subscores: ['security.sast', 'performance.n_plus_one'],
451
+ tolerate_nonzero_exit: true,
452
+ };
453
+ const gitleaks = {
454
+ id: 'gitleaks',
455
+ label: 'gitleaks',
456
+ category: 'secrets',
457
+ applies_to: ['all'],
458
+ probe: 'command -v gitleaks',
459
+ install: 'go install github.com/zricethezav/gitleaks/v8@latest',
460
+ install_prereq: 'go',
461
+ command: 'gitleaks detect --no-banner --report-format json --report-path - --redact',
462
+ timeout_minutes: 5,
463
+ parser: 'gitleaks',
464
+ subscores: ['security.secrets_hygiene'],
465
+ tolerate_nonzero_exit: true,
466
+ };
467
+ const osvScanner = {
468
+ id: 'osv-scanner',
469
+ label: 'osv-scanner',
470
+ category: 'dep-vuln',
471
+ applies_to: ['all'],
472
+ probe: 'command -v osv-scanner',
473
+ install: 'go install github.com/google/osv-scanner/cmd/osv-scanner@latest',
474
+ install_prereq: 'go',
475
+ command: 'osv-scanner --format json --recursive %REPO_ROOT%',
476
+ timeout_minutes: 10,
477
+ parser: 'osv-scanner',
478
+ subscores: ['security.dep_vulns'],
479
+ tolerate_nonzero_exit: true,
480
+ };
481
+ const scc = {
482
+ id: 'scc',
483
+ label: 'scc (LOC stats)',
484
+ category: 'loc-stats',
485
+ applies_to: ['all'],
486
+ probe: 'command -v scc',
487
+ install: 'go install github.com/boyter/scc/v3@latest',
488
+ install_prereq: 'go',
489
+ command: 'scc --format json %REPO_ROOT%',
490
+ timeout_minutes: 3,
491
+ parser: 'scc',
492
+ subscores: ['maintainability.file_distribution'],
493
+ };
494
+ const lizard = {
495
+ id: 'lizard',
496
+ label: 'lizard (complexity)',
497
+ category: 'complexity',
498
+ applies_to: ['js', 'ts', 'py', 'java', 'c', 'cpp', 'go', 'rust', 'swift'],
499
+ probe: 'command -v lizard',
500
+ install: 'pipx install "lizard>=1.17,<2.0"',
501
+ install_prereq: 'pipx',
502
+ command: 'lizard %REPO_ROOT% --xml -t 1 -C 15 -L 80',
503
+ timeout_minutes: 5,
504
+ parser: 'lizard',
505
+ subscores: ['code_quality.complexity', 'maintainability.cognitive_load'],
506
+ };
507
+ // ---------------------------------------------------------------------------
508
+ // Catalog assembly
509
+ // ---------------------------------------------------------------------------
510
+ export const TOOL_CATALOG = [
511
+ // JS/TS
512
+ eslint,
513
+ tscTypecheck,
514
+ jscpd,
515
+ madge,
516
+ depcheck,
517
+ npmAudit,
518
+ pnpmAudit,
519
+ yarnAudit,
520
+ npmOutdated,
521
+ licenseChecker,
522
+ // Python
523
+ ruff,
524
+ mypy,
525
+ bandit,
526
+ pipAudit,
527
+ radon,
528
+ vulture,
529
+ // Go
530
+ golangciLint,
531
+ gosec,
532
+ govulncheck,
533
+ gocyclo,
534
+ goModOutdated,
535
+ // Rust
536
+ clippy,
537
+ cargoAudit,
538
+ cargoDeny,
539
+ cargoOutdated,
540
+ // Ruby
541
+ rubocop,
542
+ brakeman,
543
+ bundlerAudit,
544
+ // Polyglot
545
+ semgrep,
546
+ gitleaks,
547
+ osvScanner,
548
+ scc,
549
+ lizard,
550
+ ];
551
+ /** Index for O(1) lookups by tool id. */
552
+ export const TOOL_CATALOG_BY_ID = new Map(TOOL_CATALOG.map((t) => [t.id, t]));
553
+ /** All tool ids the LLM is allowed to invoke. */
554
+ export const ALL_TOOL_IDS = TOOL_CATALOG.map((t) => t.id);
555
+ /** All install commands — used to whitelist Bash invocations during Phase 2.5. */
556
+ export const ALL_INSTALL_COMMANDS = TOOL_CATALOG.map((t) => t.install).filter((c) => c !== null);
557
+ /**
558
+ * Pick the catalog entries relevant to a detected context. This is advisory:
559
+ * the runtime still allows any catalog entry to run, but most callers should
560
+ * narrow first to avoid wasted probing.
561
+ */
562
+ export function selectToolsForContext(input) {
563
+ const langSet = new Set(input.languages.map((l) => l.toLowerCase()));
564
+ const pmSet = new Set((input.package_managers ?? []).map((p) => p.toLowerCase()));
565
+ const frameworkSet = new Set((input.frameworks ?? []).map((f) => f.toLowerCase()));
566
+ const fileSet = new Set(input.files_present ?? []);
567
+ return TOOL_CATALOG.filter((tool) => {
568
+ // Language gate
569
+ const langMatch = tool.applies_to.includes('all') ||
570
+ tool.applies_to.some((tag) => langSet.has(tag));
571
+ if (!langMatch) {
572
+ return false;
573
+ }
574
+ // Package-manager gate
575
+ if (tool.requires?.package_manager?.length) {
576
+ const anyMatch = tool.requires.package_manager.some((pm) => pmSet.has(pm));
577
+ if (!anyMatch) {
578
+ return false;
579
+ }
580
+ }
581
+ // Framework gate
582
+ if (tool.requires?.framework?.length) {
583
+ const anyMatch = tool.requires.framework.some((fw) => frameworkSet.has(fw));
584
+ if (!anyMatch) {
585
+ return false;
586
+ }
587
+ }
588
+ // File-presence gate
589
+ if (tool.requires?.file_present?.length) {
590
+ const anyMatch = tool.requires.file_present.some((f) => fileSet.has(f));
591
+ if (!anyMatch) {
592
+ return false;
593
+ }
594
+ }
595
+ return true;
596
+ });
597
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Tool execution layer for the quality-benchmark phase.
3
+ *
4
+ * Three operations exposed to the rest of the phase (and ultimately to
5
+ * the LLM via the MCP server in `mcp-server.ts`):
6
+ *
7
+ * probeTool(id, ctx) -> { available, version, install_command }
8
+ * installTool(id, ctx) -> { installed, version, error }
9
+ * executeTool(id, ctx) -> { summary, run, parser_output }
10
+ *
11
+ * All commands come from the authoritative `TOOL_CATALOG` — callers may
12
+ * not pass arbitrary command strings. Outputs are saved to a per-run
13
+ * scratch directory so the LLM never has to ferry megabytes of JSON
14
+ * through its context; only the small `ParsedToolOutput` flows back.
15
+ *
16
+ * Safety rails enforced here (in addition to those documented in the
17
+ * rubric):
18
+ * - All commands run with cwd = repo root (or an explicit override)
19
+ * - Per-command timeout; SIGKILL on overrun
20
+ * - No sudo / no system package managers (refused at install time)
21
+ * - Captured outputs truncated at 16 MiB to bound disk usage
22
+ */
23
+ import type { InstallerPrereq, ParsedToolOutput, ToolRunOutput, UnavailableTool } from './types';
24
+ export interface RunnerContext {
25
+ /** Absolute path to the repo checkout. */
26
+ repo_root: string;
27
+ /** Detected package manager (npm/pnpm/yarn) for JS commands. */
28
+ package_manager?: string;
29
+ /** Per-run scratch dir (created lazily under ~/.edsger/quality-runs/<run-id>). */
30
+ scan_dir: string;
31
+ /** When false, install attempts are skipped (CLI --no-install). */
32
+ install_enabled: boolean;
33
+ }
34
+ export interface ProbeResult {
35
+ available: boolean;
36
+ version: string | null;
37
+ install_command: string | null;
38
+ install_prereq: InstallerPrereq;
39
+ reason?: 'not_found' | 'probe_failed';
40
+ }
41
+ export interface InstallResult {
42
+ installed: boolean;
43
+ version: string | null;
44
+ error?: string;
45
+ }
46
+ export interface ExecuteResult {
47
+ /** Standardised summary for LLM consumption. */
48
+ parsed: ParsedToolOutput;
49
+ /** Audit/metadata record stored in `quality_reports.tool_outputs`. */
50
+ run: ToolRunOutput;
51
+ /** True if the runner believes the parser succeeded. */
52
+ ok: boolean;
53
+ }
54
+ export declare function createRunnerContext(opts: {
55
+ repo_root: string;
56
+ package_manager?: string;
57
+ install_enabled?: boolean;
58
+ run_id?: string;
59
+ /** Override scan dir base (defaults to ~/.edsger/quality-runs). */
60
+ base_dir?: string;
61
+ }): RunnerContext;
62
+ export declare function probeTool(id: string, ctx: RunnerContext): Promise<ProbeResult>;
63
+ export declare function probeInstaller(prereq: InstallerPrereq, ctx: RunnerContext): Promise<boolean>;
64
+ export declare function installTool(id: string, ctx: RunnerContext): Promise<InstallResult>;
65
+ export declare function executeTool(id: string, ctx: RunnerContext): Promise<ExecuteResult>;
66
+ /** Convert a failed probe into the UnavailableTool record stored in DB. */
67
+ export declare function probeToUnavailable(id: string, probe: ProbeResult, reason: UnavailableTool['reason']): UnavailableTool;
68
+ /** Convert an install failure to UnavailableTool. */
69
+ export declare function installFailureToUnavailable(id: string, res: InstallResult): UnavailableTool;