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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +25 -0
- package/README.md +13 -10
- package/dist/ast-index/client.d.ts +15 -1
- package/dist/ast-index/client.js +179 -0
- package/dist/ast-index/types.d.ts +27 -1
- package/dist/ast-index/types.js +1 -1
- package/dist/core/project-detector.d.ts +42 -0
- package/dist/core/project-detector.js +362 -0
- package/dist/core/validation.d.ts +30 -4
- package/dist/core/validation.js +67 -8
- package/dist/handlers/find-usages.d.ts +3 -3
- package/dist/handlers/find-usages.js +88 -13
- package/dist/handlers/module-info.d.ts +9 -0
- package/dist/handlers/module-info.js +123 -0
- package/dist/handlers/outline.d.ts +1 -3
- package/dist/handlers/outline.js +51 -20
- package/dist/handlers/project-overview.d.ts +2 -1
- package/dist/handlers/project-overview.js +146 -107
- package/dist/server.js +46 -6
- package/package.json +1 -1
|
@@ -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
|
|
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
|
|
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
|
package/dist/core/validation.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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<{
|