@alex-dbs/coderuler 1.4.0 → 1.6.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/README.md +3 -1
- package/dist/adapter/cli/commands/check.js +21 -10
- package/dist/adapter/cli/program.js +2 -2
- package/dist/adapter/cli/reports.js +1 -1
- package/dist/adapter/cli/services.d.ts +4 -2
- package/dist/adapter/decision/jev.js +32 -15
- package/dist/adapter/decision/openai.d.ts +11 -0
- package/dist/adapter/decision/openai.js +160 -0
- package/dist/adapter/filesystem/rule-test-lsp.d.ts +6 -0
- package/dist/adapter/filesystem/rule-test-lsp.js +189 -0
- package/dist/adapter/filesystem/rule-test-worker.d.ts +5 -0
- package/dist/adapter/filesystem/rule-test-worker.js +48 -26
- package/dist/adapter/filesystem/rule-tests.d.ts +2 -2
- package/dist/adapter/filesystem/rule-tests.js +170 -29
- package/dist/adapter/git/changes.d.ts +2 -1
- package/dist/adapter/git/changes.js +369 -35
- package/dist/adapter/git/rules.d.ts +5 -0
- package/dist/adapter/git/rules.js +126 -0
- package/dist/adapter/github/pull-request.d.ts +2 -0
- package/dist/adapter/github/pull-request.js +9 -3
- package/dist/adapter/lsp/backend.d.ts +12 -0
- package/dist/adapter/lsp/backend.js +826 -0
- package/dist/app/concurrency.d.ts +1 -1
- package/dist/app/concurrency.js +12 -8
- package/dist/app/execute.js +84 -22
- package/dist/app/lsp.d.ts +6 -0
- package/dist/app/lsp.js +141 -0
- package/dist/app/ports.d.ts +13 -1
- package/dist/app/rule.d.ts +52 -10
- package/dist/app/rule.js +14 -0
- package/dist/app/validate.js +3 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/infra/runtime.js +27 -8
- package/dist/infra/settings.js +19 -8
- package/package.json +3 -1
- package/skills/coderuler-new-rule/SKILL.md +7 -3
- package/skills/coderuler-new-rule/reference.md +80 -13
|
@@ -3,6 +3,7 @@ import { dirname, isAbsolute, resolve, sep, win32 } from 'node:path';
|
|
|
3
3
|
import { parentPort } from 'node:worker_threads';
|
|
4
4
|
import { executeRules } from '../../app/execute.js';
|
|
5
5
|
import { createDecisionResolver } from '../../infra/settings.js';
|
|
6
|
+
import { createRuleTestLspBackend } from './rule-test-lsp.js';
|
|
6
7
|
import { createProject } from './project.js';
|
|
7
8
|
import { createRuleLoader } from './rules.js';
|
|
8
9
|
function fixturePath(root, filepath) {
|
|
@@ -12,35 +13,56 @@ function fixturePath(root, filepath) {
|
|
|
12
13
|
}
|
|
13
14
|
return target;
|
|
14
15
|
}
|
|
15
|
-
async function runCase(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
async function runCase(request) {
|
|
17
|
+
const { lspPort, rubyLspPort, typescriptLspPort, root, ruleFile, files, ai, dependencies, includeDisabled } = request;
|
|
18
|
+
try {
|
|
19
|
+
for (const [filepath, source] of Object.entries(files)) {
|
|
20
|
+
const target = fixturePath(root, filepath);
|
|
21
|
+
await mkdir(dirname(target), { recursive: true });
|
|
22
|
+
await writeFile(target, typeof source === 'string' ? source : JSON.stringify(source, null, 2), 'utf8');
|
|
23
|
+
}
|
|
24
|
+
const rules = await createRuleLoader(dependencies === undefined ? {} : { preparedDependencies: dependencies }).load({ root, explicit: ruleFile, invocationCwd: root });
|
|
25
|
+
if (rules.length !== 1)
|
|
26
|
+
throw new Error('Expected exactly one freshly loaded rule');
|
|
27
|
+
if (includeDisabled === true)
|
|
28
|
+
rules[0].rule.enabled = true;
|
|
29
|
+
const reports = await executeRules(rules, {
|
|
30
|
+
project: createProject(root, { excludeFiles: [ruleFile] }),
|
|
31
|
+
isTarget: () => true,
|
|
32
|
+
concurrency: 1,
|
|
33
|
+
resolveDecision: ai ? createDecisionResolver(process.env) : () => {
|
|
34
|
+
throw new Error('Inline rule tests must not call the decision backend');
|
|
35
|
+
},
|
|
36
|
+
createLspBackend: (_project, signal) => createRuleTestLspBackend(lspPort, signal),
|
|
37
|
+
createRubyLspBackend: (_project, signal) => createRuleTestLspBackend(rubyLspPort, signal),
|
|
38
|
+
createTypescriptLspBackend: (_project, signal) => createRuleTestLspBackend(typescriptLspPort, signal),
|
|
39
|
+
});
|
|
40
|
+
const report = reports[0];
|
|
41
|
+
if (reports.length !== 1 || report === undefined)
|
|
42
|
+
throw new Error('Expected exactly one rule report');
|
|
43
|
+
let aiScores;
|
|
44
|
+
if (ai) {
|
|
45
|
+
for (const evaluation of report.evaluations) {
|
|
46
|
+
const score = evaluation.result.probability;
|
|
47
|
+
if (score !== undefined)
|
|
48
|
+
(aiScores ??= []).push(score);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { actual: report.status, ...(aiScores === undefined ? {} : { aiScores }) };
|
|
20
52
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const report = reports[0];
|
|
33
|
-
if (reports.length !== 1 || report === undefined)
|
|
34
|
-
throw new Error('Expected exactly one rule report');
|
|
35
|
-
let aiScores;
|
|
36
|
-
if (ai) {
|
|
37
|
-
for (const evaluation of report.evaluations) {
|
|
38
|
-
const score = evaluation.result.probability;
|
|
39
|
-
if (score !== undefined)
|
|
40
|
-
(aiScores ??= []).push(score);
|
|
53
|
+
finally {
|
|
54
|
+
try {
|
|
55
|
+
lspPort.close();
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
try {
|
|
59
|
+
rubyLspPort.close();
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
typescriptLspPort.close();
|
|
63
|
+
}
|
|
41
64
|
}
|
|
42
65
|
}
|
|
43
|
-
return { actual: report.status, ...(aiScores === undefined ? {} : { aiScores }) };
|
|
44
66
|
}
|
|
45
67
|
const port = parentPort;
|
|
46
68
|
if (port === null)
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { LoadedRule } from '../../app/ports.js';
|
|
1
|
+
import type { LoadedRule, LspBackendFactory } from '../../app/ports.js';
|
|
2
2
|
import type { RuleTestResult } from '../../app/rule-tests.js';
|
|
3
|
-
export declare function runRuleTests(rules: readonly LoadedRule[], onResult?: (result: RuleTestResult, lastInRule: boolean) => void, timeoutMs?: number, concurrency?: number, includeAI?: boolean): Promise<readonly RuleTestResult[]>;
|
|
3
|
+
export declare function runRuleTests(rules: readonly LoadedRule[], onResult?: (result: RuleTestResult, lastInRule: boolean) => void, timeoutMs?: number, concurrency?: number, includeAI?: boolean, includeDisabled?: boolean, createLspBackend?: LspBackendFactory, createRubyLspBackend?: LspBackendFactory, createTypescriptLspBackend?: LspBackendFactory): Promise<readonly RuleTestResult[]>;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { mkdtemp, realpath, rm } from 'node:fs/promises';
|
|
1
|
+
import { lstat, mkdtemp, realpath, rm } from 'node:fs/promises';
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { pathToFileURL } from 'node:url';
|
|
5
|
-
import { Worker } from 'node:worker_threads';
|
|
5
|
+
import { MessageChannel, Worker } from 'node:worker_threads';
|
|
6
6
|
import { isRecord, validateJsonValue } from '../../app/validate.js';
|
|
7
|
-
import {
|
|
7
|
+
import { createRuleTestLspHost } from './rule-test-lsp.js';
|
|
8
|
+
import { createProject } from './project.js';
|
|
9
|
+
import { getRuleDependencies, registerRuleDependencies } from './rule-resolution.js';
|
|
8
10
|
// A JavaScript entry lets inherited tsx hooks resolve the source worker's .js import.
|
|
9
11
|
const WORKER_MODULE = new URL('./rule-test-worker.js', import.meta.url);
|
|
10
12
|
const WORKER_ENTRY = new URL(`data:text/javascript,${encodeURIComponent(`import ${JSON.stringify(WORKER_MODULE.href)};`)}`);
|
|
@@ -24,6 +26,35 @@ function validateCase(value, label) {
|
|
|
24
26
|
}
|
|
25
27
|
return value;
|
|
26
28
|
}
|
|
29
|
+
async function loadExternalTests(ruleFile, testFile) {
|
|
30
|
+
try {
|
|
31
|
+
await lstat(testFile);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (error.code === 'ENOENT')
|
|
35
|
+
return undefined;
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const dependencies = getRuleDependencies(ruleFile);
|
|
40
|
+
if (dependencies !== undefined)
|
|
41
|
+
registerRuleDependencies(testFile, dependencies);
|
|
42
|
+
// The companion path comes from the selected rule filename.
|
|
43
|
+
return await import(pathToFileURL(testFile).href);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
47
|
+
throw new Error(`Rule test module ${testFile} failed to import: ${detail}`, { cause: error });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function addTestGroup(groups, values, sourceLabel, exportName, ai) {
|
|
51
|
+
if (values === undefined)
|
|
52
|
+
return;
|
|
53
|
+
if (!Array.isArray(values)) {
|
|
54
|
+
throw new Error(`${sourceLabel} ${exportName}: expected an array of test cases`);
|
|
55
|
+
}
|
|
56
|
+
groups.push({ values, label: `${sourceLabel} ${exportName}`, ai });
|
|
57
|
+
}
|
|
27
58
|
class RuleTestWorkerSession {
|
|
28
59
|
worker = new Worker(WORKER_ENTRY);
|
|
29
60
|
nextId = 0;
|
|
@@ -32,6 +63,7 @@ class RuleTestWorkerSession {
|
|
|
32
63
|
onFailure;
|
|
33
64
|
terminating = false;
|
|
34
65
|
termination;
|
|
66
|
+
activeLsp;
|
|
35
67
|
onError = (error) => {
|
|
36
68
|
if (this.terminating)
|
|
37
69
|
return;
|
|
@@ -54,12 +86,41 @@ class RuleTestWorkerSession {
|
|
|
54
86
|
if (this.failure !== undefined)
|
|
55
87
|
throw this.failure;
|
|
56
88
|
}
|
|
57
|
-
|
|
89
|
+
closeLsp(active = this.activeLsp) {
|
|
90
|
+
if (active === undefined)
|
|
91
|
+
return Promise.resolve();
|
|
92
|
+
if (active.closePromise !== undefined)
|
|
93
|
+
return active.closePromise;
|
|
94
|
+
active.closePromise = (async () => {
|
|
95
|
+
try {
|
|
96
|
+
const hosts = [active.go.host, active.ruby.host, active.typescript.host].filter((host) => host !== undefined);
|
|
97
|
+
const results = await Promise.allSettled(hosts.map(async (host) => host.close()));
|
|
98
|
+
const rejected = results.find(result => result.status === 'rejected');
|
|
99
|
+
if (rejected?.status === 'rejected')
|
|
100
|
+
throw rejected.reason;
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
active.go.channel.port1.close();
|
|
104
|
+
active.ruby.channel.port1.close();
|
|
105
|
+
active.typescript.channel.port1.close();
|
|
106
|
+
if (!active.transferred) {
|
|
107
|
+
active.go.channel.port2.close();
|
|
108
|
+
active.ruby.channel.port2.close();
|
|
109
|
+
active.typescript.channel.port2.close();
|
|
110
|
+
}
|
|
111
|
+
if (this.activeLsp === active)
|
|
112
|
+
this.activeLsp = undefined;
|
|
113
|
+
}
|
|
114
|
+
})();
|
|
115
|
+
return active.closePromise;
|
|
116
|
+
}
|
|
117
|
+
async run(request, deadline, onFailure, createBackend, createRubyLspBackend, createTypescriptLspBackend) {
|
|
58
118
|
this.onFailure = onFailure;
|
|
59
119
|
this.assertHealthy();
|
|
60
120
|
const id = this.nextId++;
|
|
61
121
|
const result = Promise.withResolvers();
|
|
62
122
|
let timer;
|
|
123
|
+
let activeLsp;
|
|
63
124
|
const expire = () => {
|
|
64
125
|
const remaining = deadline - performance.now();
|
|
65
126
|
if (remaining <= 0)
|
|
@@ -83,29 +144,94 @@ class RuleTestWorkerSession {
|
|
|
83
144
|
else
|
|
84
145
|
result.reject(error);
|
|
85
146
|
};
|
|
147
|
+
let operationFailed = false;
|
|
86
148
|
this.worker.on('message', onMessage);
|
|
87
149
|
try {
|
|
88
150
|
expire();
|
|
89
151
|
if (performance.now() < deadline) {
|
|
90
|
-
|
|
152
|
+
const channels = [];
|
|
153
|
+
try {
|
|
154
|
+
const goChannel = new MessageChannel();
|
|
155
|
+
channels.push(goChannel);
|
|
156
|
+
const rubyChannel = new MessageChannel();
|
|
157
|
+
channels.push(rubyChannel);
|
|
158
|
+
const typescriptChannel = new MessageChannel();
|
|
159
|
+
channels.push(typescriptChannel);
|
|
160
|
+
activeLsp = {
|
|
161
|
+
go: { channel: goChannel, host: undefined },
|
|
162
|
+
ruby: { channel: rubyChannel, host: undefined },
|
|
163
|
+
typescript: { channel: typescriptChannel, host: undefined },
|
|
164
|
+
transferred: false,
|
|
165
|
+
};
|
|
166
|
+
this.activeLsp = activeLsp;
|
|
167
|
+
const project = createProject(request.root, { excludeFiles: [request.ruleFile] });
|
|
168
|
+
activeLsp.go.host = createRuleTestLspHost(goChannel.port1, project, createBackend);
|
|
169
|
+
activeLsp.ruby.host = createRuleTestLspHost(rubyChannel.port1, project, createRubyLspBackend);
|
|
170
|
+
activeLsp.typescript.host = createRuleTestLspHost(typescriptChannel.port1, project, createTypescriptLspBackend);
|
|
171
|
+
this.worker.postMessage({
|
|
172
|
+
...request, id, lspPort: goChannel.port2, rubyLspPort: rubyChannel.port2,
|
|
173
|
+
typescriptLspPort: typescriptChannel.port2,
|
|
174
|
+
}, [goChannel.port2, rubyChannel.port2, typescriptChannel.port2]);
|
|
175
|
+
activeLsp.transferred = true;
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (activeLsp === undefined) {
|
|
179
|
+
for (const channel of channels) {
|
|
180
|
+
channel.port1.close();
|
|
181
|
+
channel.port2.close();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
91
186
|
}
|
|
92
187
|
return await result.promise;
|
|
93
188
|
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
operationFailed = true;
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
94
193
|
finally {
|
|
95
194
|
clearTimeout(timer);
|
|
96
195
|
this.worker.off('message', onMessage);
|
|
97
196
|
this.rejectPending = undefined;
|
|
197
|
+
try {
|
|
198
|
+
await this.closeLsp(activeLsp);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
if (!operationFailed)
|
|
202
|
+
throw error;
|
|
203
|
+
if (activeLsp !== undefined)
|
|
204
|
+
activeLsp.closePromise = Promise.resolve();
|
|
205
|
+
}
|
|
98
206
|
}
|
|
99
207
|
}
|
|
100
208
|
async stop() {
|
|
101
209
|
if (this.termination === undefined) {
|
|
102
210
|
this.terminating = true;
|
|
103
211
|
this.rejectPending?.(new Error('Rule test worker stopped'));
|
|
104
|
-
this.termination =
|
|
212
|
+
this.termination = (async () => {
|
|
213
|
+
let closeFailed = false;
|
|
214
|
+
let closeError;
|
|
215
|
+
try {
|
|
216
|
+
await this.closeLsp();
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
closeFailed = true;
|
|
220
|
+
closeError = error;
|
|
221
|
+
}
|
|
222
|
+
const code = await this.worker.terminate();
|
|
223
|
+
if (closeFailed)
|
|
224
|
+
throw closeError;
|
|
225
|
+
return code;
|
|
226
|
+
})();
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
await this.termination;
|
|
230
|
+
}
|
|
231
|
+
finally {
|
|
232
|
+
this.worker.off('error', this.onError);
|
|
233
|
+
this.worker.off('exit', this.onExit);
|
|
105
234
|
}
|
|
106
|
-
await this.termination;
|
|
107
|
-
this.worker.off('error', this.onError);
|
|
108
|
-
this.worker.off('exit', this.onExit);
|
|
109
235
|
}
|
|
110
236
|
}
|
|
111
237
|
async function stopSession(session) {
|
|
@@ -114,7 +240,7 @@ async function stopSession(session) {
|
|
|
114
240
|
if (session.current === current)
|
|
115
241
|
session.current = undefined;
|
|
116
242
|
}
|
|
117
|
-
async function runCase(entry, example, ai, timeoutMs, session, onFailure) {
|
|
243
|
+
async function runCase(entry, example, ai, timeoutMs, session, onFailure, includeDisabled, createLspBackend, createRubyLspBackend, createTypescriptLspBackend) {
|
|
118
244
|
const started = performance.now();
|
|
119
245
|
const deadline = started + timeoutMs;
|
|
120
246
|
let failure;
|
|
@@ -141,8 +267,9 @@ async function runCase(entry, example, ai, timeoutMs, session, onFailure) {
|
|
|
141
267
|
const dependencies = getRuleDependencies(entry.ruleFile);
|
|
142
268
|
const outcome = await active.run({
|
|
143
269
|
root, ruleFile: entry.ruleFile, files: example.files, ai,
|
|
270
|
+
...(includeDisabled ? { includeDisabled: true } : {}),
|
|
144
271
|
...(dependencies === undefined ? {} : { dependencies }),
|
|
145
|
-
}, deadline, failCase);
|
|
272
|
+
}, deadline, failCase, createLspBackend, createRubyLspBackend, createTypescriptLspBackend);
|
|
146
273
|
if (typeof outcome === 'string')
|
|
147
274
|
actual = outcome;
|
|
148
275
|
else {
|
|
@@ -180,7 +307,7 @@ async function runCase(entry, example, ai, timeoutMs, session, onFailure) {
|
|
|
180
307
|
throw cause;
|
|
181
308
|
}
|
|
182
309
|
}
|
|
183
|
-
export async function runRuleTests(rules, onResult, timeoutMs = 10_000, concurrency = 1, includeAI = false) {
|
|
310
|
+
export async function runRuleTests(rules, onResult, timeoutMs = 10_000, concurrency = 1, includeAI = false, includeDisabled = false, createLspBackend, createRubyLspBackend, createTypescriptLspBackend) {
|
|
184
311
|
if (!Number.isSafeInteger(concurrency) || concurrency < 1) {
|
|
185
312
|
throw new RangeError(`--concurrency must be a positive safe integer, received ${String(concurrency)}`);
|
|
186
313
|
}
|
|
@@ -204,24 +331,28 @@ export async function runRuleTests(rules, onResult, timeoutMs = 10_000, concurre
|
|
|
204
331
|
for (const rule of rules) {
|
|
205
332
|
if (failureObserved)
|
|
206
333
|
throw failure;
|
|
207
|
-
if (!rule.rule.enabled)
|
|
334
|
+
if (!includeDisabled && !rule.rule.enabled)
|
|
208
335
|
continue;
|
|
209
336
|
// Discovery selects the rule module path at runtime.
|
|
210
|
-
const
|
|
337
|
+
const ruleModule = await import(pathToFileURL(rule.ruleFile).href);
|
|
338
|
+
const testFile = rule.ruleFile.replace(/\.rule\.(ts|mts|js|mjs)$/, '.test.$1');
|
|
339
|
+
const externalModule = await loadExternalTests(rule.ruleFile, testFile);
|
|
340
|
+
const { tests, aiTests } = ruleModule;
|
|
211
341
|
if (failureObserved)
|
|
212
342
|
throw failure;
|
|
213
|
-
if (!includeAI && aiTests !== undefined)
|
|
343
|
+
if (!includeAI && (aiTests !== undefined || externalModule?.aiTests !== undefined)) {
|
|
214
344
|
hasExcludedAITests = true;
|
|
215
|
-
const selectedAI = includeAI ? aiTests : undefined;
|
|
216
|
-
const label = `Rule "${rule.name}"`;
|
|
217
|
-
if (tests !== undefined && !Array.isArray(tests)) {
|
|
218
|
-
throw new Error(`${label} tests: expected an array of test cases`);
|
|
219
345
|
}
|
|
220
|
-
|
|
221
|
-
|
|
346
|
+
const label = `Rule "${rule.name}"`;
|
|
347
|
+
const externalLabel = `${label} test module ${testFile}`;
|
|
348
|
+
const groups = [];
|
|
349
|
+
addTestGroup(groups, tests, label, 'tests', false);
|
|
350
|
+
addTestGroup(groups, externalModule?.tests, externalLabel, 'tests', false);
|
|
351
|
+
if (includeAI) {
|
|
352
|
+
addTestGroup(groups, aiTests, label, 'aiTests', true);
|
|
353
|
+
addTestGroup(groups, externalModule?.aiTests, externalLabel, 'aiTests', true);
|
|
222
354
|
}
|
|
223
|
-
const
|
|
224
|
-
const count = regularCount + (selectedAI?.length ?? 0);
|
|
355
|
+
const count = groups.reduce((total, group) => total + group.values.length, 0);
|
|
225
356
|
const completed = new Array(count);
|
|
226
357
|
let nextIndex = 0;
|
|
227
358
|
let nextResult = 0;
|
|
@@ -231,11 +362,15 @@ export async function runRuleTests(rules, onResult, timeoutMs = 10_000, concurre
|
|
|
231
362
|
if (index >= count)
|
|
232
363
|
return;
|
|
233
364
|
try {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
365
|
+
let groupIndex = 0;
|
|
366
|
+
let caseIndex = index;
|
|
367
|
+
while (caseIndex >= groups[groupIndex].values.length) {
|
|
368
|
+
caseIndex -= groups[groupIndex].values.length;
|
|
369
|
+
groupIndex += 1;
|
|
370
|
+
}
|
|
371
|
+
const group = groups[groupIndex];
|
|
372
|
+
const example = validateCase(group.values[caseIndex], `${group.label}[${caseIndex}]`);
|
|
373
|
+
completed[index] = await runCase(rule, example, group.ai, timeoutMs, session, fail, includeDisabled, createLspBackend, createRubyLspBackend, createTypescriptLspBackend);
|
|
239
374
|
if (failureObserved)
|
|
240
375
|
return;
|
|
241
376
|
while (completed[nextResult] !== undefined) {
|
|
@@ -269,6 +404,12 @@ export async function runRuleTests(rules, onResult, timeoutMs = 10_000, concurre
|
|
|
269
404
|
}
|
|
270
405
|
finally {
|
|
271
406
|
await cancellation;
|
|
272
|
-
|
|
407
|
+
try {
|
|
408
|
+
await Promise.all(sessions.map(stopSession));
|
|
409
|
+
}
|
|
410
|
+
catch (error) {
|
|
411
|
+
if (!failureObserved)
|
|
412
|
+
throw error;
|
|
413
|
+
}
|
|
273
414
|
}
|
|
274
415
|
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import type { DiffChanges } from '../../app/ports.js';
|
|
2
|
+
export declare function collectChanges(root: string, base: string, env?: Readonly<Record<string, string | undefined>>): Promise<DiffChanges>;
|