@drkmttr/adhere 0.17.5 → 0.18.1

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.
@@ -6,25 +6,32 @@ import {
6
6
  type ReadState,
7
7
  type Rule,
8
8
  type RuleId,
9
+ type Rules,
9
10
  } from "#config.ts";
10
11
  import { type Excerpt, type Range } from "#excerpt.ts";
11
12
  import { type ScannedFile, WalkUnavailable } from "#models/Audit.ts";
12
13
  import { AdhereConfig } from "#services/AdhereConfig.ts";
13
14
  import { AuditCache, answersOf, type Judgment, sha256, type Tally } from "#services/AuditCache.ts";
14
15
  import {
16
+ describeState,
15
17
  fits,
16
18
  Jev,
19
+ type JevBlocked,
20
+ type JevOverflow,
17
21
  JevUnavailable,
18
22
  type Judged,
19
23
  judgeQuestion,
24
+ judgeBody,
20
25
  judgeLoad,
21
26
  judgeRequests,
22
27
  linterQuestion,
23
28
  type Located,
29
+ locateBody,
24
30
  locateRequests,
25
31
  matcherQuestions,
26
32
  questionRoom,
27
33
  tokensOf,
34
+ logRequestPayload,
28
35
  } from "#services/Jev.ts";
29
36
  import { type SourceReadRequest, SourceReads } from "#services/SourceReads.ts";
30
37
  import { type SourceFile, SourceWalker } from "#services/SourceWalker.ts";
