@alint-js/core 0.0.29 → 0.0.30

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/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { r as withAgentRetry } from "./retry-C4sGjSE5.mjs";
1
+ import { o as withAgentRetry, t as combineAbortSignals } from "./agent-weXkvGkc.mjs";
2
2
  import { a as sliceRange, i as sliceLines, n as createSourceFile, r as createSourceRuntime, t as extractJsSourceTargets } from "./extract-CIWssxNK.mjs";
3
3
  import { minimatch } from "minimatch";
4
4
  import { isAbsolute, relative, resolve } from "pathe";
@@ -6,10 +6,10 @@ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
6
6
  import { dirname, extname, isAbsolute as isAbsolute$1, join, relative as relative$1, resolve as resolve$1, sep } from "node:path";
7
7
  import { AsyncLocalStorage } from "node:async_hooks";
8
8
  import process, { cwd } from "node:process";
9
- import { errorCauseFrom, errorMessageFrom } from "@moeru/std/error";
10
9
  import { createHash, randomUUID } from "node:crypto";
11
10
  import { statSync } from "node:fs";
12
11
  import { array, check, description, is, literal, number, object, optional, pipe, record, string, union, unknown } from "valibot";
12
+ import { errorMessageFrom } from "@moeru/std/error";
13
13
  //#region src/config/config-array.ts
14
14
  const effectiveBasePathSymbol = Symbol("effectiveBasePath");
15
15
  const inheritedMatcherScopesSymbol = Symbol("inheritedMatcherScopes");
@@ -580,99 +580,177 @@ function statSyncIsDirectory(path) {
580
580
  return statSync(path).isDirectory();
581
581
  }
582
582
  //#endregion
583
- //#region src/core/targets/directory.ts
584
- function createDirectoryExecutionPlans(directories, fileOffset) {
585
- return directories.map((directory, directoryOffset) => {
586
- const executions = directory.ruleRuntimes.map((runtime) => {
587
- if (runtime.handlers.onTargetWith) return {
588
- run: () => runtime.handlers.onTargetWith?.(directory.target),
589
- runtime
590
- };
591
- if (runtime.handlers.onTargetDirectory) return {
592
- run: () => runtime.handlers.onTargetDirectory?.(directory.target),
593
- runtime
594
- };
595
- }).filter((execution) => execution !== void 0);
596
- const targets = executions.length === 0 ? [] : [{
597
- activeFilePath: directory.target.path,
598
- cacheFilePaths: [],
599
- configHash: directory.configHash,
600
- executions,
601
- identity: directory.target.path,
602
- kind: "directory",
603
- language: "directory",
604
- text: directory.target.path
605
- }];
606
- return {
607
- emitFileProgress: true,
608
- fileIndex: fileOffset + directoryOffset + 1,
609
- path: directory.target.path,
610
- planned: executions.length,
611
- targets
612
- };
613
- });
614
- }
615
- //#endregion
616
583
  //#region package.json
617
- var version = "0.0.29";
584
+ var version = "0.0.30";
618
585
  //#endregion
