argus-reviewer-e2e 0.1.2 → 0.1.3
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/README.md +1 -0
- package/action/action.yml +5 -0
- package/action/sticky-comment.mjs +36 -8
- package/dist/cli.js +83 -40
- package/dist/config.d.ts +43 -1
- package/dist/config.js +30 -6
- package/dist/detect.d.ts +11 -1
- package/dist/detect.js +19 -3
- package/dist/evidence/ci.d.ts +44 -2
- package/dist/evidence/ci.js +96 -6
- package/dist/evidence/gate.d.ts +10 -0
- package/dist/evidence/gate.js +29 -0
- package/dist/evidence/link.d.ts +6 -1
- package/dist/evidence/link.js +7 -5
- package/dist/executor/sandbox.d.ts +105 -0
- package/dist/executor/sandbox.js +231 -0
- package/dist/probe/author.d.ts +50 -0
- package/dist/probe/author.js +149 -0
- package/dist/probe/harness.d.ts +30 -0
- package/dist/probe/harness.js +99 -0
- package/dist/probe/queue.d.ts +102 -0
- package/dist/probe/queue.js +408 -0
- package/package.json +1 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { posix } from 'node:path';
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
/**
|
|
4
|
+
* Probe authoring (KTD7): one bounded model call per `not_exercised`
|
|
5
|
+
* finding produces a single test file asserting the *correct* behavior —
|
|
6
|
+
* so the defect's presence fails the test on head while the base checkout
|
|
7
|
+
* passes. The file is written on the HOST (inside the ro workspace mount),
|
|
8
|
+
* so `filename` and `content` are validated hard: the model's input includes
|
|
9
|
+
* PR-controlled file contents, and a prompt-injected traversal or secret
|
|
10
|
+
* read would escape the sandbox's whole purpose.
|
|
11
|
+
*/
|
|
12
|
+
export const PROBE_SCHEMA = {
|
|
13
|
+
name: 'argus-probe',
|
|
14
|
+
strict: true,
|
|
15
|
+
schema: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
filename: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: 'Bare test filename matching the repo convention, e.g. probe-xyz.test.ts',
|
|
21
|
+
},
|
|
22
|
+
content: { type: 'string', description: 'Complete test file contents' },
|
|
23
|
+
reasoning: { type: 'string' },
|
|
24
|
+
},
|
|
25
|
+
required: ['filename', 'content', 'reasoning'],
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Basename-only, forced test extension — no separators, no `..`. The write
|
|
31
|
+
* happens on the host, so this is the traversal boundary.
|
|
32
|
+
*/
|
|
33
|
+
export const PROBE_FILENAME_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]*\.test\.[jt]sx?$/;
|
|
34
|
+
/** Probe files are bounded — a runaway generation is rejected, not truncated. */
|
|
35
|
+
export const PROBE_CONTENT_CAP = 32 * 1024;
|
|
36
|
+
/** Reads of secret-looking env vars are forbidden — defense in depth on the stripped container env. */
|
|
37
|
+
const SECRET_ENV_RE = /process\.env\s*(?:\.|\[)\s*['"]?\w*(KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL)/i;
|
|
38
|
+
/**
|
|
39
|
+
* Import specifiers via the TypeScript scanner (same seam as the index) —
|
|
40
|
+
* a regex misses bare side-effect imports like `import '/abs/x'`. Returns
|
|
41
|
+
* undefined when the scanner itself fails — fail closed, never silently
|
|
42
|
+
* skip the import check.
|
|
43
|
+
*/
|
|
44
|
+
function importSpecifiers(content) {
|
|
45
|
+
try {
|
|
46
|
+
const info = ts.preProcessFile(content, true, true);
|
|
47
|
+
return [...info.importedFiles, ...info.referencedFiles].map((f) => f.fileName);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function parseProbe(raw) {
|
|
54
|
+
let parsed;
|
|
55
|
+
try {
|
|
56
|
+
parsed = JSON.parse(raw);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return { ok: false, reason: 'unparseable model response' };
|
|
60
|
+
}
|
|
61
|
+
if (typeof parsed.filename !== 'string' || !PROBE_FILENAME_RE.test(parsed.filename)) {
|
|
62
|
+
return { ok: false, reason: `unsafe filename: ${String(parsed.filename)}` };
|
|
63
|
+
}
|
|
64
|
+
if (typeof parsed.content !== 'string' || parsed.content.trim() === '') {
|
|
65
|
+
return { ok: false, reason: 'missing or empty content' };
|
|
66
|
+
}
|
|
67
|
+
if (parsed.content.length > PROBE_CONTENT_CAP) {
|
|
68
|
+
return { ok: false, reason: `content exceeds ${PROBE_CONTENT_CAP} bytes` };
|
|
69
|
+
}
|
|
70
|
+
if (SECRET_ENV_RE.test(parsed.content)) {
|
|
71
|
+
return { ok: false, reason: 'probe reads secret-looking env vars' };
|
|
72
|
+
}
|
|
73
|
+
const imports = importSpecifiers(parsed.content);
|
|
74
|
+
if (imports === undefined) {
|
|
75
|
+
return { ok: false, reason: 'probe content failed import scanning' };
|
|
76
|
+
}
|
|
77
|
+
for (const spec of imports) {
|
|
78
|
+
// Reject anything that isn't a bare specifier or plain relative path:
|
|
79
|
+
// absolute paths, drive letters, UNC, and scheme-prefixed specifiers
|
|
80
|
+
// (`file:///etc/passwd` is a host read, `data:` smuggles a module).
|
|
81
|
+
// `node:` builtins stay allowed — node-test probes need `node:test`.
|
|
82
|
+
if (spec.startsWith('/') ||
|
|
83
|
+
/^[a-zA-Z]:/.test(spec) ||
|
|
84
|
+
spec.startsWith('\\\\') ||
|
|
85
|
+
(/^[a-z][a-z0-9+.-]*:/i.test(spec) && !spec.startsWith('node:'))) {
|
|
86
|
+
return { ok: false, reason: `unsafe import: ${spec}` };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
ok: true,
|
|
91
|
+
probe: {
|
|
92
|
+
filename: parsed.filename,
|
|
93
|
+
content: parsed.content,
|
|
94
|
+
reasoning: typeof parsed.reasoning === 'string' ? parsed.reasoning : '',
|
|
95
|
+
imports,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Verify every relative import in a probe resolves inside the repo, given
|
|
101
|
+
* the probe's repo-relative write path. `../../etc/passwd` from a shallow
|
|
102
|
+
* dir escapes the checkout — reject.
|
|
103
|
+
*/
|
|
104
|
+
export function probeImportsSafe(probe, relProbePath) {
|
|
105
|
+
// Repo-relative paths are always `/`-separated — compute in posix so a
|
|
106
|
+
// Windows host can't turn `../x` into `..\x` and slip the `../` check.
|
|
107
|
+
const dir = posix.dirname(relProbePath);
|
|
108
|
+
return probe.imports.every((spec) => {
|
|
109
|
+
if (!spec.startsWith('.'))
|
|
110
|
+
return true; // bare package specifier — resolved from node_modules
|
|
111
|
+
const resolved = posix.normalize(posix.join(dir, spec));
|
|
112
|
+
return resolved !== '..' && !resolved.startsWith('../') && !posix.isAbsolute(resolved);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
export function buildProbeMessages(target, fileContents, exemplarTest, harness) {
|
|
116
|
+
const exemplar = exemplarTest === undefined
|
|
117
|
+
? '(no existing test file was available as an exemplar — follow standard idiom)'
|
|
118
|
+
: `Exemplar test file (${exemplarTest.path}) — match its imports, naming, and assertion style:\n\n${exemplarTest.content}`;
|
|
119
|
+
const implicated = fileContents === undefined
|
|
120
|
+
? `(file contents unavailable; the finding references ${target.file ?? 'unknown'})`
|
|
121
|
+
: `Contents of ${target.file}:\n\n${fileContents}`;
|
|
122
|
+
return [
|
|
123
|
+
{
|
|
124
|
+
role: 'system',
|
|
125
|
+
content: [
|
|
126
|
+
{
|
|
127
|
+
type: 'text',
|
|
128
|
+
text: `You author a single failing test — a "probe" — that reproduces a suspected defect ` +
|
|
129
|
+
`found by code review. The probe runs under ${harness.kind} in an offline sandbox. ` +
|
|
130
|
+
`Write a self-contained ${harness.kind} test that asserts the CORRECT behavior: if the ` +
|
|
131
|
+
`defect is real, the test fails; if the code is fine, it passes. Import only repo ` +
|
|
132
|
+
`modules via relative paths and packages the repo already uses. No network, no ` +
|
|
133
|
+
`filesystem outside the repo, no timers, no process.env reads. filename must be a ` +
|
|
134
|
+
`bare filename ending in .test.ts/.test.js matching the exemplar's convention.`,
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
role: 'user',
|
|
140
|
+
content: [
|
|
141
|
+
{
|
|
142
|
+
type: 'text',
|
|
143
|
+
text: `Finding (${target.severity}) in ${target.file ?? 'unknown'}${target.line ? `:${target.line}` : ''}:\n` +
|
|
144
|
+
`${target.message}\n\n${implicated}\n\n${exemplar}`,
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test-harness detection + result classification for the B.2 probe lane
|
|
3
|
+
* (KTD3). v1 is deliberately Node-only: vitest, jest, and `node --test`.
|
|
4
|
+
*
|
|
5
|
+
* Two implementation details carry the weight:
|
|
6
|
+
* - Positional file args are *filters* intersected with the consumer's
|
|
7
|
+
* configured include/testMatch/roots, so probes must live where the
|
|
8
|
+
* runner already looks (the queue writes them beside an exemplar test).
|
|
9
|
+
* - Invocations bypass `npx` — as `nobody` on a read-only root there is no
|
|
10
|
+
* writable HOME, so the installed binary is invoked directly and each
|
|
11
|
+
* runner's cache is redirected into the /tmp tmpfs.
|
|
12
|
+
*/
|
|
13
|
+
export type ProbeOutcome = 'failed-test' | 'load-error' | 'not-collected' | 'clean';
|
|
14
|
+
export interface Harness {
|
|
15
|
+
kind: 'vitest' | 'jest' | 'node-test';
|
|
16
|
+
/** In-container argv running exactly one probe file (path relative to /work). */
|
|
17
|
+
runCmd(probeRelPath: string): string[];
|
|
18
|
+
/** Map captured output to the report vocabulary. */
|
|
19
|
+
classify(res: {
|
|
20
|
+
exitCode: number;
|
|
21
|
+
stdout: string;
|
|
22
|
+
stderr: string;
|
|
23
|
+
}): ProbeOutcome;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Detect the consumer's test harness from package.json. Returns undefined
|
|
27
|
+
* when no supported harness exists — the probe lane degrades to a detail
|
|
28
|
+
* note, not a failure.
|
|
29
|
+
*/
|
|
30
|
+
export declare function detectHarness(cwd: string): Promise<Harness | undefined>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Load-failure signatures are checked BEFORE test-failure summaries — a
|
|
5
|
+
* suite that can't import (vitest counts it as "Test Files 1 failed", TAP
|
|
6
|
+
* emits "not ok") must never classify as failed-test, and the exit code
|
|
7
|
+
* binds the result: probe output is attacker-printable, so a forged
|
|
8
|
+
* "N failed" line on a clean exit still classifies clean.
|
|
9
|
+
*/
|
|
10
|
+
const LOAD_ERROR_RE = /Cannot find module|ERR_UNKNOWN_FILE_EXTENSION|ERR_MODULE_NOT_FOUND|Failed to load|SyntaxError/;
|
|
11
|
+
const vitestHarness = {
|
|
12
|
+
kind: 'vitest',
|
|
13
|
+
runCmd: (file) => ['node', 'node_modules/vitest/vitest.mjs', 'run', file],
|
|
14
|
+
classify: ({ exitCode, stdout, stderr }) => {
|
|
15
|
+
const out = `${stdout}\n${stderr}`;
|
|
16
|
+
if (exitCode === 0)
|
|
17
|
+
return 'clean';
|
|
18
|
+
if (LOAD_ERROR_RE.test(out))
|
|
19
|
+
return 'load-error';
|
|
20
|
+
if (/no test files? found/i.test(out))
|
|
21
|
+
return 'not-collected';
|
|
22
|
+
if (/test files?\s+\d+ failed|tests\s+\d+ failed/i.test(out))
|
|
23
|
+
return 'failed-test';
|
|
24
|
+
return 'load-error';
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
const jestHarness = {
|
|
28
|
+
kind: 'jest',
|
|
29
|
+
runCmd: (file) => [
|
|
30
|
+
'node',
|
|
31
|
+
'node_modules/jest-cli/bin/jest.js',
|
|
32
|
+
'--runTestsByPath',
|
|
33
|
+
'--cacheDirectory=/tmp/jest',
|
|
34
|
+
file,
|
|
35
|
+
],
|
|
36
|
+
classify: ({ exitCode, stdout, stderr }) => {
|
|
37
|
+
const out = `${stdout}\n${stderr}`;
|
|
38
|
+
if (exitCode === 0)
|
|
39
|
+
return 'clean';
|
|
40
|
+
if (LOAD_ERROR_RE.test(out))
|
|
41
|
+
return 'load-error';
|
|
42
|
+
if (/no tests found/i.test(out))
|
|
43
|
+
return 'not-collected';
|
|
44
|
+
if (/tests:\s+\d+ failed/i.test(out))
|
|
45
|
+
return 'failed-test';
|
|
46
|
+
return 'load-error';
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* `node --test` in a TS repo only works because `scripts.test` carries the
|
|
51
|
+
* loader flags (`--import tsx`, `--experimental-strip-types`, `--loader`) —
|
|
52
|
+
* capture them verbatim or the authored .ts probe dies with
|
|
53
|
+
* ERR_UNKNOWN_FILE_EXTENSION. Both `--import tsx` and `--import=tsx` forms.
|
|
54
|
+
*/
|
|
55
|
+
const NODE_TEST_FLAG_RE = /(?:--import|--loader)(?:[\s=])\S+|--experimental-(?:strip|transform)-types/g;
|
|
56
|
+
function nodeTestHarness(flags) {
|
|
57
|
+
return {
|
|
58
|
+
kind: 'node-test',
|
|
59
|
+
// --test-reporter=tap pins the format `classify` parses — Node ≥22
|
|
60
|
+
// defaults to the spec reporter (`✖`, not TAP `not ok`) when stdout
|
|
61
|
+
// is a TTY, and real failures would fall through to load-error.
|
|
62
|
+
runCmd: (file) => ['node', ...flags, '--test', '--test-reporter=tap', file],
|
|
63
|
+
classify: ({ exitCode, stdout, stderr }) => {
|
|
64
|
+
const out = `${stdout}\n${stderr}`;
|
|
65
|
+
if (exitCode === 0)
|
|
66
|
+
return 'clean';
|
|
67
|
+
if (LOAD_ERROR_RE.test(out))
|
|
68
|
+
return 'load-error';
|
|
69
|
+
if (/^not ok/im.test(out))
|
|
70
|
+
return 'failed-test';
|
|
71
|
+
return 'load-error';
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Detect the consumer's test harness from package.json. Returns undefined
|
|
77
|
+
* when no supported harness exists — the probe lane degrades to a detail
|
|
78
|
+
* note, not a failure.
|
|
79
|
+
*/
|
|
80
|
+
export async function detectHarness(cwd) {
|
|
81
|
+
let pkg;
|
|
82
|
+
try {
|
|
83
|
+
pkg = JSON.parse(await readFile(join(cwd, 'package.json'), 'utf8'));
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
89
|
+
const testScript = pkg.scripts?.test ?? '';
|
|
90
|
+
if (deps['vitest'] !== undefined || /\bvitest\b/.test(testScript))
|
|
91
|
+
return vitestHarness;
|
|
92
|
+
if (deps['jest'] !== undefined || /\bjest\b/.test(testScript))
|
|
93
|
+
return jestHarness;
|
|
94
|
+
if (/\bnode\b.*--test|node:test/.test(testScript)) {
|
|
95
|
+
const flags = testScript.match(NODE_TEST_FLAG_RE)?.flatMap((f) => f.split(/[\s=]+/)) ?? [];
|
|
96
|
+
return nodeTestHarness(flags);
|
|
97
|
+
}
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { type ProviderRules, type Sandbox } from '../config.js';
|
|
2
|
+
import { type ExecFn } from '../detect.js';
|
|
3
|
+
import type { PrMeta } from '../evidence/ci.js';
|
|
4
|
+
import { type Evidence } from '../evidence/link.js';
|
|
5
|
+
import type { RepoIndex } from '../index/scan.js';
|
|
6
|
+
import type { VisionClient } from '../engine/loop.js';
|
|
7
|
+
import type { CallCost } from '../vision/cost.js';
|
|
8
|
+
import type { Ledger } from '../vision/ledger.js';
|
|
9
|
+
import { type ProbeOutcome } from './harness.js';
|
|
10
|
+
/**
|
|
11
|
+
* The B.2 probe lane (U4). Called after `linkFindings` inside `code-review`.
|
|
12
|
+
* Selects `not_exercised` blocking-severity findings, authors one probe each,
|
|
13
|
+
* runs it in the sandbox against head AND a merge-base worktree, and upgrades
|
|
14
|
+
* evidence to `reproduced` only on fail-head ∧ clean-base (KTD6). Everything
|
|
15
|
+
* else leaves the finding untouched — additive evidence only, never a new
|
|
16
|
+
* failure surface (KTD8): every error path degrades to a probe record or a
|
|
17
|
+
* debug note, and the queue never changes verdict, ok, or exit code.
|
|
18
|
+
*
|
|
19
|
+
* Trust note: for `pull_request` events the config file ships in the PR's
|
|
20
|
+
* own tree, so for FORK PRs the lane ignores config-supplied
|
|
21
|
+
* image/limits/allowForks and runs on `DEFAULT_SANDBOX` — forks approve via
|
|
22
|
+
* the argus-probe label or a trusted author association, never via config.
|
|
23
|
+
*/
|
|
24
|
+
/** Finding shape shared by the review report, probe targets, and this lane. */
|
|
25
|
+
export interface LinkedFinding {
|
|
26
|
+
file?: string | undefined;
|
|
27
|
+
line?: number | undefined;
|
|
28
|
+
severity?: string | undefined;
|
|
29
|
+
message?: string | undefined;
|
|
30
|
+
evidence: Evidence;
|
|
31
|
+
}
|
|
32
|
+
export type ProbeReportOutcome = 'reproduced' | 'clean' | 'load-error' | 'not-collected' | 'error';
|
|
33
|
+
export interface ProbeRecord {
|
|
34
|
+
/** Probe filename (basename — it is written beside the exemplar test). */
|
|
35
|
+
file: string;
|
|
36
|
+
findingFile: string | undefined;
|
|
37
|
+
findingLine: number | undefined;
|
|
38
|
+
outcome: ProbeReportOutcome;
|
|
39
|
+
/** Raw harness outcomes per checkout, when the run happened ('error' = infra/timeout). */
|
|
40
|
+
headOutcome?: ProbeOutcome | 'error' | undefined;
|
|
41
|
+
baseOutcome?: ProbeOutcome | 'error' | undefined;
|
|
42
|
+
durationMs: number;
|
|
43
|
+
costUsd: number;
|
|
44
|
+
tokens: number;
|
|
45
|
+
detail: string;
|
|
46
|
+
/** Capped, control-char-stripped stdout+stderr for audit. */
|
|
47
|
+
output?: string | undefined;
|
|
48
|
+
}
|
|
49
|
+
export interface ProbeLaneResult {
|
|
50
|
+
records: ProbeRecord[];
|
|
51
|
+
/** Present when the lane was enabled but skipped before running probes. */
|
|
52
|
+
skipReason?: string | undefined;
|
|
53
|
+
}
|
|
54
|
+
export interface ProbeLaneOptions {
|
|
55
|
+
/** PR checkout root (the head tree). */
|
|
56
|
+
cwd: string;
|
|
57
|
+
reportDir: string;
|
|
58
|
+
/** Caller resolves `sandbox.enabled || ARGUS_SANDBOX=1` into this object. */
|
|
59
|
+
sandbox: Sandbox;
|
|
60
|
+
meta: PrMeta | undefined;
|
|
61
|
+
token: string | undefined;
|
|
62
|
+
client: VisionClient;
|
|
63
|
+
model: string;
|
|
64
|
+
provider: ProviderRules | undefined;
|
|
65
|
+
/** Shared codeReviewBudgetUsd ledger — authoring calls record on it. */
|
|
66
|
+
ledger: Ledger;
|
|
67
|
+
/** codeReviewBudgetUsd — authoring stops once spend reaches it. */
|
|
68
|
+
budgetUsd: number | undefined;
|
|
69
|
+
/** Configured blocking severities — the queue only admits those. */
|
|
70
|
+
severityGates: string[];
|
|
71
|
+
index: RepoIndex | undefined;
|
|
72
|
+
/** Report sink — authored probe CallCosts are pushed here for the report. */
|
|
73
|
+
calls?: CallCost[] | undefined;
|
|
74
|
+
exec?: ExecFn | undefined;
|
|
75
|
+
log?: ((line: string) => void) | undefined;
|
|
76
|
+
}
|
|
77
|
+
/** Pure selection: not_exercised findings at blocking severities, capped. */
|
|
78
|
+
export declare function selectProbeTargets(findings: LinkedFinding[], severityGates: string[], maxProbes: number): LinkedFinding[];
|
|
79
|
+
/**
|
|
80
|
+
* Repo-relative path gate for anything model- or index-derived that is read
|
|
81
|
+
* or written on the HOST: no absolute paths, no `..` escapes, no backslashes.
|
|
82
|
+
* The write side is already basename-bound (PROBE_FILENAME_RE); this is the
|
|
83
|
+
* read-side and exemplar-dir boundary — a prompt-injected `../../.env`
|
|
84
|
+
* finding.file or a crafted index entry must never reach fs calls.
|
|
85
|
+
*/
|
|
86
|
+
export declare function isSafeRepoPath(p: string): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Nearest existing test file to the finding's file — same directory first,
|
|
89
|
+
* then same top-level segment, then any test file. The exemplar sets the
|
|
90
|
+
* probe's write location so the consumer's own include/roots cover it.
|
|
91
|
+
* Index paths are filtered through isSafeRepoPath — a committed/crafted
|
|
92
|
+
* index could otherwise aim the host write outside the checkout.
|
|
93
|
+
*/
|
|
94
|
+
export declare function findExemplarTest(index: RepoIndex | undefined, findingFile: string): string | undefined;
|
|
95
|
+
/**
|
|
96
|
+
* Run the probe lane. Mutates `findings` evidence in place for reproduced
|
|
97
|
+
* results and returns the per-probe audit records for code-review.json.
|
|
98
|
+
* Returns undefined only when the lane is disabled entirely; otherwise a
|
|
99
|
+
* result carrying records and (when it bowed out early) a skipReason the
|
|
100
|
+
* report surface can render.
|
|
101
|
+
*/
|
|
102
|
+
export declare function runProbeLane(findings: LinkedFinding[], o: ProbeLaneOptions): Promise<ProbeLaneResult | undefined>;
|