@@ -187,6 +194,7 @@ export interface FilePlan {
187
194
 
188
195
  /** A run worked out from the files, the rules, and the cache, before any request. */
189
196
  export interface AuditPlan {
197
+ readonly model: string;
190
198
  readonly files: ReadonlyArray<FilePlan>;
191
199
  /** The normalization used for content keys, also used when reloading a selected file. */
192
200
  readonly includeComments?: boolean;
@@ -379,9 +387,11 @@ export const planAudit = (
379
387
  ? (config.workspacePackages ?? {})
380
388
  : undefined;
381
389
  const nativeReads: SourceReadRequest = {
382
- symbols: config.reads.includes("symbols"),
383
- references: config.reads.includes("references"),
390
+ symbols: config.typeAware,
391
+ references: config.typeAware,
384
392
  includeComments: config.includeComments === true,
393
+ maxSymbolLength: config.maxSymbolLength,
394
+ maxSymbolDocumentationLength: config.maxSymbolDocumentationLength,
385
395
  };
386
396
  const walker = yield* SourceWalker;
387
397
  const files = yield* walker.files;
@@ -504,6 +514,13 @@ export const planAudit = (
504
514
  } else if (left <= 0) {
505
515
  limited.push({ ...plan, pending: {}, deferred: true });
506
516
  } else if (!fits(lines, rulesOf(checking), plan.readState)) {
517
+ yield* logRequestPayload(
518
+ `${plan.file.path}: too long preflight payload (planning)`,
519
+ () => ({
520
+ judge: judgeBody(config.model, lines, rulesOf(checking), [], plan.readState),
521
+ locate: locateBody(config.model, lines, rulesOf(checking), plan.readState),
522
+ }),
523
+ );
507
524
  limited.push({ ...plan, skipped: true, pending: {} });
508
525
  } else {
509
526
  left -= 1;
@@ -618,6 +635,7 @@ export const planAudit = (
618
635
  judged.reduce((sum, plan) => sum + count(plan), 0);
619
636
  const cachedLocateRequests = loads.reduce((sum, load) => sum + load.locating, 0);
620
637
  return {
638
+ model: config.model,
621
639
  files: planned,
622
640
  ...(config.includeComments === true ? { includeComments: true } : {}),
623
641
  ...(options.limit === 0 ? { planOnly: true } : {}),
@@ -725,8 +743,58 @@ export const executeAudit = (
725
743
  ),
726
744
  };
727
745
  }
728
- // Budget every request this file can send, never truncating native source to make it fit.
729
- if (!fits(file.lines, rulesOf(checking), readState)) {
746
+ }
747
+ let referencesOmitted = false;
748
+ const hasHooks = Object.values(checking).some(({ rule }) => rule.appendState !== undefined);
749
+ let referencesForHooks: unknown;
750
+ const omitReferences = (reason: string) =>
751
+ Effect.gen(function* () {
752
+ referencesForHooks = hasHooks ? readState.references : undefined;
753
+ readState = Record.filter(readState, (_, name) => name !== "references");
754
+ referencesOmitted = true;
755
+ yield* Effect.logDebug(`${file.path}: ${reason}; attempting without references`);
756
+ });
757
+ const rulesFor = (entries: FilePlan["prepared"]): Rules => {
758
+ const rules = rulesOf(entries);
759
+ if (!referencesOmitted) return rules;
760
+ return Record.map(rules, (rule) => {
761
+ const appendState = rule.appendState;
762
+ return appendState === undefined
763
+ ? rule
764
+ : {
765
+ ...rule,
766
+ appendState: async (state, file, bun) => {
767
+ const input =
768
+ referencesForHooks === undefined
769
+ ? state
770
+ : describeState({ ...state, references: referencesForHooks });
771
+ return Record.filter(
772
+ await appendState(input, file, bun),
773
+ (_, name) => name !== "references",
774
+ );
775
+ },
776
+ };
777
+ });
778
+ };
779
+ const retryWithoutReferences = <A>(
780
+ work: () => Effect.Effect<A, JevUnavailable | JevOverflow | JevBlocked>,
781
+ ) =>
782
+ Effect.suspend(work).pipe(
783
+ Effect.catchTag("JevOverflow", (problem) =>
784
+ referencesOmitted || (!("references" in readState) && !hasHooks)
785
+ ? Effect.fail(problem)
786
+ : omitReferences("provider context limit").pipe(Effect.andThen(Effect.suspend(work))),
787
+ ),
788
+ );
789
+ if (!isEmpty(checking) && !fits(file.lines, rulesOf(checking), readState)) {
790
+ yield* logRequestPayload(`${file.path}: too long preflight payload (full context)`, () => ({
791
+ judge: judgeBody(plan.model, file.lines, rulesOf(checking), [], readState),
792
+ locate: locateBody(plan.model, file.lines, rulesOf(checking), readState),
793
+ }));
794
+ if ("references" in readState) {
795
+ // Let the provider decide whether the reduced payload fits; do not veto the fallback.
796
+ yield* omitReferences("full context too long for Jev (preflight limit)");
797
+ } else {
730
798
  yield* Effect.logDebug(`${file.path}: full context too long for Jev, skipped`);
731
799
  return fileResult("skipped", findingsOf(filePlan, kept, original));
732
800
  }
@@ -737,13 +805,13 @@ export const executeAudit = (
737
805
  return k !== undefined && tokensOf(linterQuestion(k.rule)) <= room;
738
806
  });
739
807
  const sampleIds = Object.keys(sentSamples);
740
- if (!isEmpty(pending))
741
- sent(judgeRequests(file.lines, rulesOf(pending), sampleIds, readState));
742
808
  const { probabilities, linter, matchers }: Judged = isEmpty(pending)
743
809
  ? { probabilities: {}, linter: {} }
744
- : yield* jev
745
- .judge(file.lines, rulesOf(pending), sampleIds, asWritten, readState)
746
- .pipe(inFile(file));
810
+ : yield* retryWithoutReferences(() => {
811
+ const rules = rulesFor(pending);
812
+ sent(judgeRequests(file.lines, rules, sampleIds, readState));
813
+ return jev.judge(file.lines, rules, sampleIds, asWritten, readState).pipe(inFile(file));
814
+ });
747
815
  if (!isEmpty(linter)) yield* record(file.path, sentSamples, linter);
748
816
  const judged: Record<RuleId, Judgment> = { ...kept };
749
817
  for (const [id, probability] of Object.entries(probabilities)) {
@@ -763,7 +831,6 @@ export const executeAudit = (
763
831
  }
764
832
 
765
833
  const flagged = unlocated(prepared, judged);
766
- if (!isEmpty(flagged)) sent(locateRequests(file.lines, rulesOf(flagged), readState));
767
834
  // A blocked locate question still leaves the judgments worth caching: a rerun
768
835
  // asks only for the sections again, not for every rule.
769
836
  const {
@@ -774,7 +841,11 @@ export const executeAudit = (
774
841
  readonly blocked: Blocked | undefined;
775
842
  } = isEmpty(flagged)
776
843
  ? { places: {}, blocked: undefined }
777
- : yield* jev.locate(file.lines, rulesOf(flagged), asWritten, readState).pipe(
844
+ : yield* retryWithoutReferences(() => {
845
+ const rules = rulesFor(flagged);
846
+ sent(locateRequests(file.lines, rules, readState));
847
+ return jev.locate(file.lines, rules, asWritten, readState);
848
+ }).pipe(
778
849
  Effect.map((places) => ({ places, blocked: undefined })),
779
850
  Effect.catchTag("JevBlocked", ({ ray }) =>
780
851
  Effect.succeed({ places: {}, blocked: { file: file.path, ray } }),