@alint-js/core 0.1.0 → 0.1.2
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 +33 -0
- package/dist/agent.d.mts +1 -1
- package/dist/{define-CnBawoAI.d.mts → define-8BzWDcPs.d.mts} +1 -1
- package/dist/dsl.d.mts +3 -3
- package/dist/index.d.mts +59 -20
- package/dist/index.mjs +1720 -845
- package/dist/structured-output.d.mts +1 -1
- package/dist/{types-m5T_UL76.d.mts → types-DKylUrAn.d.mts} +17 -4
- package/package.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -3,14 +3,15 @@ import { n as definePlugin, r as defineRule, t as defineConfig } from "./define-
|
|
|
3
3
|
import { a as sliceRange, i as sliceLines, n as createSourceFile, r as createSourceRuntime, t as extractJsSourceTargets } from "./extract-CIWssxNK.mjs";
|
|
4
4
|
import { minimatch } from "minimatch";
|
|
5
5
|
import { isAbsolute, relative, resolve } from "pathe";
|
|
6
|
-
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
7
|
-
import { dirname, extname, isAbsolute as isAbsolute$1, join, relative as relative$1, resolve as resolve$1, sep } from "node:path";
|
|
8
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
|
+
import { access, mkdir, open, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
7
|
+
import { basename, dirname, extname, isAbsolute as isAbsolute$1, join, relative as relative$1, resolve as resolve$1, sep } from "node:path";
|
|
9
8
|
import process, { cwd } from "node:process";
|
|
10
|
-
import {
|
|
9
|
+
import { Buffer } from "node:buffer";
|
|
11
10
|
import { createHash, randomUUID } from "node:crypto";
|
|
12
11
|
import { statSync } from "node:fs";
|
|
12
|
+
import { array, boolean, check, custom, finite, getDotPath, literal, number, object, optional, parse, pipe, record, safeParse, string, union, unknown } from "valibot";
|
|
13
13
|
import { errorMessageFrom } from "@moeru/std/error";
|
|
14
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
14
15
|
//#region src/config/config-array.ts
|
|
15
16
|
const effectiveBasePathSymbol = Symbol("effectiveBasePath");
|
|
16
17
|
const inheritedMatcherScopesSymbol = Symbol("inheritedMatcherScopes");
|
|
@@ -253,7 +254,7 @@ function withLanguage$1(file, language) {
|
|
|
253
254
|
}
|
|
254
255
|
//#endregion
|
|
255
256
|
//#region src/core/languages/registry.ts
|
|
256
|
-
function createLanguageRegistry() {
|
|
257
|
+
function createLanguageRegistry$1() {
|
|
257
258
|
return {
|
|
258
259
|
byExtension: /* @__PURE__ */ new Map(),
|
|
259
260
|
languages: /* @__PURE__ */ new Map()
|
|
@@ -305,7 +306,10 @@ function withLanguage(file, language) {
|
|
|
305
306
|
//#endregion
|
|
306
307
|
//#region src/core/languages/resolve.ts
|
|
307
308
|
function resolveLanguage(file, registry, options) {
|
|
308
|
-
|
|
309
|
+
return resolveLanguageForPath(file.path, registry, options);
|
|
310
|
+
}
|
|
311
|
+
function resolveLanguageForPath(filePath, registry, options) {
|
|
312
|
+
const languageName = options.language ?? options.processedLanguage ?? registry.byExtension.get(extname(filePath)) ?? "text/plain";
|
|
309
313
|
const language = registry.languages.get(languageName);
|
|
310
314
|
if (!language) throw new Error(`Unknown language "${languageName}".`);
|
|
311
315
|
return language;
|
|
@@ -313,257 +317,172 @@ function resolveLanguage(file, registry, options) {
|
|
|
313
317
|
//#endregion
|
|
314
318
|
//#region src/core/languages/index.ts
|
|
315
319
|
function createBuiltInLanguageRegistry() {
|
|
316
|
-
const registry = createLanguageRegistry();
|
|
320
|
+
const registry = createLanguageRegistry$1();
|
|
317
321
|
registerLanguage(registry, textLanguage);
|
|
318
322
|
registerLanguage(registry, javascriptLanguage);
|
|
319
323
|
registerLanguage(registry, typescriptLanguage);
|
|
320
324
|
return registry;
|
|
321
325
|
}
|
|
322
326
|
//#endregion
|
|
323
|
-
//#region
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const normalizedEntry = normalizeRuleConfigEntry(entry);
|
|
338
|
-
const severity = normalizedEntry.severity;
|
|
339
|
-
if (severity === "off") continue;
|
|
340
|
-
enabledRules.push({
|
|
341
|
-
id,
|
|
342
|
-
localId: localIds.get(id) ?? id,
|
|
343
|
-
options: parseRuleOptions(id, rule.options, normalizedEntry.options),
|
|
344
|
-
rule,
|
|
345
|
-
severity
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
return {
|
|
349
|
-
enabledRules,
|
|
350
|
-
rules
|
|
327
|
+
//#region package.json
|
|
328
|
+
var version = "0.1.2";
|
|
329
|
+
//#endregion
|
|
330
|
+
//#region src/core/hash.ts
|
|
331
|
+
function createStableHasher() {
|
|
332
|
+
const hash = createHash("sha256");
|
|
333
|
+
const api = {
|
|
334
|
+
digest: () => hash.digest("hex"),
|
|
335
|
+
update: (value) => {
|
|
336
|
+
const serialized = stableStringify(value);
|
|
337
|
+
hash.update(`${serialized.length}:`);
|
|
338
|
+
hash.update(serialized);
|
|
339
|
+
return api;
|
|
340
|
+
}
|
|
351
341
|
};
|
|
342
|
+
return api;
|
|
352
343
|
}
|
|
353
|
-
function
|
|
354
|
-
return
|
|
355
|
-
const path = getDotPath(issue);
|
|
356
|
-
return path === null ? `"${index}": ${issue.message}` : `"${index}.${path}": ${issue.message}`;
|
|
357
|
-
}).join("; ");
|
|
358
|
-
}
|
|
359
|
-
function isRuleConfigTuple(entry) {
|
|
360
|
-
return Array.isArray(entry);
|
|
361
|
-
}
|
|
362
|
-
function normalizeRuleConfigEntry(entry) {
|
|
363
|
-
if (isRuleConfigTuple(entry)) {
|
|
364
|
-
const [severity = "warn", ...options] = entry;
|
|
365
|
-
return {
|
|
366
|
-
options,
|
|
367
|
-
severity
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
return {
|
|
371
|
-
options: [],
|
|
372
|
-
severity: entry ?? "warn"
|
|
373
|
-
};
|
|
344
|
+
function hashText(text) {
|
|
345
|
+
return createHash("sha256").update(text).digest("hex");
|
|
374
346
|
}
|
|
375
|
-
function
|
|
376
|
-
|
|
377
|
-
if (result.success) return result.output;
|
|
378
|
-
if (value === void 0) {
|
|
379
|
-
const emptyObjectResult = safeParse(schema, {});
|
|
380
|
-
if (emptyObjectResult.success) return emptyObjectResult.output;
|
|
381
|
-
}
|
|
382
|
-
throw new TypeError(`Invalid options for rule "${id}": ${formatRuleOptionIssues(index, result.issues)}`);
|
|
347
|
+
function stableHash(value) {
|
|
348
|
+
return hashText(stableStringify(value));
|
|
383
349
|
}
|
|
384
|
-
function
|
|
385
|
-
if (
|
|
386
|
-
|
|
387
|
-
|
|
350
|
+
function stableStringify(value) {
|
|
351
|
+
if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
352
|
+
if (typeof value === "object" && value !== null) {
|
|
353
|
+
const record = value;
|
|
354
|
+
return `{${Object.keys(record).sort().flatMap((key) => {
|
|
355
|
+
const property = record[key];
|
|
356
|
+
return property === void 0 ? [] : [`${JSON.stringify(key)}:${stableStringify(property)}`];
|
|
357
|
+
}).join(",")}}`;
|
|
388
358
|
}
|
|
389
|
-
|
|
390
|
-
return schema.map((optionSchema, index) => parseRuleOption(id, index, optionSchema, options[index]));
|
|
359
|
+
return JSON.stringify(value);
|
|
391
360
|
}
|
|
392
361
|
//#endregion
|
|
393
|
-
//#region src/
|
|
394
|
-
|
|
395
|
-
const candidates = flattenModels(registry);
|
|
396
|
-
const ruleId = options.ruleId ?? "<unknown>";
|
|
397
|
-
if (options.request !== void 0) {
|
|
398
|
-
const request = options.request;
|
|
399
|
-
const canonicalCandidates = candidates.filter((candidate) => canonicalIdentity(candidate) === request);
|
|
400
|
-
if (canonicalCandidates.length > 1) throw duplicateDefinitionError(request);
|
|
401
|
-
let candidate = canonicalCandidates[0];
|
|
402
|
-
if (candidate === void 0) {
|
|
403
|
-
const matchingCandidates = candidates.filter((candidate) => matchesRequest(candidate, request));
|
|
404
|
-
const matchesByCanonical = /* @__PURE__ */ new Map();
|
|
405
|
-
for (const matchingCandidate of matchingCandidates) {
|
|
406
|
-
const identity = canonicalIdentity(matchingCandidate);
|
|
407
|
-
const matches = matchesByCanonical.get(identity) ?? [];
|
|
408
|
-
matches.push(matchingCandidate);
|
|
409
|
-
matchesByCanonical.set(identity, matches);
|
|
410
|
-
}
|
|
411
|
-
const duplicateIdentity = [...matchesByCanonical].find(([, matches]) => matches.length > 1)?.[0];
|
|
412
|
-
if (duplicateIdentity !== void 0) throw duplicateDefinitionError(duplicateIdentity);
|
|
413
|
-
if (matchingCandidates.length === 0) throw new Error(`Unknown model "${request}".`);
|
|
414
|
-
if (matchingCandidates.length > 1) {
|
|
415
|
-
const choices = [...matchesByCanonical.keys()];
|
|
416
|
-
throw new Error(`Ambiguous model "${request}".\nSpecify a provider-qualified model:\n${choices.map((choice) => ` ${choice}`).join("\n")}`);
|
|
417
|
-
}
|
|
418
|
-
candidate = matchingCandidates[0];
|
|
419
|
-
}
|
|
420
|
-
if (!satisfiesHardRequirements(candidate.model, options.requirement)) throw new Error(`Model "${request}" does not satisfy requirement for rule "${ruleId}".`);
|
|
421
|
-
return toResolvedModel(candidate, options.requirement);
|
|
422
|
-
}
|
|
423
|
-
const candidate = preferSize(candidates.filter(({ model }) => satisfiesHardRequirements(model, options.requirement)), options.requirement);
|
|
424
|
-
if (candidate === void 0) throw new Error(`No model satisfies requirement for rule "${ruleId}".`);
|
|
425
|
-
return toResolvedModel(candidate, options.requirement);
|
|
426
|
-
}
|
|
427
|
-
function canonicalIdentity({ model, provider }) {
|
|
428
|
-
return `${provider.id}/${model.id}`;
|
|
429
|
-
}
|
|
430
|
-
function duplicateDefinitionError(identity) {
|
|
431
|
-
return /* @__PURE__ */ new Error(`Model "${identity}" is configured more than once.\nRemove duplicate provider/model definitions from the setup configuration.`);
|
|
432
|
-
}
|
|
433
|
-
function flattenModels(registry) {
|
|
434
|
-
return registry.providers.flatMap((provider) => provider.models.map((model) => ({
|
|
435
|
-
model,
|
|
436
|
-
provider
|
|
437
|
-
})));
|
|
438
|
-
}
|
|
439
|
-
function matchesRequest(candidate, request) {
|
|
440
|
-
return [
|
|
441
|
-
candidate.model.id,
|
|
442
|
-
candidate.model.name,
|
|
443
|
-
...candidate.model.aliases ?? []
|
|
444
|
-
].filter((name) => name !== void 0).some((name) => name === request || `${candidate.provider.id}/${name}` === request);
|
|
445
|
-
}
|
|
446
|
-
function preferSize(candidates, requirement) {
|
|
447
|
-
if (requirement?.size === void 0) return candidates[0];
|
|
448
|
-
return candidates.find(({ model }) => model.size === requirement.size) ?? candidates[0];
|
|
449
|
-
}
|
|
450
|
-
function satisfiesHardRequirements(model, requirement) {
|
|
451
|
-
if (requirement === void 0) return true;
|
|
452
|
-
if (requirement.capabilities !== void 0 && !requirement.capabilities.every((capability) => (model.capabilities ?? []).includes(capability))) return false;
|
|
453
|
-
if (requirement.minContextWindow !== void 0 && (model.contextWindow === void 0 || model.contextWindow < requirement.minContextWindow)) return false;
|
|
454
|
-
return true;
|
|
455
|
-
}
|
|
456
|
-
function toResolvedModel(candidate, requirement) {
|
|
457
|
-
const { model, provider } = candidate;
|
|
458
|
-
return {
|
|
459
|
-
aliases: [...model.aliases ?? []],
|
|
460
|
-
capabilities: [...model.capabilities ?? []],
|
|
461
|
-
contextWindow: model.contextWindow,
|
|
462
|
-
id: model.id,
|
|
463
|
-
name: model.name ?? model.id,
|
|
464
|
-
params: {
|
|
465
|
-
...model.defaultParams ?? {},
|
|
466
|
-
...requirement?.params ?? {}
|
|
467
|
-
},
|
|
468
|
-
provider: {
|
|
469
|
-
endpoint: provider.endpoint,
|
|
470
|
-
headers: { ...provider.headers ?? {} },
|
|
471
|
-
id: provider.id,
|
|
472
|
-
type: provider.type
|
|
473
|
-
},
|
|
474
|
-
size: model.size
|
|
475
|
-
};
|
|
476
|
-
}
|
|
362
|
+
//#region src/core/cache/types.ts
|
|
363
|
+
const CACHE_MAGIC = "ALINT_CACHE";
|
|
477
364
|
//#endregion
|
|
478
|
-
//#region src/core/cache.ts
|
|
479
|
-
const CACHE_SCHEMA_VERSION = 1;
|
|
365
|
+
//#region src/core/cache/store.ts
|
|
480
366
|
const DEFAULT_CACHE_FILE_NAME = ".alintcache";
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
}
|
|
367
|
+
const FiniteNumberSchema = pipe(number(), finite());
|
|
368
|
+
const PositionSchema = object({
|
|
369
|
+
column: FiniteNumberSchema,
|
|
370
|
+
line: FiniteNumberSchema
|
|
371
|
+
});
|
|
372
|
+
const SourceLocationSchema = object({
|
|
373
|
+
end: PositionSchema,
|
|
374
|
+
start: PositionSchema
|
|
375
|
+
});
|
|
376
|
+
const DiagnosticLocationSchema = object({
|
|
377
|
+
end: optional(PositionSchema),
|
|
378
|
+
start: PositionSchema
|
|
379
|
+
});
|
|
380
|
+
const SourceRangeSchema = object({
|
|
381
|
+
end: FiniteNumberSchema,
|
|
382
|
+
start: FiniteNumberSchema
|
|
383
|
+
});
|
|
384
|
+
const DiagnosticSchema = object({
|
|
385
|
+
cached: optional(boolean()),
|
|
386
|
+
evidence: optional(custom(isJsonValue)),
|
|
387
|
+
filePath: string(),
|
|
388
|
+
loc: optional(DiagnosticLocationSchema),
|
|
389
|
+
message: string(),
|
|
390
|
+
model: optional(object({
|
|
391
|
+
providerId: string(),
|
|
392
|
+
requested: optional(string()),
|
|
393
|
+
resolvedId: string()
|
|
394
|
+
})),
|
|
395
|
+
ruleId: string(),
|
|
396
|
+
severity: union([literal("error"), literal("warn")])
|
|
397
|
+
});
|
|
398
|
+
const UsageSchema = object({
|
|
399
|
+
filePath: optional(string()),
|
|
400
|
+
inputTokens: optional(FiniteNumberSchema),
|
|
401
|
+
metadata: optional(custom(isJsonValue)),
|
|
402
|
+
modelId: string(),
|
|
403
|
+
outputTokens: optional(FiniteNumberSchema),
|
|
404
|
+
providerId: string(),
|
|
405
|
+
ruleId: string(),
|
|
406
|
+
totalTokens: optional(FiniteNumberSchema)
|
|
407
|
+
});
|
|
408
|
+
const CacheEntrySchema = object({
|
|
409
|
+
diagnostics: array(DiagnosticSchema),
|
|
410
|
+
fingerprint: object({
|
|
411
|
+
configHash: string(),
|
|
412
|
+
modelHash: string(),
|
|
413
|
+
ruleHash: string(),
|
|
414
|
+
targetHash: string()
|
|
415
|
+
}),
|
|
416
|
+
target: object({
|
|
417
|
+
hash: string(),
|
|
418
|
+
identity: string(),
|
|
419
|
+
kind: string(),
|
|
420
|
+
loc: optional(SourceLocationSchema),
|
|
421
|
+
name: optional(string()),
|
|
422
|
+
range: optional(SourceRangeSchema)
|
|
423
|
+
}),
|
|
424
|
+
usage: array(UsageSchema)
|
|
425
|
+
});
|
|
426
|
+
function objectRecord(value) {
|
|
427
|
+
return pipe(unknown(), check((input) => typeof input === "object" && input !== null && !Array.isArray(input)), record(string(), value));
|
|
428
|
+
}
|
|
429
|
+
const CacheFileBodySchema = pipe(object({
|
|
430
|
+
createdAt: string(),
|
|
431
|
+
entries: objectRecord(CacheEntrySchema),
|
|
432
|
+
owners: objectRecord(object({
|
|
433
|
+
contentHash: optional(string()),
|
|
434
|
+
kind: union([literal("file"), literal("project")]),
|
|
435
|
+
path: string(),
|
|
436
|
+
slots: array(string())
|
|
437
|
+
})),
|
|
438
|
+
updatedAt: string()
|
|
439
|
+
}), check((body) => {
|
|
440
|
+
const entryKeys = Object.keys(body.entries);
|
|
441
|
+
const referencedSlots = Object.values(body.owners).flatMap((owner) => owner.slots);
|
|
442
|
+
const uniqueSlots = new Set(referencedSlots);
|
|
443
|
+
return uniqueSlots.size === referencedSlots.length && uniqueSlots.size === entryKeys.length && entryKeys.every((key) => uniqueSlots.has(key));
|
|
444
|
+
}));
|
|
522
445
|
async function createCacheStore(options) {
|
|
523
446
|
const location = resolveCacheLocation(options.cwd, options.location);
|
|
524
447
|
if (!options.enabled) return createNoopCacheStore(location);
|
|
525
|
-
const
|
|
448
|
+
const header = `${CACHE_MAGIC} 2 ${options.alintVersion ?? version}`;
|
|
449
|
+
const body = await loadCacheBody(location, header, options.readOnly === true);
|
|
450
|
+
if (options.readOnly) return createReadOnlyCacheStore(location, body, options.cwd);
|
|
451
|
+
const fileExists = options.fileExists ?? defaultFileExists;
|
|
452
|
+
const writeFile$1 = options.writeFile ?? writeFile;
|
|
526
453
|
return {
|
|
527
|
-
|
|
454
|
+
beginOwner: (owner) => beginOwner(body, owner, options.cwd),
|
|
528
455
|
location,
|
|
529
|
-
markFile: (filePath, contentHash, entries) => {
|
|
530
|
-
const path = normalizeCachePath(options.cwd, filePath);
|
|
531
|
-
cacheFile.files[path] = {
|
|
532
|
-
contentHash,
|
|
533
|
-
entries,
|
|
534
|
-
path
|
|
535
|
-
};
|
|
536
|
-
},
|
|
537
456
|
reconcile: async () => {
|
|
538
|
-
await
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
await writeFile(tempPath, `${JSON.stringify(cacheFile, null, 2)}\n`);
|
|
542
|
-
await rename(tempPath, location);
|
|
543
|
-
},
|
|
544
|
-
set: (key, entry) => {
|
|
545
|
-
cacheFile.entries[key] = entry;
|
|
457
|
+
await collectMissingFileOwners(body, options.cwd, fileExists);
|
|
458
|
+
body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
459
|
+
await persistCacheBody(location, header, body, writeFile$1);
|
|
546
460
|
}
|
|
547
461
|
};
|
|
548
462
|
}
|
|
549
463
|
function createTargetIdentityResolver(targets) {
|
|
550
464
|
const baseCounts = /* @__PURE__ */ new Map();
|
|
465
|
+
const duplicateCandidateCounts = /* @__PURE__ */ new Map();
|
|
551
466
|
for (const target of targets) {
|
|
552
467
|
const base = createBaseTargetIdentity(target);
|
|
553
468
|
baseCounts.set(base, (baseCounts.get(base) ?? 0) + 1);
|
|
554
469
|
}
|
|
555
|
-
|
|
470
|
+
for (const target of targets) {
|
|
471
|
+
const base = createBaseTargetIdentity(target);
|
|
472
|
+
if ((baseCounts.get(base) ?? 0) <= 1) continue;
|
|
473
|
+
const candidate = target.range ? `${base}:${target.range.start}:${target.range.end}` : base;
|
|
474
|
+
duplicateCandidateCounts.set(candidate, (duplicateCandidateCounts.get(candidate) ?? 0) + 1);
|
|
475
|
+
}
|
|
476
|
+
return (target, targetIndex) => {
|
|
556
477
|
const base = createBaseTargetIdentity(target);
|
|
557
478
|
if ((baseCounts.get(base) ?? 0) <= 1) return base;
|
|
558
|
-
|
|
559
|
-
return
|
|
479
|
+
const candidate = target.range ? `${base}:${target.range.start}:${target.range.end}` : base;
|
|
480
|
+
if ((duplicateCandidateCounts.get(candidate) ?? 0) <= 1) return candidate;
|
|
481
|
+
return `${candidate}:${targetIndex}`;
|
|
560
482
|
};
|
|
561
483
|
}
|
|
562
|
-
function hashText(text) {
|
|
563
|
-
return createHash("sha256").update(text).digest("hex");
|
|
564
|
-
}
|
|
565
484
|
function normalizeCachePath(cwd, filePath) {
|
|
566
|
-
return relative$1(cwd, isAbsolute$1(filePath) ? resolve$1(filePath) : resolve$1(cwd, filePath)).split(sep).join("/");
|
|
485
|
+
return relative$1(cwd, isAbsolute$1(filePath) ? resolve$1(filePath) : resolve$1(cwd, filePath)).split(sep).join("/") || ".";
|
|
567
486
|
}
|
|
568
487
|
function normalizeRunnerCacheConfig(cache, cwd) {
|
|
569
488
|
if (cache === false) return {
|
|
@@ -584,12 +503,50 @@ function resolveCacheLocation(cwd, location) {
|
|
|
584
503
|
const resolved = isAbsolute$1(location) ? resolve$1(location) : resolve$1(cwd, location);
|
|
585
504
|
if (location.endsWith("/") || location.endsWith("\\")) return join(resolved, DEFAULT_CACHE_FILE_NAME);
|
|
586
505
|
try {
|
|
587
|
-
if (
|
|
506
|
+
if (statSync(resolved).isDirectory()) return join(resolved, DEFAULT_CACHE_FILE_NAME);
|
|
588
507
|
} catch {}
|
|
589
508
|
return resolved;
|
|
590
509
|
}
|
|
591
|
-
function
|
|
592
|
-
|
|
510
|
+
function beginOwner(body, owner, cwd) {
|
|
511
|
+
const normalizedOwner = {
|
|
512
|
+
kind: owner.kind,
|
|
513
|
+
path: normalizeCachePath(cwd, owner.path)
|
|
514
|
+
};
|
|
515
|
+
const key = ownerKey(normalizedOwner, cwd);
|
|
516
|
+
const previousOwner = body.owners[key];
|
|
517
|
+
const nextEntries = /* @__PURE__ */ new Map();
|
|
518
|
+
return {
|
|
519
|
+
commit: (metadata = {}) => {
|
|
520
|
+
const committedEntries = metadata.mode === "merge" ? ownerEntries(body, body.owners[key]) : /* @__PURE__ */ new Map();
|
|
521
|
+
for (const [entryKey, cacheEntry] of nextEntries) committedEntries.set(entryKey, cacheEntry);
|
|
522
|
+
const replacedSlots = /* @__PURE__ */ new Set([...previousOwner?.slots ?? [], ...body.owners[key]?.slots ?? []]);
|
|
523
|
+
for (const replacedSlot of replacedSlots) delete body.entries[replacedSlot];
|
|
524
|
+
for (const [entryKey, cacheEntry] of committedEntries) body.entries[entryKey] = cacheEntry;
|
|
525
|
+
body.owners[key] = {
|
|
526
|
+
contentHash: metadata.contentHash,
|
|
527
|
+
kind: normalizedOwner.kind,
|
|
528
|
+
path: normalizedOwner.path,
|
|
529
|
+
slots: [...committedEntries.keys()].sort()
|
|
530
|
+
};
|
|
531
|
+
body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
532
|
+
},
|
|
533
|
+
discard: (cacheSlot) => nextEntries.delete(slotKey(normalizedOwner, cacheSlot, cwd)),
|
|
534
|
+
lookup: (cacheSlot, fingerprint) => {
|
|
535
|
+
const entryKey = slotKey(normalizedOwner, cacheSlot, cwd);
|
|
536
|
+
const cached = body.entries[entryKey];
|
|
537
|
+
if (!cached || !fingerprintsEqual(cached.fingerprint, fingerprint)) return void 0;
|
|
538
|
+
nextEntries.set(entryKey, cached);
|
|
539
|
+
return cached;
|
|
540
|
+
},
|
|
541
|
+
put: (cacheSlot, cacheEntry) => nextEntries.set(slotKey(normalizedOwner, cacheSlot, cwd), cacheEntry)
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
async function collectMissingFileOwners(body, cwd, fileExists) {
|
|
545
|
+
for (const [key, owner] of Object.entries(body.owners)) {
|
|
546
|
+
if (owner.kind !== "file" || await fileExists(resolve$1(cwd, owner.path))) continue;
|
|
547
|
+
for (const entryKey of owner.slots) delete body.entries[entryKey];
|
|
548
|
+
delete body.owners[key];
|
|
549
|
+
}
|
|
593
550
|
}
|
|
594
551
|
function createBaseTargetIdentity(target) {
|
|
595
552
|
if (target.identity && (target.kind !== "file" || target.identity !== "file")) return target.filePath ? `${target.kind}:${target.filePath}:${target.identity}` : `${target.kind}:${target.identity}`;
|
|
@@ -598,53 +555,216 @@ function createBaseTargetIdentity(target) {
|
|
|
598
555
|
if (target.range) return `${target.kind}:${target.range.start}:${target.range.end}`;
|
|
599
556
|
return target.kind;
|
|
600
557
|
}
|
|
601
|
-
function
|
|
558
|
+
function createEmptyCacheBody() {
|
|
602
559
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
603
560
|
return {
|
|
604
561
|
createdAt: now,
|
|
605
562
|
entries: {},
|
|
606
|
-
|
|
607
|
-
schemaVersion: CACHE_SCHEMA_VERSION,
|
|
563
|
+
owners: {},
|
|
608
564
|
updatedAt: now
|
|
609
565
|
};
|
|
610
566
|
}
|
|
611
567
|
function createNoopCacheStore(location) {
|
|
568
|
+
const transaction = {
|
|
569
|
+
commit: () => {},
|
|
570
|
+
discard: () => {},
|
|
571
|
+
lookup: () => void 0,
|
|
572
|
+
put: () => {}
|
|
573
|
+
};
|
|
574
|
+
return {
|
|
575
|
+
beginOwner: () => transaction,
|
|
576
|
+
location,
|
|
577
|
+
reconcile: async () => {}
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
function createReadOnlyCacheStore(location, body, cwd) {
|
|
581
|
+
const transaction = (owner) => ({
|
|
582
|
+
commit: () => {},
|
|
583
|
+
discard: () => {},
|
|
584
|
+
lookup: (slot, fingerprint) => {
|
|
585
|
+
const cached = body.entries[slotKey(owner, slot, cwd)];
|
|
586
|
+
return cached && fingerprintsEqual(cached.fingerprint, fingerprint) ? cached : void 0;
|
|
587
|
+
},
|
|
588
|
+
put: () => {}
|
|
589
|
+
});
|
|
612
590
|
return {
|
|
613
|
-
|
|
591
|
+
beginOwner: transaction,
|
|
614
592
|
location,
|
|
615
|
-
|
|
616
|
-
reconcile: async () => {},
|
|
617
|
-
set: () => {}
|
|
593
|
+
reconcile: async () => {}
|
|
618
594
|
};
|
|
619
595
|
}
|
|
620
|
-
function
|
|
621
|
-
|
|
596
|
+
async function defaultFileExists(path) {
|
|
597
|
+
try {
|
|
598
|
+
await access(path);
|
|
599
|
+
return true;
|
|
600
|
+
} catch (error) {
|
|
601
|
+
if (isMissingFileError(error)) return false;
|
|
602
|
+
throw error;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
function fingerprintsEqual(left, right) {
|
|
606
|
+
return left.configHash === right.configHash && left.modelHash === right.modelHash && left.ruleHash === right.ruleHash && left.targetHash === right.targetHash;
|
|
607
|
+
}
|
|
608
|
+
function hasJsonArrayProperties(input, ancestors) {
|
|
609
|
+
let arrayLength;
|
|
610
|
+
let indexCount = 0;
|
|
611
|
+
for (const key of Reflect.ownKeys(input)) {
|
|
612
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
613
|
+
if (!descriptor || !("value" in descriptor)) return false;
|
|
614
|
+
if (key === "length") {
|
|
615
|
+
if (descriptor.enumerable || typeof descriptor.value !== "number") return false;
|
|
616
|
+
arrayLength = descriptor.value;
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
if (typeof key !== "string" || !isCanonicalArrayIndex(key) || !descriptor.enumerable) return false;
|
|
620
|
+
if (!isJsonValueAt(descriptor.value, ancestors)) return false;
|
|
621
|
+
indexCount += 1;
|
|
622
|
+
}
|
|
623
|
+
return arrayLength !== void 0 && indexCount === arrayLength;
|
|
624
|
+
}
|
|
625
|
+
function hasJsonObjectProperties(input, ancestors) {
|
|
626
|
+
for (const key of Reflect.ownKeys(input)) {
|
|
627
|
+
if (typeof key !== "string") return false;
|
|
628
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
629
|
+
if (!descriptor || !descriptor.enumerable || !("value" in descriptor)) return false;
|
|
630
|
+
if (!isJsonValueAt(descriptor.value, ancestors)) return false;
|
|
631
|
+
}
|
|
632
|
+
return true;
|
|
633
|
+
}
|
|
634
|
+
function isCanonicalArrayIndex(key) {
|
|
635
|
+
const index = Number(key);
|
|
636
|
+
return Number.isInteger(index) && index >= 0 && index < 2 ** 32 - 1 && String(index) === key;
|
|
622
637
|
}
|
|
623
|
-
function
|
|
624
|
-
return
|
|
638
|
+
function isJsonValue(input) {
|
|
639
|
+
return isJsonValueAt(input, /* @__PURE__ */ new WeakSet());
|
|
625
640
|
}
|
|
626
|
-
|
|
641
|
+
function isJsonValueAt(input, ancestors) {
|
|
642
|
+
if (input === null || typeof input === "string" || typeof input === "boolean") return true;
|
|
643
|
+
if (typeof input === "number") return Number.isFinite(input);
|
|
644
|
+
if (typeof input !== "object") return false;
|
|
645
|
+
if (ancestors.has(input)) return false;
|
|
646
|
+
ancestors.add(input);
|
|
627
647
|
try {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
648
|
+
if (Array.isArray(input)) {
|
|
649
|
+
if (Object.getPrototypeOf(input) !== Array.prototype) return false;
|
|
650
|
+
return hasJsonArrayProperties(input, ancestors);
|
|
651
|
+
}
|
|
652
|
+
if (Object.getPrototypeOf(input) !== Object.prototype && Object.getPrototypeOf(input) !== null) return false;
|
|
653
|
+
return hasJsonObjectProperties(input, ancestors);
|
|
654
|
+
} catch {
|
|
655
|
+
return false;
|
|
656
|
+
} finally {
|
|
657
|
+
ancestors.delete(input);
|
|
658
|
+
}
|
|
632
659
|
}
|
|
633
|
-
function
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
660
|
+
function isMissingFileError(error) {
|
|
661
|
+
return isNodeErrorCode(error, "ENOENT") || isNodeErrorCode(error, "ENOTDIR");
|
|
662
|
+
}
|
|
663
|
+
function isNodeErrorCode(error, code) {
|
|
664
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
665
|
+
}
|
|
666
|
+
async function loadCacheBody(location, expectedHeader, readOnly) {
|
|
667
|
+
let handle;
|
|
668
|
+
try {
|
|
669
|
+
handle = await open(location, "r");
|
|
670
|
+
const header = await readHeader(handle);
|
|
671
|
+
await handle.close();
|
|
672
|
+
handle = void 0;
|
|
673
|
+
if (header !== expectedHeader) {
|
|
674
|
+
if (!readOnly) await rm(location, { force: true });
|
|
675
|
+
return createEmptyCacheBody();
|
|
676
|
+
}
|
|
677
|
+
const text = await readFile(location, "utf8");
|
|
678
|
+
return parseCacheBody(text.slice(text.indexOf("\n") + 1));
|
|
679
|
+
} catch (error) {
|
|
680
|
+
await handle?.close().catch(() => {});
|
|
681
|
+
if (isMissingFileError(error)) return createEmptyCacheBody();
|
|
682
|
+
if (!readOnly) await rm(location, { force: true }).catch(() => {});
|
|
683
|
+
return createEmptyCacheBody();
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
function ownerEntries(body, owner) {
|
|
687
|
+
const entries = /* @__PURE__ */ new Map();
|
|
688
|
+
for (const slot of owner?.slots ?? []) {
|
|
689
|
+
const cacheEntry = body.entries[slot];
|
|
690
|
+
if (cacheEntry) entries.set(slot, cacheEntry);
|
|
691
|
+
}
|
|
692
|
+
return entries;
|
|
693
|
+
}
|
|
694
|
+
function ownerKey(owner, cwd) {
|
|
695
|
+
return stableHash({
|
|
696
|
+
kind: owner.kind,
|
|
697
|
+
path: normalizeCachePath(cwd, owner.path)
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
function parseCacheBody(text) {
|
|
701
|
+
const value = JSON.parse(text);
|
|
702
|
+
return parse(CacheFileBodySchema, value);
|
|
641
703
|
}
|
|
642
|
-
function
|
|
643
|
-
|
|
704
|
+
async function persistCacheBody(location, header, body, writeFile) {
|
|
705
|
+
const validatedBody = parse(CacheFileBodySchema, body);
|
|
706
|
+
await mkdir(dirname(location), { recursive: true });
|
|
707
|
+
const tempPath = join(dirname(location), `.${basename(location)}.${process.pid}.${randomUUID()}.tmp`);
|
|
708
|
+
try {
|
|
709
|
+
await writeFile(tempPath, `${header}\n${JSON.stringify(validatedBody)}\n`);
|
|
710
|
+
await rename(tempPath, location);
|
|
711
|
+
} catch (error) {
|
|
712
|
+
await rm(tempPath, { force: true }).catch(() => {});
|
|
713
|
+
throw error;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
async function readHeader(handle) {
|
|
717
|
+
const buffer = Buffer.alloc(256);
|
|
718
|
+
const { bytesRead } = await handle.read(buffer, 0, 256, 0);
|
|
719
|
+
const newline = buffer.subarray(0, bytesRead).indexOf(10);
|
|
720
|
+
return newline < 0 ? void 0 : buffer.subarray(0, newline).toString("utf8");
|
|
721
|
+
}
|
|
722
|
+
function slotKey(owner, slot, cwd) {
|
|
723
|
+
return stableHash({
|
|
724
|
+
owner: ownerKey(owner, cwd),
|
|
725
|
+
...slot
|
|
726
|
+
});
|
|
644
727
|
}
|
|
645
728
|
//#endregion
|
|
646
|
-
//#region
|
|
647
|
-
|
|
729
|
+
//#region src/core/execution/records.ts
|
|
730
|
+
function snapshotDiagnostic(diagnostic) {
|
|
731
|
+
const snapshot = { ...diagnostic };
|
|
732
|
+
if (diagnostic.loc) snapshot.loc = {
|
|
733
|
+
...diagnostic.loc.end ? { end: { ...diagnostic.loc.end } } : {},
|
|
734
|
+
start: { ...diagnostic.loc.start }
|
|
735
|
+
};
|
|
736
|
+
if (diagnostic.model) snapshot.model = { ...diagnostic.model };
|
|
737
|
+
return snapshot;
|
|
738
|
+
}
|
|
739
|
+
function snapshotDiagnostics(diagnostics) {
|
|
740
|
+
return diagnostics.map(snapshotDiagnostic);
|
|
741
|
+
}
|
|
742
|
+
function snapshotFailure(failure) {
|
|
743
|
+
return {
|
|
744
|
+
job: snapshotProgressJobRef(failure.job),
|
|
745
|
+
kind: failure.kind,
|
|
746
|
+
message: failure.message
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
function snapshotProgressJobRef(job) {
|
|
750
|
+
return {
|
|
751
|
+
id: job.id,
|
|
752
|
+
index: job.index,
|
|
753
|
+
inputPath: job.inputPath,
|
|
754
|
+
ruleId: job.ruleId,
|
|
755
|
+
target: {
|
|
756
|
+
identity: job.target.identity,
|
|
757
|
+
kind: job.target.kind,
|
|
758
|
+
name: job.target.name
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
function snapshotUsage(record) {
|
|
763
|
+
return { ...record };
|
|
764
|
+
}
|
|
765
|
+
function snapshotUsageRecords(records) {
|
|
766
|
+
return records.map(snapshotUsage);
|
|
767
|
+
}
|
|
648
768
|
//#endregion
|
|
649
769
|
//#region src/core/execution/job.ts
|
|
650
770
|
var CacheReplayProcessingError = class {
|
|
@@ -653,76 +773,43 @@ var CacheReplayProcessingError = class {
|
|
|
653
773
|
this.cause = cause;
|
|
654
774
|
}
|
|
655
775
|
};
|
|
656
|
-
function
|
|
657
|
-
|
|
658
|
-
const total = plans.reduce((sum, plan) => sum + plan.planned, 0);
|
|
659
|
-
const ruleTotals = /* @__PURE__ */ new Map();
|
|
660
|
-
const ruleIndexes = /* @__PURE__ */ new Map();
|
|
661
|
-
for (const plan of plans) for (const target of plan.targets) for (const execution of target.executions) {
|
|
662
|
-
const ruleId = execution.runtime.enabledRule.id;
|
|
663
|
-
ruleTotals.set(ruleId, (ruleTotals.get(ruleId) ?? 0) + 1);
|
|
664
|
-
}
|
|
665
|
-
for (const plan of plans) for (const target of plan.targets) for (const execution of target.executions) {
|
|
666
|
-
const ruleId = execution.runtime.enabledRule.id;
|
|
667
|
-
const index = jobs.length + 1;
|
|
668
|
-
const ruleIndex = (ruleIndexes.get(ruleId) ?? 0) + 1;
|
|
669
|
-
ruleIndexes.set(ruleId, ruleIndex);
|
|
670
|
-
jobs.push({
|
|
671
|
-
execution,
|
|
672
|
-
job: {
|
|
673
|
-
id: stableHash({
|
|
674
|
-
index,
|
|
675
|
-
planId: plan.id,
|
|
676
|
-
ruleId,
|
|
677
|
-
targetIdentity: target.identity
|
|
678
|
-
}),
|
|
679
|
-
index,
|
|
680
|
-
inputPath: plan.path,
|
|
681
|
-
ruleId,
|
|
682
|
-
ruleIndex,
|
|
683
|
-
ruleTotal: ruleTotals.get(ruleId) ?? 0,
|
|
684
|
-
target: {
|
|
685
|
-
identity: target.identity,
|
|
686
|
-
kind: target.kind,
|
|
687
|
-
name: target.name
|
|
688
|
-
},
|
|
689
|
-
total
|
|
690
|
-
},
|
|
691
|
-
target
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
|
-
return jobs;
|
|
776
|
+
function compareJobOrder(left, right) {
|
|
777
|
+
return scopeRank(left.scope) - scopeRank(right.scope) || left.inputIndex - right.inputIndex || left.targetIndex - right.targetIndex || left.ruleIndex - right.ruleIndex;
|
|
695
778
|
}
|
|
696
779
|
async function executeRuleJob(job, options) {
|
|
697
780
|
const bucket = {
|
|
698
781
|
diagnostics: [],
|
|
699
782
|
usage: []
|
|
700
783
|
};
|
|
701
|
-
if (options.runSignal?.aborted) return finish(job,
|
|
784
|
+
if (options.runSignal?.aborted) return finish(job, bucket, {
|
|
702
785
|
cache: "miss",
|
|
703
786
|
state: "cancelled"
|
|
704
787
|
});
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
788
|
+
const slot = job.target.cacheOwner && job.execution.runtime.cacheable ? {
|
|
789
|
+
ruleId: job.execution.runtime.enabledRule.id,
|
|
790
|
+
scope: job.target.kind,
|
|
791
|
+
targetIdentity: job.target.identity
|
|
792
|
+
} : void 0;
|
|
793
|
+
const fingerprint = slot ? createFingerprint(job, options.cache.modelHash) : void 0;
|
|
794
|
+
const cachedEntry = slot && fingerprint ? job.target.cacheOwner?.lookup(slot, fingerprint) : void 0;
|
|
795
|
+
if (slot && cachedEntry) {
|
|
709
796
|
try {
|
|
710
|
-
replayCachedEntry(cachedEntry, bucket, job.
|
|
797
|
+
replayCachedEntry(cachedEntry, bucket, job.jobRef, options.progress, options.runProgress);
|
|
711
798
|
} catch (error) {
|
|
712
799
|
if (!(error instanceof CacheReplayProcessingError)) throw error;
|
|
713
|
-
|
|
800
|
+
job.target.cacheOwner?.discard(slot);
|
|
801
|
+
return finish(job, bucket, {
|
|
714
802
|
cache: "hit",
|
|
715
|
-
cause: error.cause,
|
|
716
803
|
failure: failure(error.cause, job, "cache-replay"),
|
|
717
804
|
state: "failed"
|
|
718
805
|
});
|
|
719
806
|
}
|
|
720
|
-
return finish(job,
|
|
807
|
+
return finish(job, bucket, {
|
|
721
808
|
cache: "hit",
|
|
722
809
|
state: "cached"
|
|
723
810
|
});
|
|
724
811
|
}
|
|
725
|
-
if (options.cacheOnly) return finish(job,
|
|
812
|
+
if (options.cacheOnly) return finish(job, bucket, {
|
|
726
813
|
cache: "miss",
|
|
727
814
|
state: "skipped"
|
|
728
815
|
});
|
|
@@ -740,8 +827,10 @@ async function executeRuleJob(job, options) {
|
|
|
740
827
|
const state = {
|
|
741
828
|
activeFilePath: job.target.activeFilePath,
|
|
742
829
|
bucket,
|
|
743
|
-
|
|
830
|
+
jobRef: job.jobRef,
|
|
744
831
|
reporterFailed: false,
|
|
832
|
+
runProgress: options.runProgress,
|
|
833
|
+
sealed: false,
|
|
745
834
|
signal: controller.signal
|
|
746
835
|
};
|
|
747
836
|
let handlerCause;
|
|
@@ -753,31 +842,29 @@ async function executeRuleJob(job, options) {
|
|
|
753
842
|
handlerCause = cause;
|
|
754
843
|
handlerFailed = true;
|
|
755
844
|
} finally {
|
|
845
|
+
state.sealed = true;
|
|
756
846
|
if (timer != null) clearTimeout(timer);
|
|
757
847
|
options.runSignal?.removeEventListener("abort", forwardRunAbort);
|
|
758
848
|
}
|
|
759
849
|
if (state.reporterFailed) throw state.reporterCause;
|
|
760
|
-
if (
|
|
761
|
-
options.cache.store.set(cacheKey, createCacheEntry(job, options.cache, bucket));
|
|
762
|
-
rememberCacheEntry(options.cache, job.target.cacheFilePaths, cacheKey);
|
|
763
|
-
} catch {}
|
|
764
|
-
if (options.runSignal?.aborted) return finish(job, options, bucket, {
|
|
850
|
+
if (options.runSignal?.aborted) return finish(job, bucket, {
|
|
765
851
|
cache: "miss",
|
|
766
852
|
state: "cancelled"
|
|
767
853
|
});
|
|
768
|
-
if (timedOut) return finish(job,
|
|
854
|
+
if (timedOut) return finish(job, bucket, {
|
|
769
855
|
cache: "miss",
|
|
770
|
-
cause: deadlineError,
|
|
771
856
|
failure: failure(deadlineError, job, "timeout"),
|
|
772
857
|
state: "failed"
|
|
773
858
|
});
|
|
774
|
-
if (handlerFailed) return finish(job,
|
|
859
|
+
if (handlerFailed) return finish(job, bucket, {
|
|
775
860
|
cache: "miss",
|
|
776
|
-
cause: handlerCause,
|
|
777
861
|
failure: failure(handlerCause, job, "handler"),
|
|
778
862
|
state: "failed"
|
|
779
863
|
});
|
|
780
|
-
|
|
864
|
+
if (slot && fingerprint) try {
|
|
865
|
+
job.target.cacheOwner?.put(slot, createCacheEntry(job, fingerprint, bucket));
|
|
866
|
+
} catch {}
|
|
867
|
+
return finish(job, bucket, {
|
|
781
868
|
cache: "miss",
|
|
782
869
|
state: "completed"
|
|
783
870
|
});
|
|
@@ -787,44 +874,44 @@ function resolveRuleExecutionTimeout(timeoutMs) {
|
|
|
787
874
|
if (!Number.isFinite(timeoutMs) || !Number.isInteger(timeoutMs) || timeoutMs <= 0) throw new TypeError("Rule execution timeout must be a finite positive integer.");
|
|
788
875
|
return timeoutMs;
|
|
789
876
|
}
|
|
790
|
-
function createCacheEntry(job,
|
|
877
|
+
function createCacheEntry(job, fingerprint, bucket) {
|
|
791
878
|
return {
|
|
792
|
-
diagnostics: bucket.diagnostics,
|
|
793
|
-
|
|
794
|
-
fingerprint: {
|
|
795
|
-
alintVersion: version,
|
|
796
|
-
configHash: job.target.configHash,
|
|
797
|
-
modelHash: cache.modelHash,
|
|
798
|
-
ruleHash: job.execution.runtime.ruleHash
|
|
799
|
-
},
|
|
879
|
+
diagnostics: snapshotDiagnostics(bucket.diagnostics),
|
|
880
|
+
fingerprint,
|
|
800
881
|
target: {
|
|
801
|
-
hash:
|
|
882
|
+
hash: fingerprint.targetHash,
|
|
802
883
|
identity: job.target.identity,
|
|
803
884
|
kind: job.target.kind,
|
|
804
|
-
loc: job.target.loc
|
|
885
|
+
loc: job.target.loc ? {
|
|
886
|
+
end: {
|
|
887
|
+
column: job.target.loc.end.column,
|
|
888
|
+
line: job.target.loc.end.line
|
|
889
|
+
},
|
|
890
|
+
start: {
|
|
891
|
+
column: job.target.loc.start.column,
|
|
892
|
+
line: job.target.loc.start.line
|
|
893
|
+
}
|
|
894
|
+
} : void 0,
|
|
805
895
|
name: job.target.name,
|
|
806
|
-
range: job.target.range
|
|
896
|
+
range: job.target.range ? {
|
|
897
|
+
end: job.target.range.end,
|
|
898
|
+
start: job.target.range.start
|
|
899
|
+
} : void 0
|
|
807
900
|
},
|
|
808
|
-
usage: bucket.usage
|
|
901
|
+
usage: snapshotUsageRecords(bucket.usage)
|
|
809
902
|
};
|
|
810
903
|
}
|
|
811
|
-
function
|
|
812
|
-
|
|
813
|
-
return createCacheKey({
|
|
814
|
-
alintVersion: version,
|
|
904
|
+
function createFingerprint(job, modelHash) {
|
|
905
|
+
return {
|
|
815
906
|
configHash: job.target.configHash,
|
|
816
|
-
|
|
817
|
-
modelHash: cache.modelHash,
|
|
907
|
+
modelHash,
|
|
818
908
|
ruleHash: job.execution.runtime.ruleHash,
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
targetIdentity: job.target.identity,
|
|
822
|
-
targetKind: job.target.kind
|
|
823
|
-
});
|
|
909
|
+
targetHash: createTargetHash(job)
|
|
910
|
+
};
|
|
824
911
|
}
|
|
825
912
|
function createTargetHash(job) {
|
|
826
913
|
const target = job.target;
|
|
827
|
-
return stableHash({
|
|
914
|
+
return target.cacheTargetHash ?? stableHash({
|
|
828
915
|
language: target.language,
|
|
829
916
|
loc: target.loc,
|
|
830
917
|
metadata: target.metadata,
|
|
@@ -836,36 +923,32 @@ function createTargetHash(job) {
|
|
|
836
923
|
}
|
|
837
924
|
function failure(cause, job, kind) {
|
|
838
925
|
return {
|
|
839
|
-
job: job.
|
|
926
|
+
job: snapshotProgressJobRef(job.jobRef),
|
|
840
927
|
kind,
|
|
841
|
-
message:
|
|
928
|
+
message: failureMessage$1(cause)
|
|
842
929
|
};
|
|
843
930
|
}
|
|
844
|
-
function
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
};
|
|
850
|
-
options.progress?.onJobEnd?.({
|
|
851
|
-
cache: outcome.cache,
|
|
852
|
-
endedAt: options.clock(),
|
|
853
|
-
failure: outcome.failure,
|
|
854
|
-
job: job.job,
|
|
855
|
-
startedAt: options.startedAt,
|
|
856
|
-
state: outcome.state
|
|
857
|
-
});
|
|
858
|
-
return outcome;
|
|
859
|
-
}
|
|
860
|
-
function rememberCacheEntry(cache, filePaths, cacheKey) {
|
|
861
|
-
for (const filePath of filePaths) {
|
|
862
|
-
const normalizedPath = normalizeCachePath(cache.cwd, filePath);
|
|
863
|
-
const entries = cache.fileEntryKeys.get(normalizedPath) ?? /* @__PURE__ */ new Set();
|
|
864
|
-
entries.add(cacheKey);
|
|
865
|
-
cache.fileEntryKeys.set(normalizedPath, entries);
|
|
931
|
+
function failureMessage$1(cause) {
|
|
932
|
+
try {
|
|
933
|
+
return errorMessageFrom(cause) ?? "Unknown rule failure.";
|
|
934
|
+
} catch {
|
|
935
|
+
return "Unknown rule failure.";
|
|
866
936
|
}
|
|
867
937
|
}
|
|
868
|
-
function
|
|
938
|
+
function finish(job, bucket, terminal) {
|
|
939
|
+
const detachedTerminal = terminal.state === "failed" ? {
|
|
940
|
+
...terminal,
|
|
941
|
+
failure: snapshotFailure(terminal.failure)
|
|
942
|
+
} : terminal;
|
|
943
|
+
return {
|
|
944
|
+
diagnostics: snapshotDiagnostics(bucket.diagnostics),
|
|
945
|
+
jobRef: snapshotProgressJobRef(job.jobRef),
|
|
946
|
+
orderKey: { ...job.orderKey },
|
|
947
|
+
usage: snapshotUsageRecords(bucket.usage),
|
|
948
|
+
...detachedTerminal
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
function replayCachedEntry(entry, bucket, job, progress, runProgress) {
|
|
869
952
|
let diagnostics;
|
|
870
953
|
let usage;
|
|
871
954
|
try {
|
|
@@ -875,180 +958,1132 @@ function replayCachedEntry(entry, bucket, job, progress) {
|
|
|
875
958
|
throw new CacheReplayProcessingError(cause);
|
|
876
959
|
}
|
|
877
960
|
for (const cachedDiagnostic of diagnostics) {
|
|
878
|
-
const diagnostic = {
|
|
961
|
+
const diagnostic = snapshotDiagnostic({
|
|
879
962
|
...cachedDiagnostic,
|
|
880
963
|
cached: true
|
|
881
|
-
};
|
|
964
|
+
});
|
|
882
965
|
bucket.diagnostics.push(diagnostic);
|
|
883
966
|
progress?.onDiagnostic?.({
|
|
884
|
-
diagnostic,
|
|
885
|
-
job
|
|
967
|
+
diagnostic: snapshotDiagnostic(diagnostic),
|
|
968
|
+
job: snapshotProgressJobRef(job),
|
|
969
|
+
progress: runProgress.snapshot()
|
|
886
970
|
});
|
|
887
971
|
}
|
|
888
972
|
for (const record of usage) {
|
|
889
|
-
|
|
973
|
+
const usageRecord = snapshotUsage(record);
|
|
974
|
+
bucket.usage.push(usageRecord);
|
|
890
975
|
progress?.onUsage?.({
|
|
891
|
-
job,
|
|
892
|
-
|
|
976
|
+
job: snapshotProgressJobRef(job),
|
|
977
|
+
progress: runProgress.snapshot(),
|
|
978
|
+
record: snapshotUsage(usageRecord)
|
|
893
979
|
});
|
|
894
980
|
}
|
|
895
981
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
return
|
|
900
|
-
const executions = directory.ruleRuntimes.map((runtime) => {
|
|
901
|
-
if (runtime.handlers.onTargetWith) return {
|
|
902
|
-
run: () => runtime.handlers.onTargetWith?.(directory.target),
|
|
903
|
-
runtime
|
|
904
|
-
};
|
|
905
|
-
if (runtime.handlers.onTargetDirectory) return {
|
|
906
|
-
run: () => runtime.handlers.onTargetDirectory?.(directory.target),
|
|
907
|
-
runtime
|
|
908
|
-
};
|
|
909
|
-
}).filter((execution) => execution !== void 0);
|
|
910
|
-
const targets = executions.length === 0 ? [] : [{
|
|
911
|
-
activeFilePath: directory.target.path,
|
|
912
|
-
cacheFilePaths: [],
|
|
913
|
-
configHash: directory.configHash,
|
|
914
|
-
executions,
|
|
915
|
-
identity: directory.target.path,
|
|
916
|
-
kind: "directory",
|
|
917
|
-
language: "directory",
|
|
918
|
-
text: directory.target.path
|
|
919
|
-
}];
|
|
920
|
-
return {
|
|
921
|
-
id: `directory:${directory.target.path}`,
|
|
922
|
-
index: sourcePlanCount + directoryOffset + 1,
|
|
923
|
-
kind: "directory",
|
|
924
|
-
path: directory.target.path,
|
|
925
|
-
planned: executions.length,
|
|
926
|
-
targets
|
|
927
|
-
};
|
|
928
|
-
});
|
|
982
|
+
function scopeRank(scope) {
|
|
983
|
+
if (scope === "source") return 0;
|
|
984
|
+
if (scope === "directory") return 1;
|
|
985
|
+
return 2;
|
|
929
986
|
}
|
|
930
987
|
//#endregion
|
|
931
|
-
//#region src/core/
|
|
932
|
-
function
|
|
933
|
-
|
|
934
|
-
const
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
988
|
+
//#region src/core/execution/progress.ts
|
|
989
|
+
function createRunProgress(filesTotal) {
|
|
990
|
+
assertNonNegativeInteger(filesTotal, "File total");
|
|
991
|
+
const execution = createCounts();
|
|
992
|
+
let final = false;
|
|
993
|
+
const snapshot = () => {
|
|
994
|
+
const jobsCompleted = terminalCount(execution);
|
|
995
|
+
return {
|
|
996
|
+
execution: { ...execution },
|
|
997
|
+
filesTotal,
|
|
998
|
+
final,
|
|
999
|
+
jobsCompleted,
|
|
1000
|
+
jobsStarted: jobsCompleted + execution.running,
|
|
1001
|
+
jobsTotal: execution.planned
|
|
942
1002
|
};
|
|
943
|
-
}).filter((execution) => execution !== void 0);
|
|
944
|
-
if (executions.length === 0) return;
|
|
945
|
-
const target = {
|
|
946
|
-
cacheFilePaths: options.files.map((file) => file.file.path),
|
|
947
|
-
configHash: options.configHash,
|
|
948
|
-
executions,
|
|
949
|
-
identity: "project",
|
|
950
|
-
kind: "project",
|
|
951
|
-
language: "project",
|
|
952
|
-
text: stableHash({
|
|
953
|
-
files: options.files.map((file) => ({
|
|
954
|
-
configHash: file.configHash,
|
|
955
|
-
contentHash: hashText(file.file.text),
|
|
956
|
-
path: normalizeCachePath(options.root, file.file.path)
|
|
957
|
-
})).sort((left, right) => left.path.localeCompare(right.path)),
|
|
958
|
-
targets: project.targets.map((target) => ({
|
|
959
|
-
filePath: normalizeCachePath(options.root, target.file.path),
|
|
960
|
-
identity: target.identity,
|
|
961
|
-
kind: target.kind,
|
|
962
|
-
language: target.language,
|
|
963
|
-
loc: target.loc,
|
|
964
|
-
metadata: target.metadata,
|
|
965
|
-
name: target.name,
|
|
966
|
-
origin: target.origin,
|
|
967
|
-
range: target.range,
|
|
968
|
-
text: target.text
|
|
969
|
-
})),
|
|
970
|
-
tree: createProjectTreeShape(options.files, options.root)
|
|
971
|
-
})
|
|
972
1003
|
};
|
|
973
1004
|
return {
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1005
|
+
finalize: () => {
|
|
1006
|
+
if (execution.queued !== 0 || execution.running !== 0) throw new Error("Cannot finalize progress while jobs are queued or running.");
|
|
1007
|
+
final = true;
|
|
1008
|
+
return snapshot();
|
|
1009
|
+
},
|
|
1010
|
+
finish: (from, to) => {
|
|
1011
|
+
assertMutable(final);
|
|
1012
|
+
if (execution[from] <= 0) throw new Error(`Cannot finish a job without a ${from} job.`);
|
|
1013
|
+
execution[from] -= 1;
|
|
1014
|
+
execution[to] += 1;
|
|
1015
|
+
return snapshot();
|
|
1016
|
+
},
|
|
1017
|
+
queue: (count = 1) => {
|
|
1018
|
+
assertMutable(final);
|
|
1019
|
+
assertNonNegativeInteger(count, "Queued job count");
|
|
1020
|
+
assertSafeSum(execution.planned, count, "Planned job count");
|
|
1021
|
+
assertSafeSum(execution.queued, count, "Queued job count");
|
|
1022
|
+
execution.planned += count;
|
|
1023
|
+
execution.queued += count;
|
|
1024
|
+
return snapshot();
|
|
1025
|
+
},
|
|
1026
|
+
snapshot,
|
|
1027
|
+
start: () => {
|
|
1028
|
+
assertMutable(final);
|
|
1029
|
+
if (execution.queued <= 0) throw new Error("Cannot start a job without a queued job.");
|
|
1030
|
+
execution.queued -= 1;
|
|
1031
|
+
execution.running += 1;
|
|
1032
|
+
return snapshot();
|
|
1033
|
+
}
|
|
980
1034
|
};
|
|
981
1035
|
}
|
|
982
|
-
function
|
|
983
|
-
|
|
1036
|
+
function assertMutable(final) {
|
|
1037
|
+
if (final) throw new Error("Cannot update finalized progress.");
|
|
1038
|
+
}
|
|
1039
|
+
function assertNonNegativeInteger(value, name) {
|
|
1040
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new TypeError(`${name} must be a non-negative integer.`);
|
|
1041
|
+
}
|
|
1042
|
+
function assertSafeSum(left, right, name) {
|
|
1043
|
+
if (!Number.isSafeInteger(left + right)) throw new TypeError(`${name} must remain a safe integer.`);
|
|
1044
|
+
}
|
|
1045
|
+
function createCounts() {
|
|
984
1046
|
return {
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1047
|
+
cached: 0,
|
|
1048
|
+
cancelled: 0,
|
|
1049
|
+
completed: 0,
|
|
1050
|
+
failed: 0,
|
|
1051
|
+
planned: 0,
|
|
1052
|
+
queued: 0,
|
|
1053
|
+
running: 0,
|
|
1054
|
+
skipped: 0
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
function terminalCount(execution) {
|
|
1058
|
+
return execution.cached + execution.cancelled + execution.completed + execution.failed + execution.skipped;
|
|
1059
|
+
}
|
|
1060
|
+
//#endregion
|
|
1061
|
+
//#region src/models/resolve.ts
|
|
1062
|
+
function resolveModel(registry, options = {}) {
|
|
1063
|
+
const candidates = flattenModels(registry);
|
|
1064
|
+
const ruleId = options.ruleId ?? "<unknown>";
|
|
1065
|
+
if (options.request !== void 0) {
|
|
1066
|
+
const request = options.request;
|
|
1067
|
+
const canonicalCandidates = candidates.filter((candidate) => canonicalIdentity(candidate) === request);
|
|
1068
|
+
if (canonicalCandidates.length > 1) throw duplicateDefinitionError(request);
|
|
1069
|
+
let candidate = canonicalCandidates[0];
|
|
1070
|
+
if (candidate === void 0) {
|
|
1071
|
+
const matchingCandidates = candidates.filter((candidate) => matchesRequest(candidate, request));
|
|
1072
|
+
const matchesByCanonical = /* @__PURE__ */ new Map();
|
|
1073
|
+
for (const matchingCandidate of matchingCandidates) {
|
|
1074
|
+
const identity = canonicalIdentity(matchingCandidate);
|
|
1075
|
+
const matches = matchesByCanonical.get(identity) ?? [];
|
|
1076
|
+
matches.push(matchingCandidate);
|
|
1077
|
+
matchesByCanonical.set(identity, matches);
|
|
1078
|
+
}
|
|
1079
|
+
const duplicateIdentity = [...matchesByCanonical].find(([, matches]) => matches.length > 1)?.[0];
|
|
1080
|
+
if (duplicateIdentity !== void 0) throw duplicateDefinitionError(duplicateIdentity);
|
|
1081
|
+
if (matchingCandidates.length === 0) throw new Error(`Unknown model "${request}".`);
|
|
1082
|
+
if (matchingCandidates.length > 1) {
|
|
1083
|
+
const choices = [...matchesByCanonical.keys()];
|
|
1084
|
+
throw new Error(`Ambiguous model "${request}".\nSpecify a provider-qualified model:\n${choices.map((choice) => ` ${choice}`).join("\n")}`);
|
|
1085
|
+
}
|
|
1086
|
+
candidate = matchingCandidates[0];
|
|
1087
|
+
}
|
|
1088
|
+
if (!satisfiesHardRequirements(candidate.model, options.requirement)) throw new Error(`Model "${request}" does not satisfy requirement for rule "${ruleId}".`);
|
|
1089
|
+
return toResolvedModel(candidate, options.requirement);
|
|
1090
|
+
}
|
|
1091
|
+
const candidate = preferSize(candidates.filter(({ model }) => satisfiesHardRequirements(model, options.requirement)), options.requirement);
|
|
1092
|
+
if (candidate === void 0) throw new Error(`No model satisfies requirement for rule "${ruleId}".`);
|
|
1093
|
+
return toResolvedModel(candidate, options.requirement);
|
|
1094
|
+
}
|
|
1095
|
+
function canonicalIdentity({ model, provider }) {
|
|
1096
|
+
return `${provider.id}/${model.id}`;
|
|
1097
|
+
}
|
|
1098
|
+
function duplicateDefinitionError(identity) {
|
|
1099
|
+
return /* @__PURE__ */ new Error(`Model "${identity}" is configured more than once.\nRemove duplicate provider/model definitions from the setup configuration.`);
|
|
1100
|
+
}
|
|
1101
|
+
function flattenModels(registry) {
|
|
1102
|
+
return registry.providers.flatMap((provider) => provider.models.map((model) => ({
|
|
1103
|
+
model,
|
|
1104
|
+
provider
|
|
1105
|
+
})));
|
|
1106
|
+
}
|
|
1107
|
+
function matchesRequest(candidate, request) {
|
|
1108
|
+
return [
|
|
1109
|
+
candidate.model.id,
|
|
1110
|
+
candidate.model.name,
|
|
1111
|
+
...candidate.model.aliases ?? []
|
|
1112
|
+
].filter((name) => name !== void 0).some((name) => name === request || `${candidate.provider.id}/${name}` === request);
|
|
1113
|
+
}
|
|
1114
|
+
function preferSize(candidates, requirement) {
|
|
1115
|
+
if (requirement?.size === void 0) return candidates[0];
|
|
1116
|
+
return candidates.find(({ model }) => model.size === requirement.size) ?? candidates[0];
|
|
1117
|
+
}
|
|
1118
|
+
function satisfiesHardRequirements(model, requirement) {
|
|
1119
|
+
if (requirement === void 0) return true;
|
|
1120
|
+
if (requirement.capabilities !== void 0 && !requirement.capabilities.every((capability) => (model.capabilities ?? []).includes(capability))) return false;
|
|
1121
|
+
if (requirement.minContextWindow !== void 0 && (model.contextWindow === void 0 || model.contextWindow < requirement.minContextWindow)) return false;
|
|
1122
|
+
return true;
|
|
1123
|
+
}
|
|
1124
|
+
function toResolvedModel(candidate, requirement) {
|
|
1125
|
+
const { model, provider } = candidate;
|
|
1126
|
+
return {
|
|
1127
|
+
aliases: [...model.aliases ?? []],
|
|
1128
|
+
capabilities: [...model.capabilities ?? []],
|
|
1129
|
+
contextWindow: model.contextWindow,
|
|
1130
|
+
id: model.id,
|
|
1131
|
+
name: model.name ?? model.id,
|
|
1132
|
+
params: {
|
|
1133
|
+
...model.defaultParams ?? {},
|
|
1134
|
+
...requirement?.params ?? {}
|
|
1135
|
+
},
|
|
1136
|
+
provider: {
|
|
1137
|
+
endpoint: provider.endpoint,
|
|
1138
|
+
headers: { ...provider.headers ?? {} },
|
|
1139
|
+
id: provider.id,
|
|
1140
|
+
type: provider.type
|
|
1141
|
+
},
|
|
1142
|
+
size: model.size
|
|
989
1143
|
};
|
|
990
1144
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
const
|
|
996
|
-
|
|
1145
|
+
//#endregion
|
|
1146
|
+
//#region src/core/execution/runtime.ts
|
|
1147
|
+
function createRuleRuntimes(options) {
|
|
1148
|
+
return options.rules.map(({ enabledRule, ruleIndex }) => {
|
|
1149
|
+
const executionState = new AsyncLocalStorage();
|
|
1150
|
+
const context = {
|
|
1151
|
+
agent: options.effectiveAgent ? withAgentRetry((request) => options.effectiveAgent({
|
|
1152
|
+
...request,
|
|
1153
|
+
signal: combineAbortSignals(executionState.getStore()?.signal, request.signal)
|
|
1154
|
+
}), options.runOptions.runner?.agentRetries, { onRetry: ({ attempt, maxAttempts }) => {
|
|
1155
|
+
const state = executionState.getStore();
|
|
1156
|
+
if (!state || state.sealed) return;
|
|
1157
|
+
const startedAt = Date.now();
|
|
1158
|
+
try {
|
|
1159
|
+
options.progress?.onJobRetry?.({
|
|
1160
|
+
attempt,
|
|
1161
|
+
job: snapshotProgressJobRef(state.jobRef),
|
|
1162
|
+
maxAttempts,
|
|
1163
|
+
progress: state.runProgress.snapshot(),
|
|
1164
|
+
startedAt
|
|
1165
|
+
});
|
|
1166
|
+
} catch (cause) {
|
|
1167
|
+
if (!state.reporterFailed) {
|
|
1168
|
+
state.reporterCause = cause;
|
|
1169
|
+
state.reporterFailed = true;
|
|
1170
|
+
}
|
|
1171
|
+
throw cause;
|
|
1172
|
+
}
|
|
1173
|
+
} }) : void 0,
|
|
1174
|
+
cwd: options.cwd,
|
|
1175
|
+
id: enabledRule.id,
|
|
1176
|
+
localId: enabledRule.localId,
|
|
1177
|
+
logger: { debug: () => {} },
|
|
1178
|
+
metering: { recordUsage: (record) => {
|
|
1179
|
+
const state = executionState.getStore();
|
|
1180
|
+
if (!state) throw new Error("Cannot record usage outside an active rule job.");
|
|
1181
|
+
if (state.sealed) return;
|
|
1182
|
+
const usageRecord = snapshotUsage({
|
|
1183
|
+
...record,
|
|
1184
|
+
ruleId: record.ruleId ?? enabledRule.id
|
|
1185
|
+
});
|
|
1186
|
+
state.bucket.usage.push(usageRecord);
|
|
1187
|
+
try {
|
|
1188
|
+
options.progress?.onUsage?.({
|
|
1189
|
+
job: snapshotProgressJobRef(state.jobRef),
|
|
1190
|
+
progress: state.runProgress.snapshot(),
|
|
1191
|
+
record: snapshotUsage(usageRecord)
|
|
1192
|
+
});
|
|
1193
|
+
} catch (cause) {
|
|
1194
|
+
if (!state.reporterFailed) {
|
|
1195
|
+
state.reporterCause = cause;
|
|
1196
|
+
state.reporterFailed = true;
|
|
1197
|
+
}
|
|
1198
|
+
throw cause;
|
|
1199
|
+
}
|
|
1200
|
+
} },
|
|
1201
|
+
model: async (selector) => {
|
|
1202
|
+
const request = options.runOptions.modelOverride ?? (typeof selector === "string" ? selector : void 0);
|
|
1203
|
+
const requirement = mergeModelRequirement(enabledRule.rule.model, typeof selector === "string" ? void 0 : selector);
|
|
1204
|
+
const resolvedModel = resolveModel(options.setupConfig, {
|
|
1205
|
+
request,
|
|
1206
|
+
requirement,
|
|
1207
|
+
ruleId: enabledRule.id
|
|
1208
|
+
});
|
|
1209
|
+
const state = executionState.getStore();
|
|
1210
|
+
if (state && !state.sealed) state.currentModel = toDiagnosticModel(resolvedModel, request);
|
|
1211
|
+
return resolvedModel;
|
|
1212
|
+
},
|
|
1213
|
+
options: enabledRule.options,
|
|
1214
|
+
outputLanguage: options.runOptions.outputLanguage,
|
|
1215
|
+
report: (descriptor) => {
|
|
1216
|
+
const state = executionState.getStore();
|
|
1217
|
+
if (!state) throw new Error("Cannot report a diagnostic outside an active rule job.");
|
|
1218
|
+
if (state.sealed) return;
|
|
1219
|
+
const filePath = descriptor.filePath ?? state.activeFilePath;
|
|
1220
|
+
if (!filePath) throw new Error(`Diagnostic for rule "${enabledRule.id}" is missing filePath.`);
|
|
1221
|
+
const diagnosticModel = state.currentModel ? { ...state.currentModel } : void 0;
|
|
1222
|
+
state.currentModel = void 0;
|
|
1223
|
+
const diagnostic = snapshotDiagnostic({
|
|
1224
|
+
evidence: descriptor.evidence,
|
|
1225
|
+
filePath,
|
|
1226
|
+
loc: descriptor.loc,
|
|
1227
|
+
message: descriptor.message,
|
|
1228
|
+
model: diagnosticModel,
|
|
1229
|
+
ruleId: enabledRule.id,
|
|
1230
|
+
severity: enabledRule.severity
|
|
1231
|
+
});
|
|
1232
|
+
state.bucket.diagnostics.push(diagnostic);
|
|
1233
|
+
try {
|
|
1234
|
+
options.progress?.onDiagnostic?.({
|
|
1235
|
+
diagnostic: snapshotDiagnostic(diagnostic),
|
|
1236
|
+
job: snapshotProgressJobRef(state.jobRef),
|
|
1237
|
+
progress: state.runProgress.snapshot()
|
|
1238
|
+
});
|
|
1239
|
+
} catch (cause) {
|
|
1240
|
+
if (!state.reporterFailed) {
|
|
1241
|
+
state.reporterCause = cause;
|
|
1242
|
+
state.reporterFailed = true;
|
|
1243
|
+
}
|
|
1244
|
+
throw cause;
|
|
1245
|
+
}
|
|
1246
|
+
},
|
|
1247
|
+
settings: options.effectiveSettings,
|
|
1248
|
+
get signal() {
|
|
1249
|
+
return executionState.getStore()?.signal;
|
|
1250
|
+
},
|
|
1251
|
+
src: options.src
|
|
1252
|
+
};
|
|
1253
|
+
return {
|
|
1254
|
+
cacheable: enabledRule.rule.cache !== false,
|
|
1255
|
+
enabledRule,
|
|
1256
|
+
executionState,
|
|
1257
|
+
handlers: enabledRule.rule.create(context),
|
|
1258
|
+
ruleHash: stableHash({
|
|
1259
|
+
cache: enabledRule.rule.cache ?? true,
|
|
1260
|
+
cacheKey: enabledRule.rule.cacheKey,
|
|
1261
|
+
create: String(enabledRule.rule.create),
|
|
1262
|
+
id: enabledRule.id,
|
|
1263
|
+
localId: enabledRule.localId,
|
|
1264
|
+
model: enabledRule.rule.model,
|
|
1265
|
+
options: enabledRule.options,
|
|
1266
|
+
severity: enabledRule.severity
|
|
1267
|
+
}),
|
|
1268
|
+
ruleIndex
|
|
1269
|
+
};
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
function mergeCapabilities(base, extra) {
|
|
1273
|
+
if (!base && !extra) return void 0;
|
|
1274
|
+
return [.../* @__PURE__ */ new Set([...base ?? [], ...extra ?? []])];
|
|
1275
|
+
}
|
|
1276
|
+
function mergeMinContextWindow(base, extra) {
|
|
1277
|
+
if (base === void 0) return extra;
|
|
1278
|
+
if (extra === void 0) return base;
|
|
1279
|
+
return Math.max(base, extra);
|
|
1280
|
+
}
|
|
1281
|
+
function mergeModelRequirement(base, extra) {
|
|
1282
|
+
if (!base && !extra) return void 0;
|
|
1283
|
+
const capabilities = mergeCapabilities(base?.capabilities, extra?.capabilities);
|
|
1284
|
+
const params = {
|
|
1285
|
+
...base?.params ?? {},
|
|
1286
|
+
...extra?.params ?? {}
|
|
1287
|
+
};
|
|
1288
|
+
return {
|
|
1289
|
+
capabilities,
|
|
1290
|
+
minContextWindow: mergeMinContextWindow(base?.minContextWindow, extra?.minContextWindow),
|
|
1291
|
+
params: Object.keys(params).length > 0 ? params : void 0,
|
|
1292
|
+
size: extra?.size ?? base?.size
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
function toDiagnosticModel(model, request) {
|
|
1296
|
+
return {
|
|
1297
|
+
providerId: model.provider.id,
|
|
1298
|
+
requested: request,
|
|
1299
|
+
resolvedId: model.id
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
//#endregion
|
|
1303
|
+
//#region src/core/execution/scheduler.ts
|
|
1304
|
+
var RuleScheduler = class {
|
|
1305
|
+
options;
|
|
1306
|
+
accepting = true;
|
|
1307
|
+
active = 0;
|
|
1308
|
+
cancellingQueued = false;
|
|
1309
|
+
closePromise;
|
|
1310
|
+
closeReject;
|
|
1311
|
+
closeResolve;
|
|
1312
|
+
infrastructureError;
|
|
1313
|
+
infrastructureFailed = false;
|
|
1314
|
+
lanes = [];
|
|
1315
|
+
nextAdmissionIndex = 1;
|
|
1316
|
+
pumpQueued = false;
|
|
1317
|
+
readyLanes = [];
|
|
1318
|
+
constructor(options) {
|
|
1319
|
+
this.options = options;
|
|
1320
|
+
resolveRuleConcurrency(options.concurrency);
|
|
1321
|
+
if (options.signal?.aborted) this.accepting = false;
|
|
1322
|
+
else options.signal?.addEventListener("abort", this.abort, { once: true });
|
|
1323
|
+
}
|
|
1324
|
+
cancelWithError(error) {
|
|
1325
|
+
this.fail(error);
|
|
1326
|
+
}
|
|
1327
|
+
close() {
|
|
1328
|
+
this.accepting = false;
|
|
1329
|
+
if (!this.closePromise) this.closePromise = new Promise((resolve, reject) => {
|
|
1330
|
+
this.closeResolve = resolve;
|
|
1331
|
+
this.closeReject = reject;
|
|
1332
|
+
});
|
|
1333
|
+
this.queuePump();
|
|
1334
|
+
this.settleClose();
|
|
1335
|
+
return this.closePromise;
|
|
1336
|
+
}
|
|
1337
|
+
schedule(jobs) {
|
|
1338
|
+
if (!this.accepting || jobs.length === 0) return {
|
|
1339
|
+
jobsAdded: 0,
|
|
1340
|
+
outcomes: Promise.resolve([])
|
|
1341
|
+
};
|
|
1342
|
+
const lane = createLane();
|
|
1343
|
+
this.lanes.push(lane);
|
|
1344
|
+
let jobsAdded = 0;
|
|
1345
|
+
for (const job of jobs) {
|
|
1346
|
+
if (!this.accepting) break;
|
|
1347
|
+
const index = jobsAdded;
|
|
1348
|
+
try {
|
|
1349
|
+
job.jobRef.index = this.nextAdmissionIndex;
|
|
1350
|
+
this.nextAdmissionIndex += 1;
|
|
1351
|
+
const progress = this.options.progress.queue();
|
|
1352
|
+
jobsAdded += 1;
|
|
1353
|
+
lane.remaining += 1;
|
|
1354
|
+
lane.items.push({
|
|
1355
|
+
index,
|
|
1356
|
+
job,
|
|
1357
|
+
lane
|
|
1358
|
+
});
|
|
1359
|
+
this.options.reporter?.onJobQueued?.({
|
|
1360
|
+
job: snapshotProgressJobRef(job.jobRef),
|
|
1361
|
+
progress
|
|
1362
|
+
});
|
|
1363
|
+
} catch (error) {
|
|
1364
|
+
this.fail(error);
|
|
1365
|
+
throw error;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
lane.outcomes.length = jobsAdded;
|
|
1369
|
+
if (jobsAdded === 0) this.settleLane(lane);
|
|
1370
|
+
else {
|
|
1371
|
+
this.enqueue(lane);
|
|
1372
|
+
this.queuePump();
|
|
1373
|
+
}
|
|
1374
|
+
return {
|
|
1375
|
+
jobsAdded,
|
|
1376
|
+
outcomes: lane.promise
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
snapshot() {
|
|
1380
|
+
return this.options.progress.snapshot();
|
|
1381
|
+
}
|
|
1382
|
+
abort = () => {
|
|
1383
|
+
this.accepting = false;
|
|
1384
|
+
this.cancelQueued();
|
|
1385
|
+
this.settleClose();
|
|
1386
|
+
};
|
|
1387
|
+
cancel(item, from, startedAt) {
|
|
1388
|
+
const outcome = cancelledOutcome(item.job);
|
|
1389
|
+
const progress = this.options.progress.finish(from, "cancelled");
|
|
1390
|
+
item.lane.outcomes[item.index] = outcome;
|
|
1391
|
+
item.lane.remaining -= 1;
|
|
1392
|
+
try {
|
|
1393
|
+
this.options.reporter?.onJobEnd?.({
|
|
1394
|
+
cache: "miss",
|
|
1395
|
+
endedAt: this.options.clock(),
|
|
1396
|
+
job: snapshotProgressJobRef(item.job.jobRef),
|
|
1397
|
+
progress,
|
|
1398
|
+
startedAt,
|
|
1399
|
+
state: "cancelled"
|
|
1400
|
+
});
|
|
1401
|
+
} catch (error) {
|
|
1402
|
+
this.fail(error);
|
|
1403
|
+
}
|
|
1404
|
+
this.settleLane(item.lane);
|
|
1405
|
+
}
|
|
1406
|
+
cancelQueued() {
|
|
1407
|
+
if (this.cancellingQueued) return;
|
|
1408
|
+
this.cancellingQueued = true;
|
|
1409
|
+
this.readyLanes = [];
|
|
1410
|
+
for (const lane of this.lanes) lane.ready = false;
|
|
1411
|
+
try {
|
|
1412
|
+
for (const lane of [...this.lanes]) {
|
|
1413
|
+
const queued = lane.items.splice(0);
|
|
1414
|
+
for (const item of queued) this.cancel(item, "queued");
|
|
1415
|
+
}
|
|
1416
|
+
} finally {
|
|
1417
|
+
this.cancellingQueued = false;
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
enqueue(lane) {
|
|
1421
|
+
if (lane.ready || lane.settled || lane.items.length === 0) return;
|
|
1422
|
+
lane.ready = true;
|
|
1423
|
+
this.readyLanes.push(lane);
|
|
1424
|
+
}
|
|
1425
|
+
fail(error) {
|
|
1426
|
+
this.recordFailure(error);
|
|
1427
|
+
this.accepting = false;
|
|
1428
|
+
this.cancelQueued();
|
|
1429
|
+
this.settleClose();
|
|
1430
|
+
}
|
|
1431
|
+
finish(item, outcome, startedAt) {
|
|
1432
|
+
const progress = this.options.progress.finish("running", outcome.state);
|
|
1433
|
+
item.lane.outcomes[item.index] = outcome;
|
|
1434
|
+
item.lane.remaining -= 1;
|
|
1435
|
+
try {
|
|
1436
|
+
this.options.reporter?.onJobEnd?.({
|
|
1437
|
+
cache: outcome.cache,
|
|
1438
|
+
endedAt: this.options.clock(),
|
|
1439
|
+
failure: outcome.failure ? snapshotFailure(outcome.failure) : void 0,
|
|
1440
|
+
job: snapshotProgressJobRef(item.job.jobRef),
|
|
1441
|
+
progress,
|
|
1442
|
+
startedAt,
|
|
1443
|
+
state: outcome.state
|
|
1444
|
+
});
|
|
1445
|
+
} catch (error) {
|
|
1446
|
+
this.fail(error);
|
|
1447
|
+
}
|
|
1448
|
+
this.settleLane(item.lane);
|
|
1449
|
+
}
|
|
1450
|
+
next() {
|
|
1451
|
+
while (this.readyLanes.length > 0) {
|
|
1452
|
+
const lane = this.readyLanes.shift();
|
|
1453
|
+
lane.ready = false;
|
|
1454
|
+
const item = lane.items.shift();
|
|
1455
|
+
this.enqueue(lane);
|
|
1456
|
+
if (item) return item;
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
pump() {
|
|
1460
|
+
this.pumpQueued = false;
|
|
1461
|
+
if (this.infrastructureFailed || this.options.signal?.aborted) {
|
|
1462
|
+
this.cancelQueued();
|
|
1463
|
+
this.settleClose();
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
while (this.active < this.options.concurrency) {
|
|
1467
|
+
const item = this.next();
|
|
1468
|
+
if (!item) break;
|
|
1469
|
+
let startedAt;
|
|
1470
|
+
try {
|
|
1471
|
+
startedAt = this.options.clock();
|
|
1472
|
+
} catch (error) {
|
|
1473
|
+
this.fail(error);
|
|
1474
|
+
this.cancel(item, "queued");
|
|
1475
|
+
break;
|
|
1476
|
+
}
|
|
1477
|
+
try {
|
|
1478
|
+
const progress = this.options.progress.start();
|
|
1479
|
+
this.options.reporter?.onJobStart?.({
|
|
1480
|
+
job: snapshotProgressJobRef(item.job.jobRef),
|
|
1481
|
+
progress,
|
|
1482
|
+
startedAt
|
|
1483
|
+
});
|
|
1484
|
+
} catch (error) {
|
|
1485
|
+
this.fail(error);
|
|
1486
|
+
this.cancel(item, "running");
|
|
1487
|
+
break;
|
|
1488
|
+
}
|
|
1489
|
+
this.active += 1;
|
|
1490
|
+
this.run(item, startedAt);
|
|
1491
|
+
}
|
|
1492
|
+
this.settleClose();
|
|
1493
|
+
}
|
|
1494
|
+
queuePump() {
|
|
1495
|
+
if (this.pumpQueued) return;
|
|
1496
|
+
this.pumpQueued = true;
|
|
1497
|
+
queueMicrotask(() => this.pump());
|
|
1498
|
+
}
|
|
1499
|
+
recordFailure(error) {
|
|
1500
|
+
if (this.infrastructureFailed) return;
|
|
1501
|
+
this.infrastructureFailed = true;
|
|
1502
|
+
this.infrastructureError = error;
|
|
1503
|
+
}
|
|
1504
|
+
removeLane(lane) {
|
|
1505
|
+
const index = this.lanes.indexOf(lane);
|
|
1506
|
+
if (index < 0) return;
|
|
1507
|
+
this.lanes.splice(index, 1);
|
|
1508
|
+
if (lane.ready) {
|
|
1509
|
+
const readyIndex = this.readyLanes.indexOf(lane);
|
|
1510
|
+
if (readyIndex >= 0) this.readyLanes.splice(readyIndex, 1);
|
|
1511
|
+
lane.ready = false;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
async run(item, startedAt) {
|
|
1515
|
+
try {
|
|
1516
|
+
const outcome = await this.options.execute(item.job, startedAt);
|
|
1517
|
+
this.finish(item, outcome, startedAt);
|
|
1518
|
+
} catch (error) {
|
|
1519
|
+
this.fail(error);
|
|
1520
|
+
this.cancel(item, "running", startedAt);
|
|
1521
|
+
} finally {
|
|
1522
|
+
this.active -= 1;
|
|
1523
|
+
this.queuePump();
|
|
1524
|
+
this.settleClose();
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
settleClose() {
|
|
1528
|
+
if (!this.closePromise || this.active !== 0 || this.lanes.length !== 0) return;
|
|
1529
|
+
this.options.signal?.removeEventListener("abort", this.abort);
|
|
1530
|
+
if (this.infrastructureFailed) this.closeReject?.(this.infrastructureError);
|
|
1531
|
+
else this.closeResolve?.();
|
|
1532
|
+
}
|
|
1533
|
+
settleLane(lane) {
|
|
1534
|
+
if (lane.settled || lane.remaining !== 0) return;
|
|
1535
|
+
lane.settled = true;
|
|
1536
|
+
this.removeLane(lane);
|
|
1537
|
+
lane.resolve(lane.outcomes.filter((outcome) => outcome !== void 0));
|
|
1538
|
+
this.settleClose();
|
|
1539
|
+
}
|
|
1540
|
+
};
|
|
1541
|
+
function cancelledOutcome(job) {
|
|
1542
|
+
return {
|
|
1543
|
+
cache: "miss",
|
|
1544
|
+
diagnostics: [],
|
|
1545
|
+
jobRef: snapshotProgressJobRef(job.jobRef),
|
|
1546
|
+
orderKey: { ...job.orderKey },
|
|
1547
|
+
state: "cancelled",
|
|
1548
|
+
usage: []
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
function resolveRuleConcurrency(ruleConcurrency) {
|
|
1552
|
+
if (ruleConcurrency === void 0) return 4;
|
|
1553
|
+
if (!Number.isFinite(ruleConcurrency) || !Number.isInteger(ruleConcurrency) || ruleConcurrency <= 0) throw new TypeError("Rule execution concurrency must be a finite positive integer.");
|
|
1554
|
+
return ruleConcurrency;
|
|
1555
|
+
}
|
|
1556
|
+
function createLane() {
|
|
1557
|
+
let resolve;
|
|
1558
|
+
return {
|
|
1559
|
+
items: [],
|
|
1560
|
+
outcomes: [],
|
|
1561
|
+
promise: new Promise((resolvePromise) => {
|
|
1562
|
+
resolve = resolvePromise;
|
|
1563
|
+
}),
|
|
1564
|
+
ready: false,
|
|
1565
|
+
remaining: 0,
|
|
1566
|
+
resolve,
|
|
1567
|
+
settled: false
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
//#endregion
|
|
1571
|
+
//#region src/dsl/registry.ts
|
|
1572
|
+
function buildRuleRegistry(config) {
|
|
1573
|
+
const rules = /* @__PURE__ */ new Map();
|
|
1574
|
+
const localIds = /* @__PURE__ */ new Map();
|
|
1575
|
+
const enabledRules = [];
|
|
1576
|
+
for (const [alias, plugin] of Object.entries(config.plugins)) for (const [localId, rule] of Object.entries(plugin.rules ?? {})) {
|
|
1577
|
+
const id = `${alias}/${localId}`;
|
|
1578
|
+
if (rules.has(id)) throw new Error(`Duplicate rule id "${id}".`);
|
|
1579
|
+
rules.set(id, rule);
|
|
1580
|
+
localIds.set(id, localId);
|
|
1581
|
+
}
|
|
1582
|
+
for (const [id, entry] of Object.entries(config.rules)) {
|
|
1583
|
+
const rule = rules.get(id);
|
|
1584
|
+
if (!rule) throw new Error(`Unknown rule "${id}".`);
|
|
1585
|
+
const normalizedEntry = normalizeRuleConfigEntry(entry);
|
|
1586
|
+
const severity = normalizedEntry.severity;
|
|
1587
|
+
if (severity === "off") continue;
|
|
1588
|
+
enabledRules.push({
|
|
1589
|
+
id,
|
|
1590
|
+
localId: localIds.get(id) ?? id,
|
|
1591
|
+
options: parseRuleOptions(id, rule.options, normalizedEntry.options),
|
|
1592
|
+
rule,
|
|
1593
|
+
severity
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
return {
|
|
1597
|
+
enabledRules,
|
|
1598
|
+
rules
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
function formatRuleOptionIssues(index, issues) {
|
|
1602
|
+
return issues.map((issue) => {
|
|
1603
|
+
const path = getDotPath(issue);
|
|
1604
|
+
return path === null ? `"${index}": ${issue.message}` : `"${index}.${path}": ${issue.message}`;
|
|
1605
|
+
}).join("; ");
|
|
1606
|
+
}
|
|
1607
|
+
function isRuleConfigTuple(entry) {
|
|
1608
|
+
return Array.isArray(entry);
|
|
1609
|
+
}
|
|
1610
|
+
function normalizeRuleConfigEntry(entry) {
|
|
1611
|
+
if (isRuleConfigTuple(entry)) {
|
|
1612
|
+
const [severity = "warn", ...options] = entry;
|
|
1613
|
+
return {
|
|
1614
|
+
options,
|
|
1615
|
+
severity
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
return {
|
|
1619
|
+
options: [],
|
|
1620
|
+
severity: entry ?? "warn"
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
function parseRuleOption(id, index, schema, value) {
|
|
1624
|
+
const result = safeParse(schema, value);
|
|
1625
|
+
if (result.success) return result.output;
|
|
1626
|
+
if (value === void 0) {
|
|
1627
|
+
const emptyObjectResult = safeParse(schema, {});
|
|
1628
|
+
if (emptyObjectResult.success) return emptyObjectResult.output;
|
|
1629
|
+
}
|
|
1630
|
+
throw new TypeError(`Invalid options for rule "${id}": ${formatRuleOptionIssues(index, result.issues)}`);
|
|
1631
|
+
}
|
|
1632
|
+
function parseRuleOptions(id, schema, options) {
|
|
1633
|
+
if (!schema) {
|
|
1634
|
+
if (options.length > 0) throw new TypeError(`Rule "${id}" does not accept options.`);
|
|
1635
|
+
return [];
|
|
1636
|
+
}
|
|
1637
|
+
if (options.length > schema.length) throw new TypeError(`Invalid options for rule "${id}": Unexpected option at index ${schema.length}.`);
|
|
1638
|
+
return schema.map((optionSchema, index) => parseRuleOption(id, index, optionSchema, options[index]));
|
|
1639
|
+
}
|
|
1640
|
+
//#endregion
|
|
1641
|
+
//#region src/core/preparation.ts
|
|
1642
|
+
function prepareRun(options = {}) {
|
|
1643
|
+
const cwd$2 = options.cwd ?? cwd();
|
|
1644
|
+
const config = options.config ?? [];
|
|
1645
|
+
const files = [];
|
|
1646
|
+
const directories = [];
|
|
1647
|
+
for (const filePath of options.files ?? []) {
|
|
1648
|
+
const path = resolve(cwd$2, filePath);
|
|
1649
|
+
const resolvedConfig = resolveConfigForFile(path, config, { cwd: cwd$2 });
|
|
1650
|
+
if (resolvedConfig.ignored) continue;
|
|
1651
|
+
const effectiveConfig = resolvedConfig.config;
|
|
1652
|
+
const language = resolveLanguageForPath(path, createLanguageRegistry(effectiveConfig), { language: effectiveConfig.language });
|
|
1653
|
+
files.push({
|
|
1654
|
+
agent: effectiveConfig.agent,
|
|
1655
|
+
configHash: stableHash({
|
|
1656
|
+
language: effectiveConfig.language,
|
|
1657
|
+
languageOptions: effectiveConfig.languageOptions,
|
|
1658
|
+
processor: effectiveConfig.processor,
|
|
1659
|
+
resolvedLanguage: language.name,
|
|
1660
|
+
settings: effectiveConfig.settings
|
|
1661
|
+
}),
|
|
1662
|
+
fileIndex: files.length,
|
|
1663
|
+
language,
|
|
1664
|
+
languageOptions: effectiveConfig.languageOptions,
|
|
1665
|
+
path,
|
|
1666
|
+
rules: prepareRules(effectiveConfig),
|
|
1667
|
+
settings: effectiveConfig.settings
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1670
|
+
for (const directoryPath of options.directories ?? []) {
|
|
1671
|
+
const path = resolve(cwd$2, directoryPath);
|
|
1672
|
+
const resolvedConfig = resolveConfigForDirectory(path, config, { cwd: cwd$2 });
|
|
1673
|
+
if (resolvedConfig.ignored) continue;
|
|
1674
|
+
const effectiveConfig = resolvedConfig.config;
|
|
1675
|
+
directories.push({
|
|
1676
|
+
agent: effectiveConfig.agent,
|
|
1677
|
+
configHash: stableHash({ settings: effectiveConfig.settings }),
|
|
1678
|
+
directoryIndex: directories.length,
|
|
1679
|
+
rules: prepareRules(effectiveConfig),
|
|
1680
|
+
settings: effectiveConfig.settings,
|
|
1681
|
+
target: {
|
|
1682
|
+
kind: "directory",
|
|
1683
|
+
path
|
|
1684
|
+
}
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1687
|
+
return {
|
|
1688
|
+
directories,
|
|
1689
|
+
files,
|
|
1690
|
+
project: options.projectTargets === false ? void 0 : prepareProject(cwd$2, config)
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
function createLanguageRegistry(config) {
|
|
1694
|
+
const registry = createBuiltInLanguageRegistry();
|
|
1695
|
+
for (const plugin of Object.values(config.plugins)) for (const language of Object.values(plugin.languages ?? {})) registerLanguage(registry, language);
|
|
1696
|
+
return registry;
|
|
1697
|
+
}
|
|
1698
|
+
function prepareProject(root, config) {
|
|
1699
|
+
const resolvedConfig = resolveConfigForProject(root, config, { cwd: root });
|
|
1700
|
+
if (resolvedConfig.ignored) return void 0;
|
|
1701
|
+
const effectiveConfig = resolvedConfig.config;
|
|
1702
|
+
return {
|
|
1703
|
+
agent: effectiveConfig.agent,
|
|
1704
|
+
configHash: stableHash({ settings: effectiveConfig.settings }),
|
|
1705
|
+
root,
|
|
1706
|
+
rules: prepareRules(effectiveConfig),
|
|
1707
|
+
settings: effectiveConfig.settings
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
function prepareRules(config) {
|
|
1711
|
+
return buildRuleRegistry(config).enabledRules.map((enabledRule, ruleIndex) => ({
|
|
1712
|
+
enabledRule,
|
|
1713
|
+
ruleIndex
|
|
1714
|
+
}));
|
|
1715
|
+
}
|
|
1716
|
+
//#endregion
|
|
1717
|
+
//#region src/core/project/index.ts
|
|
1718
|
+
var ProjectIndexBuilder = class {
|
|
1719
|
+
files = [];
|
|
1720
|
+
hash;
|
|
1721
|
+
hasher = createStableHasher();
|
|
1722
|
+
nextFileIndex = 0;
|
|
1723
|
+
pending = /* @__PURE__ */ new Map();
|
|
1724
|
+
root;
|
|
1725
|
+
targets = [];
|
|
1726
|
+
constructor(root) {
|
|
1727
|
+
this.root = root;
|
|
1728
|
+
this.hasher.update({ root });
|
|
1729
|
+
}
|
|
1730
|
+
add(snapshot) {
|
|
1731
|
+
if (this.hash !== void 0) throw new Error("Cannot add a project file after the index has been built.");
|
|
1732
|
+
if (!Number.isSafeInteger(snapshot.fileIndex) || snapshot.fileIndex < 0) throw new TypeError("Project file index must be a non-negative safe integer.");
|
|
1733
|
+
if (snapshot.fileIndex < this.nextFileIndex || this.pending.has(snapshot.fileIndex)) throw new Error(`Project file index ${snapshot.fileIndex} has already been added.`);
|
|
1734
|
+
this.pending.set(snapshot.fileIndex, copySnapshot(snapshot));
|
|
1735
|
+
this.flushContiguousSnapshots();
|
|
1736
|
+
}
|
|
1737
|
+
build() {
|
|
1738
|
+
if (this.pending.size > 0) throw new Error(`Cannot build project index with a gap at file index ${this.nextFileIndex}.`);
|
|
1739
|
+
this.hash ??= this.hasher.digest();
|
|
1740
|
+
return {
|
|
1741
|
+
hash: this.hash,
|
|
1742
|
+
target: {
|
|
1743
|
+
files: this.files.map(copyFile),
|
|
1744
|
+
kind: "project",
|
|
1745
|
+
root: this.root,
|
|
1746
|
+
targets: this.targets.map(copyDescriptor)
|
|
1747
|
+
}
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
flushContiguousSnapshots() {
|
|
1751
|
+
let snapshot = this.pending.get(this.nextFileIndex);
|
|
1752
|
+
while (snapshot) {
|
|
1753
|
+
this.pending.delete(this.nextFileIndex);
|
|
1754
|
+
this.files.push(copyFile(snapshot.file));
|
|
1755
|
+
this.targets.push(...snapshot.targets.map((target) => copyDescriptor(target.descriptor)));
|
|
1756
|
+
this.hasher.update({
|
|
1757
|
+
configHash: snapshot.configHash,
|
|
1758
|
+
file: snapshot.file,
|
|
1759
|
+
targets: snapshot.targets.map((target) => ({
|
|
1760
|
+
descriptor: target.descriptor,
|
|
1761
|
+
semanticHash: target.semanticHash
|
|
1762
|
+
}))
|
|
1763
|
+
});
|
|
1764
|
+
this.nextFileIndex += 1;
|
|
1765
|
+
snapshot = this.pending.get(this.nextFileIndex);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
};
|
|
1769
|
+
function createProjectJobs(options) {
|
|
1770
|
+
const executions = options.runtimes.map((runtime) => projectExecution(runtime, options.project.target)).filter((execution) => execution !== void 0);
|
|
1771
|
+
if (executions.length === 0) return { jobs: [] };
|
|
1772
|
+
const owner = options.cacheStore.beginOwner({
|
|
1773
|
+
kind: "project",
|
|
1774
|
+
path: options.project.target.root
|
|
1775
|
+
});
|
|
1776
|
+
const target = {
|
|
1777
|
+
cacheOwner: owner,
|
|
1778
|
+
cacheTargetHash: options.project.hash,
|
|
1779
|
+
configHash: options.configHash,
|
|
1780
|
+
identity: "project",
|
|
1781
|
+
kind: "project",
|
|
1782
|
+
language: "project",
|
|
1783
|
+
text: options.project.hash
|
|
1784
|
+
};
|
|
1785
|
+
return {
|
|
1786
|
+
jobs: executions.map((execution) => {
|
|
1787
|
+
const ruleId = execution.runtime.enabledRule.id;
|
|
1788
|
+
return {
|
|
1789
|
+
execution,
|
|
1790
|
+
jobRef: {
|
|
1791
|
+
id: stableHash({
|
|
1792
|
+
input: options.project.target.root,
|
|
1793
|
+
ruleId,
|
|
1794
|
+
targetIdentity: "project",
|
|
1795
|
+
targetIndex: 0
|
|
1796
|
+
}),
|
|
1797
|
+
index: 0,
|
|
1798
|
+
inputPath: options.project.target.root,
|
|
1799
|
+
ruleId,
|
|
1800
|
+
target: {
|
|
1801
|
+
identity: "project",
|
|
1802
|
+
kind: "project"
|
|
1803
|
+
}
|
|
1804
|
+
},
|
|
1805
|
+
orderKey: {
|
|
1806
|
+
inputIndex: 0,
|
|
1807
|
+
ruleIndex: execution.runtime.ruleIndex,
|
|
1808
|
+
scope: "project",
|
|
1809
|
+
targetIndex: 0
|
|
1810
|
+
},
|
|
1811
|
+
target
|
|
1812
|
+
};
|
|
1813
|
+
}),
|
|
1814
|
+
owner
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
function copyDescriptor(descriptor) {
|
|
1818
|
+
return {
|
|
1819
|
+
filePath: descriptor.filePath,
|
|
1820
|
+
identity: descriptor.identity,
|
|
1821
|
+
kind: descriptor.kind,
|
|
1822
|
+
name: descriptor.name,
|
|
1823
|
+
range: descriptor.range && { ...descriptor.range }
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
function copyFile(file) {
|
|
1827
|
+
return {
|
|
1828
|
+
contentHash: file.contentHash,
|
|
1829
|
+
language: file.language,
|
|
1830
|
+
path: file.path,
|
|
1831
|
+
targetCount: file.targetCount
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
function copySnapshot(snapshot) {
|
|
1835
|
+
return {
|
|
1836
|
+
configHash: snapshot.configHash,
|
|
1837
|
+
file: copyFile(snapshot.file),
|
|
1838
|
+
fileIndex: snapshot.fileIndex,
|
|
1839
|
+
targets: snapshot.targets.map(copyTargetSnapshot)
|
|
1840
|
+
};
|
|
1841
|
+
}
|
|
1842
|
+
function copyTargetSnapshot(target) {
|
|
1843
|
+
return {
|
|
1844
|
+
descriptor: copyDescriptor(target.descriptor),
|
|
1845
|
+
semanticHash: target.semanticHash
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
function projectExecution(runtime, target) {
|
|
1849
|
+
if (runtime.handlers.onTargetWith) return {
|
|
1850
|
+
run: () => runtime.handlers.onTargetWith?.(target),
|
|
1851
|
+
runtime
|
|
1852
|
+
};
|
|
1853
|
+
if (runtime.handlers.onTargetProject) return {
|
|
1854
|
+
run: () => runtime.handlers.onTargetProject?.(target),
|
|
1855
|
+
runtime
|
|
1856
|
+
};
|
|
1857
|
+
}
|
|
1858
|
+
async function executeSourceSession(input, options) {
|
|
1859
|
+
openSession(options.metrics);
|
|
1860
|
+
try {
|
|
1861
|
+
if (options.signal?.aborted) return { outcomes: [] };
|
|
1862
|
+
let file;
|
|
1863
|
+
try {
|
|
1864
|
+
file = await options.src.readFile(input.path);
|
|
1865
|
+
} catch (error) {
|
|
1866
|
+
return {
|
|
1867
|
+
failure: fileFailure(input, "read", failureMessage(error, "Failed to read source file.")),
|
|
1868
|
+
outcomes: []
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
if (options.signal?.aborted) return { outcomes: [] };
|
|
1872
|
+
let targets;
|
|
1873
|
+
try {
|
|
1874
|
+
targets = await input.language.extract(file, {
|
|
1875
|
+
cwd: options.cwd,
|
|
1876
|
+
languageOptions: input.languageOptions,
|
|
1877
|
+
src: options.src
|
|
1878
|
+
});
|
|
1879
|
+
} catch (error) {
|
|
1880
|
+
return {
|
|
1881
|
+
failure: fileFailure(input, "extract", failureMessage(error, "Failed to extract source targets.")),
|
|
1882
|
+
outcomes: []
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1885
|
+
const contentHash = hashText(file.text);
|
|
1886
|
+
const project = options.projectSnapshots === false ? void 0 : createProjectSnapshot(input, file.language, file.path, contentHash, targets);
|
|
1887
|
+
const cacheOwner = options.cacheStore.beginOwner({
|
|
1888
|
+
kind: "file",
|
|
1889
|
+
path: file.path
|
|
1890
|
+
});
|
|
1891
|
+
const jobs = createSourceJobs(input, createExecutionTargets(input, targets, options.ruleRuntimes, options.cwd, cacheOwner));
|
|
1892
|
+
if (options.signal?.aborted) {
|
|
1893
|
+
cacheOwner.commit({
|
|
1894
|
+
contentHash,
|
|
1895
|
+
mode: "merge"
|
|
1896
|
+
});
|
|
1897
|
+
return {
|
|
1898
|
+
outcomes: [],
|
|
1899
|
+
project
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
const batch = options.scheduler.schedule(jobs);
|
|
1903
|
+
try {
|
|
1904
|
+
options.progress?.onFileReady?.({
|
|
1905
|
+
fileIndex: input.fileIndex,
|
|
1906
|
+
inputPath: input.path,
|
|
1907
|
+
jobsAdded: batch.jobsAdded,
|
|
1908
|
+
progress: options.scheduler.snapshot()
|
|
1909
|
+
});
|
|
1910
|
+
} catch (error) {
|
|
1911
|
+
options.scheduler.cancelWithError(error);
|
|
1912
|
+
throw error;
|
|
1913
|
+
}
|
|
1914
|
+
const outcomes = await batch.outcomes;
|
|
1915
|
+
cacheOwner.commit(options.signal?.aborted ? {
|
|
1916
|
+
contentHash,
|
|
1917
|
+
mode: "merge"
|
|
1918
|
+
} : { contentHash });
|
|
1919
|
+
return {
|
|
1920
|
+
outcomes,
|
|
1921
|
+
project
|
|
1922
|
+
};
|
|
1923
|
+
} finally {
|
|
1924
|
+
closeSession(options.metrics);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
async function executeSourceSessions(inputs, options) {
|
|
1928
|
+
const results = new Array(inputs.length);
|
|
1929
|
+
let cursor = 0;
|
|
1930
|
+
let firstError;
|
|
1931
|
+
let failed = false;
|
|
1932
|
+
let stopping = false;
|
|
1933
|
+
const workerCount = Math.min(options.sourceWindow, inputs.length);
|
|
1934
|
+
const workers = Array.from({ length: workerCount }, async () => {
|
|
1935
|
+
while (!stopping && !options.signal?.aborted) {
|
|
1936
|
+
const index = cursor;
|
|
1937
|
+
cursor += 1;
|
|
1938
|
+
const input = inputs[index];
|
|
1939
|
+
if (!input) return;
|
|
1940
|
+
try {
|
|
1941
|
+
results[index] = await executeSourceSession(input, {
|
|
1942
|
+
...options,
|
|
1943
|
+
ruleRuntimes: options.createRuleRuntimes(input)
|
|
1944
|
+
});
|
|
1945
|
+
} catch (error) {
|
|
1946
|
+
stopping = true;
|
|
1947
|
+
if (!failed) {
|
|
1948
|
+
failed = true;
|
|
1949
|
+
firstError = error;
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
});
|
|
1954
|
+
await Promise.all(workers);
|
|
1955
|
+
if (failed) throw firstError;
|
|
1956
|
+
return results.filter((result) => result !== void 0);
|
|
1957
|
+
}
|
|
1958
|
+
function resolveSourceWindow(ruleConcurrency) {
|
|
1959
|
+
return Math.max(1, Math.min(ruleConcurrency, 4));
|
|
1960
|
+
}
|
|
1961
|
+
function closeSession(metrics) {
|
|
1962
|
+
if (!metrics) return;
|
|
1963
|
+
metrics.active -= 1;
|
|
1964
|
+
metrics.closed += 1;
|
|
1965
|
+
}
|
|
1966
|
+
function createExecutionTargets(input, targets, runtimes, cwd, cacheOwner) {
|
|
1967
|
+
const resolveIdentity = createTargetIdentityResolver(targets.map((target) => ({
|
|
1968
|
+
filePath: target.kind === "file" ? normalizeCachePath(cwd, input.path) : void 0,
|
|
1969
|
+
identity: target.identity,
|
|
1970
|
+
kind: target.kind,
|
|
1971
|
+
name: target.name,
|
|
1972
|
+
range: target.range
|
|
1973
|
+
})));
|
|
1974
|
+
return targets.map((target, targetIndex) => {
|
|
1975
|
+
return {
|
|
1976
|
+
executions: runtimes.map((runtime) => sourceExecution(runtime, target)).filter((execution) => execution !== void 0),
|
|
1977
|
+
target: {
|
|
1978
|
+
activeFilePath: input.path,
|
|
1979
|
+
cacheOwner,
|
|
1980
|
+
configHash: input.configHash,
|
|
1981
|
+
identity: resolveIdentity({
|
|
1982
|
+
filePath: target.kind === "file" ? normalizeCachePath(cwd, input.path) : void 0,
|
|
1983
|
+
identity: target.identity,
|
|
1984
|
+
kind: target.kind,
|
|
1985
|
+
name: target.name,
|
|
1986
|
+
range: target.range
|
|
1987
|
+
}, targetIndex),
|
|
1988
|
+
kind: target.kind,
|
|
1989
|
+
language: target.language,
|
|
1990
|
+
loc: target.loc,
|
|
1991
|
+
metadata: target.metadata,
|
|
1992
|
+
name: target.name,
|
|
1993
|
+
origin: target.origin,
|
|
1994
|
+
range: target.range,
|
|
1995
|
+
text: target.text
|
|
1996
|
+
},
|
|
1997
|
+
targetIndex
|
|
1998
|
+
};
|
|
1999
|
+
}).filter((target) => target.executions.length > 0);
|
|
2000
|
+
}
|
|
2001
|
+
function createProjectSnapshot(input, language, path, contentHash, targets) {
|
|
2002
|
+
return {
|
|
2003
|
+
configHash: input.configHash,
|
|
2004
|
+
file: {
|
|
2005
|
+
contentHash,
|
|
2006
|
+
language,
|
|
2007
|
+
path,
|
|
2008
|
+
targetCount: targets.length
|
|
2009
|
+
},
|
|
2010
|
+
fileIndex: input.fileIndex,
|
|
2011
|
+
targets: targets.map((target) => ({
|
|
2012
|
+
descriptor: {
|
|
2013
|
+
filePath: target.file.path,
|
|
2014
|
+
identity: target.identity,
|
|
2015
|
+
kind: target.kind,
|
|
2016
|
+
...target.name === void 0 ? {} : { name: target.name },
|
|
2017
|
+
...target.range === void 0 ? {} : { range: { ...target.range } }
|
|
2018
|
+
},
|
|
2019
|
+
semanticHash: stableHash({
|
|
2020
|
+
language: target.language,
|
|
2021
|
+
loc: target.loc,
|
|
2022
|
+
metadata: target.metadata,
|
|
2023
|
+
name: target.name,
|
|
2024
|
+
origin: target.origin,
|
|
2025
|
+
range: target.range,
|
|
2026
|
+
text: target.text
|
|
2027
|
+
})
|
|
2028
|
+
}))
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
function createSourceJobs(input, targets) {
|
|
2032
|
+
return targets.flatMap(({ executions, target, targetIndex }) => executions.map((execution) => {
|
|
2033
|
+
const ruleId = execution.runtime.enabledRule.id;
|
|
2034
|
+
return {
|
|
2035
|
+
execution,
|
|
2036
|
+
jobRef: {
|
|
2037
|
+
id: stableHash({
|
|
2038
|
+
fileIndex: input.fileIndex,
|
|
2039
|
+
input: input.path,
|
|
2040
|
+
ruleId,
|
|
2041
|
+
targetIdentity: target.identity,
|
|
2042
|
+
targetIndex
|
|
2043
|
+
}),
|
|
2044
|
+
index: 0,
|
|
2045
|
+
inputPath: input.path,
|
|
2046
|
+
ruleId,
|
|
2047
|
+
target: {
|
|
2048
|
+
identity: target.identity,
|
|
2049
|
+
kind: target.kind,
|
|
2050
|
+
name: target.name
|
|
2051
|
+
}
|
|
2052
|
+
},
|
|
2053
|
+
orderKey: {
|
|
2054
|
+
inputIndex: input.fileIndex,
|
|
2055
|
+
ruleIndex: execution.runtime.ruleIndex,
|
|
2056
|
+
scope: "source",
|
|
2057
|
+
targetIndex
|
|
2058
|
+
},
|
|
2059
|
+
target
|
|
2060
|
+
};
|
|
2061
|
+
}));
|
|
2062
|
+
}
|
|
2063
|
+
function failureMessage(error, fallback) {
|
|
2064
|
+
try {
|
|
2065
|
+
return errorMessageFrom(error) ?? fallback;
|
|
2066
|
+
} catch {
|
|
2067
|
+
return fallback;
|
|
997
2068
|
}
|
|
2069
|
+
}
|
|
2070
|
+
function fileFailure(input, kind, message) {
|
|
998
2071
|
return {
|
|
999
|
-
|
|
1000
|
-
|
|
2072
|
+
file: {
|
|
2073
|
+
index: input.fileIndex,
|
|
2074
|
+
path: input.path
|
|
2075
|
+
},
|
|
2076
|
+
kind,
|
|
2077
|
+
message
|
|
1001
2078
|
};
|
|
1002
2079
|
}
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
const resolveTargetIdentity = createTargetIdentityResolver(targets.map((target) => toTargetIdentityInput(cwd, preparedFile.file.path, target)));
|
|
1009
|
-
for (const target of targets) target.identity = resolveTargetIdentity(toTargetIdentityInput(cwd, preparedFile.file.path, target));
|
|
1010
|
-
const filePlan = {
|
|
1011
|
-
id: `source:${preparedFile.file.path}`,
|
|
1012
|
-
index: fileOffset + 1,
|
|
1013
|
-
kind: "source",
|
|
1014
|
-
path: preparedFile.file.path,
|
|
1015
|
-
planned: 0,
|
|
1016
|
-
preparedFile,
|
|
1017
|
-
targets
|
|
1018
|
-
};
|
|
1019
|
-
filePlan.planned = calculateFilePlanExecutions(filePlan);
|
|
1020
|
-
return filePlan;
|
|
1021
|
-
});
|
|
1022
|
-
}
|
|
1023
|
-
function calculateFilePlanExecutions(filePlan) {
|
|
1024
|
-
return filePlan.targets.reduce((total, target) => total + target.executions.length, 0);
|
|
1025
|
-
}
|
|
1026
|
-
function collectExecutionTargets(preparedFile) {
|
|
1027
|
-
const targets = [];
|
|
1028
|
-
for (const sourceTarget of preparedFile.targets) {
|
|
1029
|
-
const executions = preparedFile.ruleRuntimes.map((runtime) => {
|
|
1030
|
-
return createSourceTargetExecution(runtime, sourceTarget);
|
|
1031
|
-
}).filter((execution) => execution !== void 0);
|
|
1032
|
-
if (executions.length === 0) continue;
|
|
1033
|
-
targets.push({
|
|
1034
|
-
activeFilePath: preparedFile.file.path,
|
|
1035
|
-
cacheFilePaths: [preparedFile.file.path],
|
|
1036
|
-
configHash: preparedFile.configHash,
|
|
1037
|
-
executions,
|
|
1038
|
-
identity: sourceTarget.identity,
|
|
1039
|
-
kind: sourceTarget.kind,
|
|
1040
|
-
language: sourceTarget.language,
|
|
1041
|
-
loc: sourceTarget.loc,
|
|
1042
|
-
metadata: sourceTarget.metadata,
|
|
1043
|
-
name: sourceTarget.name,
|
|
1044
|
-
origin: sourceTarget.origin,
|
|
1045
|
-
range: sourceTarget.range,
|
|
1046
|
-
text: sourceTarget.text
|
|
1047
|
-
});
|
|
1048
|
-
}
|
|
1049
|
-
return targets;
|
|
2080
|
+
function openSession(metrics) {
|
|
2081
|
+
if (!metrics) return;
|
|
2082
|
+
metrics.active += 1;
|
|
2083
|
+
metrics.opened += 1;
|
|
2084
|
+
metrics.maximumActive = Math.max(metrics.maximumActive, metrics.active);
|
|
1050
2085
|
}
|
|
1051
|
-
function
|
|
2086
|
+
function sourceExecution(runtime, target) {
|
|
1052
2087
|
if (runtime.handlers.onTargetWith) return {
|
|
1053
2088
|
run: () => runtime.handlers.onTargetWith?.(target),
|
|
1054
2089
|
runtime
|
|
@@ -1066,13 +2101,56 @@ function createSourceTargetExecution(runtime, target) {
|
|
|
1066
2101
|
runtime
|
|
1067
2102
|
};
|
|
1068
2103
|
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
2104
|
+
//#endregion
|
|
2105
|
+
//#region src/core/targets/directory.ts
|
|
2106
|
+
function createDirectoryJobs(input, runtimes) {
|
|
2107
|
+
const target = input.target;
|
|
2108
|
+
return runtimes.flatMap((runtime) => {
|
|
2109
|
+
const execution = directoryExecution(runtime, target);
|
|
2110
|
+
if (!execution) return [];
|
|
2111
|
+
const ruleId = runtime.enabledRule.id;
|
|
2112
|
+
return [{
|
|
2113
|
+
execution,
|
|
2114
|
+
jobRef: {
|
|
2115
|
+
id: stableHash({
|
|
2116
|
+
directoryIndex: input.directoryIndex,
|
|
2117
|
+
input: target.path,
|
|
2118
|
+
ruleId,
|
|
2119
|
+
targetIdentity: target.path
|
|
2120
|
+
}),
|
|
2121
|
+
index: 0,
|
|
2122
|
+
inputPath: target.path,
|
|
2123
|
+
ruleId,
|
|
2124
|
+
target: {
|
|
2125
|
+
identity: target.path,
|
|
2126
|
+
kind: "directory"
|
|
2127
|
+
}
|
|
2128
|
+
},
|
|
2129
|
+
orderKey: {
|
|
2130
|
+
inputIndex: input.directoryIndex,
|
|
2131
|
+
ruleIndex: runtime.ruleIndex,
|
|
2132
|
+
scope: "directory",
|
|
2133
|
+
targetIndex: 0
|
|
2134
|
+
},
|
|
2135
|
+
target: {
|
|
2136
|
+
activeFilePath: target.path,
|
|
2137
|
+
configHash: input.configHash,
|
|
2138
|
+
identity: target.path,
|
|
2139
|
+
kind: "directory",
|
|
2140
|
+
language: "directory",
|
|
2141
|
+
text: target.path
|
|
2142
|
+
}
|
|
2143
|
+
}];
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
function directoryExecution(runtime, target) {
|
|
2147
|
+
if (runtime.handlers.onTargetWith) return {
|
|
2148
|
+
run: () => runtime.handlers.onTargetWith?.(target),
|
|
2149
|
+
runtime
|
|
2150
|
+
};
|
|
2151
|
+
if (runtime.handlers.onTargetDirectory) return {
|
|
2152
|
+
run: () => runtime.handlers.onTargetDirectory?.(target),
|
|
2153
|
+
runtime
|
|
1076
2154
|
};
|
|
1077
2155
|
}
|
|
1078
2156
|
//#endregion
|
|
@@ -1103,327 +2181,160 @@ var AlintRunError = class extends Error {
|
|
|
1103
2181
|
}
|
|
1104
2182
|
};
|
|
1105
2183
|
async function runAlint(options = {}) {
|
|
2184
|
+
const clock = Date.now;
|
|
1106
2185
|
const timeoutMs = resolveRuleExecutionTimeout(options.runner?.timeoutMs);
|
|
1107
2186
|
const concurrency = resolveRuleConcurrency(options.runner?.ruleConcurrency);
|
|
2187
|
+
const prepareStartedAt = clock();
|
|
2188
|
+
options.progress?.onPrepareStart?.({ startedAt: prepareStartedAt });
|
|
2189
|
+
const preparation = prepareRun(options);
|
|
2190
|
+
options.progress?.onPrepareEnd?.({
|
|
2191
|
+
endedAt: clock(),
|
|
2192
|
+
filesTotal: preparation.files.length,
|
|
2193
|
+
startedAt: prepareStartedAt
|
|
2194
|
+
});
|
|
2195
|
+
const runProgress = createRunProgress(preparation.files.length);
|
|
2196
|
+
const runStartedAt = clock();
|
|
2197
|
+
options.progress?.onExecuteStart?.({
|
|
2198
|
+
progress: runProgress.snapshot(),
|
|
2199
|
+
startedAt: runStartedAt
|
|
2200
|
+
});
|
|
1108
2201
|
const cwd$1 = options.cwd ?? cwd();
|
|
1109
|
-
const config = options.config ?? [];
|
|
1110
2202
|
const setupConfig = options.setupConfig ?? {
|
|
1111
2203
|
providers: [],
|
|
1112
2204
|
version: 1
|
|
1113
2205
|
};
|
|
1114
|
-
const clock = Date.now;
|
|
1115
2206
|
const src = createSourceRuntime();
|
|
1116
2207
|
const normalizedCacheConfig = normalizeRunnerCacheConfig(options.runner?.cache, cwd$1);
|
|
1117
2208
|
const cacheStore = await createCacheStore({
|
|
1118
2209
|
cwd: cwd$1,
|
|
1119
2210
|
enabled: normalizedCacheConfig.enabled,
|
|
1120
|
-
location: normalizedCacheConfig.location
|
|
2211
|
+
location: normalizedCacheConfig.location,
|
|
2212
|
+
readOnly: options.cacheOnly
|
|
1121
2213
|
});
|
|
1122
|
-
const cacheContext = {
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
outputLanguage: options.outputLanguage,
|
|
1129
|
-
setupConfig
|
|
1130
|
-
}),
|
|
1131
|
-
store: cacheStore
|
|
1132
|
-
};
|
|
1133
|
-
const pendingFiles = [...options.files ?? []];
|
|
1134
|
-
const pendingDirectories = [...options.directories ?? []];
|
|
1135
|
-
const files = (await Promise.all(pendingFiles.map(async (filePath) => {
|
|
1136
|
-
const file = await src.readFile(resolve(cwd$1, filePath));
|
|
1137
|
-
const resolvedConfig = resolveConfigForFile(file.path, config, { cwd: cwd$1 });
|
|
1138
|
-
if (resolvedConfig.ignored) return;
|
|
1139
|
-
const effectiveConfig = resolvedConfig.config;
|
|
1140
|
-
const languageRegistry = createBuiltInLanguageRegistry();
|
|
1141
|
-
for (const plugin of Object.values(effectiveConfig.plugins)) for (const language of Object.values(plugin.languages ?? {})) registerLanguage(languageRegistry, language);
|
|
1142
|
-
const language = resolveLanguage(file, languageRegistry, { language: effectiveConfig.language });
|
|
1143
|
-
const targets = await language.extract(file, {
|
|
1144
|
-
cwd: cwd$1,
|
|
1145
|
-
languageOptions: effectiveConfig.languageOptions,
|
|
1146
|
-
src
|
|
1147
|
-
});
|
|
1148
|
-
const registry = buildRuleRegistry(effectiveConfig);
|
|
1149
|
-
const ruleRuntimes = createRuleRuntimes({
|
|
1150
|
-
cwd: cwd$1,
|
|
1151
|
-
effectiveAgent: effectiveConfig.agent,
|
|
1152
|
-
effectiveSettings: effectiveConfig.settings,
|
|
1153
|
-
options,
|
|
1154
|
-
progress: options.progress,
|
|
1155
|
-
registry,
|
|
1156
|
-
setupConfig,
|
|
1157
|
-
src
|
|
1158
|
-
});
|
|
1159
|
-
return {
|
|
1160
|
-
configHash: stableHash({
|
|
1161
|
-
language: effectiveConfig.language,
|
|
1162
|
-
languageOptions: effectiveConfig.languageOptions,
|
|
1163
|
-
processor: effectiveConfig.processor,
|
|
1164
|
-
resolvedLanguage: language.name,
|
|
1165
|
-
settings: effectiveConfig.settings
|
|
1166
|
-
}),
|
|
1167
|
-
file,
|
|
1168
|
-
ruleRuntimes,
|
|
1169
|
-
targets
|
|
1170
|
-
};
|
|
1171
|
-
}))).filter((file) => file !== void 0);
|
|
1172
|
-
const directories = pendingDirectories.map((directoryPath) => {
|
|
1173
|
-
const target = {
|
|
1174
|
-
kind: "directory",
|
|
1175
|
-
path: resolve(cwd$1, directoryPath)
|
|
1176
|
-
};
|
|
1177
|
-
const resolvedConfig = resolveConfigForDirectory(target.path, config, { cwd: cwd$1 });
|
|
1178
|
-
if (resolvedConfig.ignored) return;
|
|
1179
|
-
const effectiveConfig = resolvedConfig.config;
|
|
1180
|
-
const registry = buildRuleRegistry(effectiveConfig);
|
|
1181
|
-
return {
|
|
1182
|
-
configHash: stableHash({ settings: effectiveConfig.settings }),
|
|
1183
|
-
ruleRuntimes: createRuleRuntimes({
|
|
1184
|
-
cwd: cwd$1,
|
|
1185
|
-
effectiveAgent: effectiveConfig.agent,
|
|
1186
|
-
effectiveSettings: effectiveConfig.settings,
|
|
1187
|
-
options,
|
|
1188
|
-
progress: options.progress,
|
|
1189
|
-
registry,
|
|
1190
|
-
setupConfig,
|
|
1191
|
-
src
|
|
1192
|
-
}),
|
|
1193
|
-
target
|
|
1194
|
-
};
|
|
1195
|
-
}).filter((directory) => directory !== void 0);
|
|
1196
|
-
const resolvedProjectConfig = resolveConfigForProject(cwd$1, config, { cwd: cwd$1 });
|
|
1197
|
-
const projectConfig = resolvedProjectConfig.config;
|
|
1198
|
-
const projectRuleRuntimes = resolvedProjectConfig.ignored ? [] : createRuleRuntimes({
|
|
2214
|
+
const cacheContext = { modelHash: stableHash({
|
|
2215
|
+
modelOverride: options.modelOverride,
|
|
2216
|
+
outputLanguage: options.outputLanguage,
|
|
2217
|
+
setupConfig
|
|
2218
|
+
}) };
|
|
2219
|
+
const createRuntimes = (input) => createRuleRuntimes({
|
|
1199
2220
|
cwd: cwd$1,
|
|
1200
|
-
effectiveAgent:
|
|
1201
|
-
effectiveSettings:
|
|
1202
|
-
options,
|
|
2221
|
+
effectiveAgent: input.agent,
|
|
2222
|
+
effectiveSettings: input.settings,
|
|
1203
2223
|
progress: options.progress,
|
|
1204
|
-
|
|
2224
|
+
rules: input.rules,
|
|
2225
|
+
runOptions: options,
|
|
2226
|
+
runProgress,
|
|
1205
2227
|
setupConfig,
|
|
1206
2228
|
src
|
|
1207
2229
|
});
|
|
1208
|
-
const
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
2230
|
+
const scheduler = new RuleScheduler({
|
|
2231
|
+
clock,
|
|
2232
|
+
concurrency,
|
|
2233
|
+
execute: (job, _startedAt) => executeRuleJob(job, {
|
|
2234
|
+
cache: cacheContext,
|
|
2235
|
+
cacheOnly: options.cacheOnly,
|
|
2236
|
+
progress: options.progress,
|
|
2237
|
+
runProgress,
|
|
2238
|
+
runSignal: options.signal,
|
|
2239
|
+
timeoutMs
|
|
2240
|
+
}),
|
|
2241
|
+
progress: runProgress,
|
|
2242
|
+
reporter: options.progress,
|
|
2243
|
+
signal: options.signal
|
|
1216
2244
|
});
|
|
1217
|
-
const
|
|
1218
|
-
|
|
1219
|
-
...directoryPlans,
|
|
1220
|
-
...projectPlan ? [projectPlan] : []
|
|
1221
|
-
]);
|
|
1222
|
-
const runStartedAt = clock();
|
|
1223
|
-
const settledOutcomes = new Array(jobs.length);
|
|
2245
|
+
const outcomes = [];
|
|
2246
|
+
const fileFailures = [];
|
|
1224
2247
|
let infrastructureError;
|
|
1225
2248
|
let infrastructureFailed = false;
|
|
1226
|
-
|
|
1227
|
-
jobsTotal: jobs.length,
|
|
1228
|
-
startedAt: runStartedAt
|
|
1229
|
-
});
|
|
1230
|
-
for (const job of jobs) options.progress?.onJobQueued?.({ job: job.job });
|
|
2249
|
+
let admissionFailed = false;
|
|
1231
2250
|
try {
|
|
1232
|
-
|
|
1233
|
-
const
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
|
-
|
|
2251
|
+
const projectRuntimes = preparation.project ? createRuntimes(preparation.project) : [];
|
|
2252
|
+
const projectBuilder = preparation.project && projectRuntimes.some((runtime) => runtime.handlers.onTargetWith || runtime.handlers.onTargetProject) ? new ProjectIndexBuilder(preparation.project.root) : void 0;
|
|
2253
|
+
const sourceResults = await executeSourceSessions(preparation.files, {
|
|
2254
|
+
cacheStore,
|
|
2255
|
+
createRuleRuntimes: createRuntimes,
|
|
2256
|
+
cwd: cwd$1,
|
|
2257
|
+
progress: options.progress,
|
|
2258
|
+
projectSnapshots: projectBuilder !== void 0,
|
|
2259
|
+
scheduler,
|
|
2260
|
+
signal: options.signal,
|
|
2261
|
+
sourceWindow: resolveSourceWindow(concurrency),
|
|
2262
|
+
src
|
|
2263
|
+
});
|
|
2264
|
+
for (const result of sourceResults) {
|
|
2265
|
+
outcomes.push(...result.outcomes);
|
|
2266
|
+
if (result.failure) fileFailures.push(result.failure);
|
|
2267
|
+
}
|
|
2268
|
+
if (projectBuilder) {
|
|
2269
|
+
for (const result of [...sourceResults].sort((left, right) => (left.project?.fileIndex ?? Number.MAX_SAFE_INTEGER) - (right.project?.fileIndex ?? Number.MAX_SAFE_INTEGER))) if (result.project) projectBuilder.add(result.project);
|
|
2270
|
+
}
|
|
2271
|
+
const directoryBatches = preparation.directories.map((input) => {
|
|
2272
|
+
return scheduler.schedule(createDirectoryJobs(input, createRuntimes(input)));
|
|
2273
|
+
});
|
|
2274
|
+
outcomes.push(...(await Promise.all(directoryBatches.map((batch) => batch.outcomes))).flat());
|
|
2275
|
+
if (fileFailures.length === 0 && preparation.project && !options.signal?.aborted) {
|
|
2276
|
+
if (projectBuilder) {
|
|
2277
|
+
const project = createProjectJobs({
|
|
2278
|
+
cacheStore,
|
|
2279
|
+
configHash: preparation.project.configHash,
|
|
2280
|
+
project: projectBuilder.build(),
|
|
2281
|
+
runtimes: projectRuntimes
|
|
2282
|
+
});
|
|
2283
|
+
const projectOwner = project.owner;
|
|
2284
|
+
const batch = scheduler.schedule(project.jobs);
|
|
2285
|
+
outcomes.push(...await batch.outcomes);
|
|
2286
|
+
projectOwner?.commit({ mode: options.signal?.aborted ? "merge" : "replace" });
|
|
2287
|
+
} else if (!options.cacheOnly) cacheStore.beginOwner({
|
|
2288
|
+
kind: "project",
|
|
2289
|
+
path: preparation.project.root
|
|
2290
|
+
}).commit({ mode: "replace" });
|
|
2291
|
+
}
|
|
2292
|
+
} catch (error) {
|
|
2293
|
+
scheduler.cancelWithError(error);
|
|
2294
|
+
infrastructureError = error;
|
|
2295
|
+
infrastructureFailed = true;
|
|
2296
|
+
admissionFailed = true;
|
|
1274
2297
|
} finally {
|
|
1275
|
-
|
|
2298
|
+
try {
|
|
2299
|
+
await scheduler.close();
|
|
2300
|
+
} catch (error) {
|
|
2301
|
+
infrastructureError ??= error;
|
|
2302
|
+
infrastructureFailed = true;
|
|
2303
|
+
}
|
|
2304
|
+
if (!admissionFailed && !options.cacheOnly) try {
|
|
2305
|
+
await cacheStore.reconcile();
|
|
2306
|
+
} catch {}
|
|
1276
2307
|
}
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
cache: "miss",
|
|
1283
|
-
job: jobs[index],
|
|
1284
|
-
state: "cancelled"
|
|
1285
|
-
});
|
|
1286
|
-
const failedOutcomes = outcomes.filter((outcome) => outcome.state === "failed");
|
|
1287
|
-
const failures = failedOutcomes.map((outcome) => outcome.failure);
|
|
2308
|
+
runProgress.finalize();
|
|
2309
|
+
if (admissionFailed) throw infrastructureError;
|
|
2310
|
+
outcomes.sort((left, right) => compareJobOrder(left.orderKey, right.orderKey));
|
|
2311
|
+
const ruleFailures = outcomes.flatMap((outcome) => outcome.state === "failed" ? [outcome.failure] : []);
|
|
2312
|
+
const failures = [...fileFailures.sort((left, right) => left.file.index - right.file.index), ...ruleFailures];
|
|
1288
2313
|
const result = {
|
|
1289
|
-
diagnostics: outcomes.flatMap((outcome) => outcome.
|
|
2314
|
+
diagnostics: outcomes.flatMap((outcome) => outcome.diagnostics),
|
|
1290
2315
|
execution: executionCounts(outcomes),
|
|
1291
2316
|
usage: runUsage(outcomes)
|
|
1292
2317
|
};
|
|
2318
|
+
options.progress?.onExecuteEnd?.({
|
|
2319
|
+
endedAt: clock(),
|
|
2320
|
+
progress: runProgress.snapshot()
|
|
2321
|
+
});
|
|
1293
2322
|
options.progress?.onRunEnd?.({
|
|
1294
|
-
diagnostics: result.diagnostics,
|
|
2323
|
+
diagnostics: snapshotDiagnostics(result.diagnostics),
|
|
1295
2324
|
endedAt: clock(),
|
|
1296
|
-
execution: result.execution,
|
|
2325
|
+
execution: { ...result.execution },
|
|
2326
|
+
progress: runProgress.snapshot(),
|
|
1297
2327
|
startedAt: runStartedAt,
|
|
1298
|
-
usage: result.usage
|
|
2328
|
+
usage: snapshotRunUsage(result.usage)
|
|
1299
2329
|
});
|
|
1300
2330
|
if (infrastructureFailed) throw infrastructureError;
|
|
1301
2331
|
if (options.signal?.aborted) throw new AlintAbortError(result, { cause: options.signal.reason });
|
|
1302
|
-
if (failures.length > 0) throw new AlintRunError(`${failures.length} rule execution${failures.length === 1 ? "" : "s"} failed.`, result, {
|
|
1303
|
-
cause: new AggregateError(
|
|
2332
|
+
if (failures.length > 0) throw new AlintRunError(fileFailures.length > 0 ? `${failures.length} alint execution${failures.length === 1 ? "" : "s"} failed.` : `${failures.length} rule execution${failures.length === 1 ? "" : "s"} failed.`, result, {
|
|
2333
|
+
cause: new AggregateError(failures.map((failure) => new Error(failure.message)), "Alint execution failures."),
|
|
1304
2334
|
failures
|
|
1305
2335
|
});
|
|
1306
2336
|
return result;
|
|
1307
2337
|
}
|
|
1308
|
-
function createRuleRuntimes(options) {
|
|
1309
|
-
return options.registry.enabledRules.map((enabledRule) => {
|
|
1310
|
-
const executionState = new AsyncLocalStorage();
|
|
1311
|
-
const context = {
|
|
1312
|
-
agent: options.effectiveAgent ? withAgentRetry((request) => options.effectiveAgent({
|
|
1313
|
-
...request,
|
|
1314
|
-
signal: combineAbortSignals(executionState.getStore()?.signal, request.signal)
|
|
1315
|
-
}), options.options.runner?.agentRetries, { onRetry: ({ attempt, maxAttempts }) => {
|
|
1316
|
-
const state = executionState.getStore();
|
|
1317
|
-
if (!state) return;
|
|
1318
|
-
const startedAt = Date.now();
|
|
1319
|
-
try {
|
|
1320
|
-
options.progress?.onJobRetry?.({
|
|
1321
|
-
attempt,
|
|
1322
|
-
job: state.job,
|
|
1323
|
-
maxAttempts,
|
|
1324
|
-
startedAt
|
|
1325
|
-
});
|
|
1326
|
-
} catch (cause) {
|
|
1327
|
-
if (!state.reporterFailed) {
|
|
1328
|
-
state.reporterCause = cause;
|
|
1329
|
-
state.reporterFailed = true;
|
|
1330
|
-
}
|
|
1331
|
-
throw cause;
|
|
1332
|
-
}
|
|
1333
|
-
} }) : void 0,
|
|
1334
|
-
cwd: options.cwd,
|
|
1335
|
-
id: enabledRule.id,
|
|
1336
|
-
localId: enabledRule.localId,
|
|
1337
|
-
logger: { debug: () => {} },
|
|
1338
|
-
metering: { recordUsage: (record) => {
|
|
1339
|
-
const state = executionState.getStore();
|
|
1340
|
-
if (!state) throw new Error("Cannot record usage outside an active rule job.");
|
|
1341
|
-
const usageRecord = {
|
|
1342
|
-
...record,
|
|
1343
|
-
ruleId: record.ruleId ?? enabledRule.id
|
|
1344
|
-
};
|
|
1345
|
-
state.bucket.usage.push(usageRecord);
|
|
1346
|
-
try {
|
|
1347
|
-
options.progress?.onUsage?.({
|
|
1348
|
-
job: state.job,
|
|
1349
|
-
record: usageRecord
|
|
1350
|
-
});
|
|
1351
|
-
} catch (cause) {
|
|
1352
|
-
if (!state.reporterFailed) {
|
|
1353
|
-
state.reporterCause = cause;
|
|
1354
|
-
state.reporterFailed = true;
|
|
1355
|
-
}
|
|
1356
|
-
throw cause;
|
|
1357
|
-
}
|
|
1358
|
-
} },
|
|
1359
|
-
model: async (selector) => {
|
|
1360
|
-
const request = options.options.modelOverride ?? (typeof selector === "string" ? selector : void 0);
|
|
1361
|
-
const requirement = mergeModelRequirement(enabledRule.rule.model, typeof selector === "string" ? void 0 : selector);
|
|
1362
|
-
const resolvedModel = resolveModel(options.setupConfig, {
|
|
1363
|
-
request,
|
|
1364
|
-
requirement,
|
|
1365
|
-
ruleId: enabledRule.id
|
|
1366
|
-
});
|
|
1367
|
-
const state = executionState.getStore();
|
|
1368
|
-
if (state) state.currentModel = toDiagnosticModel(resolvedModel, request);
|
|
1369
|
-
return resolvedModel;
|
|
1370
|
-
},
|
|
1371
|
-
options: enabledRule.options,
|
|
1372
|
-
outputLanguage: options.options.outputLanguage,
|
|
1373
|
-
report: (descriptor) => {
|
|
1374
|
-
const state = executionState.getStore();
|
|
1375
|
-
if (!state) throw new Error("Cannot report a diagnostic outside an active rule job.");
|
|
1376
|
-
const filePath = descriptor.filePath ?? state.activeFilePath;
|
|
1377
|
-
if (!filePath) throw new Error(`Diagnostic for rule "${enabledRule.id}" is missing filePath.`);
|
|
1378
|
-
const diagnosticModel = state.currentModel ? { ...state.currentModel } : void 0;
|
|
1379
|
-
state.currentModel = void 0;
|
|
1380
|
-
const diagnostic = {
|
|
1381
|
-
evidence: descriptor.evidence,
|
|
1382
|
-
filePath,
|
|
1383
|
-
loc: descriptor.loc,
|
|
1384
|
-
message: descriptor.message,
|
|
1385
|
-
model: diagnosticModel,
|
|
1386
|
-
ruleId: enabledRule.id,
|
|
1387
|
-
severity: enabledRule.severity
|
|
1388
|
-
};
|
|
1389
|
-
state.bucket.diagnostics.push(diagnostic);
|
|
1390
|
-
try {
|
|
1391
|
-
options.progress?.onDiagnostic?.({
|
|
1392
|
-
diagnostic,
|
|
1393
|
-
job: state.job
|
|
1394
|
-
});
|
|
1395
|
-
} catch (cause) {
|
|
1396
|
-
if (!state.reporterFailed) {
|
|
1397
|
-
state.reporterCause = cause;
|
|
1398
|
-
state.reporterFailed = true;
|
|
1399
|
-
}
|
|
1400
|
-
throw cause;
|
|
1401
|
-
}
|
|
1402
|
-
},
|
|
1403
|
-
settings: options.effectiveSettings,
|
|
1404
|
-
get signal() {
|
|
1405
|
-
return executionState.getStore()?.signal;
|
|
1406
|
-
},
|
|
1407
|
-
src: options.src
|
|
1408
|
-
};
|
|
1409
|
-
return {
|
|
1410
|
-
cacheable: enabledRule.rule.cache !== false,
|
|
1411
|
-
enabledRule,
|
|
1412
|
-
executionState,
|
|
1413
|
-
handlers: enabledRule.rule.create(context),
|
|
1414
|
-
ruleHash: stableHash({
|
|
1415
|
-
cache: enabledRule.rule.cache ?? true,
|
|
1416
|
-
cacheKey: enabledRule.rule.cacheKey,
|
|
1417
|
-
create: String(enabledRule.rule.create),
|
|
1418
|
-
id: enabledRule.id,
|
|
1419
|
-
localId: enabledRule.localId,
|
|
1420
|
-
model: enabledRule.rule.model,
|
|
1421
|
-
options: enabledRule.options,
|
|
1422
|
-
severity: enabledRule.severity
|
|
1423
|
-
})
|
|
1424
|
-
};
|
|
1425
|
-
});
|
|
1426
|
-
}
|
|
1427
2338
|
function executionCounts(outcomes) {
|
|
1428
2339
|
const counts = {
|
|
1429
2340
|
cached: 0,
|
|
@@ -1438,59 +2349,23 @@ function executionCounts(outcomes) {
|
|
|
1438
2349
|
for (const outcome of outcomes) counts[outcome.state] += 1;
|
|
1439
2350
|
return counts;
|
|
1440
2351
|
}
|
|
1441
|
-
function mergeCapabilities(base, extra) {
|
|
1442
|
-
if (!base && !extra) return;
|
|
1443
|
-
return [.../* @__PURE__ */ new Set([...base ?? [], ...extra ?? []])];
|
|
1444
|
-
}
|
|
1445
|
-
function mergeMinContextWindow(base, extra) {
|
|
1446
|
-
if (base === void 0) return extra;
|
|
1447
|
-
if (extra === void 0) return base;
|
|
1448
|
-
return Math.max(base, extra);
|
|
1449
|
-
}
|
|
1450
|
-
function mergeModelRequirement(base, extra) {
|
|
1451
|
-
if (!base && !extra) return;
|
|
1452
|
-
const capabilities = mergeCapabilities(base?.capabilities, extra?.capabilities);
|
|
1453
|
-
const params = {
|
|
1454
|
-
...base?.params ?? {},
|
|
1455
|
-
...extra?.params ?? {}
|
|
1456
|
-
};
|
|
1457
|
-
return {
|
|
1458
|
-
capabilities,
|
|
1459
|
-
minContextWindow: mergeMinContextWindow(base?.minContextWindow, extra?.minContextWindow),
|
|
1460
|
-
params: Object.keys(params).length > 0 ? params : void 0,
|
|
1461
|
-
size: extra?.size ?? base?.size
|
|
1462
|
-
};
|
|
1463
|
-
}
|
|
1464
|
-
async function reconcileCache(filePlans, cacheContext) {
|
|
1465
|
-
try {
|
|
1466
|
-
for (const filePlan of filePlans) {
|
|
1467
|
-
const file = filePlan.preparedFile.file;
|
|
1468
|
-
const normalizedPath = normalizeCachePath(cacheContext.cwd, file.path);
|
|
1469
|
-
const entries = [...cacheContext.fileEntryKeys.get(normalizedPath) ?? []];
|
|
1470
|
-
cacheContext.store.markFile(file.path, hashText(file.text), entries);
|
|
1471
|
-
}
|
|
1472
|
-
await cacheContext.store.reconcile();
|
|
1473
|
-
} catch {}
|
|
1474
|
-
}
|
|
1475
|
-
function resolveRuleConcurrency(ruleConcurrency) {
|
|
1476
|
-
if (ruleConcurrency === void 0) return 4;
|
|
1477
|
-
if (!Number.isFinite(ruleConcurrency) || !Number.isInteger(ruleConcurrency) || ruleConcurrency <= 0) throw new TypeError("Rule execution concurrency must be a finite positive integer.");
|
|
1478
|
-
return ruleConcurrency;
|
|
1479
|
-
}
|
|
1480
2352
|
function runUsage(outcomes) {
|
|
1481
2353
|
const live = [];
|
|
1482
2354
|
const cached = [];
|
|
1483
|
-
for (const outcome of outcomes) (outcome.state === "cached" ? cached : live).push(...outcome.
|
|
2355
|
+
for (const outcome of outcomes) (outcome.state === "cached" ? cached : live).push(...outcome.usage);
|
|
1484
2356
|
return {
|
|
1485
2357
|
...usageTotals(live),
|
|
1486
2358
|
...cached.length > 0 ? { cached: usageTotals(cached) } : {}
|
|
1487
2359
|
};
|
|
1488
2360
|
}
|
|
1489
|
-
function
|
|
2361
|
+
function snapshotRunUsage(usage) {
|
|
1490
2362
|
return {
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
2363
|
+
...usage,
|
|
2364
|
+
...usage.cached ? { cached: {
|
|
2365
|
+
...usage.cached,
|
|
2366
|
+
records: snapshotUsageRecords(usage.cached.records)
|
|
2367
|
+
} } : {},
|
|
2368
|
+
records: snapshotUsageRecords(usage.records)
|
|
1494
2369
|
};
|
|
1495
2370
|
}
|
|
1496
2371
|
function usageTotals(records) {
|