trellis 2.0.13 → 2.1.2

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 (96) hide show
  1. package/dist/cli/index.js +1 -1
  2. package/dist/embeddings/index.js +1 -1
  3. package/dist/{index-7gvjxt27.js → index-2917tjd8.js} +1 -1
  4. package/package.json +2 -10
  5. package/dist/transformers.node-bx3q9d7k.js +0 -33130
  6. package/src/cli/index.ts +0 -3356
  7. package/src/core/agents/harness.ts +0 -380
  8. package/src/core/agents/index.ts +0 -18
  9. package/src/core/agents/types.ts +0 -90
  10. package/src/core/index.ts +0 -118
  11. package/src/core/kernel/middleware.ts +0 -44
  12. package/src/core/kernel/trellis-kernel.ts +0 -593
  13. package/src/core/ontology/builtins.ts +0 -248
  14. package/src/core/ontology/index.ts +0 -34
  15. package/src/core/ontology/registry.ts +0 -209
  16. package/src/core/ontology/types.ts +0 -124
  17. package/src/core/ontology/validator.ts +0 -382
  18. package/src/core/persist/backend.ts +0 -74
  19. package/src/core/persist/sqlite-backend.ts +0 -298
  20. package/src/core/plugins/index.ts +0 -17
  21. package/src/core/plugins/registry.ts +0 -322
  22. package/src/core/plugins/types.ts +0 -126
  23. package/src/core/query/datalog.ts +0 -188
  24. package/src/core/query/engine.ts +0 -370
  25. package/src/core/query/index.ts +0 -34
  26. package/src/core/query/parser.ts +0 -481
  27. package/src/core/query/types.ts +0 -200
  28. package/src/core/store/eav-store.ts +0 -467
  29. package/src/decisions/auto-capture.ts +0 -136
  30. package/src/decisions/hooks.ts +0 -163
  31. package/src/decisions/index.ts +0 -261
  32. package/src/decisions/types.ts +0 -103
  33. package/src/embeddings/auto-embed.ts +0 -248
  34. package/src/embeddings/chunker.ts +0 -327
  35. package/src/embeddings/index.ts +0 -48
  36. package/src/embeddings/model.ts +0 -112
  37. package/src/embeddings/search.ts +0 -305
  38. package/src/embeddings/store.ts +0 -313
  39. package/src/embeddings/types.ts +0 -92
  40. package/src/engine.ts +0 -1125
  41. package/src/garden/cluster.ts +0 -330
  42. package/src/garden/garden.ts +0 -306
  43. package/src/garden/index.ts +0 -29
  44. package/src/git/git-exporter.ts +0 -286
  45. package/src/git/git-importer.ts +0 -329
  46. package/src/git/git-reader.ts +0 -189
  47. package/src/git/index.ts +0 -22
  48. package/src/identity/governance.ts +0 -211
  49. package/src/identity/identity.ts +0 -224
  50. package/src/identity/index.ts +0 -30
  51. package/src/identity/signing-middleware.ts +0 -97
  52. package/src/index.ts +0 -29
  53. package/src/links/index.ts +0 -49
  54. package/src/links/lifecycle.ts +0 -400
  55. package/src/links/parser.ts +0 -484
  56. package/src/links/ref-index.ts +0 -186
  57. package/src/links/resolver.ts +0 -314
  58. package/src/links/types.ts +0 -108
  59. package/src/mcp/index.ts +0 -22
  60. package/src/mcp/server.ts +0 -1278
  61. package/src/semantic/csharp-parser.ts +0 -493
  62. package/src/semantic/go-parser.ts +0 -585
  63. package/src/semantic/index.ts +0 -34
  64. package/src/semantic/java-parser.ts +0 -456
  65. package/src/semantic/python-parser.ts +0 -659
  66. package/src/semantic/ruby-parser.ts +0 -446
  67. package/src/semantic/rust-parser.ts +0 -784
  68. package/src/semantic/semantic-merge.ts +0 -210
  69. package/src/semantic/ts-parser.ts +0 -681
  70. package/src/semantic/types.ts +0 -175
  71. package/src/sync/http-transport.ts +0 -144
  72. package/src/sync/index.ts +0 -43
  73. package/src/sync/memory-transport.ts +0 -66
  74. package/src/sync/multi-repo.ts +0 -200
  75. package/src/sync/reconciler.ts +0 -237
  76. package/src/sync/sync-engine.ts +0 -258
  77. package/src/sync/types.ts +0 -104
  78. package/src/sync/ws-transport.ts +0 -145
  79. package/src/ui/client.html +0 -695
  80. package/src/ui/server.ts +0 -419
  81. package/src/vcs/blob-store.ts +0 -124
  82. package/src/vcs/branch.ts +0 -150
  83. package/src/vcs/checkpoint.ts +0 -64
  84. package/src/vcs/decompose.ts +0 -469
  85. package/src/vcs/diff.ts +0 -409
  86. package/src/vcs/engine-context.ts +0 -26
  87. package/src/vcs/index.ts +0 -23
  88. package/src/vcs/issue.ts +0 -800
  89. package/src/vcs/merge.ts +0 -425
  90. package/src/vcs/milestone.ts +0 -124
  91. package/src/vcs/ops.ts +0 -59
  92. package/src/vcs/types.ts +0 -213
  93. package/src/vcs/vcs-middleware.ts +0 -81
  94. package/src/watcher/fs-watcher.ts +0 -255
  95. package/src/watcher/index.ts +0 -9
  96. package/src/watcher/ingestion.ts +0 -116
