carrick 0.3.125 → 0.3.127

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "carrick",
3
- "version": "0.3.125",
3
+ "version": "0.3.127",
4
4
  "carrickCache": {
5
5
  "cache_version": 22,
6
6
  "intent_cache_version": 2
@@ -113,11 +113,11 @@
113
113
  "zod": "^3.23.0"
114
114
  },
115
115
  "optionalDependencies": {
116
- "@carrick-tools/cli-darwin-arm64": "0.3.125",
117
- "@carrick-tools/cli-darwin-x64": "0.3.125",
118
- "@carrick-tools/cli-linux-arm64": "0.3.125",
119
- "@carrick-tools/cli-linux-x64": "0.3.125",
120
- "@carrick-tools/cli-win32-x64": "0.3.125"
116
+ "@carrick-tools/cli-darwin-arm64": "0.3.127",
117
+ "@carrick-tools/cli-darwin-x64": "0.3.127",
118
+ "@carrick-tools/cli-linux-arm64": "0.3.127",
119
+ "@carrick-tools/cli-linux-x64": "0.3.127",
120
+ "@carrick-tools/cli-win32-x64": "0.3.127"
121
121
  },
122
122
  "devDependencies": {
123
123
  "@types/node": "^24.13.3"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "carrick",
3
3
  "description": "Claude Code plugin for Carrick, which indexes TypeScript codebases across service and repository boundaries. After each edit it adds the file's routes, calls, and cross-service type mismatches to the session, and it registers Carrick's language server. It pairs with the Carrick MCP server, which lets agents search functions by intent rather than name.",
4
- "version": "0.3.125",
4
+ "version": "0.3.127",
5
5
  "author": {
6
6
  "name": "Carrick",
7
7
  "email": "hello@carrick.tools"
@@ -212,10 +212,14 @@ export type SelfCheckOutcome = 'ok' | 'allowlisted_external' | 'decayed_internal
212
212
  * serialised itself, and at least one return builds that string in a way
213
213
  * the inferrer cannot trace to a typed value, so it publishes no part of
214
214
  * the contract (carrick#2200).
215
+ * - `body_built_by_callee`: a request locator names an argument of a call
216
+ * into a function that builds the request body from it, and the body that
217
+ * function builds cannot be typed, so the argument is not the body and no
218
+ * contract is published (carrick#2628).
215
219
  * - `not_recorded`: the position carries a top type and this layer has no
216
220
  * cause for it.
217
221
  */
218
- export type TypeProvenanceReason = 'declared' | 'unresolved_import' | 'budget_exhausted' | 'no_payload_evidence' | 'machinery_envelope' | 'coerced_input' | 'no_success_payload' | 'no_request_body' | 'projected_value_only' | 'serialised_body_unread' | 'not_recorded';
222
+ export type TypeProvenanceReason = 'declared' | 'unresolved_import' | 'budget_exhausted' | 'no_payload_evidence' | 'machinery_envelope' | 'coerced_input' | 'no_success_payload' | 'no_request_body' | 'projected_value_only' | 'serialised_body_unread' | 'body_built_by_callee' | 'not_recorded';
219
223
  /**
220
224
  * One `any`/`unknown` finding inside a captured or inferred type, with its
221
225
  * position and its cause. Sorted by `path` wherever a list is emitted, so the
@@ -39,6 +39,13 @@ export declare class DenoProject {
39
39
  * no identity. A file the workspace itself owns belongs to no package and
40
40
  * answers nothing, exactly as a workspace-declared type does under Node. */
41
41
  packageOf(file: string): string | undefined;
42
+ /** The npm package directory a resolved file lies in, and the package's
43
+ * name: its cache directory, which carries no `node_modules` segment, so
44
+ * only the graph can say where the package starts (carrick#2599). */
45
+ packageDirectoryOf(file: string): {
46
+ directory: string;
47
+ name: string;
48
+ } | undefined;
42
49
  private isNpmFile;
43
50
  /** Let TypeScript interpret real package exports/types using Deno's exact
44
51
  * dependency graph. Only the resolution host sees virtual node_modules;
@@ -280,6 +280,13 @@ export class DenoProject {
280
280
  const specifier = this.specifiersByPath.get(resolved);
281
281
  return specifier ? jsrPackageName(specifier) : undefined;
282
282
  }
283
+ /** The npm package directory a resolved file lies in, and the package's
284
+ * name: its cache directory, which carries no `node_modules` segment, so
285
+ * only the graph can say where the package starts (carrick#2599). */
286
+ packageDirectoryOf(file) {
287
+ const owner = this.npmOwner(path.resolve(file));
288
+ return owner?.localPath ? { directory: owner.localPath, name: owner.name } : undefined;
289
+ }
283
290
  isNpmFile(file) {
284
291
  return !!this.npmOwner(file) || file.split(path.sep).includes('node_modules');
285
292
  }
@@ -20,6 +20,17 @@ import type { ts } from 'ts-morph';
20
20
  * with one declaration needs none.
21
21
  */
22
22
  export declare function attributedDeclaration<T extends ts.Node>(programOf: () => ts.Program, declarations: readonly T[]): T | undefined;
23
+ /**
24
+ * The declaration a member is attributed to when it is read on `holder`: its
25
+ * own ({@link attributedDeclaration}), or, for a member with none (a mapped
26
+ * type's), the declaration of the holder's type: its symbol's, else its alias
27
+ * symbol's (carrick#2448). The per-position rows and a library verdict's
28
+ * `declarations` (carrick#2654) both name a member this way, so a verdict
29
+ * joins the row of the call it judges.
30
+ */
31
+ export declare function memberDeclaration(programOf: () => ts.Program, member: ts.Symbol, holder: ts.Type): ts.Declaration | undefined;
32
+ /** The declaration of a type's symbol, else of its alias symbol: a mapped type node, or the alias naming it. */
33
+ export declare function typeDeclaration(programOf: () => ts.Program, type: ts.Type): ts.Declaration | undefined;
23
34
  /**
24
35
  * The npm package a declaring file belongs to, by the module graph where
25
36
  * there is one and by its path otherwise. `undefined` when neither names one.
@@ -31,6 +31,22 @@ export function attributedDeclaration(programOf, declarations) {
31
31
  return pathA < pathB ? -1 : pathA > pathB ? 1 : a.getStart() - b.getStart();
32
32
  })[0];
33
33
  }
34
+ /**
35
+ * The declaration a member is attributed to when it is read on `holder`: its
36
+ * own ({@link attributedDeclaration}), or, for a member with none (a mapped
37
+ * type's), the declaration of the holder's type: its symbol's, else its alias
38
+ * symbol's (carrick#2448). The per-position rows and a library verdict's
39
+ * `declarations` (carrick#2654) both name a member this way, so a verdict
40
+ * joins the row of the call it judges.
41
+ */
42
+ export function memberDeclaration(programOf, member, holder) {
43
+ return attributedDeclaration(programOf, member.declarations ?? []) ?? typeDeclaration(programOf, holder);
44
+ }
45
+ /** The declaration of a type's symbol, else of its alias symbol: a mapped type node, or the alias naming it. */
46
+ export function typeDeclaration(programOf, type) {
47
+ const owner = type.getSymbol() ?? type.aliasSymbol;
48
+ return attributedDeclaration(programOf, owner?.declarations ?? []);
49
+ }
34
50
  /**
35
51
  * The npm package a declaring file belongs to, by the module graph where
36
52
  * there is one and by its path otherwise. `undefined` when neither names one.
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The implementations of a bodyless declaration, asked where discovery stops
3
+ * at one (carrick#2432 slice B): a call through an interface's method, a
4
+ * function-typed property or an abstract member, whose receiver the scanner
5
+ * cannot follow to a value.
6
+ *
7
+ * Each question is the span of a `call` (or `member`) position row, in the
8
+ * rows' numbering (UTF-16 code units from the start of the file, as the
9
+ * scanner's `sidecar_position` gives). The call's symbol and declaration are
10
+ * read exactly as the row read them ({@link PositionWalker.symbolOf}), and
11
+ * go-to-implementation is asked once per declaration, at its name, over the
12
+ * language service's program of the project that owns the file. That is the
13
+ * analysis program: nothing here releases it.
14
+ *
15
+ * Each answer:
16
+ * - holds none of the declaration's own signatures: asked at an overloaded
17
+ * interface or abstract method, go-to-implementation names only the bodies
18
+ * that implement it (TypeScript 6.0, pinned in implementations.test.ts).
19
+ * The walk's filter for them (`implementationRows`, removed in #2561) is
20
+ * not carried over: no bodyless declaration reaches it;
21
+ * - names each implementation as a row names a declaration
22
+ * ({@link PositionWalker.located}), at the implementation's declaration's
23
+ * start, so it joins to a definition as a `call` row at it would;
24
+ * - is sorted by file, then position;
25
+ * - holds at most {@link IMPLEMENTATIONS_CAP}; past it, none, and `too_many`.
26
+ *
27
+ * The position rows carry no implementations (version 4, carrick#2561): this
28
+ * action is the only place they are asked.
29
+ */
30
+ import type { ProjectLoader } from './project-loader.js';
31
+ import type { ImplementationsAnswer, ImplementationsQuery } from './types.js';
32
+ /** The most implementations one answer lists. */
33
+ export declare const IMPLEMENTATIONS_CAP = 16;
34
+ export interface ImplementationsOptions {
35
+ loader: ProjectLoader;
36
+ /** Every name in an answer is relative to it, as in the position rows. */
37
+ scanRoot: string;
38
+ queries: ImplementationsQuery[];
39
+ /** Called after each question is answered. */
40
+ onAnswer?: () => void;
41
+ }
42
+ /** One answer per question, in request order. */
43
+ export declare function findImplementations(options: ImplementationsOptions): ImplementationsAnswer[];
@@ -0,0 +1,224 @@
1
+ /**
2
+ * The implementations of a bodyless declaration, asked where discovery stops
3
+ * at one (carrick#2432 slice B): a call through an interface's method, a
4
+ * function-typed property or an abstract member, whose receiver the scanner
5
+ * cannot follow to a value.
6
+ *
7
+ * Each question is the span of a `call` (or `member`) position row, in the
8
+ * rows' numbering (UTF-16 code units from the start of the file, as the
9
+ * scanner's `sidecar_position` gives). The call's symbol and declaration are
10
+ * read exactly as the row read them ({@link PositionWalker.symbolOf}), and
11
+ * go-to-implementation is asked once per declaration, at its name, over the
12
+ * language service's program of the project that owns the file. That is the
13
+ * analysis program: nothing here releases it.
14
+ *
15
+ * Each answer:
16
+ * - holds none of the declaration's own signatures: asked at an overloaded
17
+ * interface or abstract method, go-to-implementation names only the bodies
18
+ * that implement it (TypeScript 6.0, pinned in implementations.test.ts).
19
+ * The walk's filter for them (`implementationRows`, removed in #2561) is
20
+ * not carried over: no bodyless declaration reaches it;
21
+ * - names each implementation as a row names a declaration
22
+ * ({@link PositionWalker.located}), at the implementation's declaration's
23
+ * start, so it joins to a definition as a `call` row at it would;
24
+ * - is sorted by file, then position;
25
+ * - holds at most {@link IMPLEMENTATIONS_CAP}; past it, none, and `too_many`.
26
+ *
27
+ * The position rows carry no implementations (version 4, carrick#2561): this
28
+ * action is the only place they are asked.
29
+ */
30
+ import * as path from 'node:path';
31
+ import { ts } from 'ts-morph';
32
+ import { attributedDeclaration } from './declaration-attribution.js';
33
+ import { PathNames, PositionWalker, compareNames, kindOf } from './position-rows.js';
34
+ /** The most implementations one answer lists. */
35
+ export const IMPLEMENTATIONS_CAP = 16;
36
+ /** One answer per question, in request order. */
37
+ export function findImplementations(options) {
38
+ const { loader, scanRoot, queries } = options;
39
+ const paths = new PathNames(scanRoot);
40
+ const answers = new Array(queries.length);
41
+ const groups = new Map();
42
+ queries.forEach((query, index) => {
43
+ const key = loader.projectKeyFor(path.resolve(query.file));
44
+ groups.set(key, [...(groups.get(key) ?? []), index]);
45
+ });
46
+ for (const [key, indexes] of groups) {
47
+ const service = loader.getProjectFor(key).getLanguageService().compilerObject;
48
+ const program = service.getProgram();
49
+ if (!program)
50
+ throw new Error('the language service built no program');
51
+ const finder = new ImplementationFinder(program, service, new PositionWalker(program, paths, loader));
52
+ for (const index of indexes) {
53
+ answers[index] = finder.answer(queries[index]);
54
+ options.onAnswer?.();
55
+ }
56
+ }
57
+ return answers;
58
+ }
59
+ /** An answer with nothing found, and why. */
60
+ function none(reason, declaration = null) {
61
+ return { declaration, implementations: [], too_many: false, reason };
62
+ }
63
+ class ImplementationFinder {
64
+ program;
65
+ service;
66
+ walker;
67
+ checker;
68
+ /** Answers by declaration: a declaration is searched once, however many calls reach it. */
69
+ searched = new Map();
70
+ constructor(program, service, walker) {
71
+ this.program = program;
72
+ this.service = service;
73
+ this.walker = walker;
74
+ this.checker = program.getTypeChecker();
75
+ }
76
+ answer(query) {
77
+ const sourceFile = this.program.getSourceFile(path.resolve(query.file));
78
+ if (!sourceFile)
79
+ return none('not_in_program');
80
+ const found = positionAt(sourceFile, query.start, query.end);
81
+ if (!found)
82
+ return none('no_call');
83
+ let declaration;
84
+ let named = null;
85
+ try {
86
+ const symbol = this.walker.symbolOf(found.kind, found.node);
87
+ if (!symbol)
88
+ return none('no_symbol');
89
+ declaration = attributedDeclaration(() => this.program, symbol.declarations ?? []);
90
+ if (!declaration)
91
+ return none('no_declaration');
92
+ named = this.named(declaration, ts.symbolName(symbol));
93
+ if (!isBodyless(declaration, this.checker))
94
+ return none('has_body', named);
95
+ }
96
+ catch {
97
+ return none('checker_threw', named);
98
+ }
99
+ let searched = this.searched.get(declaration);
100
+ if (!searched) {
101
+ try {
102
+ searched = this.implementationsOf(declaration);
103
+ }
104
+ catch {
105
+ return none('checker_threw', named);
106
+ }
107
+ this.searched.set(declaration, searched);
108
+ }
109
+ return { declaration: named, ...searched };
110
+ }
111
+ /** A declaration as a row names it: its symbol, file and line. */
112
+ named(declaration, symbol) {
113
+ const file = declaration.getSourceFile();
114
+ const { declFile, declLine, declPackage } = this.walker.located(file, declaration.getStart(file));
115
+ return { symbol, decl_file: declFile, decl_line: declLine, decl_package: declPackage };
116
+ }
117
+ /**
118
+ * What go-to-implementation names for `declaration`, asked at its name,
119
+ * in file and position order.
120
+ */
121
+ implementationsOf(declaration) {
122
+ const name = ts.getNameOfDeclaration(declaration) ?? declaration;
123
+ const declaringFile = name.getSourceFile();
124
+ const found = new Map();
125
+ for (const location of this.service.getImplementationAtPosition(declaringFile.fileName, name.getStart(declaringFile)) ?? []) {
126
+ const file = this.program.getSourceFile(location.fileName);
127
+ if (!file)
128
+ continue;
129
+ const start = location.textSpan.start;
130
+ found.set(`${location.fileName}\u0000${start}`, { file, start });
131
+ }
132
+ if (found.size > IMPLEMENTATIONS_CAP)
133
+ return { implementations: [], too_many: true };
134
+ const implementations = [...found.values()].map(({ file, start }) => {
135
+ const at = nodeStartingAt(file, start);
136
+ const symbol = at ? this.checker.getSymbolAtLocation(at) : undefined;
137
+ // The declaration the answer points into, so its line is the one a
138
+ // `call` row at the implementation names: the declaration's start,
139
+ // decorators and modifiers included, not its name's.
140
+ const holder = symbol?.declarations?.find((d) => d.getSourceFile() === file && d.getStart(file) <= start && start < d.end) ??
141
+ (at ? enclosingDeclaration(at) : undefined);
142
+ const placed = holder ? holder.getStart(file) : start;
143
+ const { declFile, declLine, declPackage } = this.walker.located(file, placed);
144
+ return {
145
+ file: declFile,
146
+ start: placed,
147
+ named: { symbol: symbol ? ts.symbolName(symbol) : null, decl_file: declFile, decl_line: declLine, decl_package: declPackage },
148
+ };
149
+ });
150
+ implementations.sort((a, b) => compareNames(a.file, b.file) || a.start - b.start);
151
+ return { implementations: implementations.map(({ named }) => named), too_many: false };
152
+ }
153
+ }
154
+ /**
155
+ * The `call` or `member` position whose span is exactly `start..end`: the
156
+ * node a row of that kind was written at. A call is preferred where both
157
+ * kinds share the span.
158
+ */
159
+ function positionAt(file, start, end) {
160
+ let member;
161
+ let call;
162
+ const visit = (node) => {
163
+ const nodeStart = node.getStart(file);
164
+ if (end <= nodeStart || node.getEnd() < start)
165
+ return;
166
+ if (nodeStart === start && node.getEnd() === end) {
167
+ const kind = kindOf(node);
168
+ if (kind === 'call')
169
+ call ??= node;
170
+ if (kind === 'member')
171
+ member ??= node;
172
+ }
173
+ ts.forEachChild(node, visit);
174
+ };
175
+ ts.forEachChild(file, visit);
176
+ if (call)
177
+ return { kind: 'call', node: call };
178
+ if (member)
179
+ return { kind: 'member', node: member };
180
+ return undefined;
181
+ }
182
+ /**
183
+ * Whether a declaration has no body for a call to run: an interface's or type
184
+ * literal's method, a function-typed property signature, an abstract method or
185
+ * property, or a function-typed class property or parameter with no
186
+ * initializer. Its implementations are what go-to-implementation names.
187
+ */
188
+ function isBodyless(declaration, checker) {
189
+ const functionTyped = () => checker.getTypeAtLocation(declaration).getNonNullableType().getCallSignatures().length > 0;
190
+ if (ts.isMethodSignature(declaration))
191
+ return true;
192
+ if (ts.isPropertySignature(declaration))
193
+ return functionTyped();
194
+ if (ts.isMethodDeclaration(declaration) || ts.isPropertyDeclaration(declaration)) {
195
+ if (ts.getCombinedModifierFlags(declaration) & ts.ModifierFlags.Abstract)
196
+ return true;
197
+ return ts.isPropertyDeclaration(declaration) && !declaration.initializer && functionTyped();
198
+ }
199
+ if (ts.isParameter(declaration))
200
+ return !declaration.initializer && functionTyped();
201
+ return false;
202
+ }
203
+ /** The innermost node of `file` that starts at `position` (an implementation's name). */
204
+ function nodeStartingAt(file, position) {
205
+ let found;
206
+ const visit = (node) => {
207
+ if (position < node.getStart(file) || position >= node.getEnd())
208
+ return;
209
+ if (node.getStart(file) === position)
210
+ found = node;
211
+ ts.forEachChild(node, visit);
212
+ };
213
+ ts.forEachChild(file, visit);
214
+ return found;
215
+ }
216
+ /** The nearest declaration holding `node`: what an answer with no symbol points into. */
217
+ function enclosingDeclaration(node) {
218
+ for (let at = node; at && !ts.isSourceFile(at); at = at.parent) {
219
+ if (ts.isFunctionLike(at) || ts.isClassLike(at) || ts.isPropertyAssignment(at) || ts.isVariableDeclaration(at)) {
220
+ return at;
221
+ }
222
+ }
223
+ return undefined;
224
+ }
@@ -30,6 +30,7 @@ import { PROGRESS_INTERVAL_MS, atMostEvery } from './progress.js';
30
30
  import { mergeInferTimings } from './infer-timing.js';
31
31
  import { addProgramFiles, listProgramFiles } from './program-files.js';
32
32
  import { writePositionRows } from './position-rows.js';
33
+ import { findImplementations } from './implementations.js';
33
34
  // ===========================================================================
34
35
  // Module-level state
35
36
  // ===========================================================================
@@ -55,6 +56,18 @@ function initializedLoader() {
55
56
  }
56
57
  return projectLoader;
57
58
  }
59
+ /**
60
+ * carrick#2100: the unwidened reading's budget in milliseconds, when
61
+ * `CARRICK_UNWIDENED_BUDGET_MS` sets one. A switch for tests that drive the
62
+ * scanner's binary: it forces the budget to run out on any machine. Unset or
63
+ * not a whole number of milliseconds, the inferrer's default stands.
64
+ */
65
+ function unwidenedBudgetFromEnv() {
66
+ const value = process.env.CARRICK_UNWIDENED_BUDGET_MS;
67
+ if (value === undefined || !/^\d+$/.test(value.trim()))
68
+ return undefined;
69
+ return Number(value.trim());
70
+ }
58
71
  /**
59
72
  * Get the project-backed components, building the project if this is the
60
73
  * first request that needs it.
@@ -77,6 +90,7 @@ function projectComponents(key = '') {
77
90
  project,
78
91
  repoRoot,
79
92
  packageOf: (filePath) => loader.packageOf(filePath),
93
+ unwidenedBudgetMs: unwidenedBudgetFromEnv(),
80
94
  });
81
95
  built = {
82
96
  typeBundler: new TypeBundler({ project, repoRoot }),
@@ -90,7 +104,10 @@ function projectComponents(key = '') {
90
104
  // (`npm/carrick/package.json`) resolves `typescript ^5.8.0` unpinned,
91
105
  // so an install can pair ts-morph's copy with a newer 5.x.
92
106
  retyper: new Retyper(project, typeInferrer, jsonWireDeclarations, findDisqualifyingTopTypes),
93
- claimsVerifier: new LibraryClaimsVerifier(project),
107
+ claimsVerifier: new LibraryClaimsVerifier(project, {
108
+ packageDirectoryOf: (file) => loader.packageDirectoryOf(file),
109
+ resolveModule: (name, from, options) => loader.resolveThroughGraph(name, from, options),
110
+ }),
94
111
  };
95
112
  components.set(key, built);
96
113
  }
@@ -556,6 +573,34 @@ function handleAddProgramFiles(request) {
556
573
  return { request_id: request.request_id, status: 'error', errors: [error] };
557
574
  }
558
575
  }
576
+ /**
577
+ * Handle the 'implementations' action - the implementations of the bodyless
578
+ * declaration each call goes through (carrick#2432). Asked of the analysis
579
+ * process: it reads the programs the service's other actions read, and
580
+ * releases none of them.
581
+ */
582
+ function handleImplementations(request) {
583
+ try {
584
+ const loader = initializedLoader();
585
+ let done = 0;
586
+ const report = atMostEvery(PROGRESS_INTERVAL_MS, () => writeProgress(request.request_id, 'implementations', `${done} of ${request.queries.length}`));
587
+ const answers = findImplementations({
588
+ loader,
589
+ scanRoot: request.scan_root,
590
+ queries: request.queries,
591
+ onAnswer: () => {
592
+ done += 1;
593
+ report();
594
+ },
595
+ });
596
+ return { request_id: request.request_id, status: 'success', result: { answers } };
597
+ }
598
+ catch (err) {
599
+ const error = err instanceof Error ? err.message : String(err);
600
+ logError(`Implementations failed: ${error}`);
601
+ return { request_id: request.request_id, status: 'error', errors: [error] };
602
+ }
603
+ }
559
604
  /**
560
605
  * Handle the 'position_rows' action - write the compiler's rows for each
561
606
  * call, member access, comparison and identifier of the given files
@@ -648,6 +693,8 @@ function handleRequest(request) {
648
693
  return handleAddProgramFiles(request);
649
694
  case 'position_rows':
650
695
  return handlePositionRows(request);
696
+ case 'implementations':
697
+ return handleImplementations(request);
651
698
  case 'health':
652
699
  return handleHealth(request);
653
700
  case 'shutdown':
@@ -35,7 +35,8 @@
35
35
  * package's: a `paths` alias or a `declare module` block the service writes
36
36
  * for itself is not evidence about the library.
37
37
  */
38
- import { type Project } from 'ts-morph';
38
+ import { ts, type Project } from 'ts-morph';
39
+ import { type PackageDirectoryOf } from './position-rows.js';
39
40
  import type { LibraryCheck, LibrarySurface, SemanticsCheck, SemanticsModule, SemanticsResult } from './types.js';
40
41
  export interface VerifyResult {
41
42
  semantics: SemanticsResult[];
@@ -54,9 +55,23 @@ export interface VerifyResult {
54
55
  * options at argument 1; `request_body` adds the body key there.
55
56
  */
56
57
  export declare function httpCheck(check: SemanticsCheck): LibraryCheck;
58
+ /**
59
+ * What a project's module graph says, where it resolved through one rather
60
+ * than through `node_modules` (carrick#2599). Such a resolver serves a package
61
+ * from a cache whose path has no `node_modules` segment, so neither the path
62
+ * nor a Node resolution of the package name finds the files the program
63
+ * loaded.
64
+ */
65
+ export interface ModuleGraph {
66
+ /** The installed package directory a file lies in, and its name. */
67
+ packageDirectoryOf: PackageDirectoryOf;
68
+ /** How the program resolves `name` from `from`; `undefined` where there is no graph, and Node resolution answers. */
69
+ resolveModule(name: string, from: string, options: ts.CompilerOptions): ts.ResolvedModuleFull | undefined;
70
+ }
57
71
  export declare class LibraryClaimsVerifier {
58
72
  private readonly project;
59
- constructor(project: Project);
73
+ private readonly graph;
74
+ constructor(project: Project, graph?: ModuleGraph);
60
75
  /**
61
76
  * Judge every check, in request order, spending at most `budgetMs`. Checks
62
77
  * the budget does not reach come back `unchecked` with reason `budget`.