token-pilot 0.8.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,362 @@
1
+ import { readFile, readdir, access } from 'node:fs/promises';
2
+ import { resolve, basename } from 'node:path';
3
+ // ──────────────────────────────────────────────
4
+ // Framework detection maps
5
+ // ──────────────────────────────────────────────
6
+ const PHP_FRAMEWORKS = {
7
+ 'laravel/framework': 'Laravel',
8
+ 'symfony/framework-bundle': 'Symfony',
9
+ 'symfony/symfony': 'Symfony',
10
+ 'yiisoft/yii2': 'Yii 2',
11
+ 'cakephp/cakephp': 'CakePHP',
12
+ 'slim/slim': 'Slim',
13
+ 'codeigniter4/framework': 'CodeIgniter',
14
+ };
15
+ const JS_FRAMEWORKS = {
16
+ 'next': 'Next.js',
17
+ 'react': 'React',
18
+ 'vue': 'Vue',
19
+ '@angular/core': 'Angular',
20
+ 'svelte': 'Svelte',
21
+ '@nestjs/core': 'NestJS',
22
+ 'express': 'Express',
23
+ 'fastify': 'Fastify',
24
+ 'nuxt': 'Nuxt',
25
+ '@remix-run/node': 'Remix',
26
+ };
27
+ const PYTHON_FRAMEWORKS = {
28
+ 'django': 'Django',
29
+ 'flask': 'Flask',
30
+ 'fastapi': 'FastAPI',
31
+ 'tornado': 'Tornado',
32
+ 'starlette': 'Starlette',
33
+ };
34
+ // ──────────────────────────────────────────────
35
+ // Main detection
36
+ // ──────────────────────────────────────────────
37
+ /**
38
+ * Detect project stacks by reading all config files in parallel.
39
+ * Returns dual-detection data: ast-index type + config-based stacks.
40
+ */
41
+ export async function detectProject(projectRoot, astIndexType) {
42
+ // Read all configs in parallel
43
+ const [pkgResult, composerResult, cargoResult, pyResult, goResult] = await Promise.allSettled([
44
+ readJSON(resolve(projectRoot, 'package.json')),
45
+ readJSON(resolve(projectRoot, 'composer.json')),
46
+ readText(resolve(projectRoot, 'Cargo.toml')),
47
+ readText(resolve(projectRoot, 'pyproject.toml')),
48
+ readText(resolve(projectRoot, 'go.mod')),
49
+ ]);
50
+ const configStacks = [];
51
+ // Parse each config
52
+ if (pkgResult.status === 'fulfilled' && pkgResult.value) {
53
+ configStacks.push(parsePackageJson(pkgResult.value, projectRoot));
54
+ }
55
+ if (composerResult.status === 'fulfilled' && composerResult.value) {
56
+ configStacks.push(parseComposerJson(composerResult.value, projectRoot));
57
+ }
58
+ if (cargoResult.status === 'fulfilled' && cargoResult.value) {
59
+ const stack = parseCargoToml(cargoResult.value);
60
+ if (stack)
61
+ configStacks.push(stack);
62
+ }
63
+ if (pyResult.status === 'fulfilled' && pyResult.value) {
64
+ const stack = parsePyprojectToml(pyResult.value);
65
+ if (stack)
66
+ configStacks.push(stack);
67
+ }
68
+ if (goResult.status === 'fulfilled' && goResult.value) {
69
+ const stack = parseGoMod(goResult.value);
70
+ if (stack)
71
+ configStacks.push(stack);
72
+ }
73
+ // Determine primary stack
74
+ const primaryStack = configStacks.length === 1
75
+ ? configStacks[0]
76
+ : configStacks.length > 1
77
+ ? determinePrimary(configStacks)
78
+ : undefined;
79
+ // Determine confidence
80
+ const confidence = determineConfidence(astIndexType, configStacks);
81
+ // Detect quality tools and CI
82
+ const [qualityTools, ciPipelines, hasDocker] = await Promise.all([
83
+ detectQualityTools(projectRoot),
84
+ detectCI(projectRoot),
85
+ detectDocker(projectRoot),
86
+ ]);
87
+ // Project identity from primary stack
88
+ const projectName = primaryStack?.name ?? basename(projectRoot);
89
+ const projectVersion = primaryStack?.version ?? '0.0.0';
90
+ const projectDescription = primaryStack?.description;
91
+ return {
92
+ projectName,
93
+ projectVersion,
94
+ projectDescription,
95
+ astIndexType,
96
+ configStacks,
97
+ primaryStack,
98
+ confidence,
99
+ qualityTools,
100
+ ciPipelines,
101
+ hasDocker,
102
+ };
103
+ }
104
+ function parsePackageJson(pkg, projectRoot) {
105
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
106
+ const framework = detectFramework(allDeps, JS_FRAMEWORKS);
107
+ const nodeVersion = pkg.engines?.node;
108
+ return {
109
+ type: 'Node.js/TypeScript',
110
+ name: pkg.name ?? basename(projectRoot),
111
+ version: pkg.version ?? '0.0.0',
112
+ langVersion: nodeVersion ? `Node ${normalizeVersionConstraint(nodeVersion)}` : undefined,
113
+ framework: framework ? formatFramework(framework, allDeps) : undefined,
114
+ description: pkg.description,
115
+ };
116
+ }
117
+ function parseComposerJson(composer, projectRoot) {
118
+ const allDeps = { ...composer.require, ...composer['require-dev'] };
119
+ const framework = detectFramework(allDeps, PHP_FRAMEWORKS);
120
+ const phpVersion = composer.require?.['php'];
121
+ return {
122
+ type: 'PHP',
123
+ name: composer.name ?? basename(projectRoot),
124
+ version: composer.version ?? '0.0.0',
125
+ langVersion: phpVersion ? `PHP ${normalizeVersionConstraint(phpVersion)}` : undefined,
126
+ framework: framework ? formatFramework(framework, allDeps) : undefined,
127
+ description: composer.description,
128
+ };
129
+ }
130
+ function parseCargoToml(text) {
131
+ const name = text.match(/^name\s*=\s*"(.+?)"/m)?.[1];
132
+ const version = text.match(/^version\s*=\s*"(.+?)"/m)?.[1];
133
+ if (!name && !version)
134
+ return null;
135
+ return {
136
+ type: 'Rust',
137
+ name: name ?? 'unknown',
138
+ version: version ?? '0.0.0',
139
+ };
140
+ }
141
+ function parsePyprojectToml(text) {
142
+ const name = text.match(/^name\s*=\s*"(.+?)"/m)?.[1];
143
+ const version = text.match(/^version\s*=\s*"(.+?)"/m)?.[1];
144
+ if (!name && !version)
145
+ return null;
146
+ // Try to detect framework from dependencies
147
+ let framework;
148
+ for (const [pkg, fw] of Object.entries(PYTHON_FRAMEWORKS)) {
149
+ if (text.includes(`"${pkg}`) || text.includes(`'${pkg}`)) {
150
+ framework = fw;
151
+ break;
152
+ }
153
+ }
154
+ // Python version
155
+ const pyVersion = text.match(/requires-python\s*=\s*"(.+?)"/)?.[1];
156
+ return {
157
+ type: 'Python',
158
+ name: name ?? 'unknown',
159
+ version: version ?? '0.0.0',
160
+ langVersion: pyVersion ? `Python ${normalizeVersionConstraint(pyVersion)}` : undefined,
161
+ framework,
162
+ };
163
+ }
164
+ function parseGoMod(text) {
165
+ const module = text.match(/^module\s+(.+)/m)?.[1]?.trim();
166
+ if (!module)
167
+ return null;
168
+ // Go version
169
+ const goVersion = text.match(/^go\s+(\S+)/m)?.[1];
170
+ return {
171
+ type: 'Go',
172
+ name: module,
173
+ version: '0.0.0',
174
+ langVersion: goVersion ? `Go ${goVersion}` : undefined,
175
+ };
176
+ }
177
+ // ──────────────────────────────────────────────
178
+ // Helpers
179
+ // ──────────────────────────────────────────────
180
+ function detectFramework(deps, frameworkMap) {
181
+ if (!deps)
182
+ return null;
183
+ for (const [pkg, name] of Object.entries(frameworkMap)) {
184
+ if (deps[pkg])
185
+ return { name, pkg };
186
+ }
187
+ return null;
188
+ }
189
+ function formatFramework(fw, deps) {
190
+ const versionConstraint = deps[fw.pkg];
191
+ if (!versionConstraint)
192
+ return fw.name;
193
+ // Extract major version from constraint: "^12.0" → "12", "~3.4" → "3"
194
+ const majorMatch = versionConstraint.match(/(\d+)/);
195
+ return majorMatch ? `${fw.name} ${majorMatch[1]}` : fw.name;
196
+ }
197
+ /**
198
+ * Normalize version constraint to human-readable form.
199
+ * ">=8.2" → "8.2+", "^8.2" → "8.2+", "~8.2" → "8.2+", "8.2.*" → "8.2+"
200
+ */
201
+ function normalizeVersionConstraint(constraint) {
202
+ const cleaned = constraint.replace(/[>=^~*|<\s]/g, '').split(',')[0].split('|')[0];
203
+ // Take first version-like segment
204
+ const version = cleaned.match(/\d+(\.\d+)*/)?.[0];
205
+ return version ? `${version}+` : constraint;
206
+ }
207
+ /**
208
+ * Determine primary stack when multiple configs found.
209
+ * Heuristic: backend languages (PHP, Python, Go, Rust) beat JS/TS as primary
210
+ * because JS/TS in multi-stack projects is typically frontend tooling.
211
+ */
212
+ function determinePrimary(stacks) {
213
+ // Backend-first priority
214
+ const priority = ['PHP', 'Python', 'Go', 'Rust', 'Node.js/TypeScript'];
215
+ const sorted = [...stacks].sort((a, b) => priority.indexOf(a.type) - priority.indexOf(b.type));
216
+ return sorted[0];
217
+ }
218
+ /**
219
+ * Determine confidence level based on agreement between ast-index and config detection.
220
+ */
221
+ function determineConfidence(astIndexType, configStacks) {
222
+ if (!astIndexType && configStacks.length === 0)
223
+ return 'unknown';
224
+ if (!astIndexType)
225
+ return configStacks.length === 1 ? 'medium' : 'medium';
226
+ if (configStacks.length === 0)
227
+ return 'medium';
228
+ // Check if ast-index type matches any config stack
229
+ const astLower = astIndexType.toLowerCase();
230
+ const anyMatch = configStacks.some(s => {
231
+ const typeLower = s.type.toLowerCase();
232
+ return astLower.includes(typeLower) || typeLower.includes(astLower)
233
+ || (astLower.includes('javascript') && typeLower.includes('node'))
234
+ || (astLower.includes('typescript') && typeLower.includes('node'))
235
+ || (astLower.includes('php') && typeLower === 'php');
236
+ });
237
+ if (anyMatch && configStacks.length === 1)
238
+ return 'high';
239
+ if (anyMatch && configStacks.length > 1)
240
+ return 'medium';
241
+ return 'low'; // Conflict: ast-index says X, configs say Y
242
+ }
243
+ // ──────────────────────────────────────────────
244
+ // Quality & CI detection
245
+ // ──────────────────────────────────────────────
246
+ /** Detect quality/linting tools present in project root */
247
+ export async function detectQualityTools(projectRoot) {
248
+ const tools = [];
249
+ const checks = [
250
+ { files: ['phpstan.neon', 'phpstan.neon.dist'], name: 'PHPStan' },
251
+ { files: ['psalm.xml', 'psalm.xml.dist'], name: 'Psalm' },
252
+ { files: ['phpunit.xml', 'phpunit.xml.dist'], name: 'PHPUnit' },
253
+ { files: ['pest.php'], name: 'Pest' },
254
+ { files: ['.eslintrc', '.eslintrc.js', '.eslintrc.json', '.eslintrc.yml', 'eslint.config.js', 'eslint.config.mjs'], name: 'ESLint' },
255
+ { files: ['tsconfig.json'], name: 'TypeScript' },
256
+ { files: ['vitest.config.ts', 'vitest.config.js', 'vitest.config.mts'], name: 'Vitest' },
257
+ { files: ['jest.config.js', 'jest.config.ts', 'jest.config.mjs'], name: 'Jest' },
258
+ { files: ['biome.json', 'biome.jsonc'], name: 'Biome' },
259
+ { files: ['.prettierrc', '.prettierrc.js', '.prettierrc.json', 'prettier.config.js'], name: 'Prettier' },
260
+ { files: ['deptrac.yaml', 'deptrac.yml'], name: 'Deptrac' },
261
+ { files: ['rector.php'], name: 'Rector' },
262
+ { files: ['ruff.toml', '.ruff.toml'], name: 'Ruff' },
263
+ ];
264
+ const results = await Promise.allSettled(checks.map(async (check) => {
265
+ for (const file of check.files) {
266
+ try {
267
+ await access(resolve(projectRoot, file));
268
+ return check.name;
269
+ }
270
+ catch { /* file doesn't exist */ }
271
+ }
272
+ return null;
273
+ }));
274
+ // Special case: Ruff can also be configured in pyproject.toml [tool.ruff]
275
+ // Only check if ruff.toml/.ruff.toml not already found
276
+ const ruffFound = results.some(r => r.status === 'fulfilled' && r.value === 'Ruff');
277
+ if (!ruffFound) {
278
+ try {
279
+ const pyprojectPath = resolve(projectRoot, 'pyproject.toml');
280
+ const content = await readFile(pyprojectPath, 'utf-8');
281
+ if (content.includes('[tool.ruff]')) {
282
+ // Inject Ruff into results manually below
283
+ results.push({ status: 'fulfilled', value: 'Ruff' });
284
+ }
285
+ }
286
+ catch { /* no pyproject.toml or can't read */ }
287
+ }
288
+ for (const r of results) {
289
+ if (r.status === 'fulfilled' && r.value) {
290
+ tools.push(r.value);
291
+ }
292
+ }
293
+ return tools;
294
+ }
295
+ /** Detect CI/CD pipelines present in project */
296
+ export async function detectCI(projectRoot) {
297
+ const pipelines = [];
298
+ // GitHub Actions
299
+ try {
300
+ const ghDir = resolve(projectRoot, '.github', 'workflows');
301
+ const files = await readdir(ghDir);
302
+ const ymlCount = files.filter(f => f.endsWith('.yml') || f.endsWith('.yaml')).length;
303
+ if (ymlCount > 0) {
304
+ pipelines.push(`GitHub Actions (${ymlCount} workflow${ymlCount > 1 ? 's' : ''})`);
305
+ }
306
+ }
307
+ catch { /* no .github/workflows */ }
308
+ // GitLab CI
309
+ try {
310
+ await access(resolve(projectRoot, '.gitlab-ci.yml'));
311
+ pipelines.push('GitLab CI');
312
+ }
313
+ catch { /* no gitlab ci */ }
314
+ // Other CI
315
+ const otherCI = [
316
+ { file: 'Jenkinsfile', name: 'Jenkins' },
317
+ { file: '.circleci/config.yml', name: 'CircleCI' },
318
+ { file: 'bitbucket-pipelines.yml', name: 'Bitbucket Pipelines' },
319
+ { file: '.travis.yml', name: 'Travis CI' },
320
+ ];
321
+ for (const ci of otherCI) {
322
+ try {
323
+ await access(resolve(projectRoot, ci.file));
324
+ pipelines.push(ci.name);
325
+ }
326
+ catch { /* not present */ }
327
+ }
328
+ return pipelines;
329
+ }
330
+ /** Detect Docker presence */
331
+ export async function detectDocker(projectRoot) {
332
+ const dockerFiles = ['Dockerfile', 'docker-compose.yml', 'docker-compose.yaml', 'compose.yml', 'compose.yaml'];
333
+ for (const file of dockerFiles) {
334
+ try {
335
+ await access(resolve(projectRoot, file));
336
+ return true;
337
+ }
338
+ catch { /* not present */ }
339
+ }
340
+ return false;
341
+ }
342
+ // ──────────────────────────────────────────────
343
+ // File reading helpers
344
+ // ──────────────────────────────────────────────
345
+ async function readJSON(filePath) {
346
+ try {
347
+ const content = await readFile(filePath, 'utf-8');
348
+ return JSON.parse(content);
349
+ }
350
+ catch {
351
+ return null;
352
+ }
353
+ }
354
+ async function readText(filePath) {
355
+ try {
356
+ return await readFile(filePath, 'utf-8');
357
+ }
358
+ catch {
359
+ return null;
360
+ }
361
+ }
362
+ //# sourceMappingURL=project-detector.js.map
@@ -40,10 +40,16 @@ export declare function validateReadDiffArgs(args: unknown): {
40
40
  };