@@ -1,659 +0,0 @@
1
- /**
2
- * Python Parser Adapter
3
- *
4
- * Tier 1 regex-based parser for Python source files.
5
- * Extracts classes, functions, decorators, async functions,
6
- * type hints, imports, and module-level variables.
7
- *
8
- * @see TRL-5
9
- */
10
-
11
- import type {
12
- ParserAdapter,
13
- ParseResult,
14
- ASTEntity,
15
- ASTEntityKind,
16
- ImportRelation,
17
- ExportRelation,
18
- SemanticPatch,
19
- } from './types.js';
20
-
21
- // ---------------------------------------------------------------------------
22
- // Parser Adapter
23
- // ---------------------------------------------------------------------------
24
-
25
- export const pythonParser: ParserAdapter = {
26
- languages: ['python'],
27
-
28
- parse(content: string, filePath: string): ParseResult {
29
- const fileEntityId = `file:${filePath}`;
30
-
31
- return {
32
- fileEntityId,
33
- filePath,
34
- language: 'python',
35
- declarations: extractDeclarations(content, filePath),
36
- imports: extractImports(content),
37
- exports: extractExports(content),
38
- };
39
- },
40
-
41
- diff(oldResult: ParseResult, newResult: ParseResult): SemanticPatch[] {
42
- return computeSemanticDiff(oldResult, newResult);
43
- },
44
- };
45
-
46
- // ---------------------------------------------------------------------------
47
- // Declaration extraction
48
- // ---------------------------------------------------------------------------
49
-
50
- /**
51
- * Extract top-level declarations from Python source.
52
- * Handles: class, def, async def, and module-level assignments.
53
- * Respects indentation to determine block boundaries.
54
- */
55
- function extractDeclarations(content: string, filePath: string): ASTEntity[] {
56
- const declarations: ASTEntity[] = [];
57
- const lines = content.split('\n');
58
-
59
- let i = 0;
60
- while (i < lines.length) {
61
- const line = lines[i];
62
- const trimmed = line.trim();
63
-
64
- // Skip empty lines, comments, string literals used as docstrings
65
- if (
66
- !trimmed ||
67
- trimmed.startsWith('#') ||
68
- trimmed.startsWith('"""') ||
69
- trimmed.startsWith("'''")
70
- ) {
71
- i++;
72
- continue;
73
- }
74
-
75
- // Only consider top-level (zero indentation)
76
- if ((line.length > 0 && line[0] === ' ') || line[0] === '\t') {
77
- i++;
78
- continue;
79
- }
80
-
81
- // Collect decorators
82
- const decorators: string[] = [];
83
- const decoratorStart = i;
84
- while (i < lines.length && lines[i].trim().startsWith('@')) {
85
- decorators.push(lines[i].trim());
86
- i++;
87
- }
88
-
89
- if (i >= lines.length) break;
90
- const declLine = lines[i].trim();
91
-
92
- // Class definition
93
- let match = declLine.match(/^class\s+(\w+)/);
94
- if (match) {
95
- const result = extractIndentedBlock(
96
- match[1],
97
- 'ClassDef',
98
- lines,
99
- decorators.length > 0 ? decoratorStart : i,
100
- i,
101
- filePath,
102
- decorators,
103
- );
104
- declarations.push(result.entity);
105
- i = result.endLine + 1;
106
- continue;
107
- }
108
-
109
- // Async function
110
- match = declLine.match(/^async\s+def\s+(\w+)/);
111
- if (match) {
112
- const result = extractIndentedBlock(
113
- match[1],
114
- 'FunctionDef',
115
- lines,
116
- decorators.length > 0 ? decoratorStart : i,
117
- i,
118
- filePath,
119
- decorators,
120
- );
121
- declarations.push(result.entity);
122
- i = result.endLine + 1;
123
- continue;
124
- }
125
-
126
- // Function definition
127
- match = declLine.match(/^def\s+(\w+)/);
128
- if (match) {
129
- const result = extractIndentedBlock(
130
- match[1],
131
- 'FunctionDef',
132
- lines,
133
- decorators.length > 0 ? decoratorStart : i,
134
- i,
135
- filePath,
136
- decorators,
137
- );
138
- declarations.push(result.entity);
139
- i = result.endLine + 1;
140
- continue;
141
- }
142
-
143
- // Module-level variable assignment (e.g. `MY_VAR = ...` or `MY_VAR: int = ...`)
144
- match = declLine.match(/^([A-Za-z_]\w*)\s*(?::\s*\w[^=]*)?\s*=/);
145
- if (
146
- match &&
147
- match[1] !== '__all__' &&
148
- !declLine.startsWith('import ') &&
149
- !declLine.startsWith('from ')
150
- ) {
151
- const result = extractAssignment(match[1], lines, i, filePath);
152
- if (decorators.length === 0) {
153
- declarations.push(result.entity);
154
- }
155
- i = result.endLine + 1;
156
- continue;
157
- }
158
-
159
- // If we collected decorators but no matching def/class, skip
160
- if (decorators.length > 0) {
161
- i++;
162
- continue;
163
- }
164
-
165
- i++;
166
- }
167
-
168
- return declarations;
169
- }
170
-
171
- // ---------------------------------------------------------------------------
172
- // Block extraction (indentation-based)
173
- // ---------------------------------------------------------------------------
174
-
175
- interface ExtractionResult {
176
- entity: ASTEntity;
177
- endLine: number;
178
- }
179
-
180
- /**
181
- * Extract a Python indentation-delimited block (class or function).
182
- */
183
- function extractIndentedBlock(
184
- name: string,
185
- kind: ASTEntityKind,
186
- lines: string[],
187
- startLine: number,
188
- defLine: number,
189
- filePath: string,
190
- decorators: string[],
191
- ): ExtractionResult {
192
- // Find the colon at end of def/class line (may span multiple lines for long signatures)
193
- let headerEnd = defLine;
194
- while (headerEnd < lines.length && !lines[headerEnd].includes(':')) {
195
- headerEnd++;
196
- }
197
-
198
- // Determine body indentation from the first non-empty line after header
199
- let bodyIndent = -1;
200
- let endLine = headerEnd;
201
-
202
- for (let i = headerEnd + 1; i < lines.length; i++) {
203
- const line = lines[i];
204
- const trimmed = line.trim();
205
-
206
- // Skip empty lines and comment-only lines
207
- if (!trimmed || trimmed.startsWith('#')) {
208
- endLine = i;
209
- continue;
210
- }
211
-
212
- const indent = line.length - line.trimStart().length;
213
-
214
- if (bodyIndent < 0) {
215
- // First non-empty line in body sets the expected indent
216
- bodyIndent = indent;
217
- endLine = i;
218
- continue;
219
- }
220
-
221
- if (indent >= bodyIndent) {
222
- endLine = i;
223
- } else {
224
- // Dedented — block is over
225
- break;
226
- }
227
- }
228
-
229
- // Trim trailing empty lines from the block
230
- while (endLine > headerEnd && lines[endLine].trim() === '') {
231
- endLine--;
232
- }
233
-
234
- const rawText = lines.slice(startLine, endLine + 1).join('\n');
235
- const startOffset =
236
- lines.slice(0, startLine).join('\n').length + (startLine > 0 ? 1 : 0);
237
-
238
- const children =
239
- kind === 'ClassDef'
240
- ? extractClassMembers(
241
- lines,
242
- headerEnd,
243
- endLine,
244
- bodyIndent,
245
- name,
246
- filePath,
247
- )
248
- : [];
249
-
250
- return {
251
- entity: {
252
- id: makeEntityId(filePath, kind, name),
253
- kind,
254
- name,
255
- scopePath: name,
256
- span: [startOffset, startOffset + rawText.length],
257
- rawText,
258
- signature: normalizeSignature(rawText),
259
- children,
260
- },
261
- endLine,
262
- };
263
- }
264
-
265
- /**
266
- * Extract a module-level variable assignment (may span multiple lines).
267
- */
268
- function extractAssignment(
269
- name: string,
270
- lines: string[],
271
- startLine: number,
272
- filePath: string,
273
- ): ExtractionResult {
274
- let endLine = startLine;
275
- let depth = 0;
276
-
277
- for (let i = startLine; i < lines.length; i++) {
278
- const line = lines[i];
279
- for (const ch of line) {
280
- if (ch === '(' || ch === '[' || ch === '{') depth++;
281
- else if (ch === ')' || ch === ']' || ch === '}') depth--;
282
- }
283
-
284
- endLine = i;
285
-
286
- // Assignment ends when depth returns to 0 and we're past the first line,
287
- // or the next line is a new top-level statement
288
- if (depth <= 0 && i > startLine) break;
289
- if (depth <= 0 && i === startLine) {
290
- // Check if next line is indented (continuation) or a new statement
291
- if (i + 1 < lines.length) {
292
- const next = lines[i + 1];
293
- if (!next.trim() || (!next.startsWith(' ') && !next.startsWith('\t'))) {
294
- break;
295
- }
296
- } else {
297
- break;
298
- }
299
- }
300
- }
301
-
302
- const rawText = lines.slice(startLine, endLine + 1).join('\n');
303
- const startOffset =
304
- lines.slice(0, startLine).join('\n').length + (startLine > 0 ? 1 : 0);
305
-
306
- return {
307
- entity: {
308
- id: makeEntityId(filePath, 'VariableDecl', name),
309
- kind: 'VariableDecl',
310
- name,
311
- scopePath: name,
312
- span: [startOffset, startOffset + rawText.length],
313
- rawText,
314
- signature: normalizeSignature(rawText),
315
- children: [],
316
- },
317
- endLine,
318
- };
319
- }
320
-
321
- // ---------------------------------------------------------------------------
322
- // Class member extraction
323
- // ---------------------------------------------------------------------------
324
-
325
- function extractClassMembers(
326
- lines: string[],
327
- headerEnd: number,
328
- blockEnd: number,
329
- bodyIndent: number,
330
- className: string,
331
- filePath: string,
332
- ): ASTEntity[] {
333
- const children: ASTEntity[] = [];
334
- if (bodyIndent < 0) return children;
335
-
336
- for (let i = headerEnd + 1; i <= blockEnd; i++) {
337
- const line = lines[i];
338
- const trimmed = line.trim();
339
- if (
340
- !trimmed ||
341
- trimmed.startsWith('#') ||
342
- trimmed.startsWith('"""') ||
343
- trimmed.startsWith("'''")
344
- )
345
- continue;
346
-
347
- const indent = line.length - line.trimStart().length;
348
- if (indent !== bodyIndent) continue;
349
-
350
- // Skip decorators (they precede a method)
351
- if (trimmed.startsWith('@')) continue;
352
-
353
- // Method (def / async def)
354
- let match = trimmed.match(/^(?:async\s+)?def\s+(\w+)/);
355
- if (match) {
356
- const methodName = match[1];
357
- const kind: ASTEntityKind =
358
- methodName === '__init__' ? 'Constructor' : 'MethodDef';
359
- children.push({
360
- id: makeEntityId(filePath, kind, `${className}.${methodName}`),
361
- kind,
362
- name: methodName,
363
- scopePath: `${className}.${methodName}`,
364
- span: [0, 0],
365
- rawText: trimmed,
366
- signature: normalizeSignature(trimmed),
367
- children: [],
368
- });
369
- continue;
370
- }
371
-
372
- // Class-level variable / property (e.g. `name: str = "default"`)
373
- match = trimmed.match(/^(\w+)\s*(?::\s*\S[^=]*)?\s*=/);
374
- if (match) {
375
- children.push({
376
- id: makeEntityId(filePath, 'PropertyDef', `${className}.${match[1]}`),
377
- kind: 'PropertyDef',
378
- name: match[1],
379
- scopePath: `${className}.${match[1]}`,
380
- span: [0, 0],
381
- rawText: trimmed,
382
- signature: normalizeSignature(trimmed),
383
- children: [],
384
- });
385
- }
386
- }
387
-
388
- return children;
389
- }
390
-
391
- // ---------------------------------------------------------------------------
392
- // Import extraction
393
- // ---------------------------------------------------------------------------
394
-
395
- function extractImports(content: string): ImportRelation[] {
396
- const imports: ImportRelation[] = [];
397
- const lines = content.split('\n');
398
-
399
- for (let i = 0; i < lines.length; i++) {
400
- const trimmed = lines[i].trim();
401
-
402
- // from module import ...
403
- let match = trimmed.match(/^from\s+([\w.]+)\s+import\s+(.+)/);
404
- if (match) {
405
- let specText = match[2];
406
-
407
- // Handle multi-line imports: from mod import (a, b, ...)
408
- if (specText.includes('(') && !specText.includes(')')) {
409
- while (i + 1 < lines.length && !specText.includes(')')) {
410
- i++;
411
- specText += ' ' + lines[i].trim();
412
- }
413
- }
414
-
415
- // Strip parens and trailing comments
416
- specText = specText.replace(/[()]/g, '').replace(/#.*$/, '');
417
- const specifiers = specText
418
- .split(',')
419
- .map(
420
- (s) =>
421
- s
422
- .trim()
423
- .split(/\s+as\s+/)
424
- .pop()!,
425
- )
426
- .filter(Boolean);
427
-
428
- const isWildcard = specifiers.includes('*');
429
-
430
- imports.push({
431
- source: match[1],
432
- specifiers: isWildcard ? ['*'] : specifiers,
433
- isDefault: false,
434
- isNamespace: isWildcard,
435
- rawText: trimmed,
436
- span: [0, trimmed.length],
437
- });
438
- continue;
439
- }
440
-
441
- // import module [as alias]
442
- match = trimmed.match(/^import\s+([\w.,\s]+)/);
443
- if (match) {
444
- const modules = match[1].split(',').map((m) => m.trim());
445
- for (const mod of modules) {
446
- const parts = mod.split(/\s+as\s+/);
447
- const source = parts[0].trim();
448
- const alias = parts.length > 1 ? parts[1].trim() : source;
449
- imports.push({
450
- source,
451
- specifiers: [alias],
452
- isDefault: true,
453
- isNamespace: false,
454
- rawText: trimmed,
455
- span: [0, trimmed.length],
456
- });
457
- }
458
- }
459
- }
460
-
461
- return imports;
462
- }
463
-
464
- // ---------------------------------------------------------------------------
465
- // Export extraction
466
- // ---------------------------------------------------------------------------
467
-
468
- /**
469
- * Python uses __all__ to declare public API. We also treat
470
- * top-level public names (not starting with _) as exports.
471
- */
472
- function extractExports(content: string): ExportRelation[] {
473
- const exports: ExportRelation[] = [];
474
-
475
- // Look for __all__ = [...]
476
- const allMatch = content.match(/__all__\s*=\s*\[([^\]]*)\]/s);
477
- if (allMatch) {
478
- const names = allMatch[1]
479
- .replace(/['"]/g, '')
480
- .split(',')
481
- .map((s) => s.trim())
482
- .filter(Boolean);
483
-
484
- for (const name of names) {
485
- exports.push({
486
- name,
487
- isDefault: false,
488
- rawText: `__all__: ${name}`,
489
- span: [0, 0],
490
- });
491
- }
492
- }
493
-
494
- return exports;
495
- }
496
-
497
- // ---------------------------------------------------------------------------
498
- // Semantic diff (reuses generic algorithm from ts-parser pattern)
499
- // ---------------------------------------------------------------------------
500
-
501
- function computeSemanticDiff(
502
- oldResult: ParseResult,
503
- newResult: ParseResult,
504
- ): SemanticPatch[] {
505
- const patches: SemanticPatch[] = [];
506
- const fileId = newResult.fileEntityId;
507
-
508
- const oldDecls = new Map(oldResult.declarations.map((d) => [d.id, d]));
509
- const newDecls = new Map(newResult.declarations.map((d) => [d.id, d]));
510
-
511
- // Detect additions (+ rename detection)
512
- for (const [id, entity] of newDecls) {
513
- if (!oldDecls.has(id)) {
514
- const oldEntity = findRenamedEntity(
515
- entity,
516
- oldResult.declarations,
517
- newDecls,
518
- );
519
- if (oldEntity) {
520
- patches.push({
521
- kind: 'symbolRename',
522
- entityId: oldEntity.id,
523
- oldName: oldEntity.name,
524
- newName: entity.name,
525
- });
526
- } else {
527
- patches.push({ kind: 'symbolAdd', entity });
528
- }
529
- }
530
- }
531
-
532
- // Detect removals
533
- for (const [id, entity] of oldDecls) {
534
- if (!newDecls.has(id)) {
535
- const wasRenamed = findRenamedEntity(
536
- entity,
537
- newResult.declarations,
538
- oldDecls,
539
- );
540
- if (!wasRenamed) {
541
- patches.push({
542
- kind: 'symbolRemove',
543
- entityId: id,
544
- entityName: entity.name,
545
- });
546
- }
547
- }
548
- }
549
-
550
- // Detect modifications
551
- for (const [id, newEntity] of newDecls) {
552
- const oldEntity = oldDecls.get(id);
553
- if (oldEntity && oldEntity.signature !== newEntity.signature) {
554
- patches.push({
555
- kind: 'symbolModify',
556
- entityId: id,
557
- entityName: newEntity.name,
558
- oldSignature: oldEntity.signature,
559
- newSignature: newEntity.signature,
560
- oldRawText: oldEntity.rawText,
561
- newRawText: newEntity.rawText,
562
- });
563
- }
564
- }
565
-
566
- // Diff imports
567
- const oldImports = new Map(oldResult.imports.map((imp) => [imp.source, imp]));
568
- const newImports = new Map(newResult.imports.map((imp) => [imp.source, imp]));
569
-
570
- for (const [source, imp] of newImports) {
571
- const oldImp = oldImports.get(source);
572
- if (!oldImp) {
573
- patches.push({
574
- kind: 'importAdd',
575
- fileId,
576
- source,
577
- specifiers: imp.specifiers,
578
- rawText: imp.rawText,
579
- });
580
- } else if (
581
- JSON.stringify(oldImp.specifiers.sort()) !==
582
- JSON.stringify(imp.specifiers.sort())
583
- ) {
584
- patches.push({
585
- kind: 'importModify',
586
- fileId,
587
- source,
588
- oldSpecifiers: oldImp.specifiers,
589
- newSpecifiers: imp.specifiers,
590
- });
591
- }
592
- }
593
-
594
- for (const [source] of oldImports) {
595
- if (!newImports.has(source)) {
596
- patches.push({ kind: 'importRemove', fileId, source });
597
- }
598
- }
599
-
600
- // Diff exports
601
- const oldExports = new Map(oldResult.exports.map((exp) => [exp.name, exp]));
602
- const newExports = new Map(newResult.exports.map((exp) => [exp.name, exp]));
603
-
604
- for (const [name, exp] of newExports) {
605
- if (!oldExports.has(name)) {
606
- patches.push({ kind: 'exportAdd', fileId, name, rawText: exp.rawText });
607
- }
608
- }
609
-
610
- for (const [name] of oldExports) {
611
- if (!newExports.has(name)) {
612
- patches.push({ kind: 'exportRemove', fileId, name });
613
- }
614
- }
615
-
616
- return patches;
617
- }
618
-
619
- // ---------------------------------------------------------------------------
620
- // Helpers
621
- // ---------------------------------------------------------------------------
622
-
623
- function findRenamedEntity(
624
- entity: ASTEntity,
625
- candidates: ASTEntity[],
626
- existingIds: Map<string, ASTEntity>,
627
- ): ASTEntity | null {
628
- for (const candidate of candidates) {
629
- if (candidate.kind !== entity.kind) continue;
630
- if (candidate.name === entity.name) continue;
631
- if (existingIds.has(candidate.id)) continue;
632
-
633
- const normalizedOld = candidate.signature.replace(
634
- new RegExp(candidate.name, 'g'),
635
- '___',
636
- );
637
- const normalizedNew = entity.signature.replace(
638
- new RegExp(entity.name, 'g'),
639
- '___',
640
- );
641
- if (normalizedOld === normalizedNew) {
642
- return candidate;
643
- }
644
- }
645
- return null;
646
- }
647
-
648
- function makeEntityId(filePath: string, kind: string, name: string): string {
649
- return `${kind}:${filePath}:${name}`;
650
- }
651
-
652
- function normalizeSignature(text: string): string {
653
- return text
654
- .replace(/#[^\n]*/g, '') // line comments
655
- .replace(/"""[\s\S]*?"""/g, '') // docstrings (triple double)
656
- .replace(/'''[\s\S]*?'''/g, '') // docstrings (triple single)
657
- .replace(/\s+/g, ' ') // collapse whitespace
658
- .trim();
659
- }