smart-context-mcp 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,684 @@
1
+ import { joinSections, toUniqueLines, truncateSection } from './shared.js';
2
+
3
+ const extractBraceBlock = (content, symbolName, declarationPattern) => {
4
+ const lines = content.split('\n');
5
+
6
+ for (let i = 0; i < lines.length; i++) {
7
+ const trimmed = lines[i].trim();
8
+ if (!declarationPattern(trimmed, symbolName)) continue;
9
+
10
+ let depth = 0;
11
+ let opened = false;
12
+ let endIdx = i;
13
+ for (let j = i; j < lines.length; j++) {
14
+ if (!opened && j > i && !lines[j].trim()) break;
15
+ const opens = (lines[j].match(/\{/g) ?? []).length;
16
+ const closes = (lines[j].match(/\}/g) ?? []).length;
17
+ depth += opens;
18
+ depth -= closes;
19
+ if (opens > 0) opened = true;
20
+ endIdx = j;
21
+ if (opened && depth <= 0) break;
22
+ }
23
+
24
+ if (!opened) endIdx = i;
25
+
26
+ const slice = lines.slice(i, endIdx + 1);
27
+ return slice.map((l, idx) => `${i + idx + 1}|${l}`).join('\n');
28
+ }
29
+
30
+ return `Symbol not found: ${symbolName}`;
31
+ };
32
+
33
+ export const extractGoSymbol = (content, symbolName) =>
34
+ extractBraceBlock(content, symbolName, (line, name) =>
35
+ new RegExp(`^func\\s+(?:\\([^)]+\\)\\s+)?${name}\\s*\\(`).test(line) ||
36
+ new RegExp(`^type\\s+${name}\\s+`).test(line));
37
+
38
+ export const extractRustSymbol = (content, symbolName) =>
39
+ extractBraceBlock(content, symbolName, (line, name) =>
40
+ new RegExp(`^(?:pub\\s+)?(?:async\\s+)?fn\\s+${name}\\s*[(<]`).test(line) ||
41
+ new RegExp(`^(?:pub\\s+)?(?:struct|enum|trait|impl)\\s+${name}`).test(line));
42
+
43
+ export const extractJavaSymbol = (content, symbolName) =>
44
+ extractBraceBlock(content, symbolName, (line, name) =>
45
+ new RegExp(`(?:class|interface|enum|record)\\s+${name}`).test(line) ||
46
+ new RegExp(`^\\s*(?:(?:public|protected|private|static|final|abstract|synchronized|native|default)\\s+)*(?:<[^>]+>\\s+)?[A-Za-z0-9_$.<>\\[\\]]+\\s+${name}\\s*\\(`).test(line));
47
+
48
+ export const summarizeGo = (content, mode) => {
49
+ const lines = content.split('\n');
50
+ const packages = [];
51
+ const imports = [];
52
+ const declarations = [];
53
+ const methods = [];
54
+ const constants = [];
55
+ let inImportBlock = false;
56
+
57
+ for (const rawLine of lines) {
58
+ const trimmed = rawLine.trim();
59
+
60
+ if (!trimmed || trimmed.startsWith('//')) {
61
+ continue;
62
+ }
63
+
64
+ if (trimmed === 'import (') {
65
+ inImportBlock = true;
66
+ continue;
67
+ }
68
+
69
+ if (inImportBlock) {
70
+ if (trimmed === ')') {
71
+ inImportBlock = false;
72
+ continue;
73
+ }
74
+
75
+ imports.push(trimmed.replace(/^([A-Za-z_][A-Za-z0-9_]*\s+)?"([^"]+)"$/, '$2'));
76
+ continue;
77
+ }
78
+
79
+ const packageMatch = trimmed.match(/^package\s+([A-Za-z_][A-Za-z0-9_]*)$/);
80
+ if (packageMatch) {
81
+ packages.push(`package ${packageMatch[1]}`);
82
+ continue;
83
+ }
84
+
85
+ const importMatch = trimmed.match(/^import\s+(?:[A-Za-z_][A-Za-z0-9_]*\s+)?"([^"]+)"$/);
86
+ if (importMatch) {
87
+ imports.push(importMatch[1]);
88
+ continue;
89
+ }
90
+
91
+ const methodMatch = trimmed.match(/^func\s*\(([^)]+)\)\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
92
+ if (methodMatch) {
93
+ methods.push(`method ${methodMatch[2]}(${methodMatch[1].trim()})`);
94
+ continue;
95
+ }
96
+
97
+ const functionMatch = trimmed.match(/^func\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
98
+ if (functionMatch) {
99
+ declarations.push(`func ${functionMatch[1]}()`);
100
+ continue;
101
+ }
102
+
103
+ const typeMatch = trimmed.match(/^type\s+([A-Za-z_][A-Za-z0-9_]*)\s+(struct|interface|map|\[\])/);
104
+ if (typeMatch) {
105
+ declarations.push(`type ${typeMatch[1]} ${typeMatch[2]}`);
106
+ continue;
107
+ }
108
+
109
+ const constMatch = trimmed.match(/^(const|var)\s+([A-Za-z_][A-Za-z0-9_]*)/);
110
+ if (constMatch) {
111
+ constants.push(`${constMatch[1]} ${constMatch[2]}`);
112
+ }
113
+ }
114
+
115
+ if (mode === 'signatures') {
116
+ return joinSections([
117
+ truncateSection('# Declarations', toUniqueLines([...declarations, ...methods]), 2600),
118
+ truncateSection('# Imports', toUniqueLines(imports, 10), 1000),
119
+ ], 4000);
120
+ }
121
+
122
+ return joinSections([
123
+ truncateSection('# Package', toUniqueLines(packages, 4), 300),
124
+ truncateSection('# Declarations', toUniqueLines(declarations, 20), 1800),
125
+ truncateSection('# Methods', toUniqueLines(methods, 20), 1500),
126
+ truncateSection('# Imports', toUniqueLines(imports, 12), 900),
127
+ truncateSection('# Constants', toUniqueLines(constants, 12), 700),
128
+ ], 5000);
129
+ };
130
+
131
+ export const summarizeRust = (content, mode) => {
132
+ const lines = content.split('\n');
133
+ const uses = [];
134
+ const declarations = [];
135
+ const implBlocks = [];
136
+ const methods = [];
137
+ let currentImpl = null;
138
+ let currentImplDepth = 0;
139
+
140
+ for (let i = 0; i < lines.length; i++) {
141
+ const trimmed = lines[i].trim();
142
+
143
+ if (!trimmed || trimmed.startsWith('//')) {
144
+ continue;
145
+ }
146
+
147
+ if (/^use\s+.+;$/.test(trimmed)) {
148
+ uses.push(trimmed);
149
+ continue;
150
+ }
151
+
152
+ const implMatch = trimmed.match(/^impl(?:<[^>]+>)?\s+([^\s{]+)/);
153
+ if (implMatch && !trimmed.endsWith(';')) {
154
+ currentImpl = implMatch[1];
155
+ const braces = (trimmed.match(/\{/g) ?? []).length - (trimmed.match(/\}/g) ?? []).length;
156
+ if (braces > 0) {
157
+ currentImplDepth = braces;
158
+ } else {
159
+ currentImplDepth = 0;
160
+ for (let j = i + 1; j < Math.min(lines.length, i + 6); j++) {
161
+ const ahead = lines[j].trim();
162
+ if (!ahead) break;
163
+ const opens = (ahead.match(/\{/g) ?? []).length;
164
+ if (opens > 0) {
165
+ currentImplDepth = opens - (ahead.match(/\}/g) ?? []).length;
166
+ i = j;
167
+ break;
168
+ }
169
+ }
170
+ if (currentImplDepth <= 0) { currentImpl = null; continue; }
171
+ }
172
+ implBlocks.push(`impl ${currentImpl}`);
173
+ continue;
174
+ }
175
+
176
+ if (currentImpl) {
177
+ currentImplDepth += (trimmed.match(/\{/g) ?? []).length;
178
+ currentImplDepth -= (trimmed.match(/\}/g) ?? []).length;
179
+
180
+ const methodMatch = trimmed.match(/^(?:pub\s+)?(?:async\s+)?fn\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
181
+ if (methodMatch) {
182
+ methods.push(`${currentImpl}::${methodMatch[1]}()`);
183
+ }
184
+
185
+ if (currentImplDepth <= 0) {
186
+ currentImpl = null;
187
+ currentImplDepth = 0;
188
+ }
189
+ continue;
190
+ }
191
+
192
+ const declMatch = trimmed.match(/^(?:pub\s+)?(struct|enum|trait|type|const|static)\s+([A-Za-z_][A-Za-z0-9_]*)/);
193
+ if (declMatch) {
194
+ declarations.push(`${declMatch[1]} ${declMatch[2]}`);
195
+ continue;
196
+ }
197
+
198
+ const functionMatch = trimmed.match(/^(?:pub\s+)?(?:async\s+)?fn\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
199
+ if (functionMatch) {
200
+ declarations.push(`fn ${functionMatch[1]}()`);
201
+ }
202
+ }
203
+
204
+ if (mode === 'signatures') {
205
+ return joinSections([
206
+ truncateSection('# Declarations', toUniqueLines([...declarations, ...implBlocks, ...methods]), 2800),
207
+ truncateSection('# Uses', toUniqueLines(uses, 10), 900),
208
+ ], 4000);
209
+ }
210
+
211
+ return joinSections([
212
+ truncateSection('# Declarations', toUniqueLines(declarations, 20), 1800),
213
+ truncateSection('# Impl blocks', toUniqueLines(implBlocks, 12), 900),
214
+ truncateSection('# Methods', toUniqueLines(methods, 20), 1500),
215
+ truncateSection('# Uses', toUniqueLines(uses, 12), 800),
216
+ ], 5000);
217
+ };
218
+
219
+ export const summarizeJava = (content, mode) => {
220
+ const lines = content.split('\n');
221
+ const packages = [];
222
+ const imports = [];
223
+ const declarations = [];
224
+ const methods = [];
225
+ let currentType = null;
226
+ let braceDepth = 0;
227
+
228
+ for (const rawLine of lines) {
229
+ const trimmed = rawLine.trim();
230
+
231
+ if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('*')) {
232
+ continue;
233
+ }
234
+
235
+ const packageMatch = trimmed.match(/^package\s+([A-Za-z0-9_.]+);$/);
236
+ if (packageMatch) {
237
+ packages.push(`package ${packageMatch[1]}`);
238
+ continue;
239
+ }
240
+
241
+ const importMatch = trimmed.match(/^import\s+([A-Za-z0-9_.*]+);$/);
242
+ if (importMatch) {
243
+ imports.push(importMatch[1]);
244
+ continue;
245
+ }
246
+
247
+ const typeMatch = trimmed.match(/^(?:public\s+)?(?:abstract\s+|final\s+)?(class|interface|enum|record)\s+([A-Za-z_][A-Za-z0-9_]*)/);
248
+ if (typeMatch) {
249
+ currentType = typeMatch[2];
250
+ declarations.push(`${typeMatch[1]} ${typeMatch[2]}`);
251
+ }
252
+
253
+ const methodMatch = trimmed.match(/^(?:public|protected|private)\s+(?:static\s+)?(?:final\s+)?(?:synchronized\s+)?(?:<[^>]+>\s+)?(?:[A-Za-z0-9_$.<>\[\]]+\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*\(([^)]*)\)\s*(?:throws\s+[A-Za-z0-9_.,\s]+)?\{$/);
254
+ if (methodMatch) {
255
+ const owner = currentType ? `${currentType}::` : '';
256
+ methods.push(`${owner}${methodMatch[1]}(${methodMatch[2].trim()})`);
257
+ }
258
+
259
+ braceDepth += (trimmed.match(/\{/g) ?? []).length;
260
+ braceDepth -= (trimmed.match(/\}/g) ?? []).length;
261
+
262
+ if (braceDepth <= 0) {
263
+ currentType = null;
264
+ braceDepth = 0;
265
+ }
266
+ }
267
+
268
+ if (mode === 'signatures') {
269
+ return joinSections([
270
+ truncateSection('# Declarations', toUniqueLines([...declarations, ...methods]), 2600),
271
+ truncateSection('# Imports', toUniqueLines(imports, 10), 900),
272
+ ], 4000);
273
+ }
274
+
275
+ return joinSections([
276
+ truncateSection('# Package', toUniqueLines(packages, 2), 300),
277
+ truncateSection('# Declarations', toUniqueLines(declarations, 20), 1400),
278
+ truncateSection('# Methods', toUniqueLines(methods, 20), 1800),
279
+ truncateSection('# Imports', toUniqueLines(imports, 12), 1000),
280
+ ], 5000);
281
+ };
282
+
283
+ export const summarizeShell = (content, mode) => {
284
+ const lines = content.split('\n');
285
+ const shebangs = [];
286
+ const options = [];
287
+ const functions = [];
288
+ const exports = [];
289
+ const commands = [];
290
+
291
+ for (const rawLine of lines) {
292
+ const trimmed = rawLine.trim();
293
+
294
+ if (!trimmed || trimmed.startsWith('#')) {
295
+ if (trimmed.startsWith('#!')) {
296
+ shebangs.push(trimmed);
297
+ }
298
+ continue;
299
+ }
300
+
301
+ const functionMatch = trimmed.match(/^(?:function\s+)?([A-Za-z_][A-Za-z0-9_-]*)\s*\(\)\s*\{$/);
302
+ if (functionMatch) {
303
+ functions.push(`function ${functionMatch[1]}()`);
304
+ continue;
305
+ }
306
+
307
+ if (/^(set -[A-Za-z]+|set -o\s+\w+)/.test(trimmed) || /^trap\s+/.test(trimmed)) {
308
+ options.push(trimmed);
309
+ continue;
310
+ }
311
+
312
+ const exportMatch = trimmed.match(/^export\s+([A-Za-z_][A-Za-z0-9_]*)/);
313
+ if (exportMatch) {
314
+ exports.push(`export ${exportMatch[1]}`);
315
+ continue;
316
+ }
317
+
318
+ const commandMatch = trimmed.match(/^([A-Za-z0-9_./-]+)(?:\s+.+)?$/);
319
+ if (commandMatch) {
320
+ commands.push(commandMatch[1]);
321
+ }
322
+ }
323
+
324
+ if (mode === 'signatures') {
325
+ return joinSections([
326
+ truncateSection('# Shell entrypoints', toUniqueLines([...shebangs, ...functions]), 1800),
327
+ truncateSection('# Commands', toUniqueLines(commands, 15), 1200),
328
+ truncateSection('# Exports', toUniqueLines(exports, 10), 700),
329
+ ], 4000);
330
+ }
331
+
332
+ return joinSections([
333
+ truncateSection('# Shebang and options', toUniqueLines([...shebangs, ...options], 12), 900),
334
+ truncateSection('# Functions', toUniqueLines(functions, 20), 1400),
335
+ truncateSection('# Exports', toUniqueLines(exports, 12), 700),
336
+ truncateSection('# Commands', toUniqueLines(commands, 20), 1200),
337
+ ], 5000);
338
+ };
339
+
340
+ export const summarizeTerraform = (content, mode) => {
341
+ const lines = content.split('\n');
342
+ const blocks = [];
343
+ const assignments = [];
344
+
345
+ for (const rawLine of lines) {
346
+ const trimmed = rawLine.trim();
347
+
348
+ if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('//')) {
349
+ continue;
350
+ }
351
+
352
+ const blockMatch = trimmed.match(/^(terraform|locals|provider|module|resource|data|variable|output)\s*(?:"([^"]+)")?\s*(?:"([^"]+)")?\s*\{$/);
353
+ if (blockMatch) {
354
+ const [, kind, first, second] = blockMatch;
355
+ const suffix = [first, second].filter(Boolean).map((value) => `"${value}"`).join(' ');
356
+ blocks.push(`${kind}${suffix ? ` ${suffix}` : ''}`);
357
+ continue;
358
+ }
359
+
360
+ const assignmentMatch = trimmed.match(/^([A-Za-z0-9_./-]+)\s*=\s*/);
361
+ if (assignmentMatch) {
362
+ assignments.push(assignmentMatch[1]);
363
+ }
364
+ }
365
+
366
+ if (mode === 'signatures') {
367
+ return joinSections([
368
+ truncateSection('# Terraform blocks', toUniqueLines(blocks, 20), 2600),
369
+ truncateSection('# Assignments', toUniqueLines(assignments, 20), 1000),
370
+ ], 4000);
371
+ }
372
+
373
+ return joinSections([
374
+ truncateSection('# Terraform blocks', toUniqueLines(blocks, 24), 2500),
375
+ truncateSection('# Assignments', toUniqueLines(assignments, 24), 1200),
376
+ ], 5000);
377
+ };
378
+
379
+ export const summarizeDockerfile = (content, mode) => {
380
+ const lines = content.split('\n');
381
+ const instructions = [];
382
+ const stages = [];
383
+
384
+ for (const rawLine of lines) {
385
+ const trimmed = rawLine.trim();
386
+
387
+ if (!trimmed || trimmed.startsWith('#')) {
388
+ continue;
389
+ }
390
+
391
+ const fromMatch = trimmed.match(/^FROM\s+([^\s]+)(?:\s+AS\s+([^\s]+))?/i);
392
+ if (fromMatch) {
393
+ stages.push(`FROM ${fromMatch[1]}${fromMatch[2] ? ` as ${fromMatch[2]}` : ''}`);
394
+ continue;
395
+ }
396
+
397
+ if (/^[A-Z]+\s+/.test(trimmed)) {
398
+ instructions.push(trimmed.replace(/\s+/g, ' '));
399
+ }
400
+ }
401
+
402
+ if (mode === 'signatures') {
403
+ return joinSections([
404
+ truncateSection('# Docker stages', toUniqueLines(stages, 12), 1500),
405
+ truncateSection('# Docker instructions', toUniqueLines(instructions, 18), 2200),
406
+ ], 4000);
407
+ }
408
+
409
+ return joinSections([
410
+ truncateSection('# Docker stages', toUniqueLines(stages, 12), 1200),
411
+ truncateSection('# Docker instructions', toUniqueLines(instructions, 20), 2600),
412
+ ], 5000);
413
+ };
414
+
415
+ export const summarizeSql = (content, mode) => {
416
+ const lines = content.split('\n');
417
+ const statements = [];
418
+ const ctes = [];
419
+
420
+ for (const rawLine of lines) {
421
+ const trimmed = rawLine.trim();
422
+
423
+ if (!trimmed || trimmed.startsWith('--')) {
424
+ continue;
425
+ }
426
+
427
+ const statementMatch = trimmed.match(/^(create\s+(?:or\s+replace\s+)?(?:table|view|index|function|procedure|trigger)|alter\s+table|drop\s+(?:table|view|index)|insert\s+into|update\s+[A-Za-z_][A-Za-z0-9_.]*|delete\s+from|select\b|with\b)/i);
428
+ if (statementMatch) {
429
+ statements.push(trimmed.replace(/\s+/g, ' '));
430
+ continue;
431
+ }
432
+
433
+ const cteMatch = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)\s+as\s*\(/i);
434
+ if (cteMatch) {
435
+ ctes.push(`cte ${cteMatch[1]}`);
436
+ }
437
+ }
438
+
439
+ if (mode === 'signatures') {
440
+ return joinSections([
441
+ truncateSection('# SQL statements', toUniqueLines(statements, 18), 2800),
442
+ truncateSection('# CTEs', toUniqueLines(ctes, 12), 700),
443
+ ], 4000);
444
+ }
445
+
446
+ return joinSections([
447
+ truncateSection('# SQL statements', toUniqueLines(statements, 20), 3200),
448
+ truncateSection('# CTEs', toUniqueLines(ctes, 16), 900),
449
+ ], 5000);
450
+ };
451
+
452
+ // ---------------------------------------------------------------------------
453
+ // C#
454
+ // ---------------------------------------------------------------------------
455
+
456
+ export const extractCsharpSymbol = (content, symbolName) =>
457
+ extractBraceBlock(content, symbolName, (line, name) =>
458
+ new RegExp(`(?:class|struct|interface|enum|record)\\s+${name}`).test(line) ||
459
+ new RegExp(`[\\w<>\\[\\]]+\\s+${name}\\s*\\(`).test(line));
460
+
461
+ export const summarizeCsharp = (content, mode) => {
462
+ const lines = content.split('\n');
463
+ const usings = [];
464
+ const namespaces = [];
465
+ const declarations = [];
466
+ const methods = [];
467
+ let currentType = null;
468
+ let braceDepth = 0;
469
+
470
+ for (const rawLine of lines) {
471
+ const trimmed = rawLine.trim();
472
+ if (!trimmed || trimmed.startsWith('//')) continue;
473
+
474
+ const usingMatch = trimmed.match(/^using\s+([\w.]+);$/);
475
+ if (usingMatch) { usings.push(usingMatch[1]); continue; }
476
+
477
+ const nsMatch = trimmed.match(/^namespace\s+([\w.]+)/);
478
+ if (nsMatch) { namespaces.push(`namespace ${nsMatch[1]}`); }
479
+
480
+ const typeMatch = trimmed.match(/^(?:public\s+)?(?:static\s+|abstract\s+|sealed\s+|partial\s+)*(class|struct|interface|enum|record)\s+([A-Za-z_]\w*)/);
481
+ if (typeMatch) {
482
+ currentType = typeMatch[2];
483
+ declarations.push(`${typeMatch[1]} ${typeMatch[2]}`);
484
+ }
485
+
486
+ const methodMatch = trimmed.match(/^(?:public|protected|private|internal)\s+(?:static\s+)?(?:virtual\s+|override\s+|abstract\s+|async\s+)?(?:[A-Za-z0-9_<>\[\],.\s]+\s+)([A-Za-z_]\w*)\s*\(/);
487
+ if (methodMatch && currentType) {
488
+ methods.push(`${currentType}::${methodMatch[1]}`);
489
+ }
490
+
491
+ braceDepth += (trimmed.match(/\{/g) ?? []).length;
492
+ braceDepth -= (trimmed.match(/\}/g) ?? []).length;
493
+ if (braceDepth <= 0) { currentType = null; braceDepth = 0; }
494
+ }
495
+
496
+ if (mode === 'signatures') {
497
+ return joinSections([
498
+ truncateSection('# Declarations', toUniqueLines([...declarations, ...methods]), 2600),
499
+ truncateSection('# Usings', toUniqueLines(usings, 10), 900),
500
+ ], 4000);
501
+ }
502
+
503
+ return joinSections([
504
+ truncateSection('# Namespaces', toUniqueLines(namespaces, 5), 300),
505
+ truncateSection('# Declarations', toUniqueLines(declarations, 20), 1400),
506
+ truncateSection('# Methods', toUniqueLines(methods, 20), 1800),
507
+ truncateSection('# Usings', toUniqueLines(usings, 12), 1000),
508
+ ], 5000);
509
+ };
510
+
511
+ // ---------------------------------------------------------------------------
512
+ // Kotlin
513
+ // ---------------------------------------------------------------------------
514
+
515
+ export const extractKotlinSymbol = (content, symbolName) =>
516
+ extractBraceBlock(content, symbolName, (line, name) =>
517
+ new RegExp(`(?:class|object|interface|enum)\\s+${name}`).test(line) ||
518
+ new RegExp(`fun\\s+(?:<[^>]+>\\s+)?${name}\\s*\\(`).test(line));
519
+
520
+ export const summarizeKotlin = (content, mode) => {
521
+ const lines = content.split('\n');
522
+ const imports = [];
523
+ const packages = [];
524
+ const declarations = [];
525
+ const functions = [];
526
+ let currentType = null;
527
+ let braceDepth = 0;
528
+
529
+ for (const rawLine of lines) {
530
+ const trimmed = rawLine.trim();
531
+ if (!trimmed || trimmed.startsWith('//')) continue;
532
+
533
+ const pkgMatch = trimmed.match(/^package\s+([\w.]+)$/);
534
+ if (pkgMatch) { packages.push(`package ${pkgMatch[1]}`); continue; }
535
+
536
+ const impMatch = trimmed.match(/^import\s+([\w.*]+)$/);
537
+ if (impMatch) { imports.push(impMatch[1]); continue; }
538
+
539
+ const typeMatch = trimmed.match(/^(?:open|abstract|data|sealed|internal|private|protected|\s)*(class|object|interface|enum)\s+([A-Za-z_]\w*)/);
540
+ if (typeMatch) {
541
+ currentType = typeMatch[2];
542
+ declarations.push(`${typeMatch[1]} ${typeMatch[2]}`);
543
+ }
544
+
545
+ const funMatch = trimmed.match(/^(?:(?:public|private|protected|internal|open|override|suspend|inline)\s+)*fun\s+(?:<[^>]+>\s+)?([A-Za-z_]\w*)\s*\(/);
546
+ if (funMatch) {
547
+ const owner = currentType && braceDepth > 0 ? `${currentType}::` : '';
548
+ functions.push(`${owner}${funMatch[1]}`);
549
+ }
550
+
551
+ braceDepth += (trimmed.match(/\{/g) ?? []).length;
552
+ braceDepth -= (trimmed.match(/\}/g) ?? []).length;
553
+ if (braceDepth <= 0) { currentType = null; braceDepth = 0; }
554
+ }
555
+
556
+ if (mode === 'signatures') {
557
+ return joinSections([
558
+ truncateSection('# Declarations', toUniqueLines([...declarations, ...functions]), 2600),
559
+ truncateSection('# Imports', toUniqueLines(imports, 10), 900),
560
+ ], 4000);
561
+ }
562
+
563
+ return joinSections([
564
+ truncateSection('# Package', toUniqueLines(packages, 2), 300),
565
+ truncateSection('# Declarations', toUniqueLines(declarations, 20), 1400),
566
+ truncateSection('# Functions', toUniqueLines(functions, 20), 1800),
567
+ truncateSection('# Imports', toUniqueLines(imports, 12), 1000),
568
+ ], 5000);
569
+ };
570
+
571
+ // ---------------------------------------------------------------------------
572
+ // PHP
573
+ // ---------------------------------------------------------------------------
574
+
575
+ export const extractPhpSymbol = (content, symbolName) =>
576
+ extractBraceBlock(content, symbolName, (line, name) =>
577
+ new RegExp(`(?:class|interface|trait|enum)\\s+${name}`).test(line) ||
578
+ new RegExp(`function\\s+${name}\\s*\\(`).test(line));
579
+
580
+ export const summarizePhp = (content, mode) => {
581
+ const lines = content.split('\n');
582
+ const uses = [];
583
+ const namespaces = [];
584
+ const declarations = [];
585
+ const functions = [];
586
+ let currentType = null;
587
+ let braceDepth = 0;
588
+
589
+ for (const rawLine of lines) {
590
+ const trimmed = rawLine.trim();
591
+ if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('#') || trimmed.startsWith('*')) continue;
592
+
593
+ const nsMatch = trimmed.match(/^namespace\s+([\w\\]+);?$/);
594
+ if (nsMatch) { namespaces.push(`namespace ${nsMatch[1]}`); continue; }
595
+
596
+ const useMatch = trimmed.match(/^use\s+([\w\\]+)(?:\s+as\s+\w+)?;$/);
597
+ if (useMatch) { uses.push(useMatch[1]); continue; }
598
+
599
+ const typeMatch = trimmed.match(/^(?:abstract|final|\s)*(class|interface|trait|enum)\s+([A-Za-z_]\w*)/);
600
+ if (typeMatch) {
601
+ currentType = typeMatch[2];
602
+ declarations.push(`${typeMatch[1]} ${typeMatch[2]}`);
603
+ }
604
+
605
+ const funcMatch = trimmed.match(/^(?:public|protected|private|static|\s)*function\s+([A-Za-z_]\w*)\s*\(/);
606
+ if (funcMatch) {
607
+ const owner = currentType && braceDepth > 0 ? `${currentType}::` : '';
608
+ functions.push(`${owner}${funcMatch[1]}`);
609
+ }
610
+
611
+ braceDepth += (trimmed.match(/\{/g) ?? []).length;
612
+ braceDepth -= (trimmed.match(/\}/g) ?? []).length;
613
+ if (braceDepth <= 0) { currentType = null; braceDepth = 0; }
614
+ }
615
+
616
+ if (mode === 'signatures') {
617
+ return joinSections([
618
+ truncateSection('# Declarations', toUniqueLines([...declarations, ...functions]), 2600),
619
+ truncateSection('# Uses', toUniqueLines(uses, 10), 900),
620
+ ], 4000);
621
+ }
622
+
623
+ return joinSections([
624
+ truncateSection('# Namespace', toUniqueLines(namespaces, 2), 300),
625
+ truncateSection('# Declarations', toUniqueLines(declarations, 20), 1400),
626
+ truncateSection('# Functions', toUniqueLines(functions, 20), 1800),
627
+ truncateSection('# Uses', toUniqueLines(uses, 12), 1000),
628
+ ], 5000);
629
+ };
630
+
631
+ // ---------------------------------------------------------------------------
632
+ // Swift
633
+ // ---------------------------------------------------------------------------
634
+
635
+ export const extractSwiftSymbol = (content, symbolName) =>
636
+ extractBraceBlock(content, symbolName, (line, name) =>
637
+ new RegExp(`(?:class|struct|enum|protocol|actor)\\s+${name}`).test(line) ||
638
+ new RegExp(`func\\s+${name}`).test(line));
639
+
640
+ export const summarizeSwift = (content, mode) => {
641
+ const lines = content.split('\n');
642
+ const imports = [];
643
+ const declarations = [];
644
+ const functions = [];
645
+ let currentType = null;
646
+ let braceDepth = 0;
647
+
648
+ for (const rawLine of lines) {
649
+ const trimmed = rawLine.trim();
650
+ if (!trimmed || trimmed.startsWith('//')) continue;
651
+
652
+ const impMatch = trimmed.match(/^import\s+(\w+)$/);
653
+ if (impMatch) { imports.push(impMatch[1]); continue; }
654
+
655
+ const typeMatch = trimmed.match(/^(?:public|private|internal|open|final|\s)*(class|struct|enum|protocol|actor)\s+([A-Za-z_]\w*)/);
656
+ if (typeMatch) {
657
+ currentType = typeMatch[2];
658
+ declarations.push(`${typeMatch[1]} ${typeMatch[2]}`);
659
+ }
660
+
661
+ const funcMatch = trimmed.match(/^(?:(?:public|private|internal|open|override|static|class|@\w+)\s+)*func\s+([A-Za-z_]\w*)/);
662
+ if (funcMatch) {
663
+ const owner = currentType && braceDepth > 0 ? `${currentType}::` : '';
664
+ functions.push(`${owner}${funcMatch[1]}`);
665
+ }
666
+
667
+ braceDepth += (trimmed.match(/\{/g) ?? []).length;
668
+ braceDepth -= (trimmed.match(/\}/g) ?? []).length;
669
+ if (braceDepth <= 0) { currentType = null; braceDepth = 0; }
670
+ }
671
+
672
+ if (mode === 'signatures') {
673
+ return joinSections([
674
+ truncateSection('# Declarations', toUniqueLines([...declarations, ...functions]), 2600),
675
+ truncateSection('# Imports', toUniqueLines(imports, 10), 900),
676
+ ], 4000);
677
+ }
678
+
679
+ return joinSections([
680
+ truncateSection('# Declarations', toUniqueLines(declarations, 20), 1400),
681
+ truncateSection('# Functions', toUniqueLines(functions, 20), 1800),
682
+ truncateSection('# Imports', toUniqueLines(imports, 12), 1000),
683
+ ], 5000);
684
+ };