41
41
  /**
42
42
  * Validate find_usages arguments.
43
+ * v1.1: added scope, kind, limit, lang filters.
43
44
  */
44
- export declare function validateFindUsagesArgs(args: unknown): {
45
+ export interface FindUsagesArgs {
45
46
  symbol: string;
46
- };
47
+ scope?: string;
48
+ kind?: 'definitions' | 'imports' | 'usages' | 'all';
49
+ limit?: number;
50
+ lang?: string;
51
+ }
52
+ export declare function validateFindUsagesArgs(args: unknown): FindUsagesArgs;
47
53
  /**
48
54
  * Validate smart_read_many arguments.
49
55
  */
@@ -67,10 +73,14 @@ export declare function validateRelatedFilesArgs(args: unknown): {
67
73
  };
68
74
  /**
69
75
  * Validate outline arguments.
76
+ * v1.1: added recursive, max_depth.
70
77
  */
71
- export declare function validateOutlineArgs(args: unknown): {
78
+ export interface OutlineArgs {
72
79
  path: string;
73
- };
80
+ recursive?: boolean;
81
+ max_depth?: number;
82
+ }
83
+ export declare function validateOutlineArgs(args: unknown): OutlineArgs;
74
84
  export declare function validateFindUnusedArgs(args: unknown): {
75
85
  module?: string;
76
86
  export_only?: boolean;
@@ -84,6 +94,22 @@ export interface CodeAuditArgs {
84
94
  limit?: number;
85
95
  }
86
96
  export declare function validateCodeAuditArgs(args: unknown): CodeAuditArgs;
97
+ /**
98
+ * Validate project_overview arguments.
99
+ * v1.1: added include filter.
100
+ */
101
+ export interface ProjectOverviewArgs {
102
+ include?: Array<'stack' | 'ci' | 'quality' | 'architecture'>;
103
+ }
104
+ export declare function validateProjectOverviewArgs(args: unknown): ProjectOverviewArgs;
105
+ /**
106
+ * Validate module_info arguments.
107
+ */
108
+ export interface ModuleInfoArgs {
109
+ module: string;
110
+ check?: 'deps' | 'dependents' | 'api' | 'unused-deps' | 'all';
111
+ }
112
+ export declare function validateModuleInfoArgs(args: unknown): ModuleInfoArgs;
87
113
  /** Detect roots that would cause ast-index to scan the entire filesystem */
88
114
  export declare function isDangerousRoot(root: string): boolean;
89
115
  //# sourceMappingURL=validation.d.ts.map
@@ -98,9 +98,6 @@ export function validateReadDiffArgs(args) {
98
98
  context_lines: optionalNumber(a.context_lines, 'context_lines'),
99
99
  };
100
100
  }