619
- //#region src/core/targets/execution.ts
620
- var AlintRuleExecutionError = class extends Error {
621
- failure;
622
- constructor(error, path) {
623
- const message = errorMessageFrom(error) ?? String(error);
624
- super(message, { cause: error });
625
- this.name = "AlintRuleExecutionError";
626
- this.failure = {
627
- filePath: path.file.path,
628
- message,
629
- ruleId: path.rule.id,
630
- target: {
631
- kind: path.target.kind,
632
- name: path.target.name
633
- }
634
- };
586
+ //#region src/core/execution/job.ts
587
+ var CacheReplayProcessingError = class {
588
+ cause;
589
+ constructor(cause) {
590
+ this.cause = cause;
635
591
  }
636
592
  };
637
- async function executeTargetPlans(options) {
638
- for (const plan of options.plans) await executeTargetPlan(plan, options);
593
+ function createRuleJobs(plans) {
594
+ const jobs = [];
595
+ const total = plans.reduce((sum, plan) => sum + plan.planned, 0);
596
+ for (const plan of plans) for (const target of plan.targets) for (const execution of target.executions) {
597
+ const ruleId = execution.runtime.enabledRule.id;
598
+ const index = jobs.length + 1;
599
+ jobs.push({
600
+ execution,
601
+ job: {
602
+ id: stableHash({
603
+ index,
604
+ planId: plan.id,
605
+ ruleId,
606
+ targetIdentity: target.identity
607
+ }),
608
+ index,
609
+ inputPath: plan.path,
610
+ ruleId,
611
+ target: {
612
+ identity: target.identity,
613
+ kind: target.kind,
614
+ name: target.name
615
+ },
616
+ total
617
+ },
618
+ target
619
+ });
620
+ }
621
+ return jobs;
639
622
  }
640
- function createExecutionCacheKey(runtime, target, path, cache) {
641
- if (!cache.enabled || !runtime.cacheable || target.cacheFilePaths.length === 0) return;
642
- return createCacheKey({
643
- alintVersion: version,
644
- configHash: target.configHash,
645
- filePath: normalizeCachePath(cache.cwd, path.file.path),
646
- modelHash: cache.modelHash,
647
- ruleHash: runtime.ruleHash,
648
- schemaVersion: 1,
649
- targetHash: createTargetHash(target),
650
- targetIdentity: target.identity,
651
- targetKind: target.kind
623
+ async function executeRuleJob(job, options) {
624
+ const bucket = {
625
+ diagnostics: [],
626
+ usage: []
627
+ };
628
+ if (options.runSignal?.aborted) return finish(job, options, bucket, {
629
+ cache: "miss",
630
+ state: "cancelled"
631
+ });
632
+ const cacheKey = createExecutionCacheKey(job, options.cache);
633
+ const cachedEntry = cacheKey ? options.cache.store.get(cacheKey) : void 0;
634
+ if (cacheKey && cachedEntry) {
635
+ rememberCacheEntry(options.cache, job.target.cacheFilePaths, cacheKey);
636
+ try {
637
+ replayCachedEntry(cachedEntry, bucket, job.job, options.progress);
638
+ } catch (error) {
639
+ if (!(error instanceof CacheReplayProcessingError)) throw error;
640
+ return finish(job, options, bucket, {
641
+ cache: "hit",
642
+ cause: error.cause,
643
+ failure: failure(error.cause, job, "cache-replay"),
644
+ state: "failed"
645
+ });
646
+ }
647
+ return finish(job, options, bucket, {
648
+ cache: "hit",
649
+ state: "cached"
650
+ });
651
+ }
652
+ if (options.cacheOnly) return finish(job, options, bucket, {
653
+ cache: "miss",
654
+ state: "skipped"
655
+ });
656
+ const controller = new AbortController();
657
+ let timedOut = false;
658
+ let deadlineError;
659
+ const forwardRunAbort = () => controller.abort(options.runSignal?.reason);
660
+ if (options.runSignal?.aborted) forwardRunAbort();
661
+ else options.runSignal?.addEventListener("abort", forwardRunAbort, { once: true });
662
+ const timer = options.timeoutMs == null ? void 0 : setTimeout(() => {
663
+ timedOut = true;
664
+ deadlineError = /* @__PURE__ */ new Error(`Rule execution timed out after ${options.timeoutMs}ms.`);
665
+ controller.abort(deadlineError);
666
+ }, options.timeoutMs);
667
+ const state = {
668
+ activeFilePath: job.target.activeFilePath,
669
+ bucket,
670
+ job: job.job,
671
+ reporterFailed: false,
672
+ signal: controller.signal
673
+ };
674
+ let handlerCause;
675
+ let handlerFailed = false;
676
+ try {
677
+ await job.execution.runtime.executionState.run(state, job.execution.run);
678
+ } catch (cause) {
679
+ if (state.reporterFailed) throw state.reporterCause;
680
+ handlerCause = cause;
681
+ handlerFailed = true;
682
+ } finally {
683
+ if (timer != null) clearTimeout(timer);
684
+ options.runSignal?.removeEventListener("abort", forwardRunAbort);
685
+ }
686
+ if (state.reporterFailed) throw state.reporterCause;
687
+ if (!timedOut && !handlerFailed && cacheKey) try {
688
+ options.cache.store.set(cacheKey, createCacheEntry(job, options.cache, bucket));
689
+ rememberCacheEntry(options.cache, job.target.cacheFilePaths, cacheKey);
690
+ } catch {}
691
+ if (options.runSignal?.aborted) return finish(job, options, bucket, {
692
+ cache: "miss",
693
+ state: "cancelled"
694
+ });
695
+ if (timedOut) return finish(job, options, bucket, {
696
+ cache: "miss",
697
+ cause: deadlineError,
698
+ failure: failure(deadlineError, job, "timeout"),
699
+ state: "failed"
700
+ });
701
+ if (handlerFailed) return finish(job, options, bucket, {
702
+ cache: "miss",
703
+ cause: handlerCause,
704
+ failure: failure(handlerCause, job, "handler"),
705
+ state: "failed"
706
+ });
707
+ return finish(job, options, bucket, {
708
+ cache: "miss",
709
+ state: "completed"
652
710
  });
653
711
  }
654
- function createProgressPath(filePath, ruleId, entry) {
712
+ function resolveRuleExecutionTimeout(timeoutMs) {
713
+ if (timeoutMs === void 0) return void 0;
714
+ if (!Number.isFinite(timeoutMs) || !Number.isInteger(timeoutMs) || timeoutMs <= 0) throw new TypeError("Rule execution timeout must be a finite positive integer.");
715
+ return timeoutMs;
716
+ }
717
+ function createCacheEntry(job, cache, bucket) {
655
718
  return {
656
- file: {
657
- index: entry.fileIndex,
658
- path: filePath,
659
- planned: entry.filePlanned,
660
- total: entry.fileTotal
661
- },
662
- rule: {
663
- id: ruleId,
664
- index: entry.ruleIndex,
665
- total: entry.ruleTotal
719
+ diagnostics: bucket.diagnostics,
720
+ filePath: normalizeCachePath(cache.cwd, job.job.inputPath),
721
+ fingerprint: {
722
+ alintVersion: version,
723
+ configHash: job.target.configHash,
724
+ modelHash: cache.modelHash,
725
+ ruleHash: job.execution.runtime.ruleHash
666
726
  },
667
727
  target: {
668
- index: entry.targetIndex,
669
- kind: entry.targetKind,
670
- name: entry.targetName,
671
- total: entry.targetTotal
672
- }
728
+ hash: createTargetHash(job),
729
+ identity: job.target.identity,
730
+ kind: job.target.kind,
731
+ loc: job.target.loc,
732
+ name: job.target.name,
733
+ range: job.target.range
734
+ },
735
+ usage: bucket.usage
673
736
  };
674
737
  }
675
- function createTargetHash(target) {
738
+ function createExecutionCacheKey(job, cache) {
739
+ if (!cache.enabled || !job.execution.runtime.cacheable || job.target.cacheFilePaths.length === 0) return void 0;
740
+ return createCacheKey({
741
+ alintVersion: version,
742
+ configHash: job.target.configHash,
743
+ filePath: normalizeCachePath(cache.cwd, job.job.inputPath),
744
+ modelHash: cache.modelHash,
745
+ ruleHash: job.execution.runtime.ruleHash,
746
+ schemaVersion: 1,
747
+ targetHash: createTargetHash(job),
748
+ targetIdentity: job.target.identity,
749
+ targetKind: job.target.kind
750
+ });
751
+ }
752
+ function createTargetHash(job) {
753
+ const target = job.target;
676
754
  return stableHash({
677
755
  language: target.language,
678
756
  loc: target.loc,
@@ -683,191 +761,28 @@ function createTargetHash(target) {
683
761
  text: target.text
684
762
  });
685
763
  }
686
- function emitErroredRuleEnd(progress, clock, path, startedAt, cache) {
687
- try {
688
- progress?.onRuleEnd?.({
689
- cache,
690
- endedAt: clock(),
691
- path,
692
- startedAt,
693
- state: "errored"
694
- });
695
- } catch {}
696
- }
697
- async function executeRule(execution, path, target, options) {
698
- const { cache, clock, counters, diagnostics, progress, signal, usage } = options;
699
- signal?.throwIfAborted();
700
- const startedAt = clock();
701
- const cacheKey = createExecutionCacheKey(execution.runtime, target, path, cache);
702
- const cachedEntry = cacheKey ? cache.store.get(cacheKey) : void 0;
703
- progress?.onRuleStart?.({
704
- path,
705
- startedAt
706
- });
707
- if (cacheKey && cachedEntry) {
708
- rememberCacheEntry(cache, target.cacheFilePaths, cacheKey);
709
- try {
710
- replayCachedEntry(cachedEntry, path, diagnostics, usage, progress);
711
- } catch (error) {
712
- counters.error();
713
- emitErroredRuleEnd(progress, clock, path, startedAt, "hit");
714
- throw new AlintRuleExecutionError(error, path);
715
- }
716
- counters.cache();
717
- progress?.onRuleEnd?.({
718
- cache: "hit",
719
- endedAt: clock(),
720
- path,
721
- startedAt,
722
- state: "completed"
723
- });
724
- return;
725
- }
726
- if (options.cacheOnly) {
727
- counters.skip();
728
- progress?.onRuleEnd?.({
729
- cache: "miss",
730
- endedAt: clock(),
731
- path,
732
- startedAt,
733
- state: "skipped"
734
- });
735
- return;
736
- }
737
- let handlerError;
738
- let handlerFailed = false;
739
- const cacheDiagnostics = cacheKey ? [] : void 0;
740
- const cacheUsage = cacheKey ? [] : void 0;
741
- try {
742
- await execution.runtime.executionState.run({
743
- activeFilePath: target.activeFilePath,
744
- cacheDiagnostics,
745
- cacheUsage,
746
- progressPath: path
747
- }, execution.run);
748
- } catch (error) {
749
- handlerError = error;
750
- handlerFailed = true;
751
- }
752
- if (handlerFailed) {
753
- signal?.throwIfAborted();
754
- counters.error();
755
- emitErroredRuleEnd(progress, clock, path, startedAt, "miss");
756
- throw new AlintRuleExecutionError(handlerError, path);
757
- }
758
- if (cacheKey) {
759
- cache.store.set(cacheKey, {
760
- diagnostics: cacheDiagnostics ?? [],
761
- filePath: normalizeCachePath(cache.cwd, path.file.path),
762
- fingerprint: {
763
- alintVersion: version,
764
- configHash: target.configHash,
765
- modelHash: cache.modelHash,
766
- ruleHash: execution.runtime.ruleHash
767
- },
768
- target: {
769
- hash: createTargetHash(target),
770
- identity: target.identity,
771
- kind: target.kind,
772
- loc: target.loc,
773
- name: target.name,
774
- range: target.range
775
- },
776
- usage: cacheUsage ?? []
777
- });
778
- rememberCacheEntry(cache, target.cacheFilePaths, cacheKey);
779
- }
780
- counters.complete();
781
- progress?.onRuleEnd?.({
782
- cache: "miss",
783
- endedAt: clock(),
784
- path,
785
- startedAt,
786
- state: "completed"
787
- });
764
+ function failure(cause, job, kind) {
765
+ return {
766
+ job: job.job,
767
+ kind,
768
+ message: errorMessageFrom(cause) ?? String(cause)
769
+ };
788
770
  }
789
- async function executeTargetPlan(plan, options) {
790
- const { clock, filesTotal, progress } = options;
791
- const fileStartedAt = clock();
792
- const fileProgress = {
793
- index: plan.fileIndex,
794
- path: plan.path,
795
- planned: plan.planned,
796
- total: filesTotal
771
+ function finish(job, options, bucket, terminal) {
772
+ const outcome = {
773
+ bucket,
774
+ job,
775
+ ...terminal
797
776
  };
798
- if (plan.emitFileProgress) progress?.onFileStart?.({
799
- file: fileProgress,
800
- startedAt: fileStartedAt
777
+ options.progress?.onJobEnd?.({
778
+ cache: outcome.cache,
779
+ endedAt: options.clock(),
780
+ failure: outcome.failure,
781
+ job: job.job,
782
+ startedAt: options.startedAt,
783
+ state: outcome.state
801
784
  });
802
- let planError;
803
- let planFailed = false;
804
- try {
805
- for (const [targetOffset, target] of plan.targets.entries()) {
806
- const targetPath = createProgressPath(plan.path, target.executions[0]?.runtime.enabledRule.id ?? "", {
807
- fileIndex: plan.fileIndex,
808
- filePlanned: plan.planned,
809
- fileTotal: filesTotal,
810
- ruleIndex: 1,
811
- ruleTotal: target.executions.length,
812
- targetIndex: targetOffset + 1,
813
- targetKind: target.kind,
814
- targetName: target.name,
815
- targetTotal: plan.targets.length
816
- });
817
- const targetStartedAt = clock();
818
- progress?.onTargetStart?.({
819
- path: targetPath,
820
- startedAt: targetStartedAt
821
- });
822
- let targetError;
823
- let targetFailed = false;
824
- try {
825
- for (const [executionOffset, execution] of target.executions.entries()) await executeRule(execution, createProgressPath(plan.path, execution.runtime.enabledRule.id, {
826
- fileIndex: plan.fileIndex,
827
- filePlanned: plan.planned,
828
- fileTotal: filesTotal,
829
- ruleIndex: executionOffset + 1,
830
- ruleTotal: target.executions.length,
831
- targetIndex: targetOffset + 1,
832
- targetKind: target.kind,
833
- targetName: target.name,
834
- targetTotal: plan.targets.length
835
- }), target, options);
836
- } catch (error) {
837
- targetError = error;
838
- targetFailed = true;
839
- }
840
- try {
841
- progress?.onTargetEnd?.({
842
- endedAt: clock(),
843
- path: targetPath,
844
- startedAt: targetStartedAt
845
- });
846
- } catch (error) {
847
- if (!targetFailed) {
848
- targetError = error;
849
- targetFailed = true;
850
- }
851
- }
852
- if (targetFailed) throw targetError;
853
- }
854
- } catch (error) {
855
- planError = error;
856
- planFailed = true;
857
- }
858
- if (plan.emitFileProgress) try {
859
- progress?.onFileEnd?.({
860
- endedAt: clock(),
861
- file: fileProgress,
862
- startedAt: fileStartedAt
863
- });
864
- } catch (error) {
865
- if (!planFailed) {
866
- planError = error;
867
- planFailed = true;
868
- }
869
- }
870
- if (planFailed) throw planError;
785
+ return outcome;
871
786
  }
872
787
  function rememberCacheEntry(cache, filePaths, cacheKey) {
873
788
  for (const filePath of filePaths) {
@@ -877,29 +792,69 @@ function rememberCacheEntry(cache, filePaths, cacheKey) {
877
792
  cache.fileEntryKeys.set(normalizedPath, entries);
878
793
  }
879
794
  }
880
- function replayCachedEntry(entry, path, diagnostics, usage, progress) {
881
- for (const cachedDiagnostic of entry.diagnostics) {
795
+ function replayCachedEntry(entry, bucket, job, progress) {
796
+ let diagnostics;
797
+ let usage;
798
+ try {
799
+ diagnostics = entry.diagnostics;
800
+ usage = entry.usage;
801
+ } catch (cause) {
802
+ throw new CacheReplayProcessingError(cause);
803
+ }
804
+ for (const cachedDiagnostic of diagnostics) {
882
805
  const diagnostic = {
883
806
  ...cachedDiagnostic,
884
807
  cached: true
885
808
  };
886
- diagnostics.push(diagnostic);
809
+ bucket.diagnostics.push(diagnostic);
887
810
  progress?.onDiagnostic?.({
888
811
  diagnostic,
889
- diagnostics: [...diagnostics],
890
- path
812
+ job
891
813
  });
892
814
  }
893
- for (const cachedUsage of entry.usage) {
894
- const record = usage.recordCached({ ...cachedUsage });
815
+ for (const record of usage) {
816
+ bucket.usage.push(record);
895
817
  progress?.onUsage?.({
896
- path,
897
- record,
898
- total: usage.toJSON()
818
+ job,
819
+ record
899
820
  });
900
821
  }
901
822
  }
902
823
  //#endregion
824
+ //#region src/core/targets/directory.ts
825
+ function createDirectoryExecutionPlans(directories, sourcePlanCount) {
826
+ return directories.map((directory, directoryOffset) => {
827
+ const executions = directory.ruleRuntimes.map((runtime) => {
828
+ if (runtime.handlers.onTargetWith) return {
829
+ run: () => runtime.handlers.onTargetWith?.(directory.target),
830
+ runtime
831
+ };
832
+ if (runtime.handlers.onTargetDirectory) return {
833
+ run: () => runtime.handlers.onTargetDirectory?.(directory.target),
834
+ runtime
835
+ };
836
+ }).filter((execution) => execution !== void 0);
837
+ const targets = executions.length === 0 ? [] : [{
838
+ activeFilePath: directory.target.path,
839
+ cacheFilePaths: [],
840
+ configHash: directory.configHash,
841
+ executions,
842
+ identity: directory.target.path,
843
+ kind: "directory",
844
+ language: "directory",
845
+ text: directory.target.path
846
+ }];
847
+ return {
848
+ id: `directory:${directory.target.path}`,
849
+ index: sourcePlanCount + directoryOffset + 1,
850
+ kind: "directory",
851
+ path: directory.target.path,
852
+ planned: executions.length,
853
+ targets
854
+ };
855
+ });
856
+ }
857
+ //#endregion
903
858
  //#region src/core/targets/project.ts
904
859
  function createProjectExecutionPlan(options) {
905
860
  const project = createProjectTarget(options.root, options.files);
@@ -943,8 +898,9 @@ function createProjectExecutionPlan(options) {
943
898
  })
944
899
  };
945
900
  return {
946
- emitFileProgress: false,
947
- fileIndex: 0,
901
+ id: `project:${options.root}`,
902
+ index: options.index,
903
+ kind: "project",
948
904
  path: options.root,
949
905
  planned: executions.length,
950
906
  targets: [target]
@@ -979,8 +935,9 @@ function createSourceExecutionPlans(files, cwd) {
979
935
  const resolveTargetIdentity = createTargetIdentityResolver(targets.map((target) => toTargetIdentityInput(cwd, preparedFile.file.path, target)));
980
936
  for (const target of targets) target.identity = resolveTargetIdentity(toTargetIdentityInput(cwd, preparedFile.file.path, target));
981
937
  const filePlan = {
982
- emitFileProgress: true,
983
- fileIndex: fileOffset + 1,
938
+ id: `source:${preparedFile.file.path}`,
939
+ index: fileOffset + 1,
940
+ kind: "source",
984
941
  path: preparedFile.file.path,
985
942
  planned: 0,
986
943
  preparedFile,
@@ -1047,29 +1004,34 @@ function toTargetIdentityInput(cwd, filePath, target) {
1047
1004
  }
1048
1005
  //#endregion
1049
1006
  //#region src/core/run.ts
1050
- var AlintRunError = class extends Error {
1051
- failure;
1007
+ var AlintRunCancelledError = class extends Error {
1052
1008
  result;
1053
- constructor(message, result, options = {}) {
1054
- super(message, { cause: options.cause });
1055
- this.name = "AlintRunError";
1056
- this.failure = options.failure;
1009
+ constructor(result, cause) {
1010
+ super("Alint run cancelled.", { cause });
1011
+ this.name = "AlintRunCancelledError";
1057
1012
  this.result = result;
1058
1013
  }
1059
1014
  };
1060
- /**
1061
- * Thrown when {@link RunOptions.signal} aborts a run.
1062
- *
1063
- * Extends {@link AlintRunError} so existing handlers still receive the partial `result`, while
1064
- * callers that treat cancellation differently from failure can test for this type.
1065
- */
1066
- var AlintAbortError = class extends AlintRunError {
1015
+ /** Cancellation error carrying the partial result gathered before the abort. */
1016
+ var AlintAbortError = class extends AlintRunCancelledError {
1067
1017
  constructor(result, options = {}) {
1068
- super("alint run was aborted", result, { cause: options.cause });
1018
+ super(result, options.cause);
1069
1019
  this.name = "AlintAbortError";
1070
1020
  }
1071
1021
  };
1022
+ var AlintRunError = class extends Error {
1023
+ failures;
1024
+ result;
1025
+ constructor(message, result, options = {}) {
1026
+ super(message, { cause: options.cause });
1027
+ this.name = "AlintRunError";
1028
+ this.failures = options.failures ?? [];
1029
+ this.result = result;
1030
+ }
1031
+ };
1072
1032
  async function runAlint(options = {}) {
1033
+ const timeoutMs = resolveRuleExecutionTimeout(options.runner?.timeoutMs);
1034
+ const concurrency = resolveRuleConcurrency(options.runner?.ruleConcurrency);
1073
1035
  const cwd$1 = options.cwd ?? cwd();
1074
1036
  const config = options.config ?? [];
1075
1037
  const setupConfig = options.setupConfig ?? {
@@ -1077,8 +1039,6 @@ async function runAlint(options = {}) {
1077
1039
  version: 1
1078
1040
  };
1079
1041
  const clock = Date.now;
1080
- const diagnostics = [];
1081
- const usage = createUsageAccumulator();
1082
1042
  const src = createSourceRuntime();
1083
1043
  const normalizedCacheConfig = normalizeRunnerCacheConfig(options.runner?.cache, cwd$1);
1084
1044
  const cacheStore = await createCacheStore({
@@ -1115,14 +1075,13 @@ async function runAlint(options = {}) {
1115
1075
  const registry = buildRuleRegistry(effectiveConfig);
1116
1076
  const ruleRuntimes = createRuleRuntimes({
1117
1077
  cwd: cwd$1,
1118
- diagnostics,
1119
1078
  effectiveAgent: effectiveConfig.agent,
1120
1079
  effectiveSettings: effectiveConfig.settings,
1121
1080
  options,
1081
+ progress: options.progress,
1122
1082
  registry,
1123
1083
  setupConfig,
1124
- src,
1125
- usage
1084
+ src
1126
1085
  });
1127
1086
  return {
1128
1087
  configHash: stableHash({
@@ -1150,14 +1109,13 @@ async function runAlint(options = {}) {
1150
1109
  configHash: stableHash({ settings: effectiveConfig.settings }),
1151
1110
  ruleRuntimes: createRuleRuntimes({
1152
1111
  cwd: cwd$1,
1153
- diagnostics,
1154
1112
  effectiveAgent: effectiveConfig.agent,
1155
1113
  effectiveSettings: effectiveConfig.settings,
1156
1114
  options,
1115
+ progress: options.progress,
1157
1116
  registry,
1158
1117
  setupConfig,
1159
- src,
1160
- usage
1118
+ src
1161
1119
  }),
1162
1120
  target
1163
1121
  };
@@ -1166,181 +1124,146 @@ async function runAlint(options = {}) {
1166
1124
  const projectConfig = resolvedProjectConfig.config;
1167
1125
  const projectRuleRuntimes = resolvedProjectConfig.ignored ? [] : createRuleRuntimes({
1168
1126
  cwd: cwd$1,
1169
- diagnostics,
1170
1127
  effectiveAgent: projectConfig.agent,
1171
1128
  effectiveSettings: projectConfig.settings,
1172
1129
  options,
1130
+ progress: options.progress,
1173
1131
  registry: buildRuleRegistry(projectConfig),
1174
1132
  setupConfig,
1175
- src,
1176
- usage
1133
+ src
1134
+ });
1135
+ const filePlans = createSourceExecutionPlans(files, cwd$1);
1136
+ const directoryPlans = createDirectoryExecutionPlans(directories, filePlans.length);
1137
+ const projectPlan = resolvedProjectConfig.ignored ? void 0 : createProjectExecutionPlan({
1138
+ configHash: stableHash({ settings: projectConfig.settings }),
1139
+ files,
1140
+ index: filePlans.length + directoryPlans.length + 1,
1141
+ root: cwd$1,
1142
+ ruleRuntimes: projectRuleRuntimes
1143
+ });
1144
+ const jobs = createRuleJobs([
1145
+ ...filePlans,
1146
+ ...directoryPlans,
1147
+ ...projectPlan ? [projectPlan] : []
1148
+ ]);
1149
+ const runStartedAt = clock();
1150
+ const settledOutcomes = new Array(jobs.length);
1151
+ let infrastructureError;
1152
+ let infrastructureFailed = false;
1153
+ options.progress?.onRunStart?.({
1154
+ jobsTotal: jobs.length,
1155
+ startedAt: runStartedAt
1177
1156
  });
1178
- let planned = 0;
1179
- const counters = createRuleEndCounters();
1180
- let filePlans = [];
1181
- let directoryPlans = [];
1182
- let projectPlan;
1183
- let runStartedAt;
1184
- let runError;
1157
+ for (const job of jobs) options.progress?.onJobQueued?.({ job: job.job });
1185
1158
  try {
1186
- options.signal?.throwIfAborted();
1187
- filePlans = createSourceExecutionPlans(files, cwd$1);
1188
- directoryPlans = createDirectoryExecutionPlans(directories, filePlans.length);
1189
- projectPlan = resolvedProjectConfig.ignored ? void 0 : createProjectExecutionPlan({
1190
- configHash: stableHash({ settings: projectConfig.settings }),
1191
- files,
1192
- root: cwd$1,
1193
- ruleRuntimes: projectRuleRuntimes
1194
- });
1195
- const activeDirectoryPlans = directoryPlans.filter((plan) => plan.targets.length > 0).map((plan, directoryOffset) => ({
1196
- ...plan,
1197
- fileIndex: filePlans.length + directoryOffset + 1
1198
- }));
1199
- const inputsTotal = files.length + activeDirectoryPlans.length;
1200
- planned = calculatePlannedExecutions([...filePlans, ...directoryPlans]) + (projectPlan?.planned ?? 0);
1201
- runStartedAt = clock();
1202
- options.progress?.onRunStart?.({
1203
- files: [...filePlans, ...activeDirectoryPlans].map((plan) => toProgressFilePath(plan, inputsTotal)),
1204
- filesTotal: inputsTotal,
1205
- planned,
1206
- rulesTotal: countEnabledRuleIds(files, directories, projectRuleRuntimes),
1207
- startedAt: runStartedAt
1208
- });
1209
- await runConcurrently(filePlans.filter((filePlan) => filePlan.targets.length > 0), resolveFileConcurrency(options.runner?.fileConcurrency), (filePlan) => executeTargetPlans({
1210
- cache: cacheContext,
1211
- cacheOnly: options.cacheOnly,
1212
- clock,
1213
- counters,
1214
- diagnostics,
1215
- filesTotal: inputsTotal,
1216
- plans: [filePlan],
1217
- progress: options.progress,
1218
- signal: options.signal,
1219
- usage
1220
- }));
1221
- await runConcurrently(activeDirectoryPlans, resolveFileConcurrency(options.runner?.fileConcurrency), (directoryPlan) => executeTargetPlans({
1222
- cache: cacheContext,
1223
- cacheOnly: options.cacheOnly,
1224
- clock,
1225
- counters,
1226
- diagnostics,
1227
- filesTotal: inputsTotal,
1228
- plans: [directoryPlan],
1229
- progress: options.progress,
1230
- signal: options.signal,
1231
- usage
1232
- }));
1233
- if (projectPlan) await executeTargetPlans({
1234
- cache: cacheContext,
1235
- cacheOnly: options.cacheOnly,
1236
- clock,
1237
- counters,
1238
- diagnostics,
1239
- filesTotal: inputsTotal,
1240
- plans: [projectPlan],
1241
- progress: options.progress,
1242
- signal: options.signal,
1243
- usage
1244
- });
1245
- } catch (error) {
1246
- runError = error;
1159
+ let cursor = 0;
1160
+ const worker = async () => {
1161
+ while (cursor < jobs.length) {
1162
+ const index = cursor;
1163
+ cursor += 1;
1164
+ const job = jobs[index];
1165
+ if (options.signal?.aborted) {
1166
+ settledOutcomes[index] = {
1167
+ bucket: {
1168
+ diagnostics: [],
1169
+ usage: []
1170
+ },
1171
+ cache: "miss",
1172
+ job,
1173
+ state: "cancelled"
1174
+ };
1175
+ continue;
1176
+ }
1177
+ try {
1178
+ const startedAt = clock();
1179
+ options.progress?.onJobStart?.({
1180
+ job: job.job,
1181
+ startedAt
1182
+ });
1183
+ settledOutcomes[index] = await executeRuleJob(job, {
1184
+ cache: cacheContext,
1185
+ cacheOnly: options.cacheOnly,
1186
+ clock,
1187
+ progress: options.progress,
1188
+ runSignal: options.signal,
1189
+ startedAt,
1190
+ timeoutMs
1191
+ });
1192
+ } catch (error) {
1193
+ if (!infrastructureFailed) {
1194
+ infrastructureError = error;
1195
+ infrastructureFailed = true;
1196
+ }
1197
+ }
1198
+ }
1199
+ };
1200
+ await Promise.all(Array.from({ length: Math.min(concurrency, jobs.length) }, worker));
1247
1201
  } finally {
1248
1202
  if (!options.cacheOnly) await reconcileCache(filePlans, cacheContext);
1249
- try {
1250
- options.progress?.onRunEnd?.({
1251
- ...counters.snapshot(planned),
1252
- diagnostics,
1253
- endedAt: clock(),
1254
- startedAt: runStartedAt ?? clock(),
1255
- usage: usage.toJSON()
1256
- });
1257
- } catch (error) {
1258
- runError ??= error;
1259
- }
1260
1203
  }
1204
+ const outcomes = Array.from({ length: jobs.length }, (_, index) => settledOutcomes[index] ?? {
1205
+ bucket: {
1206
+ diagnostics: [],
1207
+ usage: []
1208
+ },
1209
+ cache: "miss",
1210
+ job: jobs[index],
1211
+ state: "cancelled"
1212
+ });
1213
+ const failedOutcomes = outcomes.filter((outcome) => outcome.state === "failed");
1214
+ const failures = failedOutcomes.map((outcome) => outcome.failure);
1261
1215
  const result = {
1262
- diagnostics,
1263
- execution: counters.snapshot(planned),
1264
- usage: usage.toJSON()
1216
+ diagnostics: outcomes.flatMap((outcome) => outcome.bucket.diagnostics),
1217
+ execution: executionCounts(outcomes),
1218
+ usage: runUsage(outcomes)
1265
1219
  };
1266
- if (runError) {
1267
- if (options.signal?.aborted) throw new AlintAbortError(result, { cause: runError });
1268
- throw createAlintRunError(runError, result);
1269
- }
1270
- return result;
1271
- }
1272
- function addTokenCount(base, value) {
1273
- return typeof value === "number" && Number.isFinite(value) ? base + value : base;
1274
- }
1275
- function calculatePlannedExecutions(filePlans) {
1276
- return filePlans.reduce((total, filePlan) => total + filePlan.planned, 0);
1277
- }
1278
- function countEnabledRuleIds(files, directories, projectRuleRuntimes) {
1279
- const ids = /* @__PURE__ */ new Set();
1280
- for (const file of files) for (const runtime of file.ruleRuntimes) ids.add(runtime.enabledRule.id);
1281
- for (const directory of directories) for (const runtime of directory.ruleRuntimes) ids.add(runtime.enabledRule.id);
1282
- for (const runtime of projectRuleRuntimes) ids.add(runtime.enabledRule.id);
1283
- return ids.size;
1284
- }
1285
- function createAlintRunError(error, result) {
1286
- if (error instanceof AlintRunError) return error;
1287
- if (error instanceof AlintRuleExecutionError) return new AlintRunError(error.message, result, {
1288
- cause: errorCauseFrom(error),
1289
- failure: error.failure
1220
+ options.progress?.onRunEnd?.({
1221
+ diagnostics: result.diagnostics,
1222
+ endedAt: clock(),
1223
+ execution: result.execution,
1224
+ startedAt: runStartedAt,
1225
+ usage: result.usage
1290
1226
  });
1291
- return new AlintRunError(errorMessageFrom(error) ?? String(error), result, { cause: error });
1292
- }
1293
- function createRuleEndCounters() {
1294
- let cached = 0;
1295
- let completed = 0;
1296
- let errored = 0;
1297
- let skipped = 0;
1298
- return {
1299
- cache() {
1300
- cached += 1;
1301
- },
1302
- complete() {
1303
- completed += 1;
1304
- },
1305
- error() {
1306
- errored += 1;
1307
- },
1308
- skip() {
1309
- skipped += 1;
1310
- },
1311
- snapshot(planned) {
1312
- return {
1313
- cached,
1314
- completed,
1315
- errored,
1316
- planned,
1317
- skipped
1318
- };
1319
- }
1320
- };
1227
+ if (infrastructureFailed) throw infrastructureError;
1228
+ if (options.signal?.aborted) throw new AlintAbortError(result, { cause: options.signal.reason });
1229
+ if (failures.length > 0) throw new AlintRunError(`${failures.length} rule execution${failures.length === 1 ? "" : "s"} failed.`, result, {
1230
+ cause: new AggregateError(failedOutcomes.map((outcome) => outcome.cause), "Rule execution failures."),
1231
+ failures
1232
+ });
1233
+ return result;
1321
1234
  }
1322
1235
  function createRuleRuntimes(options) {
1323
- const agent = options.effectiveAgent ? withAgentRetry(options.effectiveAgent, options.options.runner?.agentRetries) : void 0;
1324
1236
  return options.registry.enabledRules.map((enabledRule) => {
1325
1237
  const executionState = new AsyncLocalStorage();
1326
1238
  const context = {
1327
- agent,
1239
+ agent: options.effectiveAgent ? withAgentRetry((request) => options.effectiveAgent({
1240
+ ...request,
1241
+ signal: combineAbortSignals(executionState.getStore()?.signal, request.signal)
1242
+ }), options.options.runner?.agentRetries) : void 0,
1328
1243
  cwd: options.cwd,
1329
1244
  id: enabledRule.id,
1330
1245
  localId: enabledRule.localId,
1331
1246
  logger: { debug: () => {} },
1332
1247
  metering: { recordUsage: (record) => {
1333
- const usageRecord = options.usage.record({
1248
+ const state = executionState.getStore();
1249
+ if (!state) throw new Error("Cannot record usage outside an active rule job.");
1250
+ const usageRecord = {
1334
1251
  ...record,
1335
1252
  ruleId: record.ruleId ?? enabledRule.id
1336
- });
1337
- const state = executionState.getStore();
1338
- state?.cacheUsage?.push(usageRecord);
1339
- options.options.progress?.onUsage?.({
1340
- path: state?.progressPath,
1341
- record: usageRecord,
1342
- total: options.usage.toJSON()
1343
- });
1253
+ };
1254
+ state.bucket.usage.push(usageRecord);
1255
+ try {
1256
+ options.progress?.onUsage?.({
1257
+ job: state.job,
1258
+ record: usageRecord
1259
+ });
1260
+ } catch (cause) {
1261
+ if (!state.reporterFailed) {
1262
+ state.reporterCause = cause;
1263
+ state.reporterFailed = true;
1264
+ }
1265
+ throw cause;
1266
+ }
1344
1267
  } },
1345
1268
  model: async (selector) => {
1346
1269
  const request = options.options.modelOverride ?? (typeof selector === "string" ? selector : void 0);
@@ -1357,10 +1280,11 @@ function createRuleRuntimes(options) {
1357
1280
  outputLanguage: options.options.outputLanguage,
1358
1281
  report: (descriptor) => {
1359
1282
  const state = executionState.getStore();
1360
- const filePath = descriptor.filePath ?? state?.activeFilePath;
1283
+ if (!state) throw new Error("Cannot report a diagnostic outside an active rule job.");
1284
+ const filePath = descriptor.filePath ?? state.activeFilePath;
1361
1285
  if (!filePath) throw new Error(`Diagnostic for rule "${enabledRule.id}" is missing filePath.`);
1362
- const diagnosticModel = state?.currentModel ? { ...state.currentModel } : void 0;
1363
- if (state) state.currentModel = void 0;
1286
+ const diagnosticModel = state.currentModel ? { ...state.currentModel } : void 0;
1287
+ state.currentModel = void 0;
1364
1288
  const diagnostic = {
1365
1289
  evidence: descriptor.evidence,
1366
1290
  filePath,
@@ -1370,16 +1294,24 @@ function createRuleRuntimes(options) {
1370
1294
  ruleId: enabledRule.id,
1371
1295
  severity: enabledRule.severity
1372
1296
  };
1373
- options.diagnostics.push(diagnostic);
1374
- state?.cacheDiagnostics?.push(diagnostic);
1375
- options.options.progress?.onDiagnostic?.({
1376
- diagnostic,
1377
- diagnostics: [...options.diagnostics],
1378
- path: state?.progressPath
1379
- });
1297
+ state.bucket.diagnostics.push(diagnostic);
1298
+ try {
1299
+ options.progress?.onDiagnostic?.({
1300
+ diagnostic,
1301
+ job: state.job
1302
+ });
1303
+ } catch (cause) {
1304
+ if (!state.reporterFailed) {
1305
+ state.reporterCause = cause;
1306
+ state.reporterFailed = true;
1307
+ }
1308
+ throw cause;
1309
+ }
1380
1310
  },
1381
1311
  settings: options.effectiveSettings,
1382
- signal: options.options.signal,
1312
+ get signal() {
1313
+ return executionState.getStore()?.signal;
1314
+ },
1383
1315
  src: options.src
1384
1316
  };
1385
1317
  return {
@@ -1399,47 +1331,19 @@ function createRuleRuntimes(options) {
1399
1331
  };
1400
1332
  });
1401
1333
  }
1402
- function createUsageAccumulator() {
1403
- const live = createUsageTotals();
1404
- const cached = createUsageTotals();
1405
- return {
1406
- record(record) {
1407
- return live.record(record);
1408
- },
1409
- recordCached(record) {
1410
- return cached.record(record);
1411
- },
1412
- toJSON() {
1413
- const cachedUsage = cached.toJSON();
1414
- return {
1415
- ...live.toJSON(),
1416
- ...cachedUsage.records.length > 0 ? { cached: cachedUsage } : {}
1417
- };
1418
- }
1419
- };
1420
- }
1421
- function createUsageTotals() {
1422
- const records = [];
1423
- let inputTokens = 0;
1424
- let outputTokens = 0;
1425
- let totalTokens = 0;
1426
- return {
1427
- record(record) {
1428
- records.push(record);
1429
- inputTokens = addTokenCount(inputTokens, record.inputTokens);
1430
- outputTokens = addTokenCount(outputTokens, record.outputTokens);
1431
- totalTokens = addTokenCount(totalTokens, record.totalTokens);
1432
- return record;
1433
- },
1434
- toJSON() {
1435
- return {
1436
- inputTokens,
1437
- outputTokens,
1438
- records,
1439
- totalTokens
1440
- };
1441
- }
1334
+ function executionCounts(outcomes) {
1335
+ const counts = {
1336
+ cached: 0,
1337
+ cancelled: 0,
1338
+ completed: 0,
1339
+ failed: 0,
1340
+ planned: outcomes.length,
1341
+ queued: 0,
1342
+ running: 0,
1343
+ skipped: 0
1442
1344
  };
1345
+ for (const outcome of outcomes) counts[outcome.state] += 1;
1346
+ return counts;
1443
1347
  }
1444
1348
  function mergeCapabilities(base, extra) {
1445
1349
  if (!base && !extra) return;
@@ -1475,28 +1379,19 @@ async function reconcileCache(filePlans, cacheContext) {
1475
1379
  await cacheContext.store.reconcile();
1476
1380
  } catch {}
1477
1381
  }
1478
- function resolveFileConcurrency(fileConcurrency) {
1479
- return fileConcurrency ?? 1;
1480
- }
1481
- async function runConcurrently(items, concurrency, runItem) {
1482
- let firstError;
1483
- let nextIndex = 0;
1484
- const workerCount = Math.min(Math.max(concurrency, 1), items.length);
1485
- async function runWorker() {
1486
- while (firstError === void 0) {
1487
- const currentIndex = nextIndex;
1488
- nextIndex += 1;
1489
- if (currentIndex >= items.length) return;
1490
- try {
1491
- await runItem(items[currentIndex]);
1492
- } catch (error) {
1493
- firstError ??= error;
1494
- return;
1495
- }
1496
- }
1497
- }
1498
- await Promise.all(Array.from({ length: workerCount }, () => runWorker()));
1499
- if (firstError !== void 0) throw firstError;
1382
+ function resolveRuleConcurrency(ruleConcurrency) {
1383
+ if (ruleConcurrency === void 0) return 4;
1384
+ if (!Number.isFinite(ruleConcurrency) || !Number.isInteger(ruleConcurrency) || ruleConcurrency <= 0) throw new TypeError("Rule execution concurrency must be a finite positive integer.");
1385
+ return ruleConcurrency;
1386
+ }
1387
+ function runUsage(outcomes) {
1388
+ const live = [];
1389
+ const cached = [];
1390
+ for (const outcome of outcomes) (outcome.state === "cached" ? cached : live).push(...outcome.bucket.usage);
1391
+ return {
1392
+ ...usageTotals(live),
1393
+ ...cached.length > 0 ? { cached: usageTotals(cached) } : {}
1394
+ };
1500
1395
  }
1501
1396
  function toDiagnosticModel(model, request) {
1502
1397
  return {
@@ -1505,12 +1400,20 @@ function toDiagnosticModel(model, request) {
1505
1400
  resolvedId: model.id
1506
1401
  };
1507
1402
  }
1508
- function toProgressFilePath(filePlan, filesTotal) {
1403
+ function usageTotals(records) {
1404
+ let inputTokens = 0;
1405
+ let outputTokens = 0;
1406
+ let totalTokens = 0;
1407
+ for (const record of records) {
1408
+ if (record.inputTokens != null && Number.isFinite(record.inputTokens)) inputTokens += record.inputTokens;
1409
+ if (record.outputTokens != null && Number.isFinite(record.outputTokens)) outputTokens += record.outputTokens;
1410
+ if (record.totalTokens != null && Number.isFinite(record.totalTokens)) totalTokens += record.totalTokens;
1411
+ }
1509
1412
  return {
1510
- index: filePlan.fileIndex,
1511
- path: filePlan.path,
1512
- planned: filePlan.planned,
1513
- total: filesTotal
1413
+ inputTokens,
1414
+ outputTokens,
1415
+ records: [...records],
1416
+ totalTokens
1514
1417
  };
1515
1418
  }
1516
1419
  //#endregion
@@ -1525,4 +1428,4 @@ function defineRule(rule) {
1525
1428
  return rule;
1526
1429
  }
1527
1430
  //#endregion
1528
- export { AlintRunError, buildRuleRegistry, createBuiltInLanguageRegistry, createSourceFile, createSourceRuntime, defineConfig, definePlugin, defineRule, hasDiscoveryFilePatterns, matchesDiscoveryFile, normalizeConfig, registerLanguage, resolveConfigForDirectory, resolveConfigForFile, resolveConfigForProject, resolveLanguage, resolveModel, runAlint, sliceLines, sliceRange };
1431
+ export { AlintAbortError, AlintRunCancelledError, AlintRunError, buildRuleRegistry, createBuiltInLanguageRegistry, createSourceFile, createSourceRuntime, defineConfig, definePlugin, defineRule, hasDiscoveryFilePatterns, matchesDiscoveryFile, normalizeConfig, registerLanguage, resolveConfigForDirectory, resolveConfigForFile, resolveConfigForProject, resolveLanguage, resolveModel, runAlint, sliceLines, sliceRange };