@rigour-labs/core 3.0.4 → 3.0.6
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/dist/gates/deprecated-apis-rules-lang.d.ts +21 -0
- package/dist/gates/deprecated-apis-rules-lang.js +311 -0
- package/dist/gates/deprecated-apis-rules-node.d.ts +19 -0
- package/dist/gates/deprecated-apis-rules-node.js +199 -0
- package/dist/gates/deprecated-apis-rules.d.ts +6 -0
- package/dist/gates/deprecated-apis-rules.js +6 -0
- package/dist/gates/deprecated-apis.js +1 -502
- package/dist/gates/hallucinated-imports-lang.d.ts +16 -0
- package/dist/gates/hallucinated-imports-lang.js +374 -0
- package/dist/gates/hallucinated-imports-stdlib.d.ts +12 -0
- package/dist/gates/hallucinated-imports-stdlib.js +228 -0
- package/dist/gates/hallucinated-imports.d.ts +0 -98
- package/dist/gates/hallucinated-imports.js +10 -678
- package/dist/gates/phantom-apis-data.d.ts +33 -0
- package/dist/gates/phantom-apis-data.js +398 -0
- package/dist/gates/phantom-apis.js +1 -393
- package/dist/gates/phantom-apis.test.js +52 -0
- package/dist/gates/promise-safety-helpers.d.ts +19 -0
- package/dist/gates/promise-safety-helpers.js +101 -0
- package/dist/gates/promise-safety-rules.d.ts +7 -0
- package/dist/gates/promise-safety-rules.js +19 -0
- package/dist/gates/promise-safety.d.ts +1 -21
- package/dist/gates/promise-safety.js +51 -257
- package/dist/gates/test-quality-lang.d.ts +30 -0
- package/dist/gates/test-quality-lang.js +188 -0
- package/dist/gates/test-quality.d.ts +0 -14
- package/dist/gates/test-quality.js +13 -186
- package/dist/pattern-index/indexer-helpers.d.ts +38 -0
- package/dist/pattern-index/indexer-helpers.js +111 -0
- package/dist/pattern-index/indexer-lang.d.ts +13 -0
- package/dist/pattern-index/indexer-lang.js +244 -0
- package/dist/pattern-index/indexer-ts.d.ts +22 -0
- package/dist/pattern-index/indexer-ts.js +258 -0
- package/dist/pattern-index/indexer.d.ts +4 -106
- package/dist/pattern-index/indexer.js +58 -707
- package/dist/pattern-index/staleness-data.d.ts +6 -0
- package/dist/pattern-index/staleness-data.js +262 -0
- package/dist/pattern-index/staleness.js +1 -258
- package/dist/templates/index.d.ts +12 -16
- package/dist/templates/index.js +11 -527
- package/dist/templates/paradigms.d.ts +2 -0
- package/dist/templates/paradigms.js +46 -0
- package/dist/templates/presets.d.ts +14 -0
- package/dist/templates/presets.js +227 -0
- package/dist/templates/universal-config.d.ts +2 -0
- package/dist/templates/universal-config.js +171 -0
- package/package.json +1 -1
|
@@ -3,45 +3,41 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Scans the codebase and extracts patterns using AST parsing.
|
|
5
5
|
* This is the core engine of the Pattern Index system.
|
|
6
|
+
*
|
|
7
|
+
* Language-specific extractors live in indexer-lang.ts.
|
|
8
|
+
* TypeScript/JS AST helpers live in indexer-ts.ts.
|
|
9
|
+
* Pure utility functions live in indexer-helpers.ts.
|
|
6
10
|
*/
|
|
7
11
|
import * as fs from 'fs/promises';
|
|
8
12
|
import * as path from 'path';
|
|
9
|
-
import { createHash } from 'crypto';
|
|
10
13
|
import { globby } from 'globby';
|
|
11
14
|
import ts from 'typescript';
|
|
12
15
|
import { generateEmbedding } from './embeddings.js';
|
|
13
|
-
|
|
16
|
+
import { hashContent } from './indexer-helpers.js';
|
|
17
|
+
import { extractGoPatterns, extractRustPatterns, extractJVMStylePatterns, extractPythonPatterns, extractGenericCPatterns, } from './indexer-lang.js';
|
|
18
|
+
import { nodeToPattern } from './indexer-ts.js';
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Configuration
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
14
22
|
const DEFAULT_CONFIG = {
|
|
15
23
|
include: ['src/**/*', 'lib/**/*', 'app/**/*', 'components/**/*', 'utils/**/*', 'hooks/**/*', '**/tests/**/*', '**/test/**/*'],
|
|
16
24
|
exclude: [
|
|
17
|
-
'**/node_modules/**',
|
|
18
|
-
'**/
|
|
19
|
-
'**/
|
|
20
|
-
'**/.
|
|
21
|
-
'**/coverage/**',
|
|
22
|
-
'**/venv/**',
|
|
23
|
-
'**/.venv/**',
|
|
24
|
-
'**/__pycache__/**',
|
|
25
|
-
'**/site-packages/**',
|
|
26
|
-
'**/.pytest_cache/**',
|
|
27
|
-
'**/target/**', // Rust build dir
|
|
28
|
-
'**/bin/**',
|
|
29
|
-
'**/.gradle/**',
|
|
30
|
-
'**/.mvn/**'
|
|
25
|
+
'**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**',
|
|
26
|
+
'**/coverage/**', '**/venv/**', '**/.venv/**', '**/__pycache__/**',
|
|
27
|
+
'**/site-packages/**', '**/.pytest_cache/**', '**/target/**',
|
|
28
|
+
'**/bin/**', '**/.gradle/**', '**/.mvn/**',
|
|
31
29
|
],
|
|
32
30
|
extensions: ['.ts', '.tsx', '.js', '.jsx', '.py', '.go', '.rs', '.java', '.cpp', '.h', '.rb', '.php', '.cs', '.kt'],
|
|
33
31
|
indexTests: false,
|
|
34
32
|
indexNodeModules: false,
|
|
35
33
|
minNameLength: 2,
|
|
36
34
|
categories: {},
|
|
37
|
-
useEmbeddings: false
|
|
35
|
+
useEmbeddings: false,
|
|
38
36
|
};
|
|
39
|
-
/** Current index format version */
|
|
40
37
|
const INDEX_VERSION = '1.0.0';
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
*/
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// PatternIndexer class
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
45
41
|
export class PatternIndexer {
|
|
46
42
|
config;
|
|
47
43
|
rootDir;
|
|
@@ -51,9 +47,7 @@ export class PatternIndexer {
|
|
|
51
47
|
}
|
|
52
48
|
async buildIndex() {
|
|
53
49
|
const startTime = Date.now();
|
|
54
|
-
// Find all files to index
|
|
55
50
|
const files = await this.findFiles();
|
|
56
|
-
// Process files in parallel batches (concurrency: 10)
|
|
57
51
|
const BATCH_SIZE = 10;
|
|
58
52
|
const patterns = [];
|
|
59
53
|
const indexedFiles = [];
|
|
@@ -63,7 +57,7 @@ export class PatternIndexer {
|
|
|
63
57
|
try {
|
|
64
58
|
const relativePath = path.relative(this.rootDir, file);
|
|
65
59
|
const content = await fs.readFile(file, 'utf-8');
|
|
66
|
-
const fileHash =
|
|
60
|
+
const fileHash = hashContent(content);
|
|
67
61
|
const filePatterns = await this.extractPatterns(file, content);
|
|
68
62
|
return {
|
|
69
63
|
patterns: filePatterns,
|
|
@@ -71,8 +65,8 @@ export class PatternIndexer {
|
|
|
71
65
|
path: relativePath,
|
|
72
66
|
hash: fileHash,
|
|
73
67
|
patternCount: filePatterns.length,
|
|
74
|
-
indexedAt: new Date().toISOString()
|
|
75
|
-
}
|
|
68
|
+
indexedAt: new Date().toISOString(),
|
|
69
|
+
},
|
|
76
70
|
};
|
|
77
71
|
}
|
|
78
72
|
catch (error) {
|
|
@@ -87,9 +81,7 @@ export class PatternIndexer {
|
|
|
87
81
|
}
|
|
88
82
|
}
|
|
89
83
|
}
|
|
90
|
-
// Generate embeddings in parallel batches if enabled
|
|
91
84
|
if (this.config.useEmbeddings && patterns.length > 0) {
|
|
92
|
-
// Use stderr to avoid contaminating JSON output on stdout
|
|
93
85
|
process.stderr.write(`Generating embeddings for ${patterns.length} patterns...\n`);
|
|
94
86
|
for (let i = 0; i < patterns.length; i += BATCH_SIZE) {
|
|
95
87
|
const batch = patterns.slice(i, i + BATCH_SIZE);
|
|
@@ -100,60 +92,49 @@ export class PatternIndexer {
|
|
|
100
92
|
}
|
|
101
93
|
const endTime = Date.now();
|
|
102
94
|
const stats = this.calculateStats(patterns, indexedFiles, endTime - startTime);
|
|
103
|
-
|
|
95
|
+
return {
|
|
104
96
|
version: INDEX_VERSION,
|
|
105
97
|
lastUpdated: new Date().toISOString(),
|
|
106
98
|
rootDir: this.rootDir,
|
|
107
99
|
patterns,
|
|
108
100
|
stats,
|
|
109
|
-
files: indexedFiles
|
|
101
|
+
files: indexedFiles,
|
|
110
102
|
};
|
|
111
|
-
return index;
|
|
112
103
|
}
|
|
113
|
-
/**
|
|
114
|
-
* Incremental index update - only reindex changed files.
|
|
115
|
-
*/
|
|
116
104
|
async updateIndex(existingIndex) {
|
|
117
105
|
const startTime = Date.now();
|
|
118
106
|
const files = await this.findFiles();
|
|
119
107
|
const updatedPatterns = [];
|
|
120
108
|
const updatedFiles = [];
|
|
121
|
-
// Create a map of existing file hashes
|
|
122
109
|
const existingFileMap = new Map(existingIndex.files.map(f => [f.path, f]));
|
|
123
|
-
// Process files in parallel batches (concurrency: 10)
|
|
124
110
|
const BATCH_SIZE = 10;
|
|
125
111
|
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
126
112
|
const batch = files.slice(i, i + BATCH_SIZE);
|
|
127
113
|
const results = await Promise.all(batch.map(async (file) => {
|
|
128
114
|
const relativePath = path.relative(this.rootDir, file);
|
|
129
115
|
const content = await fs.readFile(file, 'utf-8');
|
|
130
|
-
const fileHash =
|
|
116
|
+
const fileHash = hashContent(content);
|
|
131
117
|
const existingFile = existingFileMap.get(relativePath);
|
|
132
118
|
if (existingFile && existingFile.hash === fileHash) {
|
|
133
|
-
// File unchanged, keep existing patterns
|
|
134
119
|
const existingPatterns = existingIndex.patterns.filter(p => p.file === relativePath);
|
|
135
120
|
return { patterns: existingPatterns, fileInfo: existingFile };
|
|
136
121
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
}
|
|
122
|
+
const filePatterns = await this.extractPatterns(file, content);
|
|
123
|
+
return {
|
|
124
|
+
patterns: filePatterns,
|
|
125
|
+
fileInfo: {
|
|
126
|
+
path: relativePath,
|
|
127
|
+
hash: fileHash,
|
|
128
|
+
patternCount: filePatterns.length,
|
|
129
|
+
indexedAt: new Date().toISOString(),
|
|
130
|
+
},
|
|
131
|
+
};
|
|
150
132
|
}));
|
|
151
133
|
for (const result of results) {
|
|
152
134
|
updatedPatterns.push(...result.patterns);
|
|
153
135
|
updatedFiles.push(result.fileInfo);
|
|
154
136
|
}
|
|
155
137
|
}
|
|
156
|
-
// Update embeddings for new/changed patterns if enabled
|
|
157
138
|
if (this.config.useEmbeddings && updatedPatterns.length > 0) {
|
|
158
139
|
for (let i = 0; i < updatedPatterns.length; i += BATCH_SIZE) {
|
|
159
140
|
const batch = updatedPatterns.slice(i, i + BATCH_SIZE);
|
|
@@ -172,48 +153,44 @@ export class PatternIndexer {
|
|
|
172
153
|
rootDir: this.rootDir,
|
|
173
154
|
patterns: updatedPatterns,
|
|
174
155
|
stats,
|
|
175
|
-
files: updatedFiles
|
|
156
|
+
files: updatedFiles,
|
|
176
157
|
};
|
|
177
158
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
159
|
+
// -------------------------------------------------------------------------
|
|
160
|
+
// Private helpers
|
|
161
|
+
// -------------------------------------------------------------------------
|
|
181
162
|
async findFiles() {
|
|
182
|
-
const patterns = this.config.include
|
|
183
|
-
|
|
163
|
+
const patterns = this.config.include
|
|
164
|
+
.map(p => this.config.extensions.map(ext => (p.endsWith('*') ? `${p}${ext}` : p)))
|
|
165
|
+
.flat();
|
|
166
|
+
const exclude = [...this.config.exclude];
|
|
184
167
|
if (!this.config.indexTests) {
|
|
185
168
|
exclude.push('**/*.test.*', '**/*.spec.*', '**/__tests__/**');
|
|
186
169
|
}
|
|
187
|
-
|
|
170
|
+
return globby(patterns, {
|
|
188
171
|
cwd: this.rootDir,
|
|
189
172
|
absolute: true,
|
|
190
173
|
ignore: exclude,
|
|
191
|
-
gitignore: true
|
|
174
|
+
gitignore: true,
|
|
192
175
|
});
|
|
193
|
-
return files;
|
|
194
176
|
}
|
|
195
|
-
/**
|
|
196
|
-
* Extract patterns from a single file using TypeScript AST.
|
|
197
|
-
*/
|
|
198
177
|
async extractPatterns(filePath, content) {
|
|
199
178
|
const ext = path.extname(filePath).toLowerCase();
|
|
200
|
-
|
|
179
|
+
const { rootDir, config } = this;
|
|
201
180
|
if (ext === '.py')
|
|
202
|
-
return
|
|
181
|
+
return extractPythonPatterns(filePath, content, rootDir, config.minNameLength);
|
|
203
182
|
if (ext === '.go')
|
|
204
|
-
return
|
|
183
|
+
return extractGoPatterns(filePath, content, rootDir);
|
|
205
184
|
if (ext === '.rs')
|
|
206
|
-
return
|
|
185
|
+
return extractRustPatterns(filePath, content, rootDir);
|
|
207
186
|
if (ext === '.java' || ext === '.kt' || ext === '.cs')
|
|
208
|
-
return
|
|
209
|
-
// Fallback for TS/JS or other C-style languages
|
|
210
|
-
const patterns = [];
|
|
211
|
-
const relativePath = path.relative(this.rootDir, filePath);
|
|
212
|
-
// For TS/JS, use AST
|
|
187
|
+
return extractJVMStylePatterns(filePath, content, rootDir);
|
|
213
188
|
if (['.ts', '.tsx', '.js', '.jsx'].includes(ext)) {
|
|
189
|
+
const patterns = [];
|
|
190
|
+
const relativePath = path.relative(this.rootDir, filePath);
|
|
214
191
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, this.getScriptKind(filePath));
|
|
215
192
|
const visit = (node) => {
|
|
216
|
-
const pattern =
|
|
193
|
+
const pattern = nodeToPattern(node, sourceFile, relativePath, content, config.minNameLength);
|
|
217
194
|
if (pattern)
|
|
218
195
|
patterns.push(pattern);
|
|
219
196
|
ts.forEachChild(node, visit);
|
|
@@ -221,621 +198,10 @@ export class PatternIndexer {
|
|
|
221
198
|
visit(sourceFile);
|
|
222
199
|
return patterns;
|
|
223
200
|
}
|
|
224
|
-
|
|
225
|
-
return this.extractGenericCPatterns(filePath, content);
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* Extract patterns from Go files.
|
|
229
|
-
*/
|
|
230
|
-
extractGoPatterns(filePath, content) {
|
|
231
|
-
const patterns = [];
|
|
232
|
-
const relativePath = path.relative(this.rootDir, filePath);
|
|
233
|
-
const lines = content.split('\n');
|
|
234
|
-
const funcRegex = /^func\s+(?:\([^)]*\)\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*\(([^)]*)\)\s*([^\{]*)\s*\{/;
|
|
235
|
-
const typeRegex = /^type\s+([A-Za-z_][A-Za-z0-9_]*)\s+(struct|interface)/;
|
|
236
|
-
for (let i = 0; i < lines.length; i++) {
|
|
237
|
-
const line = lines[i];
|
|
238
|
-
// Functions
|
|
239
|
-
const funcMatch = line.match(funcRegex);
|
|
240
|
-
if (funcMatch) {
|
|
241
|
-
const name = funcMatch[1];
|
|
242
|
-
patterns.push(this.createPatternEntry({
|
|
243
|
-
type: 'function',
|
|
244
|
-
name,
|
|
245
|
-
file: relativePath,
|
|
246
|
-
line: i + 1,
|
|
247
|
-
endLine: this.findBraceBlockEnd(lines, i),
|
|
248
|
-
signature: `func ${name}(${funcMatch[2]}) ${funcMatch[3].trim()}`,
|
|
249
|
-
description: this.getCOMLineComments(lines, i - 1),
|
|
250
|
-
keywords: this.extractKeywords(name),
|
|
251
|
-
content: this.getBraceBlockContent(lines, i),
|
|
252
|
-
exported: /^[A-Z]/.test(name)
|
|
253
|
-
}));
|
|
254
|
-
}
|
|
255
|
-
// Types/Structs
|
|
256
|
-
const typeMatch = line.match(typeRegex);
|
|
257
|
-
if (typeMatch) {
|
|
258
|
-
const name = typeMatch[1];
|
|
259
|
-
patterns.push(this.createPatternEntry({
|
|
260
|
-
type: typeMatch[2],
|
|
261
|
-
name,
|
|
262
|
-
file: relativePath,
|
|
263
|
-
line: i + 1,
|
|
264
|
-
endLine: this.findBraceBlockEnd(lines, i),
|
|
265
|
-
signature: `type ${name} ${typeMatch[2]}`,
|
|
266
|
-
description: this.getCOMLineComments(lines, i - 1),
|
|
267
|
-
keywords: this.extractKeywords(name),
|
|
268
|
-
content: this.getBraceBlockContent(lines, i),
|
|
269
|
-
exported: /^[A-Z]/.test(name)
|
|
270
|
-
}));
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
return patterns;
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* Extract patterns from Rust files.
|
|
277
|
-
*/
|
|
278
|
-
extractRustPatterns(filePath, content) {
|
|
279
|
-
const patterns = [];
|
|
280
|
-
const relativePath = path.relative(this.rootDir, filePath);
|
|
281
|
-
const lines = content.split('\n');
|
|
282
|
-
const fnRegex = /^(?:pub\s+)?(?:async\s+)?fn\s+([A-Za-z_][A-Za-z0-9_]*)\s*[<(][^)]*[>)]\s*(?:->\s*[^\{]+)?\s*\{/;
|
|
283
|
-
const typeRegex = /^(?:pub\s+)?(struct|enum|trait)\s+([A-Za-z_][A-Za-z0-9_]*)/;
|
|
284
|
-
for (let i = 0; i < lines.length; i++) {
|
|
285
|
-
const line = lines[i];
|
|
286
|
-
const fnMatch = line.match(fnRegex);
|
|
287
|
-
if (fnMatch) {
|
|
288
|
-
const name = fnMatch[1];
|
|
289
|
-
patterns.push(this.createPatternEntry({
|
|
290
|
-
type: 'function',
|
|
291
|
-
name,
|
|
292
|
-
file: relativePath,
|
|
293
|
-
line: i + 1,
|
|
294
|
-
endLine: this.findBraceBlockEnd(lines, i),
|
|
295
|
-
signature: line.split('{')[0].trim(),
|
|
296
|
-
description: this.getCOMLineComments(lines, i - 1),
|
|
297
|
-
keywords: this.extractKeywords(name),
|
|
298
|
-
content: this.getBraceBlockContent(lines, i),
|
|
299
|
-
exported: line.startsWith('pub')
|
|
300
|
-
}));
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return patterns;
|
|
304
|
-
}
|
|
305
|
-
/**
|
|
306
|
-
* Generic extraction for C-style languages (Java, C++, PHP, etc.)
|
|
307
|
-
*/
|
|
308
|
-
extractJVMStylePatterns(filePath, content) {
|
|
309
|
-
const patterns = [];
|
|
310
|
-
const relativePath = path.relative(this.rootDir, filePath);
|
|
311
|
-
const lines = content.split('\n');
|
|
312
|
-
// Simplified for classes and methods
|
|
313
|
-
const classRegex = /^(?:public|private|protected|internal)?\s*(?:static\s+)?(?:final\s+)?(?:class|interface|enum)\s+([A-Za-z0-9_]+)/;
|
|
314
|
-
const methodRegex = /^(?:public|private|protected|internal)\s+(?:static\s+)?(?:async\s+)?(?:[A-Za-z0-9_<>\[\]]+\s+)([A-Za-z0-9_]+)\s*\(/;
|
|
315
|
-
for (let i = 0; i < lines.length; i++) {
|
|
316
|
-
const line = lines[i].trim();
|
|
317
|
-
const classMatch = line.match(classRegex);
|
|
318
|
-
if (classMatch) {
|
|
319
|
-
patterns.push(this.createPatternEntry({
|
|
320
|
-
type: 'class',
|
|
321
|
-
name: classMatch[1],
|
|
322
|
-
file: relativePath,
|
|
323
|
-
line: i + 1,
|
|
324
|
-
endLine: this.findBraceBlockEnd(lines, i),
|
|
325
|
-
signature: line,
|
|
326
|
-
description: this.getJavaDoc(lines, i - 1),
|
|
327
|
-
keywords: this.extractKeywords(classMatch[1]),
|
|
328
|
-
content: this.getBraceBlockContent(lines, i),
|
|
329
|
-
exported: line.includes('public')
|
|
330
|
-
}));
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
return patterns;
|
|
334
|
-
}
|
|
335
|
-
extractGenericCPatterns(filePath, content) {
|
|
336
|
-
// Fallback for everything else
|
|
337
|
-
return [];
|
|
338
|
-
}
|
|
339
|
-
getCOMLineComments(lines, startIndex) {
|
|
340
|
-
let comments = [];
|
|
341
|
-
for (let i = startIndex; i >= 0; i--) {
|
|
342
|
-
const line = lines[i].trim();
|
|
343
|
-
if (line.startsWith('//'))
|
|
344
|
-
comments.unshift(line.replace('//', '').trim());
|
|
345
|
-
else
|
|
346
|
-
break;
|
|
347
|
-
}
|
|
348
|
-
return comments.join(' ');
|
|
349
|
-
}
|
|
350
|
-
getJavaDoc(lines, startIndex) {
|
|
351
|
-
let comments = [];
|
|
352
|
-
let inDoc = false;
|
|
353
|
-
for (let i = startIndex; i >= 0; i--) {
|
|
354
|
-
const line = lines[i].trim();
|
|
355
|
-
if (line.endsWith('*/'))
|
|
356
|
-
inDoc = true;
|
|
357
|
-
if (inDoc)
|
|
358
|
-
comments.unshift(line.replace('/**', '').replace('*/', '').replace('*', '').trim());
|
|
359
|
-
if (line.startsWith('/**'))
|
|
360
|
-
break;
|
|
361
|
-
}
|
|
362
|
-
return comments.join(' ');
|
|
363
|
-
}
|
|
364
|
-
findBraceBlockEnd(lines, startIndex) {
|
|
365
|
-
let braceCount = 0;
|
|
366
|
-
let started = false;
|
|
367
|
-
for (let i = startIndex; i < lines.length; i++) {
|
|
368
|
-
const line = lines[i];
|
|
369
|
-
if (line.includes('{')) {
|
|
370
|
-
braceCount += (line.match(/\{/g) || []).length;
|
|
371
|
-
started = true;
|
|
372
|
-
}
|
|
373
|
-
if (line.includes('}')) {
|
|
374
|
-
braceCount -= (line.match(/\}/g) || []).length;
|
|
375
|
-
}
|
|
376
|
-
if (started && braceCount === 0)
|
|
377
|
-
return i + 1;
|
|
378
|
-
}
|
|
379
|
-
return lines.length;
|
|
380
|
-
}
|
|
381
|
-
getBraceBlockContent(lines, startIndex) {
|
|
382
|
-
const end = this.findBraceBlockEnd(lines, startIndex);
|
|
383
|
-
return lines.slice(startIndex, end).join('\n');
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
|
-
* Extract patterns from Python files using regex.
|
|
387
|
-
*/
|
|
388
|
-
extractPythonPatterns(filePath, content) {
|
|
389
|
-
const patterns = [];
|
|
390
|
-
const relativePath = path.relative(this.rootDir, filePath);
|
|
391
|
-
const lines = content.split('\n');
|
|
392
|
-
// Regex for Class definitions
|
|
393
|
-
const classRegex = /^class\s+([A-Za-z_][A-Za-z0-9_]*)\s*(\([^)]*\))?\s*:/;
|
|
394
|
-
// Regex for Function definitions (including async)
|
|
395
|
-
const funcRegex = /^(?:async\s+)?def\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(([^)]*)\)\s*(?:->\s*[^:]+)?\s*:/;
|
|
396
|
-
// Regex for Constants (Top-level UPPER_CASE variables)
|
|
397
|
-
const constRegex = /^([A-Z][A-Z0-9_]*)\s*=\s*(.+)$/;
|
|
398
|
-
for (let i = 0; i < lines.length; i++) {
|
|
399
|
-
const lineContent = lines[i].trim();
|
|
400
|
-
const originalLine = lines[i];
|
|
401
|
-
const lineNum = i + 1;
|
|
402
|
-
// Classes
|
|
403
|
-
const classMatch = originalLine.match(classRegex);
|
|
404
|
-
if (classMatch) {
|
|
405
|
-
const name = classMatch[1];
|
|
406
|
-
if (name.length >= this.config.minNameLength) {
|
|
407
|
-
patterns.push(this.createPatternEntry({
|
|
408
|
-
type: this.detectPythonClassType(name),
|
|
409
|
-
name,
|
|
410
|
-
file: relativePath,
|
|
411
|
-
line: lineNum,
|
|
412
|
-
endLine: this.findPythonBlockEnd(lines, i),
|
|
413
|
-
signature: `class ${name}${classMatch[2] || ''}`,
|
|
414
|
-
description: this.getPythonDocstring(lines, i + 1),
|
|
415
|
-
keywords: this.extractKeywords(name),
|
|
416
|
-
content: this.getPythonBlockContent(lines, i),
|
|
417
|
-
exported: !name.startsWith('_')
|
|
418
|
-
}));
|
|
419
|
-
continue;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
// Functions
|
|
423
|
-
const funcMatch = originalLine.match(funcRegex);
|
|
424
|
-
if (funcMatch) {
|
|
425
|
-
const name = funcMatch[1];
|
|
426
|
-
if (name.length >= this.config.minNameLength) {
|
|
427
|
-
patterns.push(this.createPatternEntry({
|
|
428
|
-
type: this.detectPythonFunctionType(name),
|
|
429
|
-
name,
|
|
430
|
-
file: relativePath,
|
|
431
|
-
line: lineNum,
|
|
432
|
-
endLine: this.findPythonBlockEnd(lines, i),
|
|
433
|
-
signature: `def ${name}(${funcMatch[2]})`,
|
|
434
|
-
description: this.getPythonDocstring(lines, i + 1),
|
|
435
|
-
keywords: this.extractKeywords(name),
|
|
436
|
-
content: this.getPythonBlockContent(lines, i),
|
|
437
|
-
exported: !name.startsWith('_')
|
|
438
|
-
}));
|
|
439
|
-
continue;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
// Constants
|
|
443
|
-
const constMatch = originalLine.match(constRegex);
|
|
444
|
-
if (constMatch) {
|
|
445
|
-
const name = constMatch[1];
|
|
446
|
-
if (name.length >= this.config.minNameLength) {
|
|
447
|
-
patterns.push(this.createPatternEntry({
|
|
448
|
-
type: 'constant',
|
|
449
|
-
name,
|
|
450
|
-
file: relativePath,
|
|
451
|
-
line: lineNum,
|
|
452
|
-
endLine: lineNum,
|
|
453
|
-
signature: `${name} = ...`,
|
|
454
|
-
description: '',
|
|
455
|
-
keywords: this.extractKeywords(name),
|
|
456
|
-
content: originalLine,
|
|
457
|
-
exported: !name.startsWith('_')
|
|
458
|
-
}));
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
return patterns;
|
|
463
|
-
}
|
|
464
|
-
detectPythonClassType(name) {
|
|
465
|
-
if (name.endsWith('Error') || name.endsWith('Exception'))
|
|
466
|
-
return 'error';
|
|
467
|
-
if (name.endsWith('Model'))
|
|
468
|
-
return 'model';
|
|
469
|
-
if (name.endsWith('Schema'))
|
|
470
|
-
return 'schema';
|
|
471
|
-
return 'class';
|
|
472
|
-
}
|
|
473
|
-
detectPythonFunctionType(name) {
|
|
474
|
-
if (name.startsWith('test_'))
|
|
475
|
-
return 'function'; // Tests are filtered by indexTests config
|
|
476
|
-
if (name.includes('middleware'))
|
|
477
|
-
return 'middleware';
|
|
478
|
-
if (name.includes('handler'))
|
|
479
|
-
return 'handler';
|
|
480
|
-
return 'function';
|
|
481
|
-
}
|
|
482
|
-
getPythonDocstring(lines, startIndex) {
|
|
483
|
-
if (startIndex >= lines.length)
|
|
484
|
-
return '';
|
|
485
|
-
const nextLine = lines[startIndex].trim();
|
|
486
|
-
if (nextLine.startsWith('"""') || nextLine.startsWith("'''")) {
|
|
487
|
-
const quote = nextLine.startsWith('"""') ? '"""' : "'''";
|
|
488
|
-
let doc = nextLine.replace(quote, '');
|
|
489
|
-
if (doc.endsWith(quote))
|
|
490
|
-
return doc.replace(quote, '').trim();
|
|
491
|
-
for (let i = startIndex + 1; i < lines.length; i++) {
|
|
492
|
-
if (lines[i].includes(quote)) {
|
|
493
|
-
doc += ' ' + lines[i].split(quote)[0].trim();
|
|
494
|
-
break;
|
|
495
|
-
}
|
|
496
|
-
doc += ' ' + lines[i].trim();
|
|
497
|
-
}
|
|
498
|
-
return doc.trim();
|
|
499
|
-
}
|
|
500
|
-
return '';
|
|
201
|
+
return extractGenericCPatterns(filePath, content);
|
|
501
202
|
}
|
|
502
|
-
findPythonBlockEnd(lines, startIndex) {
|
|
503
|
-
const startIndent = lines[startIndex].search(/\S/);
|
|
504
|
-
for (let i = startIndex + 1; i < lines.length; i++) {
|
|
505
|
-
if (lines[i].trim() === '')
|
|
506
|
-
continue;
|
|
507
|
-
const currentIndent = lines[i].search(/\S/);
|
|
508
|
-
if (currentIndent <= startIndent)
|
|
509
|
-
return i;
|
|
510
|
-
}
|
|
511
|
-
return lines.length;
|
|
512
|
-
}
|
|
513
|
-
getPythonBlockContent(lines, startIndex) {
|
|
514
|
-
const endLine = this.findPythonBlockEnd(lines, startIndex);
|
|
515
|
-
return lines.slice(startIndex, endLine).join('\n');
|
|
516
|
-
}
|
|
517
|
-
/**
|
|
518
|
-
* Convert an AST node to a PatternEntry if applicable.
|
|
519
|
-
*/
|
|
520
|
-
nodeToPattern(node, sourceFile, filePath, content) {
|
|
521
|
-
const startPos = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
522
|
-
const endPos = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
523
|
-
const line = startPos.line + 1;
|
|
524
|
-
const endLine = endPos.line + 1;
|
|
525
|
-
// Function declarations
|
|
526
|
-
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
527
|
-
const name = node.name.text;
|
|
528
|
-
if (name.length < this.config.minNameLength)
|
|
529
|
-
return null;
|
|
530
|
-
return this.createPatternEntry({
|
|
531
|
-
type: this.detectFunctionType(name, node),
|
|
532
|
-
name,
|
|
533
|
-
file: filePath,
|
|
534
|
-
line,
|
|
535
|
-
endLine,
|
|
536
|
-
signature: this.getFunctionSignature(node, sourceFile),
|
|
537
|
-
description: this.getJSDocDescription(node, sourceFile),
|
|
538
|
-
keywords: this.extractKeywords(name),
|
|
539
|
-
content: node.getText(sourceFile),
|
|
540
|
-
exported: this.isExported(node)
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
// Variable declarations with arrow functions
|
|
544
|
-
if (ts.isVariableStatement(node)) {
|
|
545
|
-
const patterns = [];
|
|
546
|
-
for (const decl of node.declarationList.declarations) {
|
|
547
|
-
if (ts.isIdentifier(decl.name) && decl.initializer) {
|
|
548
|
-
const name = decl.name.text;
|
|
549
|
-
if (name.length < this.config.minNameLength)
|
|
550
|
-
continue;
|
|
551
|
-
if (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer)) {
|
|
552
|
-
return this.createPatternEntry({
|
|
553
|
-
type: this.detectFunctionType(name, decl.initializer),
|
|
554
|
-
name,
|
|
555
|
-
file: filePath,
|
|
556
|
-
line,
|
|
557
|
-
endLine,
|
|
558
|
-
signature: this.getArrowFunctionSignature(decl.initializer, sourceFile),
|
|
559
|
-
description: this.getJSDocDescription(node, sourceFile),
|
|
560
|
-
keywords: this.extractKeywords(name),
|
|
561
|
-
content: node.getText(sourceFile),
|
|
562
|
-
exported: this.isExported(node)
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
// Constants
|
|
566
|
-
if (ts.isStringLiteral(decl.initializer) ||
|
|
567
|
-
ts.isNumericLiteral(decl.initializer) ||
|
|
568
|
-
ts.isObjectLiteralExpression(decl.initializer)) {
|
|
569
|
-
const isConstant = node.declarationList.flags & ts.NodeFlags.Const;
|
|
570
|
-
if (isConstant && name === name.toUpperCase()) {
|
|
571
|
-
return this.createPatternEntry({
|
|
572
|
-
type: 'constant',
|
|
573
|
-
name,
|
|
574
|
-
file: filePath,
|
|
575
|
-
line,
|
|
576
|
-
endLine,
|
|
577
|
-
signature: '',
|
|
578
|
-
description: this.getJSDocDescription(node, sourceFile),
|
|
579
|
-
keywords: this.extractKeywords(name),
|
|
580
|
-
content: node.getText(sourceFile),
|
|
581
|
-
exported: this.isExported(node)
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
// Class declarations
|
|
589
|
-
if (ts.isClassDeclaration(node) && node.name) {
|
|
590
|
-
const name = node.name.text;
|
|
591
|
-
if (name.length < this.config.minNameLength)
|
|
592
|
-
return null;
|
|
593
|
-
return this.createPatternEntry({
|
|
594
|
-
type: this.detectClassType(name, node),
|
|
595
|
-
name,
|
|
596
|
-
file: filePath,
|
|
597
|
-
line,
|
|
598
|
-
endLine,
|
|
599
|
-
signature: this.getClassSignature(node, sourceFile),
|
|
600
|
-
description: this.getJSDocDescription(node, sourceFile),
|
|
601
|
-
keywords: this.extractKeywords(name),
|
|
602
|
-
content: node.getText(sourceFile),
|
|
603
|
-
exported: this.isExported(node)
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
// Interface declarations
|
|
607
|
-
if (ts.isInterfaceDeclaration(node)) {
|
|
608
|
-
const name = node.name.text;
|
|
609
|
-
if (name.length < this.config.minNameLength)
|
|
610
|
-
return null;
|
|
611
|
-
return this.createPatternEntry({
|
|
612
|
-
type: 'interface',
|
|
613
|
-
name,
|
|
614
|
-
file: filePath,
|
|
615
|
-
line,
|
|
616
|
-
endLine,
|
|
617
|
-
signature: this.getInterfaceSignature(node, sourceFile),
|
|
618
|
-
description: this.getJSDocDescription(node, sourceFile),
|
|
619
|
-
keywords: this.extractKeywords(name),
|
|
620
|
-
content: node.getText(sourceFile),
|
|
621
|
-
exported: this.isExported(node)
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
// Type alias declarations
|
|
625
|
-
if (ts.isTypeAliasDeclaration(node)) {
|
|
626
|
-
const name = node.name.text;
|
|
627
|
-
if (name.length < this.config.minNameLength)
|
|
628
|
-
return null;
|
|
629
|
-
return this.createPatternEntry({
|
|
630
|
-
type: 'type',
|
|
631
|
-
name,
|
|
632
|
-
file: filePath,
|
|
633
|
-
line,
|
|
634
|
-
endLine,
|
|
635
|
-
signature: node.getText(sourceFile).split('=')[0].trim(),
|
|
636
|
-
description: this.getJSDocDescription(node, sourceFile),
|
|
637
|
-
keywords: this.extractKeywords(name),
|
|
638
|
-
content: node.getText(sourceFile),
|
|
639
|
-
exported: this.isExported(node)
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
// Enum declarations
|
|
643
|
-
if (ts.isEnumDeclaration(node)) {
|
|
644
|
-
const name = node.name.text;
|
|
645
|
-
if (name.length < this.config.minNameLength)
|
|
646
|
-
return null;
|
|
647
|
-
return this.createPatternEntry({
|
|
648
|
-
type: 'enum',
|
|
649
|
-
name,
|
|
650
|
-
file: filePath,
|
|
651
|
-
line,
|
|
652
|
-
endLine,
|
|
653
|
-
signature: `enum ${name}`,
|
|
654
|
-
description: this.getJSDocDescription(node, sourceFile),
|
|
655
|
-
keywords: this.extractKeywords(name),
|
|
656
|
-
content: node.getText(sourceFile),
|
|
657
|
-
exported: this.isExported(node)
|
|
658
|
-
});
|
|
659
|
-
}
|
|
660
|
-
return null;
|
|
661
|
-
}
|
|
662
|
-
/**
|
|
663
|
-
* Detect the specific type of a function based on naming conventions.
|
|
664
|
-
*/
|
|
665
|
-
detectFunctionType(name, node) {
|
|
666
|
-
// React hooks
|
|
667
|
-
if (name.startsWith('use') && name.length > 3 && name[3] === name[3].toUpperCase()) {
|
|
668
|
-
return 'hook';
|
|
669
|
-
}
|
|
670
|
-
// React components (PascalCase and returns JSX)
|
|
671
|
-
if (name[0] === name[0].toUpperCase() && this.containsJSX(node)) {
|
|
672
|
-
return 'component';
|
|
673
|
-
}
|
|
674
|
-
// Middleware patterns
|
|
675
|
-
if (name.includes('Middleware') || name.includes('middleware')) {
|
|
676
|
-
return 'middleware';
|
|
677
|
-
}
|
|
678
|
-
// Handler patterns
|
|
679
|
-
if (name.includes('Handler') || name.includes('handler')) {
|
|
680
|
-
return 'handler';
|
|
681
|
-
}
|
|
682
|
-
// Factory patterns
|
|
683
|
-
if (name.startsWith('create') || name.startsWith('make') || name.includes('Factory')) {
|
|
684
|
-
return 'factory';
|
|
685
|
-
}
|
|
686
|
-
return 'function';
|
|
687
|
-
}
|
|
688
|
-
/**
|
|
689
|
-
* Detect the specific type of a class.
|
|
690
|
-
*/
|
|
691
|
-
detectClassType(name, node) {
|
|
692
|
-
// Error classes
|
|
693
|
-
if (name.endsWith('Error') || name.endsWith('Exception')) {
|
|
694
|
-
return 'error';
|
|
695
|
-
}
|
|
696
|
-
// Check for React component (extends Component/PureComponent)
|
|
697
|
-
if (node.heritageClauses) {
|
|
698
|
-
for (const clause of node.heritageClauses) {
|
|
699
|
-
const text = clause.getText();
|
|
700
|
-
if (text.includes('Component') || text.includes('PureComponent')) {
|
|
701
|
-
return 'component';
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
// Store patterns
|
|
706
|
-
if (name.endsWith('Store') || name.endsWith('State')) {
|
|
707
|
-
return 'store';
|
|
708
|
-
}
|
|
709
|
-
// Model patterns
|
|
710
|
-
if (name.endsWith('Model') || name.endsWith('Entity')) {
|
|
711
|
-
return 'model';
|
|
712
|
-
}
|
|
713
|
-
return 'class';
|
|
714
|
-
}
|
|
715
|
-
/**
|
|
716
|
-
* Check if a node contains JSX.
|
|
717
|
-
*/
|
|
718
|
-
containsJSX(node) {
|
|
719
|
-
let hasJSX = false;
|
|
720
|
-
const visit = (n) => {
|
|
721
|
-
if (ts.isJsxElement(n) || ts.isJsxSelfClosingElement(n) || ts.isJsxFragment(n)) {
|
|
722
|
-
hasJSX = true;
|
|
723
|
-
return;
|
|
724
|
-
}
|
|
725
|
-
ts.forEachChild(n, visit);
|
|
726
|
-
};
|
|
727
|
-
visit(node);
|
|
728
|
-
return hasJSX;
|
|
729
|
-
}
|
|
730
|
-
/**
|
|
731
|
-
* Get function signature.
|
|
732
|
-
*/
|
|
733
|
-
getFunctionSignature(node, sourceFile) {
|
|
734
|
-
const params = node.parameters
|
|
735
|
-
.map(p => p.getText(sourceFile))
|
|
736
|
-
.join(', ');
|
|
737
|
-
const returnType = node.type ? `: ${node.type.getText(sourceFile)}` : '';
|
|
738
|
-
return `(${params})${returnType}`;
|
|
739
|
-
}
|
|
740
|
-
/**
|
|
741
|
-
* Get arrow function signature.
|
|
742
|
-
*/
|
|
743
|
-
getArrowFunctionSignature(node, sourceFile) {
|
|
744
|
-
const params = node.parameters
|
|
745
|
-
.map(p => p.getText(sourceFile))
|
|
746
|
-
.join(', ');
|
|
747
|
-
const returnType = node.type ? `: ${node.type.getText(sourceFile)}` : '';
|
|
748
|
-
return `(${params})${returnType}`;
|
|
749
|
-
}
|
|
750
|
-
/**
|
|
751
|
-
* Get class signature.
|
|
752
|
-
*/
|
|
753
|
-
getClassSignature(node, sourceFile) {
|
|
754
|
-
let sig = `class ${node.name?.text || 'Anonymous'}`;
|
|
755
|
-
if (node.heritageClauses) {
|
|
756
|
-
sig += ' ' + node.heritageClauses.map(c => c.getText(sourceFile)).join(' ');
|
|
757
|
-
}
|
|
758
|
-
return sig;
|
|
759
|
-
}
|
|
760
|
-
/**
|
|
761
|
-
* Get interface signature.
|
|
762
|
-
*/
|
|
763
|
-
getInterfaceSignature(node, sourceFile) {
|
|
764
|
-
let sig = `interface ${node.name.text}`;
|
|
765
|
-
if (node.typeParameters) {
|
|
766
|
-
sig += `<${node.typeParameters.map(p => p.getText(sourceFile)).join(', ')}>`;
|
|
767
|
-
}
|
|
768
|
-
return sig;
|
|
769
|
-
}
|
|
770
|
-
/**
|
|
771
|
-
* Extract JSDoc description from a node.
|
|
772
|
-
*/
|
|
773
|
-
getJSDocDescription(node, sourceFile) {
|
|
774
|
-
const jsDocTags = ts.getJSDocTags(node);
|
|
775
|
-
const jsDocComment = ts.getJSDocCommentsAndTags(node);
|
|
776
|
-
for (const tag of jsDocComment) {
|
|
777
|
-
if (ts.isJSDoc(tag) && tag.comment) {
|
|
778
|
-
if (typeof tag.comment === 'string') {
|
|
779
|
-
return tag.comment;
|
|
780
|
-
}
|
|
781
|
-
return tag.comment.map(c => c.getText(sourceFile)).join(' ');
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
return '';
|
|
785
|
-
}
|
|
786
|
-
/**
|
|
787
|
-
* Check if a node is exported.
|
|
788
|
-
*/
|
|
789
|
-
isExported(node) {
|
|
790
|
-
if (ts.canHaveModifiers(node)) {
|
|
791
|
-
const modifiers = ts.getModifiers(node);
|
|
792
|
-
if (modifiers) {
|
|
793
|
-
return modifiers.some(m => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
return false;
|
|
797
|
-
}
|
|
798
|
-
/**
|
|
799
|
-
* Extract keywords from a name for semantic matching.
|
|
800
|
-
*/
|
|
801
|
-
extractKeywords(name) {
|
|
802
|
-
// Split camelCase and PascalCase
|
|
803
|
-
const words = name
|
|
804
|
-
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
805
|
-
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
806
|
-
.toLowerCase()
|
|
807
|
-
.split(/[\s_-]+/)
|
|
808
|
-
.filter(w => w.length > 1);
|
|
809
|
-
return [...new Set(words)];
|
|
810
|
-
}
|
|
811
|
-
/**
|
|
812
|
-
* Create a PatternEntry with computed fields.
|
|
813
|
-
*/
|
|
814
|
-
createPatternEntry(params) {
|
|
815
|
-
const id = this.hashContent(`${params.file}:${params.name}:${params.line}`);
|
|
816
|
-
const hash = this.hashContent(params.content);
|
|
817
|
-
return {
|
|
818
|
-
id,
|
|
819
|
-
type: params.type,
|
|
820
|
-
name: params.name,
|
|
821
|
-
file: params.file,
|
|
822
|
-
line: params.line,
|
|
823
|
-
endLine: params.endLine,
|
|
824
|
-
signature: params.signature,
|
|
825
|
-
description: params.description,
|
|
826
|
-
keywords: params.keywords,
|
|
827
|
-
hash,
|
|
828
|
-
exported: params.exported,
|
|
829
|
-
usageCount: 0, // Will be calculated in a separate pass
|
|
830
|
-
indexedAt: new Date().toISOString()
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
|
-
/**
|
|
834
|
-
* Get the TypeScript ScriptKind for a file.
|
|
835
|
-
*/
|
|
836
203
|
getScriptKind(filePath) {
|
|
837
|
-
|
|
838
|
-
switch (ext) {
|
|
204
|
+
switch (path.extname(filePath).toLowerCase()) {
|
|
839
205
|
case '.ts': return ts.ScriptKind.TS;
|
|
840
206
|
case '.tsx': return ts.ScriptKind.TSX;
|
|
841
207
|
case '.js': return ts.ScriptKind.JS;
|
|
@@ -843,9 +209,6 @@ export class PatternIndexer {
|
|
|
843
209
|
default: return ts.ScriptKind.TS;
|
|
844
210
|
}
|
|
845
211
|
}
|
|
846
|
-
/**
|
|
847
|
-
* Calculate index statistics.
|
|
848
|
-
*/
|
|
849
212
|
calculateStats(patterns, files, durationMs) {
|
|
850
213
|
const byType = {};
|
|
851
214
|
for (const pattern of patterns) {
|
|
@@ -855,26 +218,17 @@ export class PatternIndexer {
|
|
|
855
218
|
totalPatterns: patterns.length,
|
|
856
219
|
totalFiles: files.length,
|
|
857
220
|
byType: byType,
|
|
858
|
-
indexDurationMs: durationMs
|
|
221
|
+
indexDurationMs: durationMs,
|
|
859
222
|
};
|
|
860
223
|
}
|
|
861
|
-
/**
|
|
862
|
-
* Hash content using SHA-256.
|
|
863
|
-
*/
|
|
864
|
-
hashContent(content) {
|
|
865
|
-
return createHash('sha256').update(content).digest('hex').slice(0, 16);
|
|
866
|
-
}
|
|
867
224
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
// Standalone utilities (re-exported through pattern-index/index.ts)
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
871
228
|
export async function savePatternIndex(index, outputPath) {
|
|
872
229
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
873
230
|
await fs.writeFile(outputPath, JSON.stringify(index, null, 2), 'utf-8');
|
|
874
231
|
}
|
|
875
|
-
/**
|
|
876
|
-
* Load a pattern index from disk.
|
|
877
|
-
*/
|
|
878
232
|
export async function loadPatternIndex(indexPath) {
|
|
879
233
|
try {
|
|
880
234
|
const content = await fs.readFile(indexPath, 'utf-8');
|
|
@@ -884,9 +238,6 @@ export async function loadPatternIndex(indexPath) {
|
|
|
884
238
|
return null;
|
|
885
239
|
}
|
|
886
240
|
}
|
|
887
|
-
/**
|
|
888
|
-
* Get the default index path for a project.
|
|
889
|
-
*/
|
|
890
241
|
export function getDefaultIndexPath(rootDir) {
|
|
891
242
|
return path.join(rootDir, '.rigour', 'patterns.json');
|
|
892
243
|
}
|