101
- /**
102
- * Validate find_usages arguments.
103
- */
104
101
  export function validateFindUsagesArgs(args) {
105
102
  if (!args || typeof args !== 'object') {
106
103
  throw new Error('Arguments must be an object.');
@@ -109,7 +106,25 @@ export function validateFindUsagesArgs(args) {
109
106
  if (typeof a.symbol !== 'string' || a.symbol.length === 0) {
110
107
  throw new Error('Required parameter "symbol" must be a non-empty string.');
111
108
  }
112
- return { symbol: a.symbol };
109
+ let kind;
110
+ if (a.kind !== undefined && a.kind !== null) {
111
+ const validKinds = ['definitions', 'imports', 'usages', 'all'];
112
+ if (typeof a.kind !== 'string' || !validKinds.includes(a.kind)) {
113
+ throw new Error(`"kind" must be one of: ${validKinds.join(', ')}`);
114
+ }
115
+ kind = a.kind;
116
+ }
117
+ const limit = optionalNumber(a.limit, 'limit');
118
+ if (limit !== undefined && (limit < 1 || limit > 500)) {
119
+ throw new Error('"limit" must be between 1 and 500.');
120
+ }
121
+ return {
122
+ symbol: a.symbol,
123
+ scope: optionalString(a.scope, 'scope'),
124
+ kind,
125
+ limit,
126
+ lang: optionalString(a.lang, 'lang'),
127
+ };
113
128
  }
114
129
  /**
115
130
  * Validate smart_read_many arguments.
@@ -184,9 +199,6 @@ export function validateRelatedFilesArgs(args) {
184
199
  }
185
200
  return { path: a.path };
186
201
  }
187
- /**
188
- * Validate outline arguments.
189
- */
190
202
  export function validateOutlineArgs(args) {
191
203
  if (!args || typeof args !== 'object') {
192
204
  throw new Error('Arguments must be an object.');
@@ -195,7 +207,15 @@ export function validateOutlineArgs(args) {
195
207
  if (typeof a.path !== 'string' || a.path.length === 0) {
196
208
  throw new Error('Required parameter "path" must be a non-empty string.');
197
209
  }
198
- return { path: a.path };
210
+ const maxDepth = optionalNumber(a.max_depth, 'max_depth');
211
+ if (maxDepth !== undefined && (maxDepth < 1 || maxDepth > 5)) {
212
+ throw new Error('"max_depth" must be between 1 and 5.');
213
+ }
214
+ return {
215
+ path: a.path,
216
+ recursive: optionalBool(a.recursive, 'recursive'),
217
+ max_depth: maxDepth,
218
+ };
199
219
  }
200
220
  export function validateFindUnusedArgs(args) {
201
221
  if (!args || typeof args !== 'object')
@@ -234,6 +254,45 @@ export function validateCodeAuditArgs(args) {
234
254
  limit: optionalNumber(a.limit, 'limit'),
235
255
  };
236
256
  }
257
+ const VALID_INCLUDE_SECTIONS = ['stack', 'ci', 'quality', 'architecture'];
258
+ export function validateProjectOverviewArgs(args) {
259
+ if (!args || typeof args !== 'object')
260
+ return {};
261
+ const a = args;
262
+ if (a.include !== undefined && a.include !== null) {
263
+ if (!Array.isArray(a.include)) {
264
+ throw new Error('"include" must be an array of section names.');
265
+ }
266
+ for (const item of a.include) {
267
+ if (typeof item !== 'string' || !VALID_INCLUDE_SECTIONS.includes(item)) {
268
+ throw new Error(`Each element of "include" must be one of: ${VALID_INCLUDE_SECTIONS.join(', ')}. Got: "${item}"`);
269
+ }
270
+ }
271
+ return { include: a.include };
272
+ }
273
+ return {};
274
+ }
275
+ export function validateModuleInfoArgs(args) {
276
+ if (!args || typeof args !== 'object') {
277
+ throw new Error('Arguments must be an object with a "module" parameter.');
278
+ }
279
+ const a = args;
280
+ if (typeof a.module !== 'string' || a.module.length === 0) {
281
+ throw new Error('Required parameter "module" must be a non-empty string.');
282
+ }
283
+ let check;
284
+ if (a.check !== undefined && a.check !== null) {
285
+ const validChecks = ['deps', 'dependents', 'api', 'unused-deps', 'all'];
286
+ if (typeof a.check !== 'string' || !validChecks.includes(a.check)) {
287
+ throw new Error(`"check" must be one of: ${validChecks.join(', ')}`);
288
+ }
289
+ check = a.check;
290
+ }
291
+ return {
292
+ module: a.module,
293
+ check: check ?? 'all',
294
+ };
295
+ }
237
296
  /** Detect roots that would cause ast-index to scan the entire filesystem */
238
297
  export function isDangerousRoot(root) {
239
298
  const normalized = root.replace(/\/+$/, '') || '/';
@@ -1,7 +1,5 @@
1
1
  import type { AstIndexClient } from '../ast-index/client.js';
2
- export interface FindUsagesArgs {
3
- symbol: string;
4
- }
2
+ import type { FindUsagesArgs } from '../core/validation.js';
5
3
  /**
6
4
  * Find all usages of a symbol across the project.
7
5
  *
@@ -9,6 +7,8 @@ export interface FindUsagesArgs {
9
7
  * with `search` (text: catches imports and self-references that refs misses).
10
8
  * Filter search results to exact word matches only (no substring matches).
11
9
  * Deduplicate by file:line.
10
+ *
11
+ * v1.1: added scope, kind, limit, lang post-filters.
12
12
  */
13
13
  export declare function handleFindUsages(args: FindUsagesArgs, astIndex: AstIndexClient): Promise<{
14
14
  content: Array<{