skannr 0.1.5 → 0.2.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.
Files changed (61) hide show
  1. package/dist/languages/PythonAdapter.d.ts.map +1 -1
  2. package/dist/languages/PythonAdapter.js +27 -4
  3. package/dist/languages/PythonAdapter.js.map +1 -1
  4. package/dist/languages/TreeSitterAdapter.d.ts +30 -0
  5. package/dist/languages/TreeSitterAdapter.d.ts.map +1 -0
  6. package/dist/languages/TreeSitterAdapter.js +466 -0
  7. package/dist/languages/TreeSitterAdapter.js.map +1 -0
  8. package/dist/languages/grammars/tree-sitter-go.wasm +0 -0
  9. package/dist/languages/grammars/tree-sitter-java.wasm +0 -0
  10. package/dist/languages/grammars/tree-sitter-python.wasm +0 -0
  11. package/dist/languages/grammars/tree-sitter-rust.wasm +0 -0
  12. package/dist/languages/lang-config.d.ts +38 -0
  13. package/dist/languages/lang-config.d.ts.map +1 -0
  14. package/dist/languages/lang-config.js +85 -0
  15. package/dist/languages/lang-config.js.map +1 -0
  16. package/dist/languages/registry.d.ts +6 -0
  17. package/dist/languages/registry.d.ts.map +1 -1
  18. package/dist/languages/registry.js +18 -4
  19. package/dist/languages/registry.js.map +1 -1
  20. package/package.json +4 -5
  21. package/src/languages/TreeSitterAdapter.ts +431 -0
  22. package/src/languages/lang-config.ts +113 -0
  23. package/src/languages/registry.ts +65 -49
  24. package/src/languages/PythonAdapter.ts +0 -285
  25. package/tests/agent.tools.test.ts +0 -81
  26. package/tests/benchmark.test.ts +0 -31
  27. package/tests/blast-radius.test.ts +0 -290
  28. package/tests/fixtures/sample.py +0 -17
  29. package/tests/fixtures/sample.ts +0 -13
  30. package/tests/fixtures/src/api/routes.ts +0 -1
  31. package/tests/fixtures/src/auth/permission.ts +0 -3
  32. package/tests/python-adapter.test.ts +0 -31
  33. package/tests/ranker-enhanced.test.ts +0 -70
  34. package/tests/ranker.test.ts +0 -79
  35. package/tests/scanner.scope.test.ts +0 -67
  36. package/tests/setup-fixtures.js +0 -29
  37. package/tests/skeletonizer.test.ts +0 -149
  38. package/uca-landing/index.html +0 -17
  39. package/uca-landing/package.json +0 -23
  40. package/uca-landing/postcss.config.js +0 -6
  41. package/uca-landing/src/App.jsx +0 -45
  42. package/uca-landing/src/components/AgentMode.jsx +0 -45
  43. package/uca-landing/src/components/CliReference.jsx +0 -89
  44. package/uca-landing/src/components/Features.jsx +0 -88
  45. package/uca-landing/src/components/Footer.jsx +0 -35
  46. package/uca-landing/src/components/Hero.jsx +0 -125
  47. package/uca-landing/src/components/HowItWorks.jsx +0 -65
  48. package/uca-landing/src/components/Install.jsx +0 -103
  49. package/uca-landing/src/components/LanguageSupport.jsx +0 -63
  50. package/uca-landing/src/components/Navbar.jsx +0 -86
  51. package/uca-landing/src/components/Problem.jsx +0 -51
  52. package/uca-landing/src/components/Reveal.jsx +0 -40
  53. package/uca-landing/src/components/TokenSavings.jsx +0 -126
  54. package/uca-landing/src/components/WorksWith.jsx +0 -59
  55. package/uca-landing/src/hooks/useScrollNav.js +0 -13
  56. package/uca-landing/src/hooks/useTypewriter.js +0 -41
  57. package/uca-landing/src/index.css +0 -13
  58. package/uca-landing/src/main.jsx +0 -10
  59. package/uca-landing/tailwind.config.js +0 -68
  60. package/uca-landing/vercel.json +0 -4
  61. package/uca-landing/vite.config.js +0 -6
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Per-language AST node type mappings for tree-sitter grammars.
3
+ * Adding a new language = adding one entry to this map.
4
+ */
5
+
6
+ export interface LangConfig {
7
+ /** Language identifier. */
8
+ id: string;
9
+ /** File extensions this language handles (lowercase, with dot). */
10
+ extensions: string[];
11
+ /** Filename of the .wasm grammar (relative to grammars/ dir). */
12
+ grammarFile: string;
13
+ /** AST node types that represent top-level function declarations. */
14
+ functionTypes: string[];
15
+ /** AST node types that represent class declarations. */
16
+ classTypes: string[];
17
+ /** AST node types that represent methods inside classes. */
18
+ methodTypes: string[];
19
+ /** AST node types that represent import statements. */
20
+ importTypes: string[];
21
+ /** AST node types that represent interface/protocol declarations. */
22
+ interfaceTypes: string[];
23
+ /** AST node types that represent type alias declarations. */
24
+ typeTypes: string[];
25
+ /** The field name used to extract the symbol name (usually 'name'). */
26
+ nameField: string;
27
+ /** The field name for the function/method body (usually 'body' or 'block'). */
28
+ bodyField: string;
29
+ /** The field name for class body. */
30
+ classBodyField: string;
31
+ /** The field name for function parameters. */
32
+ parametersField: string;
33
+ /** The field name for return type annotation (if the language supports it). */
34
+ returnTypeField: string;
35
+ }
36
+
37
+ export const LANG_CONFIGS: Record<string, LangConfig> = {
38
+ python: {
39
+ id: 'python',
40
+ extensions: ['.py', '.pyi'],
41
+ grammarFile: 'tree-sitter-python.wasm',
42
+ functionTypes: ['function_definition'],
43
+ classTypes: ['class_definition'],
44
+ methodTypes: ['function_definition'],
45
+ importTypes: ['import_statement', 'import_from_statement'],
46
+ interfaceTypes: [],
47
+ typeTypes: [],
48
+ nameField: 'name',
49
+ bodyField: 'body',
50
+ classBodyField: 'body',
51
+ parametersField: 'parameters',
52
+ returnTypeField: 'return_type',
53
+ },
54
+ go: {
55
+ id: 'go',
56
+ extensions: ['.go'],
57
+ grammarFile: 'tree-sitter-go.wasm',
58
+ functionTypes: ['function_declaration'],
59
+ classTypes: ['type_declaration'],
60
+ methodTypes: ['method_declaration'],
61
+ importTypes: ['import_declaration'],
62
+ interfaceTypes: ['type_declaration'],
63
+ typeTypes: ['type_declaration'],
64
+ nameField: 'name',
65
+ bodyField: 'body',
66
+ classBodyField: 'body',
67
+ parametersField: 'parameters',
68
+ returnTypeField: 'result',
69
+ },
70
+ rust: {
71
+ id: 'rust',
72
+ extensions: ['.rs'],
73
+ grammarFile: 'tree-sitter-rust.wasm',
74
+ functionTypes: ['function_item'],
75
+ classTypes: ['struct_item', 'enum_item'],
76
+ methodTypes: ['function_item'],
77
+ importTypes: ['use_declaration'],
78
+ interfaceTypes: ['trait_item'],
79
+ typeTypes: ['type_item'],
80
+ nameField: 'name',
81
+ bodyField: 'body',
82
+ classBodyField: 'body',
83
+ parametersField: 'parameters',
84
+ returnTypeField: 'return_type',
85
+ },
86
+ java: {
87
+ id: 'java',
88
+ extensions: ['.java'],
89
+ grammarFile: 'tree-sitter-java.wasm',
90
+ functionTypes: ['method_declaration', 'constructor_declaration'],
91
+ classTypes: ['class_declaration', 'enum_declaration'],
92
+ methodTypes: ['method_declaration', 'constructor_declaration'],
93
+ importTypes: ['import_declaration'],
94
+ interfaceTypes: ['interface_declaration'],
95
+ typeTypes: [],
96
+ nameField: 'name',
97
+ bodyField: 'body',
98
+ classBodyField: 'body',
99
+ parametersField: 'parameters',
100
+ returnTypeField: 'type',
101
+ },
102
+ };
103
+
104
+ /** Get the language config for a file extension, or null if unsupported. */
105
+ export function getLangConfigForExt(ext: string): LangConfig | null {
106
+ const lower = ext.toLowerCase();
107
+ for (const config of Object.values(LANG_CONFIGS)) {
108
+ if (config.extensions.includes(lower)) {
109
+ return config;
110
+ }
111
+ }
112
+ return null;
113
+ }
@@ -1,49 +1,65 @@
1
- import { LanguageAdapter } from './LanguageAdapter';
2
- import { TypeScriptAdapter } from './TypeScriptAdapter';
3
- import { PythonAdapter } from './PythonAdapter';
4
- import { GenericAdapter } from './GenericAdapter';
5
- import * as path from 'path';
6
-
7
- const adapters: LanguageAdapter[] = [
8
- new TypeScriptAdapter(),
9
- new PythonAdapter(),
10
- ];
11
-
12
- const generic = new GenericAdapter();
13
-
14
- export function getAdapter(filePath: string): LanguageAdapter {
15
- const ext = path.extname(filePath).toLowerCase();
16
- return adapters.find((a) => a.extensions.includes(ext)) ?? generic;
17
- }
18
-
19
- export function detectRepoLanguages(root: string): string[] {
20
- const fs = require('fs');
21
- const extCount: Record<string, number> = {};
22
- walkAndCount(root, extCount, 0, 3);
23
- return Object.entries(extCount)
24
- .sort((a, b) => b[1] - a[1])
25
- .slice(0, 3)
26
- .map(([ext]) => ext);
27
- }
28
-
29
- function walkAndCount(dir: string, counts: Record<string, number>, depth: number, maxDepth: number): void {
30
- if (depth > maxDepth) return;
31
- const fs = require('fs');
32
- const path = require('path');
33
- const ignored = new Set(['node_modules', '.git', 'dist', 'build', 'vendor', '__pycache__']);
34
- try {
35
- for (const entry of fs.readdirSync(dir)) {
36
- if (ignored.has(entry)) continue;
37
- const full = path.join(dir, entry);
38
- const stat = fs.statSync(full);
39
- if (stat.isDirectory()) {
40
- walkAndCount(full, counts, depth + 1, maxDepth);
41
- } else {
42
- const ext = path.extname(entry).toLowerCase();
43
- if (ext) counts[ext] = (counts[ext] ?? 0) + 1;
44
- }
45
- }
46
- } catch {
47
- // Ignore unreadable directories.
48
- }
49
- }
1
+ /**
2
+ * Language adapter registry.
3
+ * Routes files to the appropriate parser based on extension.
4
+ */
5
+
6
+ import { LanguageAdapter } from './LanguageAdapter';
7
+ import { TypeScriptAdapter } from './TypeScriptAdapter';
8
+ import { TreeSitterAdapter } from './TreeSitterAdapter';
9
+ import { GenericAdapter } from './GenericAdapter';
10
+ import { LANG_CONFIGS } from './lang-config';
11
+ import * as path from 'path';
12
+
13
+ const tsAdapter = new TypeScriptAdapter();
14
+ const treeSitterAdapter = new TreeSitterAdapter(Object.values(LANG_CONFIGS));
15
+ const generic = new GenericAdapter();
16
+
17
+ // Initialize tree-sitter WASM in background (non-blocking).
18
+ treeSitterAdapter.initializeSync();
19
+
20
+ const adapters: LanguageAdapter[] = [
21
+ tsAdapter,
22
+ treeSitterAdapter,
23
+ ];
24
+
25
+ export function getAdapter(filePath: string): LanguageAdapter {
26
+ const ext = path.extname(filePath).toLowerCase();
27
+ return adapters.find((a) => a.extensions.includes(ext)) ?? generic;
28
+ }
29
+
30
+ /** Await full initialization of WASM-based adapters. */
31
+ export async function initializeAdapters(): Promise<void> {
32
+ await treeSitterAdapter.initialize();
33
+ }
34
+
35
+ export function detectRepoLanguages(root: string): string[] {
36
+ const fs = require('fs');
37
+ const extCount: Record<string, number> = {};
38
+ walkAndCount(root, extCount, 0, 3);
39
+ return Object.entries(extCount)
40
+ .sort((a, b) => b[1] - a[1])
41
+ .slice(0, 3)
42
+ .map(([ext]) => ext);
43
+ }
44
+
45
+ function walkAndCount(dir: string, counts: Record<string, number>, depth: number, maxDepth: number): void {
46
+ if (depth > maxDepth) return;
47
+ const fs = require('fs');
48
+ const path = require('path');
49
+ const ignored = new Set(['node_modules', '.git', 'dist', 'build', 'vendor', '__pycache__']);
50
+ try {
51
+ for (const entry of fs.readdirSync(dir)) {
52
+ if (ignored.has(entry)) continue;
53
+ const full = path.join(dir, entry);
54
+ const stat = fs.statSync(full);
55
+ if (stat.isDirectory()) {
56
+ walkAndCount(full, counts, depth + 1, maxDepth);
57
+ } else {
58
+ const ext = path.extname(entry).toLowerCase();
59
+ if (ext) counts[ext] = (counts[ext] ?? 0) + 1;
60
+ }
61
+ }
62
+ } catch {
63
+ // Ignore unreadable directories.
64
+ }
65
+ }
@@ -1,285 +0,0 @@
1
- import * as path from 'path';
2
- import Parser = require('tree-sitter');
3
- import { LanguageAdapter, SkeletonResult, Symbol } from './LanguageAdapter';
4
-
5
- type SyntaxNode = Parser.SyntaxNode;
6
-
7
- let pythonLanguage: Parser.Language | undefined;
8
-
9
- function ensureParserLoaded(): void {
10
- if (!pythonLanguage) {
11
- // eslint-disable-next-line @typescript-eslint/no-require-imports
12
- pythonLanguage = require('tree-sitter-python') as Parser.Language;
13
- }
14
- }
15
-
16
- function getParser(): Parser {
17
- ensureParserLoaded();
18
- const parser = new Parser();
19
- parser.setLanguage(pythonLanguage!);
20
- return parser;
21
- }
22
-
23
- function collapseOneLineBody(line: string): string {
24
- const idx = line.indexOf(':');
25
- if (idx === -1) {
26
- return line;
27
- }
28
- return `${line.slice(0, idx + 1)} ...`;
29
- }
30
-
31
- function baseIndent(line: string | undefined): string {
32
- if (!line) {
33
- return '';
34
- }
35
- return line.match(/^(\s*)/)?.[1] ?? '';
36
- }
37
-
38
- interface SkeletonState {
39
- lines: string[];
40
- keep: Set<number>;
41
- replace: Map<number, string>;
42
- ellipsisAfter: Set<number>;
43
- }
44
-
45
- function addRange(state: SkeletonState, start: number, end: number): void {
46
- const max = state.lines.length - 1;
47
- const lo = Math.max(0, start);
48
- const hi = Math.min(max, end);
49
- for (let i = lo; i <= hi; i++) {
50
- state.keep.add(i);
51
- }
52
- }
53
-
54
- function emitFunctionHeader(state: SkeletonState, node: SyntaxNode): void {
55
- const body = node.childForFieldName('body');
56
- const start = node.startPosition.row;
57
- if (!body) {
58
- addRange(state, start, node.endPosition.row);
59
- return;
60
- }
61
- if (body.startPosition.row > start) {
62
- addRange(state, start, body.startPosition.row - 1);
63
- state.ellipsisAfter.add(body.startPosition.row - 1);
64
- return;
65
- }
66
- state.replace.set(start, collapseOneLineBody(state.lines[start]));
67
- }
68
-
69
- function emitClassHeaderLines(state: SkeletonState, node: SyntaxNode): void {
70
- const body = node.childForFieldName('body');
71
- const start = node.startPosition.row;
72
- if (!body) {
73
- addRange(state, start, node.endPosition.row);
74
- return;
75
- }
76
- if (body.startPosition.row > start) {
77
- addRange(state, start, body.startPosition.row - 1);
78
- return;
79
- }
80
- state.replace.set(start, collapseOneLineBody(state.lines[start]));
81
- }
82
-
83
- function processDecoratedDefinition(state: SkeletonState, node: SyntaxNode): void {
84
- for (const child of node.namedChildren) {
85
- if (child.type === 'decorator') {
86
- addRange(state, child.startPosition.row, child.endPosition.row);
87
- } else {
88
- processStatement(state, child);
89
- }
90
- }
91
- }
92
-
93
- function processStatement(state: SkeletonState, node: SyntaxNode): void {
94
- switch (node.type) {
95
- case 'import_statement':
96
- case 'import_from_statement':
97
- addRange(state, node.startPosition.row, node.endPosition.row);
98
- return;
99
- case 'type_alias_statement':
100
- addRange(state, node.startPosition.row, node.endPosition.row);
101
- return;
102
- case 'decorated_definition':
103
- processDecoratedDefinition(state, node);
104
- return;
105
- case 'function_definition':
106
- emitFunctionHeader(state, node);
107
- return;
108
- case 'class_definition': {
109
- emitClassHeaderLines(state, node);
110
- const body = node.childForFieldName('body');
111
- if (body?.type !== 'block') {
112
- return;
113
- }
114
- const start = node.startPosition.row;
115
- const meaningful = body.namedChildren.filter(
116
- (c: SyntaxNode) => c.type !== 'pass_statement',
117
- );
118
- if (meaningful.length === 0) {
119
- if (body.startPosition.row > start) {
120
- state.ellipsisAfter.add(body.startPosition.row - 1);
121
- } else if (!state.replace.has(start)) {
122
- state.replace.set(start, collapseOneLineBody(state.lines[start]));
123
- }
124
- return;
125
- }
126
- for (const st of body.namedChildren) {
127
- processStatement(state, st);
128
- }
129
- return;
130
- }
131
- case 'expression_statement': {
132
- const hasAssign = node.namedChildren.some(
133
- (c: SyntaxNode) => c.type === 'assignment',
134
- );
135
- if (hasAssign) {
136
- addRange(state, node.startPosition.row, node.endPosition.row);
137
- }
138
- return;
139
- }
140
- default:
141
- return;
142
- }
143
- }
144
-
145
- function buildSkeletonAst(content: string): string {
146
- const parser = getParser();
147
- const tree = parser.parse(content);
148
- const lines = content.split(/\r?\n/);
149
- const state: SkeletonState = {
150
- lines,
151
- keep: new Set<number>(),
152
- replace: new Map<number, string>(),
153
- ellipsisAfter: new Set<number>(),
154
- };
155
-
156
- if (tree.rootNode.type === 'module') {
157
- for (const child of tree.rootNode.namedChildren) {
158
- processStatement(state, child);
159
- }
160
- }
161
-
162
- const out: string[] = [];
163
- for (let i = 0; i < lines.length; i++) {
164
- if (state.replace.has(i)) {
165
- out.push(state.replace.get(i)!);
166
- continue;
167
- }
168
- if (state.keep.has(i)) {
169
- out.push(lines[i]);
170
- if (state.ellipsisAfter.has(i)) {
171
- const ind = baseIndent(lines[i]);
172
- out.push(`${ind} ...`);
173
- }
174
- }
175
- }
176
-
177
- return out.join('\n').trim();
178
- }
179
-
180
- function walkImports(node: SyntaxNode, acc: Set<string>): void {
181
- if (node.type === 'import_from_statement') {
182
- const mod = node.childForFieldName('module_name');
183
- if (mod) {
184
- acc.add(mod.text);
185
- }
186
- } else if (node.type === 'import_statement') {
187
- for (const child of node.namedChildren) {
188
- if (child.type === 'dotted_name') {
189
- acc.add(child.text);
190
- } else if (child.type === 'aliased_import') {
191
- const n = child.childForFieldName('name');
192
- if (n?.type === 'dotted_name') {
193
- acc.add(n.text);
194
- }
195
- }
196
- }
197
- }
198
- for (const child of node.namedChildren) {
199
- walkImports(child, acc);
200
- }
201
- }
202
-
203
- function walkSymbols(node: SyntaxNode, symbols: Symbol[]): void {
204
- if (node.type === 'function_definition') {
205
- const nameNode = node.childForFieldName('name');
206
- if (nameNode) {
207
- symbols.push({
208
- name: nameNode.text,
209
- kind: 'function',
210
- line: node.startPosition.row + 1,
211
- });
212
- }
213
- } else if (node.type === 'class_definition') {
214
- const nameNode = node.childForFieldName('name');
215
- if (nameNode) {
216
- symbols.push({
217
- name: nameNode.text,
218
- kind: 'class',
219
- line: node.startPosition.row + 1,
220
- });
221
- }
222
- } else if (node.type === 'type_alias_statement') {
223
- const left = node.childForFieldName('left');
224
- if (left) {
225
- symbols.push({
226
- name: left.text,
227
- kind: 'type',
228
- line: node.startPosition.row + 1,
229
- });
230
- }
231
- } else if (node.type === 'assignment') {
232
- const left = node.childForFieldName('left');
233
- if (left?.type === 'identifier') {
234
- symbols.push({
235
- name: left.text,
236
- kind: 'variable',
237
- line: node.startPosition.row + 1,
238
- });
239
- }
240
- }
241
- for (const child of node.namedChildren) {
242
- walkSymbols(child, symbols);
243
- }
244
- }
245
-
246
- export class PythonAdapter implements LanguageAdapter {
247
- name = 'python';
248
- extensions = ['.py', '.pyi'];
249
-
250
- canHandle(filePath: string): boolean {
251
- return this.extensions.includes(path.extname(filePath).toLowerCase());
252
- }
253
-
254
- generateSkeleton(content: string, _filePath: string, _rootDir?: string): SkeletonResult {
255
- try {
256
- return { skeleton: buildSkeletonAst(content) };
257
- } catch {
258
- return { skeleton: content.split('\n').slice(0, 50).join('\n') };
259
- }
260
- }
261
-
262
- extractSymbols(content: string): Symbol[] {
263
- try {
264
- const parser = getParser();
265
- const tree = parser.parse(content);
266
- const symbols: Symbol[] = [];
267
- walkSymbols(tree.rootNode, symbols);
268
- return symbols;
269
- } catch {
270
- return [];
271
- }
272
- }
273
-
274
- extractImports(content: string): string[] {
275
- try {
276
- const parser = getParser();
277
- const tree = parser.parse(content);
278
- const mods = new Set<string>();
279
- walkImports(tree.rootNode, mods);
280
- return [...mods];
281
- } catch {
282
- return [];
283
- }
284
- }
285
- }
@@ -1,81 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as os from 'os';
3
- import * as path from 'path';
4
- import { CodeAnalysisAgent } from '../src/agent';
5
- import { buildSkeletonWithMapping, saveMappingToFile, type SymbolMapping } from '../src/mapper';
6
-
7
- describe('CodeAnalysisAgent local tool wrappers', () => {
8
- let tempDir: string;
9
- let sampleFile: string;
10
- let mappingPath: string;
11
- let agent: CodeAnalysisAgent;
12
-
13
- beforeAll(() => {
14
- tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'uca-agent-test-'));
15
- sampleFile = path.join(tempDir, 'sample.ts');
16
- mappingPath = path.join(tempDir, 'code-analyzer.mapping.json');
17
-
18
- fs.writeFileSync(
19
- sampleFile,
20
- [
21
- 'export class AuthService {',
22
- ' login(user: string): boolean {',
23
- ' return user.length > 0;',
24
- ' }',
25
- '}',
26
- '',
27
- 'export function sendMessage(room: string, text: string): string {',
28
- ' return `${room}:${text}`;',
29
- '}',
30
- ].join('\n'),
31
- 'utf-8'
32
- );
33
-
34
- const mappingResult = buildSkeletonWithMapping(sampleFile, tempDir);
35
- const mapping: SymbolMapping = {
36
- generatedAt: new Date().toISOString(),
37
- rootPath: tempDir,
38
- files: {
39
- 'sample.ts': {
40
- originalPath: sampleFile,
41
- symbols: mappingResult.symbols,
42
- },
43
- },
44
- };
45
-
46
- saveMappingToFile(mapping, mappingPath);
47
-
48
- agent = new CodeAnalysisAgent('test-api-key');
49
- agent.loadMapping(mappingPath);
50
- });
51
-
52
- afterAll(() => {
53
- if (fs.existsSync(tempDir)) {
54
- fs.rmSync(tempDir, { recursive: true, force: true });
55
- }
56
- });
57
-
58
- it('searchSymbols should find matching symbols', () => {
59
- const result = agent.searchSymbols('login');
60
- expect(result.error).toBeUndefined();
61
- expect(result.count).toBeGreaterThan(0);
62
-
63
- const found = result.results.some((item: any) => item.symbolId === 'AuthService.login');
64
- expect(found).toBe(true);
65
- });
66
-
67
- it('getSymbolDetails should return implementation with location', () => {
68
- const result = agent.getSymbolDetails('sendMessage');
69
- expect(result.error).toBeUndefined();
70
- expect(result.content).toContain('return `${room}:${text}`;');
71
- expect(result.location.file).toBe('sample.ts');
72
- });
73
-
74
- it('analyzeFileDependencies should return symbol list for file', () => {
75
- const result = agent.analyzeFileDependencies('sample.ts');
76
- expect(result.error).toBeUndefined();
77
- expect(result.file).toBe('sample.ts');
78
- expect(Array.isArray(result.symbols)).toBe(true);
79
- expect(result.symbols.length).toBeGreaterThan(0);
80
- });
81
- });
@@ -1,31 +0,0 @@
1
- import { benchmarkProject } from '../src/benchmark';
2
- import * as path from 'path';
3
-
4
- describe('Benchmark Tests', () => {
5
- it('BENCHMARK_SCENARIOS module keys are strings', () => {
6
- const { BENCHMARK_SCENARIOS } = require('../src/benchmark');
7
- BENCHMARK_SCENARIOS.forEach((scenario: { moduleKey: string }) => {
8
- expect(typeof scenario.moduleKey).toBe('string');
9
- expect(scenario.moduleKey.length).toBeGreaterThan(0);
10
- });
11
- });
12
-
13
- it('Scenario path resolution is deterministic and scoped', async () => {
14
- const fixtureRoot = path.join(__dirname, 'fixtures');
15
- const moduleKey = 'src';
16
-
17
- const benchmarkResult = await benchmarkProject(fixtureRoot, moduleKey, 'Test Project', 'authentication flow');
18
-
19
- const expectedPath = path.join(fixtureRoot, 'src');
20
- expect(benchmarkResult.projectRoot).toBe(expectedPath);
21
-
22
- expect(benchmarkResult.top1KeywordCoverage).toBeGreaterThanOrEqual(0);
23
- expect(benchmarkResult.top1KeywordCoverage).toBeLessThanOrEqual(1);
24
- expect(benchmarkResult.avgTopKKeywordCoverage).toBeGreaterThanOrEqual(0);
25
- expect(benchmarkResult.avgTopKKeywordCoverage).toBeLessThanOrEqual(1);
26
- expect(benchmarkResult.pathIntentMatchRate).toBeGreaterThanOrEqual(0);
27
- expect(benchmarkResult.pathIntentMatchRate).toBeLessThanOrEqual(1);
28
- expect(benchmarkResult.directoryDiversity).toBeGreaterThanOrEqual(0);
29
- expect(benchmarkResult.directoryDiversity).toBeLessThanOrEqual(1);
30
- });
31
- });