sigmap 1.5.1 → 2.0.0-beta.1
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/CHANGELOG.md +30 -0
- package/README.md +3 -3
- package/gen-context.config.json.example +28 -1
- package/gen-context.js +683 -115
- package/package.json +2 -1
- package/src/config/defaults.js +43 -1
- package/src/extractors/coverage.js +60 -0
- package/src/extractors/cpp.js +15 -6
- package/src/extractors/csharp.js +14 -3
- package/src/extractors/dart.js +9 -7
- package/src/extractors/deps.js +81 -0
- package/src/extractors/go.js +9 -5
- package/src/extractors/java.js +14 -3
- package/src/extractors/javascript.js +34 -6
- package/src/extractors/kotlin.js +9 -5
- package/src/extractors/php.js +13 -4
- package/src/extractors/prdiff.js +45 -0
- package/src/extractors/python.js +136 -20
- package/src/extractors/ruby.js +13 -2
- package/src/extractors/rust.js +15 -5
- package/src/extractors/scala.js +13 -4
- package/src/extractors/svelte.js +11 -4
- package/src/extractors/swift.js +15 -5
- package/src/extractors/todos.js +26 -0
- package/src/extractors/typescript.js +31 -4
- package/src/extractors/vue.js +16 -2
- package/src/mcp/server.js +1 -1
package/gen-context.js
CHANGED
|
@@ -35,7 +35,12 @@ __factories["./src/config/defaults"] = function(module, exports) {
|
|
|
35
35
|
outputs: ['copilot'],
|
|
36
36
|
|
|
37
37
|
// Directories to scan (relative to project root)
|
|
38
|
-
srcDirs: [
|
|
38
|
+
srcDirs: [
|
|
39
|
+
'src', 'app', 'lib', 'packages', 'services', 'api',
|
|
40
|
+
'server', 'client', 'web', 'frontend', 'backend',
|
|
41
|
+
'desktop', 'mobile', 'shared', 'common', 'core',
|
|
42
|
+
'workers', 'functions', 'lambda', 'cmd',
|
|
43
|
+
],
|
|
39
44
|
|
|
40
45
|
// Directory/file names to exclude entirely
|
|
41
46
|
exclude: [
|
|
@@ -195,9 +200,11 @@ __factories["./src/extractors/cpp"] = function(module, exports) {
|
|
|
195
200
|
}
|
|
196
201
|
|
|
197
202
|
// Top-level function declarations/definitions (not inside a class)
|
|
198
|
-
for (const m of stripped.matchAll(/^(?!class|struct|if|for|while|switch)[\w:*&<> ]
|
|
199
|
-
if (m[
|
|
200
|
-
|
|
203
|
+
for (const m of stripped.matchAll(/^(?!class|struct|if|for|while|switch)([\w:*&<> ]+?)\s+(\w+)\s*\(([^)]*)\)\s*(?:const\s*)?\{/gm)) {
|
|
204
|
+
if (m[2].startsWith('_')) continue;
|
|
205
|
+
const ret = normalizeType(m[1]);
|
|
206
|
+
const retStr = ret ? ` \u2192 ${ret}` : '';
|
|
207
|
+
sigs.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
|
|
201
208
|
}
|
|
202
209
|
|
|
203
210
|
return sigs.slice(0, 25);
|
|
@@ -216,10 +223,12 @@ __factories["./src/extractors/cpp"] = function(module, exports) {
|
|
|
216
223
|
|
|
217
224
|
function extractMembers(block) {
|
|
218
225
|
const members = [];
|
|
219
|
-
const methodRe = /^\s+(?:virtual\s+|static\s+|inline\s+)?(?!private:|protected:|public:)[\w:*&<> ]
|
|
226
|
+
const methodRe = /^\s+(?:virtual\s+|static\s+|inline\s+)?(?!private:|protected:|public:)([\w:*&<> ]+?)\s+(\w+)\s*\(([^)]*)\)\s*(?:const\s*)?(?:override\s*)?(?:=\s*0\s*)?;/gm;
|
|
220
227
|
for (const m of block.matchAll(methodRe)) {
|
|
221
|
-
if (m[
|
|
222
|
-
|
|
228
|
+
if (m[2].startsWith('_')) continue;
|
|
229
|
+
const ret = normalizeType(m[1]);
|
|
230
|
+
const retStr = ret ? ` \u2192 ${ret}` : '';
|
|
231
|
+
members.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
|
|
223
232
|
}
|
|
224
233
|
return members.slice(0, 8);
|
|
225
234
|
}
|
|
@@ -228,6 +237,11 @@ __factories["./src/extractors/cpp"] = function(module, exports) {
|
|
|
228
237
|
if (!params) return '';
|
|
229
238
|
return params.trim().replace(/\s+/g, ' ');
|
|
230
239
|
}
|
|
240
|
+
|
|
241
|
+
function normalizeType(type) {
|
|
242
|
+
if (!type) return '';
|
|
243
|
+
return type.trim().replace(/\s+/g, ' ').slice(0, 30);
|
|
244
|
+
}
|
|
231
245
|
|
|
232
246
|
module.exports = { extract };
|
|
233
247
|
|
|
@@ -273,13 +287,24 @@ __factories["./src/extractors/csharp"] = function(module, exports) {
|
|
|
273
287
|
|
|
274
288
|
function extractMembers(block) {
|
|
275
289
|
const members = [];
|
|
276
|
-
const methodRe = /^\s+(?:public|internal|protected)\s+(?:static\s+|virtual\s+|override\s+|async\s+)*(?:[\w<>\[\]
|
|
290
|
+
const methodRe = /^\s+(?:public|internal|protected)\s+(?:static\s+|virtual\s+|override\s+|async\s+)*(?:where\s+\w+\s*:\s*[^\n]+\s+)?([\w<>\[\]?., ]+)\s+(\w+)\s*\(([^)]*)\)/gm;
|
|
277
291
|
for (const m of block.matchAll(methodRe)) {
|
|
278
|
-
const
|
|
279
|
-
|
|
292
|
+
const ret = normalizeType(m[1]);
|
|
293
|
+
const retStr = ret ? ` \u2192 ${ret}` : '';
|
|
294
|
+
members.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
|
|
280
295
|
}
|
|
281
296
|
return members.slice(0, 8);
|
|
282
297
|
}
|
|
298
|
+
|
|
299
|
+
function normalizeParams(params) {
|
|
300
|
+
if (!params) return '';
|
|
301
|
+
return params.trim().replace(/\s+/g, ' ');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function normalizeType(type) {
|
|
305
|
+
if (!type) return '';
|
|
306
|
+
return type.trim().replace(/\s+/g, ' ').slice(0, 30);
|
|
307
|
+
}
|
|
283
308
|
|
|
284
309
|
module.exports = { extract };
|
|
285
310
|
|
|
@@ -365,9 +390,11 @@ __factories["./src/extractors/dart"] = function(module, exports) {
|
|
|
365
390
|
}
|
|
366
391
|
|
|
367
392
|
// Top-level functions
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
393
|
+
// Top-level functions — capture return type (prefix before name) and show as suffix
|
|
394
|
+
for (const m of stripped.matchAll(/^((?:Future<[\w<>?,\s]*>|[\w<>?]+))\s+(\w+)\s*\(([^)]*)\)/gm)) {
|
|
395
|
+
if (m[2].startsWith('_')) continue;
|
|
396
|
+
const retStr = (m[1] && m[1] !== 'void') ? ` \u2192 ${m[1].replace(/\s+/g, '').slice(0, 25)}` : '';
|
|
397
|
+
sigs.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
|
|
371
398
|
}
|
|
372
399
|
|
|
373
400
|
return sigs.slice(0, 25);
|
|
@@ -386,9 +413,10 @@ __factories["./src/extractors/dart"] = function(module, exports) {
|
|
|
386
413
|
|
|
387
414
|
function extractMembers(block) {
|
|
388
415
|
const members = [];
|
|
389
|
-
for (const m of block.matchAll(/^\s+(?:@override\s+)?(?:Future
|
|
390
|
-
if (m[
|
|
391
|
-
|
|
416
|
+
for (const m of block.matchAll(/^\s+(?:@override\s+)?(?:@\w+\s+)*((?:Future<[\w<>?,\s]*>|[\w<>?]+))\s+(\w+)\s*\(([^)]*)\)/gm)) {
|
|
417
|
+
if (m[2].startsWith('_')) continue;
|
|
418
|
+
const retStr = (m[1] && m[1] !== 'void') ? ` \u2192 ${m[1].replace(/\s+/g, '').slice(0, 25)}` : '';
|
|
419
|
+
members.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
|
|
392
420
|
}
|
|
393
421
|
return members.slice(0, 8);
|
|
394
422
|
}
|
|
@@ -484,9 +512,12 @@ __factories["./src/extractors/go"] = function(module, exports) {
|
|
|
484
512
|
}
|
|
485
513
|
|
|
486
514
|
// Functions and methods
|
|
487
|
-
|
|
515
|
+
// Functions and methods — capture return type between ) and {
|
|
516
|
+
for (const m of stripped.matchAll(/^func\s+(?:\((\w+)\s+[\w*]+\)\s+)?(\w+)\s*\(([^)]*)\)([^{]*)\{/gm)) {
|
|
488
517
|
const receiver = m[1] ? `(${m[1]}) ` : '';
|
|
489
|
-
|
|
518
|
+
const retType = m[4] ? m[4].trim().replace(/\s+/g, ' ') : '';
|
|
519
|
+
const retStr = retType ? ` \u2192 ${retType.slice(0, 30)}` : '';
|
|
520
|
+
sigs.push(`func ${receiver}${m[2]}(${normalizeParams(m[3])})${retStr}`);
|
|
490
521
|
}
|
|
491
522
|
|
|
492
523
|
return sigs.slice(0, 25);
|
|
@@ -505,8 +536,10 @@ __factories["./src/extractors/go"] = function(module, exports) {
|
|
|
505
536
|
|
|
506
537
|
function extractInterfaceMethods(block) {
|
|
507
538
|
const methods = [];
|
|
508
|
-
for (const m of block.matchAll(/^\s+(\w+)\s*\(([^)]*)\)/gm)) {
|
|
509
|
-
|
|
539
|
+
for (const m of block.matchAll(/^\s+(\w+)\s*\(([^)]*)\)([^\n]*)/gm)) {
|
|
540
|
+
const retType = m[3] ? m[3].trim().replace(/\s+/g, ' ') : '';
|
|
541
|
+
const retStr = retType ? ` \u2192 ${retType.slice(0, 30)}` : '';
|
|
542
|
+
methods.push(`${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
510
543
|
}
|
|
511
544
|
return methods.slice(0, 8);
|
|
512
545
|
}
|
|
@@ -604,13 +637,24 @@ __factories["./src/extractors/java"] = function(module, exports) {
|
|
|
604
637
|
|
|
605
638
|
function extractMembers(block) {
|
|
606
639
|
const members = [];
|
|
607
|
-
const methodRe = /^\s+(?:public|protected)\s+(?:static\s+)?(?:final\s+)?(?:[\w<>\[\]]
|
|
640
|
+
const methodRe = /^\s+(?:public|protected)\s+(?:static\s+)?(?:final\s+)?(?:synchronized\s+)?(?:<[^>]+>\s+)?([\w<>\[\], ?.]+)\s+(\w+)\s*\(([^)]*)\)/gm;
|
|
608
641
|
for (const m of block.matchAll(methodRe)) {
|
|
609
|
-
const
|
|
610
|
-
|
|
642
|
+
const ret = normalizeType(m[1]);
|
|
643
|
+
const retStr = ret ? ` \u2192 ${ret}` : '';
|
|
644
|
+
members.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
|
|
611
645
|
}
|
|
612
646
|
return members.slice(0, 8);
|
|
613
647
|
}
|
|
648
|
+
|
|
649
|
+
function normalizeParams(params) {
|
|
650
|
+
if (!params) return '';
|
|
651
|
+
return params.trim().replace(/\s+/g, ' ');
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function normalizeType(type) {
|
|
655
|
+
if (!type) return '';
|
|
656
|
+
return type.trim().replace(/\s+/g, ' ').slice(0, 30);
|
|
657
|
+
}
|
|
614
658
|
|
|
615
659
|
module.exports = { extract };
|
|
616
660
|
|
|
@@ -627,6 +671,7 @@ __factories["./src/extractors/javascript"] = function(module, exports) {
|
|
|
627
671
|
function extract(src) {
|
|
628
672
|
if (!src || typeof src !== 'string') return [];
|
|
629
673
|
const sigs = [];
|
|
674
|
+
const returnHints = buildReturnHints(src);
|
|
630
675
|
|
|
631
676
|
const stripped = src
|
|
632
677
|
.replace(/\/\/.*$/gm, '')
|
|
@@ -638,19 +683,21 @@ __factories["./src/extractors/javascript"] = function(module, exports) {
|
|
|
638
683
|
const prefix = m[1] ? m[1].trim() + ' ' : '';
|
|
639
684
|
sigs.push(`${prefix}class ${m[2]}`);
|
|
640
685
|
const block = extractBlock(stripped, m.index + m[0].length);
|
|
641
|
-
for (const meth of extractClassMembers(block)) sigs.push(` ${meth}`);
|
|
686
|
+
for (const meth of extractClassMembers(block, returnHints)) sigs.push(` ${meth}`);
|
|
642
687
|
}
|
|
643
688
|
|
|
644
689
|
// Exported named functions
|
|
645
690
|
for (const m of stripped.matchAll(/^export\s+(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/gm)) {
|
|
646
691
|
const asyncKw = /export\s+async/.test(m[0]) ? 'async ' : '';
|
|
647
|
-
|
|
692
|
+
const retStr = formatReturnHint(returnHints.get(m[1]));
|
|
693
|
+
sigs.push(`export ${asyncKw}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
648
694
|
}
|
|
649
695
|
|
|
650
696
|
// Exported arrow functions
|
|
651
697
|
for (const m of stripped.matchAll(/^export\s+const\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s*=>/gm)) {
|
|
652
698
|
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
653
|
-
|
|
699
|
+
const retStr = formatReturnHint(returnHints.get(m[1]));
|
|
700
|
+
sigs.push(`export const ${m[1]} = ${asyncKw}(${normalizeParams(m[2])}) =>${retStr}`);
|
|
654
701
|
}
|
|
655
702
|
|
|
656
703
|
// module.exports = { ... }
|
|
@@ -663,7 +710,8 @@ __factories["./src/extractors/javascript"] = function(module, exports) {
|
|
|
663
710
|
// Top-level named functions (non-exported)
|
|
664
711
|
for (const m of stripped.matchAll(/^(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/gm)) {
|
|
665
712
|
const asyncKw = m[0].startsWith('async') ? 'async ' : '';
|
|
666
|
-
|
|
713
|
+
const retStr = formatReturnHint(returnHints.get(m[1]));
|
|
714
|
+
sigs.push(`${asyncKw}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
667
715
|
}
|
|
668
716
|
|
|
669
717
|
return sigs.slice(0, 25);
|
|
@@ -681,17 +729,41 @@ __factories["./src/extractors/javascript"] = function(module, exports) {
|
|
|
681
729
|
return src.slice(startIndex, i - 1);
|
|
682
730
|
}
|
|
683
731
|
|
|
684
|
-
function extractClassMembers(block) {
|
|
732
|
+
function extractClassMembers(block, returnHints) {
|
|
685
733
|
const members = [];
|
|
686
734
|
for (const m of block.matchAll(/^\s+(?:static\s+|async\s+|get\s+|set\s+)*(\w+)\s*\(([^)]*)\)\s*\{/gm)) {
|
|
687
735
|
if (/^_/.test(m[1])) continue;
|
|
688
736
|
if (m[1] === 'constructor') { members.push(`constructor(${normalizeParams(m[2])})`); continue; }
|
|
689
737
|
const isAsync = m[0].includes('async ') ? 'async ' : '';
|
|
690
738
|
const isStatic = m[0].includes('static ') ? 'static ' : '';
|
|
691
|
-
|
|
739
|
+
const retStr = formatReturnHint(returnHints.get(m[1]));
|
|
740
|
+
members.push(`${isStatic}${isAsync}${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
692
741
|
}
|
|
693
742
|
return members.slice(0, 8);
|
|
694
743
|
}
|
|
744
|
+
|
|
745
|
+
function buildReturnHints(src) {
|
|
746
|
+
const hints = new Map();
|
|
747
|
+
for (const m of src.matchAll(/\/\*\*[\s\S]*?@returns?\s+\{([^}]+)\}[\s\S]*?\*\/\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/g)) {
|
|
748
|
+
hints.set(m[2], normalizeType(m[1]));
|
|
749
|
+
}
|
|
750
|
+
for (const m of src.matchAll(/\/\*\*[\s\S]*?@returns?\s+\{([^}]+)\}[\s\S]*?\*\/\s*export\s+const\s+(\w+)\s*=\s*(?:async\s+)?\(/g)) {
|
|
751
|
+
hints.set(m[2], normalizeType(m[1]));
|
|
752
|
+
}
|
|
753
|
+
for (const m of src.matchAll(/\/\*\*[\s\S]*?@returns?\s+\{([^}]+)\}[\s\S]*?\*\/\s*(?:static\s+|async\s+|get\s+|set\s+)*(\w+)\s*\(/g)) {
|
|
754
|
+
hints.set(m[2], normalizeType(m[1]));
|
|
755
|
+
}
|
|
756
|
+
return hints;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function normalizeType(type) {
|
|
760
|
+
if (!type) return '';
|
|
761
|
+
return type.trim().replace(/\s+/g, ' ').slice(0, 25);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function formatReturnHint(type) {
|
|
765
|
+
return type ? ` \u2192 ${type}` : '';
|
|
766
|
+
}
|
|
695
767
|
|
|
696
768
|
function normalizeParams(params) {
|
|
697
769
|
if (!params) return '';
|
|
@@ -726,9 +798,12 @@ __factories["./src/extractors/kotlin"] = function(module, exports) {
|
|
|
726
798
|
}
|
|
727
799
|
|
|
728
800
|
// Top-level functions
|
|
729
|
-
|
|
801
|
+
// Top-level functions — capture `: RetType` after params
|
|
802
|
+
for (const m of stripped.matchAll(/^(?:public\s+|internal\s+)?(?:suspend\s+)?fun\s+(\w+)\s*(?:<[^(]*>)?\s*\(([^)]*)\)(?:\s*:\s*([^\n{=]+))?/gm)) {
|
|
730
803
|
const suspend = m[0].includes('suspend') ? 'suspend ' : '';
|
|
731
|
-
|
|
804
|
+
const retType = m[3] ? m[3].trim().replace(/\s+/g, ' ') : '';
|
|
805
|
+
const retStr = retType ? ` \u2192 ${retType.slice(0, 25)}` : '';
|
|
806
|
+
sigs.push(`${suspend}fun ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
732
807
|
}
|
|
733
808
|
|
|
734
809
|
return sigs.slice(0, 25);
|
|
@@ -747,10 +822,12 @@ __factories["./src/extractors/kotlin"] = function(module, exports) {
|
|
|
747
822
|
|
|
748
823
|
function extractMembers(block) {
|
|
749
824
|
const members = [];
|
|
750
|
-
for (const m of block.matchAll(/^\s+(?:public\s+|internal\s+|override\s+)?(?:suspend\s+)?fun\s+(\w+)\s*(?:<[^(]*>)?\s*\(([^)]*)\)
|
|
825
|
+
for (const m of block.matchAll(/^\s+(?:public\s+|internal\s+|override\s+)?(?:suspend\s+)?fun\s+(\w+)\s*(?:<[^(]*>)?\s*\(([^)]*)\)(?:\s*:\s*([^\n{=]+))?/gm)) {
|
|
751
826
|
if (m[1].startsWith('_')) continue;
|
|
752
827
|
const suspend = m[0].includes('suspend') ? 'suspend ' : '';
|
|
753
|
-
|
|
828
|
+
const retType = m[3] ? m[3].trim().replace(/\s+/g, ' ') : '';
|
|
829
|
+
const retStr = retType ? ` \u2192 ${retType.slice(0, 25)}` : '';
|
|
830
|
+
members.push(`${suspend}fun ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
754
831
|
}
|
|
755
832
|
return members.slice(0, 8);
|
|
756
833
|
}
|
|
@@ -796,8 +873,10 @@ __factories["./src/extractors/php"] = function(module, exports) {
|
|
|
796
873
|
}
|
|
797
874
|
|
|
798
875
|
// Top-level functions
|
|
799
|
-
for (const m of stripped.matchAll(/^function\s+(\w+)\s*\(([^)]*)\)
|
|
800
|
-
|
|
876
|
+
for (const m of stripped.matchAll(/^function\s+(\w+)\s*\(([^)]*)\)\s*(?::\s*([^\n{]+))?/gm)) {
|
|
877
|
+
const ret = normalizeType(m[3]);
|
|
878
|
+
const retStr = ret ? ` \u2192 ${ret}` : '';
|
|
879
|
+
sigs.push(`function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
801
880
|
}
|
|
802
881
|
|
|
803
882
|
return sigs.slice(0, 25);
|
|
@@ -816,11 +895,13 @@ __factories["./src/extractors/php"] = function(module, exports) {
|
|
|
816
895
|
|
|
817
896
|
function extractMembers(block) {
|
|
818
897
|
const members = [];
|
|
819
|
-
const methodRe = /^\s+(?:public|protected)\s+(?:static\s+)?function\s+(\w+)\s*\(([^)]*)\)
|
|
898
|
+
const methodRe = /^\s+(?:public|protected)\s+(?:static\s+)?function\s+(\w+)\s*\(([^)]*)\)\s*(?::\s*([^\n{]+))?/gm;
|
|
820
899
|
for (const m of block.matchAll(methodRe)) {
|
|
821
900
|
if (m[1].startsWith('_')) continue;
|
|
822
901
|
const isStatic = m[0].includes('static ') ? 'static ' : '';
|
|
823
|
-
|
|
902
|
+
const ret = normalizeType(m[3]);
|
|
903
|
+
const retStr = ret ? ` \u2192 ${ret}` : '';
|
|
904
|
+
members.push(`${isStatic}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
824
905
|
}
|
|
825
906
|
return members.slice(0, 8);
|
|
826
907
|
}
|
|
@@ -829,6 +910,11 @@ __factories["./src/extractors/php"] = function(module, exports) {
|
|
|
829
910
|
if (!params) return '';
|
|
830
911
|
return params.trim().replace(/\s+/g, ' ');
|
|
831
912
|
}
|
|
913
|
+
|
|
914
|
+
function normalizeType(type) {
|
|
915
|
+
if (!type) return '';
|
|
916
|
+
return type.trim().replace(/[;\s]+$/g, '').replace(/\s+/g, ' ').slice(0, 25);
|
|
917
|
+
}
|
|
832
918
|
|
|
833
919
|
module.exports = { extract };
|
|
834
920
|
|
|
@@ -846,60 +932,152 @@ __factories["./src/extractors/python"] = function(module, exports) {
|
|
|
846
932
|
if (!src || typeof src !== 'string') return [];
|
|
847
933
|
const sigs = [];
|
|
848
934
|
|
|
849
|
-
//
|
|
850
|
-
const
|
|
851
|
-
|
|
935
|
+
// noComments: strip only # comments, keep docstrings (needed for @decorator detection)
|
|
936
|
+
const noComments = src.replace(/#.*$/gm, '');
|
|
937
|
+
// stripped: also strip docstrings (safe for regex matching)
|
|
938
|
+
const stripped = noComments
|
|
852
939
|
.replace(/"""[\s\S]*?"""/g, '')
|
|
853
940
|
.replace(/'''[\s\S]*?'''/g, '');
|
|
854
941
|
|
|
855
942
|
// Classes
|
|
856
943
|
for (const m of stripped.matchAll(/^class\s+(\w+)(?:\s*\(([^)]*)\))?\s*:/gm)) {
|
|
857
|
-
const
|
|
858
|
-
|
|
859
|
-
// Get class body methods
|
|
944
|
+
const className = m[1];
|
|
945
|
+
const baseName = m[2] ? m[2].trim() : '';
|
|
860
946
|
const bodyStart = m.index + m[0].length;
|
|
947
|
+
|
|
948
|
+
// Try @dataclass collapse
|
|
949
|
+
const dcFields = tryExtractDataclassFields(stripped, m.index);
|
|
950
|
+
if (dcFields !== null) {
|
|
951
|
+
sigs.push(`@dataclass ${className}(${dcFields})`);
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// Try BaseModel/BaseSettings collapse
|
|
956
|
+
if (/(BaseModel|BaseSettings)/.test(baseName)) {
|
|
957
|
+
const bmFields = tryExtractBaseModelFields(stripped, bodyStart);
|
|
958
|
+
if (bmFields) {
|
|
959
|
+
sigs.push(`class ${className}(${baseName}) ${bmFields}`);
|
|
960
|
+
continue;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
const baseStr = baseName ? `(${baseName})` : '';
|
|
965
|
+
sigs.push(`class ${className}${baseStr}`);
|
|
966
|
+
|
|
967
|
+
// Class-level ALL_CAPS constants
|
|
968
|
+
for (const c of extractClassConstants(stripped, bodyStart)) {
|
|
969
|
+
sigs.push(` ${c}`);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// Methods
|
|
861
973
|
const methods = extractClassMethods(stripped, bodyStart);
|
|
862
974
|
for (const meth of methods) sigs.push(` ${meth}`);
|
|
863
975
|
}
|
|
864
976
|
|
|
865
977
|
// Top-level functions
|
|
866
|
-
for (const m of stripped.matchAll(/^(?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)
|
|
867
|
-
if (/^_/.test(m[
|
|
868
|
-
const asyncKw = m[
|
|
869
|
-
const params = normalizeParams(m[
|
|
870
|
-
|
|
978
|
+
for (const m of stripped.matchAll(/^((?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)(?:\s*->\s*[^:]+)?)\s*:/gm)) {
|
|
979
|
+
if (/^_/.test(m[2])) continue;
|
|
980
|
+
const asyncKw = m[1].trimStart().startsWith('async') ? 'async ' : '';
|
|
981
|
+
const params = normalizeParams(m[3]);
|
|
982
|
+
const retType = extractReturnType(m[1] + ':');
|
|
983
|
+
const retStr = retType ? ` \u2192 ${retType}` : '';
|
|
984
|
+
sigs.push(`${asyncKw}def ${m[2]}(${params})${retStr}`);
|
|
871
985
|
}
|
|
872
986
|
|
|
873
987
|
return sigs.slice(0, 25);
|
|
874
988
|
}
|
|
875
989
|
|
|
876
|
-
function extractClassMethods(
|
|
990
|
+
function extractClassMethods(stripped, startIndex) {
|
|
877
991
|
const methods = [];
|
|
878
|
-
|
|
879
|
-
const lines = src.slice(startIndex).split('\n');
|
|
992
|
+
const lines = stripped.slice(startIndex).split('\n');
|
|
880
993
|
for (const line of lines) {
|
|
881
994
|
if (line.trim() === '') continue;
|
|
882
|
-
// End of class body: line with no leading indent that is not blank
|
|
883
995
|
const indent = line.match(/^(\s+)/);
|
|
884
996
|
if (!indent) break;
|
|
885
|
-
const m = line.match(/^\s+(?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)
|
|
997
|
+
const m = line.match(/^\s+(?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)(?:\s*->\s*([^:]+))?\s*:/);
|
|
886
998
|
if (m) {
|
|
887
999
|
if (m[1].startsWith('__') && m[1] !== '__init__') continue;
|
|
888
1000
|
if (m[1].startsWith('_') && !m[1].startsWith('__')) continue;
|
|
889
1001
|
const asyncKw = line.trimStart().startsWith('async') ? 'async ' : '';
|
|
890
|
-
const params = normalizeParams(m[2]).replace(/^self,?\s*/, '');
|
|
891
|
-
|
|
1002
|
+
const params = normalizeParams(m[2]).replace(/^self,?\s*/, '').replace(/^cls,?\s*/, '');
|
|
1003
|
+
const retType = m[3] ? m[3].trim().replace(/Optional\[([^\]]+)\]/, '$1|None').slice(0, 30) : '';
|
|
1004
|
+
const retStr = retType ? ` \u2192 ${retType}` : '';
|
|
1005
|
+
methods.push(`${asyncKw}def ${m[1]}(${params})${retStr}`);
|
|
892
1006
|
}
|
|
893
1007
|
}
|
|
894
1008
|
return methods.slice(0, 8);
|
|
895
1009
|
}
|
|
896
1010
|
|
|
1011
|
+
function tryExtractDataclassFields(stripped, classIndex) {
|
|
1012
|
+
const before = stripped.slice(Math.max(0, classIndex - 120), classIndex);
|
|
1013
|
+
if (!/@dataclass/.test(before)) return null;
|
|
1014
|
+
const lines = stripped.slice(classIndex).split('\n');
|
|
1015
|
+
const fields = [];
|
|
1016
|
+
for (const line of lines.slice(1)) {
|
|
1017
|
+
if (line.trim() === '') continue;
|
|
1018
|
+
if (!line.match(/^\s+/)) break;
|
|
1019
|
+
const f = line.match(/^\s+(\w+)\s*:\s*([^=\n]+?)(?:\s*=.*)?$/);
|
|
1020
|
+
if (!f) break;
|
|
1021
|
+
const isOptional = f[2].includes('Optional') || /=\s*None/.test(line);
|
|
1022
|
+
fields.push(isOptional ? `${f[1]}?` : f[1]);
|
|
1023
|
+
}
|
|
1024
|
+
return fields.length ? fields.join(', ') : null;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
function tryExtractBaseModelFields(stripped, bodyStart) {
|
|
1028
|
+
const lines = stripped.slice(bodyStart, bodyStart + 800).split('\n');
|
|
1029
|
+
const fields = [];
|
|
1030
|
+
for (const line of lines) {
|
|
1031
|
+
if (!line.match(/^\s{4}\w/)) continue;
|
|
1032
|
+
const f = line.match(/^\s{4}(\w+)\s*(?::\s*([^=\n]+?))?(?:\s*=\s*(.*))?$/);
|
|
1033
|
+
if (!f || f[1].startsWith('_') || f[1] === 'class' || f[1] === 'def') continue;
|
|
1034
|
+
const isOptional = (f[2] || '').includes('Optional') || f[3] !== undefined;
|
|
1035
|
+
fields.push(isOptional ? `${f[1]}?` : `${f[1]}*`);
|
|
1036
|
+
}
|
|
1037
|
+
return fields.length ? `{${fields.slice(0, 6).join(', ')}}` : null;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function extractClassConstants(stripped, startIndex) {
|
|
1041
|
+
const lines = stripped.slice(startIndex).split('\n');
|
|
1042
|
+
const consts = [];
|
|
1043
|
+
for (const line of lines) {
|
|
1044
|
+
if (!line.match(/^\s+/)) break;
|
|
1045
|
+
const c = line.match(/^\s+([A-Z][A-Z0-9_]{2,})\s*=\s*(.+)/);
|
|
1046
|
+
if (!c) continue;
|
|
1047
|
+
let val = c[2].trim();
|
|
1048
|
+
const items = (val.match(/"([^"]+)"/g) || val.match(/'([^']+)'/g) || []);
|
|
1049
|
+
if (items.length > 3) val = `[${items[0].replace(/['"]/g, '')}..${items[items.length - 1].replace(/['"]/g, '')}]`;
|
|
1050
|
+
if (val.length > 40) val = val.slice(0, 37) + '...';
|
|
1051
|
+
consts.push(`${c[1]}=${val}`);
|
|
1052
|
+
}
|
|
1053
|
+
return consts.slice(0, 3);
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
function extractReturnType(sigLine) {
|
|
1057
|
+
const m = sigLine.match(/->\s*([^:]+):/);
|
|
1058
|
+
if (!m) return '';
|
|
1059
|
+
let rt = m[1].trim();
|
|
1060
|
+
rt = rt.replace(/Optional\[([^\]]+)\]/, '$1|None');
|
|
1061
|
+
return rt.length > 30 ? rt.slice(0, 27) + '...' : rt;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
897
1064
|
function normalizeParams(params) {
|
|
898
1065
|
if (!params) return '';
|
|
899
1066
|
return params.trim()
|
|
900
1067
|
.split(',')
|
|
901
|
-
.map((p) =>
|
|
902
|
-
|
|
1068
|
+
.map((p) => {
|
|
1069
|
+
const part = p.trim();
|
|
1070
|
+
if (!part) return '';
|
|
1071
|
+
const stars = part.match(/^(\*{1,2})/)?.[1] || '';
|
|
1072
|
+
const rest = part.slice(stars.length);
|
|
1073
|
+
const eqIdx = rest.indexOf('=');
|
|
1074
|
+
const noDefault = eqIdx !== -1 ? rest.slice(0, eqIdx).trim() : rest.trim();
|
|
1075
|
+
const clean = noDefault
|
|
1076
|
+
.replace(/Optional\[([^\]]+)\]/, '$1?')
|
|
1077
|
+
.replace(/Union\[([^\]]+),\s*None\]/, '$1?');
|
|
1078
|
+
return stars + clean;
|
|
1079
|
+
})
|
|
1080
|
+
.filter((p) => p && p !== 'self' && p !== 'cls')
|
|
903
1081
|
.join(', ');
|
|
904
1082
|
}
|
|
905
1083
|
|
|
@@ -932,14 +1110,16 @@ __factories["./src/extractors/ruby"] = function(module, exports) {
|
|
|
932
1110
|
if (m[1].startsWith('_')) continue;
|
|
933
1111
|
const params = m[2] ? `(${normalizeParams(m[2])})` : '';
|
|
934
1112
|
const selfPrefix = m[0].includes('self.') ? 'self.' : '';
|
|
935
|
-
|
|
1113
|
+
const retStr = extractReturnHint(stripped, m.index);
|
|
1114
|
+
sigs.push(` def ${selfPrefix}${m[1]}${params}${retStr}`);
|
|
936
1115
|
}
|
|
937
1116
|
|
|
938
1117
|
// Top-level def
|
|
939
1118
|
for (const m of stripped.matchAll(/^def\s+(\w+)(?:\s*\(([^)]*)\))?/gm)) {
|
|
940
1119
|
if (m[1].startsWith('_')) continue;
|
|
941
1120
|
const params = m[2] ? `(${normalizeParams(m[2])})` : '';
|
|
942
|
-
|
|
1121
|
+
const retStr = extractReturnHint(stripped, m.index);
|
|
1122
|
+
sigs.push(`def ${m[1]}${params}${retStr}`);
|
|
943
1123
|
}
|
|
944
1124
|
|
|
945
1125
|
return sigs.slice(0, 25);
|
|
@@ -949,6 +1129,15 @@ __factories["./src/extractors/ruby"] = function(module, exports) {
|
|
|
949
1129
|
if (!params) return '';
|
|
950
1130
|
return params.trim().replace(/\s+/g, ' ');
|
|
951
1131
|
}
|
|
1132
|
+
|
|
1133
|
+
function extractReturnHint(stripped, index) {
|
|
1134
|
+
const start = Math.max(0, index - 180);
|
|
1135
|
+
const before = stripped.slice(start, index);
|
|
1136
|
+
const m = before.match(/sig\s*\{[\s\S]*?returns\(([^)]+)\)[\s\S]*?\}\s*$/);
|
|
1137
|
+
if (!m) return '';
|
|
1138
|
+
const type = m[1].trim().replace(/\s+/g, ' ').slice(0, 25);
|
|
1139
|
+
return type ? ` \u2192 ${type}` : '';
|
|
1140
|
+
}
|
|
952
1141
|
|
|
953
1142
|
module.exports = { extract };
|
|
954
1143
|
|
|
@@ -993,9 +1182,11 @@ __factories["./src/extractors/rust"] = function(module, exports) {
|
|
|
993
1182
|
}
|
|
994
1183
|
|
|
995
1184
|
// Top-level pub fns
|
|
996
|
-
|
|
1185
|
+
// Top-level pub fns — capture everything after ) up to { or ; for return type
|
|
1186
|
+
for (const m of stripped.matchAll(/^pub(?:\s+async)?\s+fn\s+(\w+)(?:<[^(]*>)?\s*\(([^)]*)\)([^{;]*)/gm)) {
|
|
997
1187
|
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
998
|
-
|
|
1188
|
+
const retStr = extractReturnType(m[3]);
|
|
1189
|
+
sigs.push(`pub ${asyncKw}fn ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
999
1190
|
}
|
|
1000
1191
|
|
|
1001
1192
|
return sigs.slice(0, 25);
|
|
@@ -1014,9 +1205,10 @@ __factories["./src/extractors/rust"] = function(module, exports) {
|
|
|
1014
1205
|
|
|
1015
1206
|
function extractMethods(block) {
|
|
1016
1207
|
const methods = [];
|
|
1017
|
-
for (const m of block.matchAll(/^\s+pub(?:\s+async)?\s+fn\s+(\w+)(?:<[^(]*>)?\s*\(([^)]*)\)/gm)) {
|
|
1208
|
+
for (const m of block.matchAll(/^\s+pub(?:\s+async)?\s+fn\s+(\w+)(?:<[^(]*>)?\s*\(([^)]*)\)([^{;]*)/gm)) {
|
|
1018
1209
|
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
1019
|
-
|
|
1210
|
+
const retStr = extractReturnType(m[3]);
|
|
1211
|
+
methods.push(`pub ${asyncKw}fn ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
1020
1212
|
}
|
|
1021
1213
|
return methods.slice(0, 8);
|
|
1022
1214
|
}
|
|
@@ -1026,6 +1218,14 @@ __factories["./src/extractors/rust"] = function(module, exports) {
|
|
|
1026
1218
|
return params.trim().replace(/\s+/g, ' ');
|
|
1027
1219
|
}
|
|
1028
1220
|
|
|
1221
|
+
function extractReturnType(afterParen) {
|
|
1222
|
+
if (!afterParen) return '';
|
|
1223
|
+
const m = afterParen.match(/->\s*([^{;]+)/);
|
|
1224
|
+
if (!m) return '';
|
|
1225
|
+
const rt = m[1].trim().replace(/\s+/g, ' ');
|
|
1226
|
+
return ` \u2192 ${rt.length > 30 ? rt.slice(0, 27) + '...' : rt}`;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1029
1229
|
module.exports = { extract };
|
|
1030
1230
|
|
|
1031
1231
|
};
|
|
@@ -1058,10 +1258,12 @@ __factories["./src/extractors/scala"] = function(module, exports) {
|
|
|
1058
1258
|
}
|
|
1059
1259
|
|
|
1060
1260
|
// Top-level defs
|
|
1061
|
-
for (const m of stripped.matchAll(/^def\s+(\w+)(?:\[[\w, ]+\])?\s*(?:\(([^)]*)\))?/gm)) {
|
|
1261
|
+
for (const m of stripped.matchAll(/^def\s+(\w+)(?:\[[\w, ]+\])?\s*(?:\(([^)]*)\))?(?:\s*:\s*([^=\n{]+))?/gm)) {
|
|
1062
1262
|
if (m[1].startsWith('_')) continue;
|
|
1063
1263
|
const params = m[2] ? `(${normalizeParams(m[2])})` : '';
|
|
1064
|
-
|
|
1264
|
+
const ret = normalizeType(m[3]);
|
|
1265
|
+
const retStr = ret ? ` \u2192 ${ret}` : '';
|
|
1266
|
+
sigs.push(`def ${m[1]}${params}${retStr}`);
|
|
1065
1267
|
}
|
|
1066
1268
|
|
|
1067
1269
|
return sigs.slice(0, 25);
|
|
@@ -1080,10 +1282,12 @@ __factories["./src/extractors/scala"] = function(module, exports) {
|
|
|
1080
1282
|
|
|
1081
1283
|
function extractMembers(block) {
|
|
1082
1284
|
const members = [];
|
|
1083
|
-
for (const m of block.matchAll(/^\s+def\s+(\w+)(?:\[[\w, ]+\])?\s*(?:\(([^)]*)\))?/gm)) {
|
|
1285
|
+
for (const m of block.matchAll(/^\s+def\s+(\w+)(?:\[[\w, ]+\])?\s*(?:\(([^)]*)\))?(?:\s*:\s*([^=\n{]+))?/gm)) {
|
|
1084
1286
|
if (m[1].startsWith('_')) continue;
|
|
1085
1287
|
const params = m[2] ? `(${normalizeParams(m[2])})` : '';
|
|
1086
|
-
|
|
1288
|
+
const ret = normalizeType(m[3]);
|
|
1289
|
+
const retStr = ret ? ` \u2192 ${ret}` : '';
|
|
1290
|
+
members.push(`def ${m[1]}${params}${retStr}`);
|
|
1087
1291
|
}
|
|
1088
1292
|
return members.slice(0, 8);
|
|
1089
1293
|
}
|
|
@@ -1096,6 +1300,11 @@ __factories["./src/extractors/scala"] = function(module, exports) {
|
|
|
1096
1300
|
.filter(Boolean)
|
|
1097
1301
|
.join(', ');
|
|
1098
1302
|
}
|
|
1303
|
+
|
|
1304
|
+
function normalizeType(type) {
|
|
1305
|
+
if (!type) return '';
|
|
1306
|
+
return type.trim().replace(/\s+/g, ' ').slice(0, 25);
|
|
1307
|
+
}
|
|
1099
1308
|
|
|
1100
1309
|
module.exports = { extract };
|
|
1101
1310
|
|
|
@@ -1174,16 +1383,18 @@ __factories["./src/extractors/svelte"] = function(module, exports) {
|
|
|
1174
1383
|
}
|
|
1175
1384
|
|
|
1176
1385
|
// Exported functions
|
|
1177
|
-
for (const m of script.matchAll(/^export\s+(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)
|
|
1386
|
+
for (const m of script.matchAll(/^export\s+(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^{=\n]+))?/gm)) {
|
|
1178
1387
|
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
1179
|
-
|
|
1388
|
+
const retStr = m[3] ? ` \u2192 ${normalizeType(m[3])}` : '';
|
|
1389
|
+
sigs.push(`export ${asyncKw}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
1180
1390
|
}
|
|
1181
1391
|
|
|
1182
1392
|
// Top-level functions
|
|
1183
|
-
for (const m of script.matchAll(/^(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)
|
|
1393
|
+
for (const m of script.matchAll(/^(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^{=\n]+))?/gm)) {
|
|
1184
1394
|
if (m[1].startsWith('_')) continue;
|
|
1185
1395
|
const asyncKw = m[0].startsWith('async') ? 'async ' : '';
|
|
1186
|
-
|
|
1396
|
+
const retStr = m[3] ? ` \u2192 ${normalizeType(m[3])}` : '';
|
|
1397
|
+
sigs.push(`${asyncKw}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
1187
1398
|
}
|
|
1188
1399
|
|
|
1189
1400
|
// Reactive declarations $:
|
|
@@ -1198,6 +1409,11 @@ __factories["./src/extractors/svelte"] = function(module, exports) {
|
|
|
1198
1409
|
if (!params) return '';
|
|
1199
1410
|
return params.trim().replace(/\s+/g, ' ');
|
|
1200
1411
|
}
|
|
1412
|
+
|
|
1413
|
+
function normalizeType(type) {
|
|
1414
|
+
if (!type) return '';
|
|
1415
|
+
return type.trim().replace(/[;\s]+$/g, '').replace(/\s+/g, ' ').slice(0, 25);
|
|
1416
|
+
}
|
|
1201
1417
|
|
|
1202
1418
|
module.exports = { extract };
|
|
1203
1419
|
|
|
@@ -1228,9 +1444,11 @@ __factories["./src/extractors/swift"] = function(module, exports) {
|
|
|
1228
1444
|
}
|
|
1229
1445
|
|
|
1230
1446
|
// Top-level public functions
|
|
1231
|
-
|
|
1447
|
+
// Top-level public functions — capture everything after ) to end of line for arrow type
|
|
1448
|
+
for (const m of stripped.matchAll(/^(?:public\s+|internal\s+)?(?:static\s+)?(?:async\s+)?func\s+(\w+)(?:<[^(]*>)?\s*\(([^)]*)\)([^{\n]*)/gm)) {
|
|
1232
1449
|
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
1233
|
-
|
|
1450
|
+
const retStr = extractArrowType(m[3]);
|
|
1451
|
+
sigs.push(`${asyncKw}func ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
1234
1452
|
}
|
|
1235
1453
|
|
|
1236
1454
|
return sigs.slice(0, 25);
|
|
@@ -1249,10 +1467,11 @@ __factories["./src/extractors/swift"] = function(module, exports) {
|
|
|
1249
1467
|
|
|
1250
1468
|
function extractMembers(block) {
|
|
1251
1469
|
const members = [];
|
|
1252
|
-
for (const m of block.matchAll(/^\s+(?:public\s+|internal\s+|open\s+)?(?:static\s+|class\s+)?(?:mutating\s+)?(?:async\s+)?func\s+(\w+)(?:<[^(]*>)?\s*\(([^)]*)\)/gm)) {
|
|
1470
|
+
for (const m of block.matchAll(/^\s+(?:public\s+|internal\s+|open\s+)?(?:static\s+|class\s+)?(?:mutating\s+)?(?:async\s+)?func\s+(\w+)(?:<[^(]*>)?\s*\(([^)]*)\)([^{\n]*)/gm)) {
|
|
1253
1471
|
if (m[1].startsWith('_')) continue;
|
|
1254
1472
|
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
1255
|
-
|
|
1473
|
+
const retStr = extractArrowType(m[3]);
|
|
1474
|
+
members.push(`${asyncKw}func ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
1256
1475
|
}
|
|
1257
1476
|
return members.slice(0, 8);
|
|
1258
1477
|
}
|
|
@@ -1266,6 +1485,14 @@ __factories["./src/extractors/swift"] = function(module, exports) {
|
|
|
1266
1485
|
.join(', ');
|
|
1267
1486
|
}
|
|
1268
1487
|
|
|
1488
|
+
function extractArrowType(str) {
|
|
1489
|
+
if (!str) return '';
|
|
1490
|
+
const m = str.match(/->\s*([^\n{]+)/);
|
|
1491
|
+
if (!m) return '';
|
|
1492
|
+
const rt = m[1].trim().replace(/\s+/g, ' ');
|
|
1493
|
+
return ` \u2192 ${rt.length > 25 ? rt.slice(0, 22) + '...' : rt}`;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1269
1496
|
module.exports = { extract };
|
|
1270
1497
|
|
|
1271
1498
|
};
|
|
@@ -1323,7 +1550,10 @@ __factories["./src/extractors/typescript"] = function(module, exports) {
|
|
|
1323
1550
|
for (const m of stripped.matchAll(/^export\s+(?:async\s+)?function\s+(\w+)\s*(?:<[^(]*>)?\s*\(([^)]*)\)(?:\s*:\s*[^{]+)?\s*\{/gm)) {
|
|
1324
1551
|
const asyncKw = /export\s+async/.test(m[0]) ? 'async ' : '';
|
|
1325
1552
|
const params = normalizeParams(m[2]);
|
|
1326
|
-
|
|
1553
|
+
const retMatch = m[0].match(/\)\s*:\s*([^{]+)\s*\{/);
|
|
1554
|
+
const retType = retMatch ? retMatch[1].trim().replace(/\s+/g, ' ').slice(0, 30) : '';
|
|
1555
|
+
const retStr = retType ? ` \u2192 ${retType}` : '';
|
|
1556
|
+
sigs.push(`export ${asyncKw}function ${m[1]}(${params})${retStr}`);
|
|
1327
1557
|
}
|
|
1328
1558
|
|
|
1329
1559
|
// Exported arrow functions / const functions
|
|
@@ -1350,9 +1580,11 @@ __factories["./src/extractors/typescript"] = function(module, exports) {
|
|
|
1350
1580
|
|
|
1351
1581
|
function extractInterfaceMembers(block) {
|
|
1352
1582
|
const members = [];
|
|
1353
|
-
for (const m of block.matchAll(/^\s+(readonly\s+)?(\w+)
|
|
1583
|
+
for (const m of block.matchAll(/^\s+(readonly\s+)?(\w+)(\??):\s*([^;]+);/gm)) {
|
|
1354
1584
|
const readonly = m[1] ? 'readonly ' : '';
|
|
1355
|
-
|
|
1585
|
+
const optional = m[3] ? '?' : '';
|
|
1586
|
+
const typeStr = m[4].trim().replace(/\s+/g, ' ').slice(0, 20);
|
|
1587
|
+
members.push(`${readonly}${m[2]}${optional}: ${typeStr}`);
|
|
1356
1588
|
}
|
|
1357
1589
|
for (const m of block.matchAll(/^\s+(\w+)\s*(?:<[^(]*>)?\s*\(([^)]*)\)\s*:/gm)) {
|
|
1358
1590
|
members.push(`${m[1]}(${normalizeParams(m[2])})`);
|
|
@@ -1369,7 +1601,10 @@ __factories["./src/extractors/typescript"] = function(module, exports) {
|
|
|
1369
1601
|
if (m[1] === 'constructor') { members.push(`constructor(${normalizeParams(m[2])})`); continue; }
|
|
1370
1602
|
const isAsync = m[0].includes('async ') ? 'async ' : '';
|
|
1371
1603
|
const isStatic = m[0].includes('static ') ? 'static ' : '';
|
|
1372
|
-
|
|
1604
|
+
const retMatch = m[0].match(/\)\s*:\s*([^{;]+)\s*\{/);
|
|
1605
|
+
const retType = retMatch ? retMatch[1].trim().replace(/\s+/g, ' ').slice(0, 20) : '';
|
|
1606
|
+
const retStr = retType ? ` \u2192 ${retType}` : '';
|
|
1607
|
+
members.push(`${isStatic}${isAsync}${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
1373
1608
|
}
|
|
1374
1609
|
return members.slice(0, 8);
|
|
1375
1610
|
}
|
|
@@ -1383,6 +1618,58 @@ __factories["./src/extractors/typescript"] = function(module, exports) {
|
|
|
1383
1618
|
|
|
1384
1619
|
};
|
|
1385
1620
|
|
|
1621
|
+
// ── ./src/extractors/deps ──
|
|
1622
|
+
__factories["./src/extractors/deps"] = function(module, exports) {
|
|
1623
|
+
|
|
1624
|
+
'use strict';
|
|
1625
|
+
|
|
1626
|
+
const PYTHON_STDLIB = new Set([
|
|
1627
|
+
'os', 'sys', 're', 'json', 'time', 'threading', 'logging', 'typing',
|
|
1628
|
+
'dataclasses', 'datetime', 'uuid', 'pathlib', 'collections', 'functools',
|
|
1629
|
+
'itertools', 'math', 'random', 'string', 'struct', 'io', 'copy', 'pprint',
|
|
1630
|
+
'traceback', 'inspect', 'abc', 'enum', 'contextlib', 'weakref', 'gc',
|
|
1631
|
+
'socket', 'ssl', 'http', 'urllib', 'email', 'html', 'xml', 'csv', 'sqlite3',
|
|
1632
|
+
'argparse', 'subprocess', 'shutil', 'tempfile', 'glob', 'fnmatch', 'stat',
|
|
1633
|
+
'hashlib', 'hmac', 'base64', 'binascii', 'codecs', 'unicodedata', 'locale',
|
|
1634
|
+
'decimal', 'fractions', 'numbers', 'cmath', 'heapq', 'bisect', 'array',
|
|
1635
|
+
'queue', 'asyncio', 'concurrent', 'multiprocessing', 'signal', 'mmap',
|
|
1636
|
+
'builtins', 'warnings', 'operator', 'textwrap', 'difflib', 'readline',
|
|
1637
|
+
]);
|
|
1638
|
+
|
|
1639
|
+
function extractPythonDeps(src) {
|
|
1640
|
+
const deps = new Set();
|
|
1641
|
+
for (const m of src.matchAll(/^from\s+([\w.]+)\s+import/gm)) {
|
|
1642
|
+
const mod = m[1];
|
|
1643
|
+
const root = mod.replace(/^\.+/, '').split('.')[0];
|
|
1644
|
+
if (mod.startsWith('.') || (root && !PYTHON_STDLIB.has(root))) {
|
|
1645
|
+
deps.add(root || mod);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
for (const m of src.matchAll(/^import\s+([\w.]+)/gm)) {
|
|
1649
|
+
const root = m[1].split('.')[0];
|
|
1650
|
+
if (root && !PYTHON_STDLIB.has(root)) deps.add(root);
|
|
1651
|
+
}
|
|
1652
|
+
return [...deps].filter(Boolean).slice(0, 5);
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
function extractTSDeps(src) {
|
|
1656
|
+
// Strip single-line comments to avoid matching commented-out imports
|
|
1657
|
+
const stripped = src.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
1658
|
+
const deps = new Set();
|
|
1659
|
+
for (const m of stripped.matchAll(/from\s+['"](\.[\/\w.-]+)['"]/g)) {
|
|
1660
|
+
const clean = m[1]
|
|
1661
|
+
.replace(/^\.\.\//, '')
|
|
1662
|
+
.replace(/^\.\//, '')
|
|
1663
|
+
.replace(/\.\w+$/, '');
|
|
1664
|
+
if (clean) deps.add(clean);
|
|
1665
|
+
}
|
|
1666
|
+
return [...deps].slice(0, 5);
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
module.exports = { extractPythonDeps, extractTSDeps };
|
|
1670
|
+
|
|
1671
|
+
};
|
|
1672
|
+
|
|
1386
1673
|
// ── ./src/extractors/vue ──
|
|
1387
1674
|
__factories["./src/extractors/vue"] = function(module, exports) {
|
|
1388
1675
|
|
|
@@ -1420,12 +1707,21 @@ __factories["./src/extractors/vue"] = function(module, exports) {
|
|
|
1420
1707
|
// Methods in options API
|
|
1421
1708
|
const methodsMatch = script.match(/methods\s*:\s*\{([\s\S]*?)\},?\s*(?:computed|watch|mounted|created|data|\})/);
|
|
1422
1709
|
if (methodsMatch) {
|
|
1423
|
-
for (const m of methodsMatch[1].matchAll(/^\s+(?:async\s+)?(\w+)\s*\(([^)]*)\)
|
|
1710
|
+
for (const m of methodsMatch[1].matchAll(/^\s+(?:async\s+)?(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^{=\n]+))?/gm)) {
|
|
1424
1711
|
if (m[1].startsWith('_')) continue;
|
|
1425
1712
|
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
1426
|
-
|
|
1713
|
+
const retStr = m[3] ? ` \u2192 ${normalizeType(m[3])}` : '';
|
|
1714
|
+
sigs.push(` ${asyncKw}${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
1427
1715
|
}
|
|
1428
1716
|
}
|
|
1717
|
+
|
|
1718
|
+
// Top-level functions in <script> (e.g., composition API helpers)
|
|
1719
|
+
for (const m of script.matchAll(/^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^{=\n]+))?/gm)) {
|
|
1720
|
+
if (m[1].startsWith('_')) continue;
|
|
1721
|
+
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
1722
|
+
const retStr = m[3] ? ` \u2192 ${normalizeType(m[3])}` : '';
|
|
1723
|
+
sigs.push(`${asyncKw}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
1724
|
+
}
|
|
1429
1725
|
|
|
1430
1726
|
// defineProps (Composition API)
|
|
1431
1727
|
const definePropsMatch = script.match(/defineProps(?:<[^>]*>)?\s*\(\s*(\{[\s\S]*?\})\s*\)/);
|
|
@@ -1448,6 +1744,11 @@ __factories["./src/extractors/vue"] = function(module, exports) {
|
|
|
1448
1744
|
if (!params) return '';
|
|
1449
1745
|
return params.trim().replace(/\s+/g, ' ');
|
|
1450
1746
|
}
|
|
1747
|
+
|
|
1748
|
+
function normalizeType(type) {
|
|
1749
|
+
if (!type) return '';
|
|
1750
|
+
return type.trim().replace(/[;\s]+$/g, '').replace(/\s+/g, ' ').slice(0, 25);
|
|
1751
|
+
}
|
|
1451
1752
|
|
|
1452
1753
|
module.exports = { extract };
|
|
1453
1754
|
|
|
@@ -3219,14 +3520,23 @@ const path = require('path');
|
|
|
3219
3520
|
const os = require('os');
|
|
3220
3521
|
const { execSync } = require('child_process');
|
|
3221
3522
|
|
|
3222
|
-
const VERSION = '
|
|
3523
|
+
const VERSION = '2.0.0-beta.1';
|
|
3223
3524
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
3224
3525
|
|
|
3526
|
+
function requireSourceOrBundled(key) {
|
|
3527
|
+
try {
|
|
3528
|
+
const rel = key.replace(/^\.\//, '') + '.js';
|
|
3529
|
+
const abs = path.join(__dirname, rel);
|
|
3530
|
+
if (fs.existsSync(abs)) return require(abs);
|
|
3531
|
+
} catch (_) {}
|
|
3532
|
+
return __require(key);
|
|
3533
|
+
}
|
|
3534
|
+
|
|
3225
3535
|
// ---------------------------------------------------------------------------
|
|
3226
3536
|
// Config — delegate to src/config/loader.js
|
|
3227
3537
|
// ---------------------------------------------------------------------------
|
|
3228
|
-
const { loadConfig } =
|
|
3229
|
-
const { DEFAULTS } =
|
|
3538
|
+
const { loadConfig } = requireSourceOrBundled('./src/config/loader');
|
|
3539
|
+
const { DEFAULTS } = requireSourceOrBundled('./src/config/defaults');
|
|
3230
3540
|
|
|
3231
3541
|
// ---------------------------------------------------------------------------
|
|
3232
3542
|
// Language → extractor mapping (by file extension)
|
|
@@ -3335,7 +3645,7 @@ const _extractorCache = {};
|
|
|
3335
3645
|
function getExtractor(name) {
|
|
3336
3646
|
if (_extractorCache[name]) return _extractorCache[name];
|
|
3337
3647
|
try {
|
|
3338
|
-
const mod =
|
|
3648
|
+
const mod = requireSourceOrBundled(`./src/extractors/${name}`);
|
|
3339
3649
|
_extractorCache[name] = mod;
|
|
3340
3650
|
return mod;
|
|
3341
3651
|
} catch (err) {
|
|
@@ -3363,6 +3673,35 @@ function detectAndExtract(filePath, content, maxSigsPerFile) {
|
|
|
3363
3673
|
}
|
|
3364
3674
|
}
|
|
3365
3675
|
|
|
3676
|
+
function extractFileDeps(filePath, content, config) {
|
|
3677
|
+
if (config && config.depMap === false) return [];
|
|
3678
|
+
try {
|
|
3679
|
+
const { extractPythonDeps, extractTSDeps } = requireSourceOrBundled('./src/extractors/deps');
|
|
3680
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
3681
|
+
if (ext === '.py' || ext === '.pyw') return extractPythonDeps(content);
|
|
3682
|
+
if (['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(ext)) return extractTSDeps(content);
|
|
3683
|
+
} catch (_) {}
|
|
3684
|
+
return [];
|
|
3685
|
+
}
|
|
3686
|
+
|
|
3687
|
+
function extractSignatureName(sig) {
|
|
3688
|
+
const s = (sig || '').trim();
|
|
3689
|
+
const m = s.match(/(?:def|function|func|constructor|async\s+def|async\s+function)?\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
|
|
3690
|
+
return m ? m[1] : '';
|
|
3691
|
+
}
|
|
3692
|
+
|
|
3693
|
+
function annotateCoverage(sigs, testIndex, enabled) {
|
|
3694
|
+
if (!enabled || !testIndex || testIndex.size === 0) return sigs;
|
|
3695
|
+
const { isTested } = requireSourceOrBundled('./src/extractors/coverage');
|
|
3696
|
+
return sigs.map((sig) => {
|
|
3697
|
+
const name = extractSignatureName(sig);
|
|
3698
|
+
if (!name) return sig;
|
|
3699
|
+
// Skip non-callable/type declarations.
|
|
3700
|
+
if (/^(class|interface|enum|type|module|trait|struct|record)\b/.test(sig.trim())) return sig;
|
|
3701
|
+
return `${sig} ${isTested(name, testIndex) ? '✓' : '✗'}`;
|
|
3702
|
+
});
|
|
3703
|
+
}
|
|
3704
|
+
|
|
3366
3705
|
// ---------------------------------------------------------------------------
|
|
3367
3706
|
// Token budget enforcement
|
|
3368
3707
|
// ---------------------------------------------------------------------------
|
|
@@ -3454,10 +3793,163 @@ function getDiffFiles(cwd, stagedOnly) {
|
|
|
3454
3793
|
}
|
|
3455
3794
|
}
|
|
3456
3795
|
|
|
3796
|
+
function getFilesChangedSinceBase(cwd, baseRef) {
|
|
3797
|
+
try {
|
|
3798
|
+
const out = execSync(`git diff ${baseRef}..HEAD --name-only`, {
|
|
3799
|
+
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
3800
|
+
});
|
|
3801
|
+
return new Set(out.split('\n').map((f) => f.trim()).filter(Boolean).map((f) => path.resolve(cwd, f)));
|
|
3802
|
+
} catch (_) {
|
|
3803
|
+
return new Set();
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3807
|
+
function buildDiffSectionFromBase(cwd, baseRef, currentEntries, config) {
|
|
3808
|
+
const { diffSignatures, extractName } = requireSourceOrBundled('./src/extractors/prdiff');
|
|
3809
|
+
const lines = ['## diff (vs ' + baseRef + ')', '```'];
|
|
3810
|
+
|
|
3811
|
+
for (const entry of currentEntries) {
|
|
3812
|
+
const rel = path.relative(cwd, entry.filePath);
|
|
3813
|
+
let baseSrc = '';
|
|
3814
|
+
try {
|
|
3815
|
+
baseSrc = execSync(`git show ${baseRef}:"${rel}" 2>/dev/null`, {
|
|
3816
|
+
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
3817
|
+
});
|
|
3818
|
+
} catch (_) {
|
|
3819
|
+
baseSrc = '';
|
|
3820
|
+
}
|
|
3821
|
+
|
|
3822
|
+
const baseSigs = baseSrc ? detectAndExtract(entry.filePath, baseSrc, config.maxSigsPerFile) : [];
|
|
3823
|
+
const d = diffSignatures(baseSigs, entry.sigs || []);
|
|
3824
|
+
|
|
3825
|
+
const markers = [];
|
|
3826
|
+
for (const a of d.added.slice(0, 4)) {
|
|
3827
|
+
const n = extractName(a);
|
|
3828
|
+
if (n) markers.push(`+${n}`);
|
|
3829
|
+
}
|
|
3830
|
+
for (const r of d.removed.slice(0, 4)) {
|
|
3831
|
+
const n = extractName(r);
|
|
3832
|
+
if (n) markers.push(`-${n}`);
|
|
3833
|
+
}
|
|
3834
|
+
for (const m of d.modified.slice(0, 4)) markers.push(`~${m}`);
|
|
3835
|
+
if (markers.length === 0) continue;
|
|
3836
|
+
lines.push(`${rel}`);
|
|
3837
|
+
lines.push(` ${[...new Set(markers)].slice(0, 6).join(' ')}`);
|
|
3838
|
+
}
|
|
3839
|
+
|
|
3840
|
+
lines.push('```', '');
|
|
3841
|
+
return lines.length > 3 ? lines : [];
|
|
3842
|
+
}
|
|
3843
|
+
|
|
3457
3844
|
// ---------------------------------------------------------------------------
|
|
3458
3845
|
// Output formatter
|
|
3459
3846
|
// ---------------------------------------------------------------------------
|
|
3460
|
-
function
|
|
3847
|
+
function buildDepMap(fileEntries, cwd) {
|
|
3848
|
+
const lines = [];
|
|
3849
|
+
for (const entry of fileEntries) {
|
|
3850
|
+
const deps = entry.deps;
|
|
3851
|
+
if (!deps || deps.length === 0) continue;
|
|
3852
|
+
const rel = path.relative(cwd, entry.filePath);
|
|
3853
|
+
lines.push(`${rel} \u2190 ${deps.join(', ')}`);
|
|
3854
|
+
}
|
|
3855
|
+
return lines;
|
|
3856
|
+
}
|
|
3857
|
+
|
|
3858
|
+
function buildTodoSection(fileEntries, cwd, config) {
|
|
3859
|
+
if (config && config.todos === false) return [];
|
|
3860
|
+
const { extractTodos } = requireSourceOrBundled('./src/extractors/todos');
|
|
3861
|
+
const out = [];
|
|
3862
|
+
for (const entry of fileEntries) {
|
|
3863
|
+
const todos = extractTodos(entry.content || '');
|
|
3864
|
+
if (!todos || todos.length === 0) continue;
|
|
3865
|
+
const rel = path.relative(cwd, entry.filePath);
|
|
3866
|
+
for (const t of todos) {
|
|
3867
|
+
out.push(`${rel}:${t.line} # ${t.tag}: ${t.text}`);
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3870
|
+
return out.slice(0, 20);
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3873
|
+
function buildChangesSection(cwd, config, fileEntries) {
|
|
3874
|
+
if (config && config.changes === false) return [];
|
|
3875
|
+
const n = (config && config.changesCommits) || 5;
|
|
3876
|
+
try {
|
|
3877
|
+
let range = `HEAD~${n}..HEAD`;
|
|
3878
|
+
try {
|
|
3879
|
+
execSync(`git rev-parse --verify HEAD~${n} 2>/dev/null`, {
|
|
3880
|
+
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
3881
|
+
});
|
|
3882
|
+
} catch (_) {
|
|
3883
|
+
range = 'HEAD~1..HEAD';
|
|
3884
|
+
}
|
|
3885
|
+
|
|
3886
|
+
const namesOnly = execSync(`git diff ${range} --name-only 2>/dev/null`, {
|
|
3887
|
+
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
3888
|
+
});
|
|
3889
|
+
const changed = new Set(namesOnly.split('\n').map((l) => l.trim()).filter(Boolean).map((f) => path.resolve(cwd, f)));
|
|
3890
|
+
if (changed.size === 0) return [];
|
|
3891
|
+
|
|
3892
|
+
const timeAgo = execSync('git log -1 --format="%cr" 2>/dev/null', {
|
|
3893
|
+
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
3894
|
+
}).trim();
|
|
3895
|
+
|
|
3896
|
+
const lines = [`## changes (last ${n} commits — ${timeAgo})`, '```'];
|
|
3897
|
+
let hasDelta = false;
|
|
3898
|
+
for (const entry of fileEntries) {
|
|
3899
|
+
if (!changed.has(entry.filePath)) continue;
|
|
3900
|
+
const rel = path.relative(cwd, entry.filePath);
|
|
3901
|
+
let diff = '';
|
|
3902
|
+
try {
|
|
3903
|
+
diff = execSync(`git diff ${range} -- "${rel}" 2>/dev/null`, {
|
|
3904
|
+
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
3905
|
+
});
|
|
3906
|
+
} catch (_) {
|
|
3907
|
+
continue;
|
|
3908
|
+
}
|
|
3909
|
+
const plus = [...diff.matchAll(/^\+.*(?:def\s+|function\s+|class\s+|func\s+)(\w+)/gm)].map((m) => `+${m[1]}`);
|
|
3910
|
+
const mod = [...diff.matchAll(/^-.*(?:def\s+|function\s+|class\s+|func\s+)(\w+)/gm)].map((m) => `~${m[1]}`);
|
|
3911
|
+
const delta = [...new Set([...plus, ...mod])].slice(0, 4).join(' ');
|
|
3912
|
+
if (delta) {
|
|
3913
|
+
hasDelta = true;
|
|
3914
|
+
lines.push(`${rel.padEnd(45)} ${delta}`);
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3917
|
+
lines.push('```', '');
|
|
3918
|
+
return hasDelta ? lines : [];
|
|
3919
|
+
} catch (_) {
|
|
3920
|
+
return [];
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
|
|
3924
|
+
function normalizeRelNoExt(rel) {
|
|
3925
|
+
return rel.replace(/\.[^.]+$/, '').replace(/\\/g, '/');
|
|
3926
|
+
}
|
|
3927
|
+
|
|
3928
|
+
function resolveImpactRadius(fileEntries, cwd, config) {
|
|
3929
|
+
const map = new Map();
|
|
3930
|
+
if (!config || !config.impactRadius) return map;
|
|
3931
|
+
const rels = fileEntries.map((e) => path.relative(cwd, e.filePath));
|
|
3932
|
+
const keys = rels.map((r) => ({ rel: r, norm: normalizeRelNoExt(r), base: path.basename(normalizeRelNoExt(r)) }));
|
|
3933
|
+
|
|
3934
|
+
for (const entry of fileEntries) {
|
|
3935
|
+
const importer = path.relative(cwd, entry.filePath);
|
|
3936
|
+
for (const dep of (entry.deps || [])) {
|
|
3937
|
+
const depNorm = dep.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\.[^.]+$/, '');
|
|
3938
|
+
const matches = keys.filter((k) => k.norm.endsWith(depNorm) || k.base === depNorm || k.norm === depNorm);
|
|
3939
|
+
for (const m of matches) {
|
|
3940
|
+
if (!map.has(m.rel)) map.set(m.rel, []);
|
|
3941
|
+
map.get(m.rel).push(importer);
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3946
|
+
for (const [k, v] of map.entries()) {
|
|
3947
|
+
map.set(k, [...new Set(v)].slice(0, 5));
|
|
3948
|
+
}
|
|
3949
|
+
return map;
|
|
3950
|
+
}
|
|
3951
|
+
|
|
3952
|
+
function formatOutput(fileEntries, cwd, routingEnabled, config, extras) {
|
|
3461
3953
|
const lines = [
|
|
3462
3954
|
'<!-- Generated by SigMap gen-context.js v' + VERSION + ' -->',
|
|
3463
3955
|
'<!-- DO NOT EDIT below the marker line — run gen-context.js to regenerate -->',
|
|
@@ -3466,6 +3958,34 @@ function formatOutput(fileEntries, cwd, routingEnabled) {
|
|
|
3466
3958
|
'',
|
|
3467
3959
|
];
|
|
3468
3960
|
|
|
3961
|
+
// Compact dependency map section (shows import relationships, ~50-100 tokens)
|
|
3962
|
+
const depLines = buildDepMap(fileEntries, cwd);
|
|
3963
|
+
if ((!config || config.depMap !== false) && depLines.length) {
|
|
3964
|
+
lines.push('## deps');
|
|
3965
|
+
lines.push('```');
|
|
3966
|
+
lines.push(...depLines);
|
|
3967
|
+
lines.push('```');
|
|
3968
|
+
lines.push('');
|
|
3969
|
+
}
|
|
3970
|
+
|
|
3971
|
+
const todoLines = buildTodoSection(fileEntries, cwd, config || {});
|
|
3972
|
+
if (todoLines.length) {
|
|
3973
|
+
lines.push('## todos');
|
|
3974
|
+
lines.push('```');
|
|
3975
|
+
lines.push(...todoLines);
|
|
3976
|
+
lines.push('```');
|
|
3977
|
+
lines.push('');
|
|
3978
|
+
}
|
|
3979
|
+
|
|
3980
|
+
const changesLines = buildChangesSection(cwd, config || {}, fileEntries);
|
|
3981
|
+
if (changesLines.length) lines.push(...changesLines);
|
|
3982
|
+
|
|
3983
|
+
if (extras && Array.isArray(extras.diffSection) && extras.diffSection.length) {
|
|
3984
|
+
lines.push(...extras.diffSection);
|
|
3985
|
+
}
|
|
3986
|
+
|
|
3987
|
+
const impactMap = resolveImpactRadius(fileEntries, cwd, config || {});
|
|
3988
|
+
|
|
3469
3989
|
// Group by top-level src dir
|
|
3470
3990
|
const groups = {};
|
|
3471
3991
|
for (const entry of fileEntries) {
|
|
@@ -3481,7 +4001,9 @@ function formatOutput(fileEntries, cwd, routingEnabled) {
|
|
|
3481
4001
|
lines.push('');
|
|
3482
4002
|
for (const { rel, sigs } of entries) {
|
|
3483
4003
|
if (sigs.length === 0) continue;
|
|
3484
|
-
|
|
4004
|
+
const usedBy = impactMap.get(rel);
|
|
4005
|
+
const usedByStr = usedBy && usedBy.length ? ` (used by: ${usedBy.join(', ')})` : '';
|
|
4006
|
+
lines.push(`### ${rel}${usedByStr}`);
|
|
3485
4007
|
lines.push('```');
|
|
3486
4008
|
lines.push(...sigs);
|
|
3487
4009
|
lines.push('```');
|
|
@@ -3491,8 +4013,8 @@ function formatOutput(fileEntries, cwd, routingEnabled) {
|
|
|
3491
4013
|
|
|
3492
4014
|
if (routingEnabled) {
|
|
3493
4015
|
try {
|
|
3494
|
-
const { classifyAll } =
|
|
3495
|
-
const { formatRoutingSection } =
|
|
4016
|
+
const { classifyAll } = requireSourceOrBundled('./src/routing/classifier');
|
|
4017
|
+
const { formatRoutingSection } = requireSourceOrBundled('./src/routing/hints');
|
|
3496
4018
|
const groups = classifyAll(fileEntries, cwd);
|
|
3497
4019
|
lines.push(formatRoutingSection(groups));
|
|
3498
4020
|
} catch (err) {
|
|
@@ -3743,7 +4265,7 @@ function runPerModuleStrategy(cwd, config, fileEntries, inputTokenTotal) {
|
|
|
3743
4265
|
const modBudget = Math.max(1000, Math.floor(config.maxTokens / moduleNames.length));
|
|
3744
4266
|
const budgeted = applyTokenBudget(modEntries, modBudget);
|
|
3745
4267
|
|
|
3746
|
-
const content = formatOutput(budgeted, cwd, false);
|
|
4268
|
+
const content = formatOutput(budgeted, cwd, false, config, null);
|
|
3747
4269
|
ensureDir(outPath);
|
|
3748
4270
|
fs.writeFileSync(outPath, content, 'utf8');
|
|
3749
4271
|
const modTokens = estimateTokens(content);
|
|
@@ -3774,7 +4296,7 @@ function runHotColdStrategy(cwd, config, fileEntries, recentFiles, inputTokenTot
|
|
|
3774
4296
|
|
|
3775
4297
|
// Hot → primary output (auto-injected by IDE)
|
|
3776
4298
|
const hotContent = hotEntries.length > 0
|
|
3777
|
-
? formatOutput(hotEntries, cwd, false)
|
|
4299
|
+
? formatOutput(hotEntries, cwd, false, config, null)
|
|
3778
4300
|
: '<!-- Generated by SigMap — no recently changed files -->\n';
|
|
3779
4301
|
const primaryTargets = (config.outputs || ['copilot']).filter((t) => t !== 'claude');
|
|
3780
4302
|
writeOutputs(hotContent, primaryTargets, cwd);
|
|
@@ -3787,7 +4309,7 @@ function runHotColdStrategy(cwd, config, fileEntries, recentFiles, inputTokenTot
|
|
|
3787
4309
|
'<!-- NOT auto-injected. Retrieve via MCP: read_context({ module: "..." }) -->',
|
|
3788
4310
|
'',
|
|
3789
4311
|
].join('\n');
|
|
3790
|
-
const coldContent = coldHeader + formatOutput(coldEntries, cwd, false);
|
|
4312
|
+
const coldContent = coldHeader + formatOutput(coldEntries, cwd, false, config, null);
|
|
3791
4313
|
ensureDir(coldPath);
|
|
3792
4314
|
fs.writeFileSync(coldPath, coldContent, 'utf8');
|
|
3793
4315
|
const coldTokens = estimateTokens(coldContent);
|
|
@@ -3805,11 +4327,11 @@ function runHotColdStrategy(cwd, config, fileEntries, recentFiles, inputTokenTot
|
|
|
3805
4327
|
// ---------------------------------------------------------------------------
|
|
3806
4328
|
// Diff-mode pipeline — context for changed files only
|
|
3807
4329
|
// ---------------------------------------------------------------------------
|
|
3808
|
-
function runDiff(cwd, config, stagedOnly) {
|
|
3809
|
-
const diffFiles = getDiffFiles(cwd, stagedOnly);
|
|
4330
|
+
function runDiff(cwd, config, stagedOnly, baseRef) {
|
|
4331
|
+
const diffFiles = baseRef ? getFilesChangedSinceBase(cwd, baseRef) : getDiffFiles(cwd, stagedOnly);
|
|
3810
4332
|
|
|
3811
4333
|
if (diffFiles.size === 0) {
|
|
3812
|
-
const scope = stagedOnly ? 'staged' : 'working tree';
|
|
4334
|
+
const scope = baseRef ? `base ${baseRef}` : (stagedOnly ? 'staged' : 'working tree');
|
|
3813
4335
|
console.warn(`[sigmap] --diff: no changed files found in ${scope} — running full generate`);
|
|
3814
4336
|
runGenerate(cwd, config, false);
|
|
3815
4337
|
return;
|
|
@@ -3833,6 +4355,15 @@ function runDiff(cwd, config, stagedOnly) {
|
|
|
3833
4355
|
|
|
3834
4356
|
let inputTokenTotal = 0;
|
|
3835
4357
|
let fileEntries = [];
|
|
4358
|
+
let testIndex = null;
|
|
4359
|
+
if (config.testCoverage) {
|
|
4360
|
+
try {
|
|
4361
|
+
const { buildTestIndex } = requireSourceOrBundled('./src/extractors/coverage');
|
|
4362
|
+
testIndex = buildTestIndex(cwd, config.testDirs);
|
|
4363
|
+
} catch (err) {
|
|
4364
|
+
console.warn(`[sigmap] coverage index failed: ${err.message}`);
|
|
4365
|
+
}
|
|
4366
|
+
}
|
|
3836
4367
|
|
|
3837
4368
|
for (const filePath of diffFiltered) {
|
|
3838
4369
|
let content = '';
|
|
@@ -3848,7 +4379,7 @@ function runDiff(cwd, config, stagedOnly) {
|
|
|
3848
4379
|
inputTokenTotal += estimateTokens(content);
|
|
3849
4380
|
|
|
3850
4381
|
if (config.secretScan) {
|
|
3851
|
-
const { scan } =
|
|
4382
|
+
const { scan } = requireSourceOrBundled('./src/security/scanner');
|
|
3852
4383
|
const result = scan(sigs, filePath);
|
|
3853
4384
|
if (result.redacted) {
|
|
3854
4385
|
console.warn(`[sigmap] secrets redacted in ${path.relative(cwd, filePath)}`);
|
|
@@ -3856,7 +4387,9 @@ function runDiff(cwd, config, stagedOnly) {
|
|
|
3856
4387
|
sigs = result.safe;
|
|
3857
4388
|
}
|
|
3858
4389
|
|
|
3859
|
-
|
|
4390
|
+
sigs = annotateCoverage(sigs, testIndex, !!config.testCoverage);
|
|
4391
|
+
|
|
4392
|
+
fileEntries.push({ filePath, sigs, deps: extractFileDeps(filePath, content, config), content, mtime: Date.now() });
|
|
3860
4393
|
}
|
|
3861
4394
|
|
|
3862
4395
|
if (fileEntries.length === 0) {
|
|
@@ -3866,11 +4399,12 @@ function runDiff(cwd, config, stagedOnly) {
|
|
|
3866
4399
|
}
|
|
3867
4400
|
|
|
3868
4401
|
const routingEnabled = !!(config.routing || process.argv.includes('--routing'));
|
|
3869
|
-
const
|
|
4402
|
+
const diffSection = baseRef ? buildDiffSectionFromBase(cwd, baseRef, fileEntries, config) : [];
|
|
4403
|
+
const content = formatOutput(fileEntries, cwd, routingEnabled, config, { diffSection });
|
|
3870
4404
|
const finalTokens = estimateTokens(content);
|
|
3871
4405
|
writeOutputs(content, config.outputs, cwd);
|
|
3872
4406
|
|
|
3873
|
-
const scope = stagedOnly ? 'staged' : 'diff';
|
|
4407
|
+
const scope = baseRef ? `diff-vs-${baseRef}` : (stagedOnly ? 'staged' : 'diff');
|
|
3874
4408
|
console.warn(`[sigmap] ${scope} files: ${fileEntries.length}, diff tokens: ~${finalTokens}`);
|
|
3875
4409
|
|
|
3876
4410
|
if (process.argv.includes('--report')) {
|
|
@@ -3904,6 +4438,15 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
3904
4438
|
|
|
3905
4439
|
let inputTokenTotal = 0;
|
|
3906
4440
|
let fileEntries = [];
|
|
4441
|
+
let testIndex = null;
|
|
4442
|
+
if (config.testCoverage) {
|
|
4443
|
+
try {
|
|
4444
|
+
const { buildTestIndex } = requireSourceOrBundled('./src/extractors/coverage');
|
|
4445
|
+
testIndex = buildTestIndex(cwd, config.testDirs);
|
|
4446
|
+
} catch (err) {
|
|
4447
|
+
console.warn(`[sigmap] coverage index failed: ${err.message}`);
|
|
4448
|
+
}
|
|
4449
|
+
}
|
|
3907
4450
|
|
|
3908
4451
|
for (const filePath of allFiles) {
|
|
3909
4452
|
let content = '';
|
|
@@ -3920,7 +4463,7 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
3920
4463
|
inputTokenTotal += estimateTokens(content);
|
|
3921
4464
|
|
|
3922
4465
|
if (config.secretScan) {
|
|
3923
|
-
const { scan } =
|
|
4466
|
+
const { scan } = requireSourceOrBundled('./src/security/scanner');
|
|
3924
4467
|
const result = scan(sigs, filePath);
|
|
3925
4468
|
if (result.redacted) {
|
|
3926
4469
|
console.warn(`[sigmap] secrets redacted in ${path.relative(cwd, filePath)}`);
|
|
@@ -3928,6 +4471,8 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
3928
4471
|
sigs = result.safe;
|
|
3929
4472
|
}
|
|
3930
4473
|
|
|
4474
|
+
sigs = annotateCoverage(sigs, testIndex, !!config.testCoverage);
|
|
4475
|
+
|
|
3931
4476
|
let mtime = 0;
|
|
3932
4477
|
try {
|
|
3933
4478
|
mtime = fs.statSync(filePath).mtimeMs;
|
|
@@ -3936,7 +4481,7 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
3936
4481
|
// Boost recently committed files (give them max mtime so they aren't dropped first)
|
|
3937
4482
|
if (recentFiles.has(filePath)) mtime = Date.now();
|
|
3938
4483
|
|
|
3939
|
-
fileEntries.push({ filePath, sigs, mtime });
|
|
4484
|
+
fileEntries.push({ filePath, sigs, deps: extractFileDeps(filePath, content, config), content, mtime });
|
|
3940
4485
|
}
|
|
3941
4486
|
|
|
3942
4487
|
const strategy = config.strategy || 'full';
|
|
@@ -3962,7 +4507,7 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
3962
4507
|
fileEntries = applyTokenBudget(fileEntries, config.maxTokens);
|
|
3963
4508
|
const droppedCount = beforeCount - fileEntries.length;
|
|
3964
4509
|
const routingEnabled = !!(config.routing || process.argv.includes('--routing'));
|
|
3965
|
-
const content = formatOutput(fileEntries, cwd, routingEnabled);
|
|
4510
|
+
const content = formatOutput(fileEntries, cwd, routingEnabled, config, null);
|
|
3966
4511
|
const finalTokens = estimateTokens(content);
|
|
3967
4512
|
const formatIdx = process.argv.indexOf('--format');
|
|
3968
4513
|
const formatValue = formatIdx >= 0 ? process.argv[formatIdx + 1] : (config.format || 'default');
|
|
@@ -3974,7 +4519,7 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
3974
4519
|
// report mode: always run full pipeline for accurate stats
|
|
3975
4520
|
const budgeted = applyTokenBudget([...fileEntries], config.maxTokens);
|
|
3976
4521
|
const droppedCount = beforeCount - budgeted.length;
|
|
3977
|
-
const content = formatOutput(budgeted, cwd, false);
|
|
4522
|
+
const content = formatOutput(budgeted, cwd, false, config, null);
|
|
3978
4523
|
const finalTokens = estimateTokens(content);
|
|
3979
4524
|
result = { inputTokenTotal, finalTokens, fileCount: beforeCount, droppedCount };
|
|
3980
4525
|
}
|
|
@@ -3987,7 +4532,7 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
3987
4532
|
const trackingEnabled = !!(config.tracking || process.argv.includes('--track'));
|
|
3988
4533
|
if (trackingEnabled && !reportMode) {
|
|
3989
4534
|
try {
|
|
3990
|
-
const { logRun } =
|
|
4535
|
+
const { logRun } = requireSourceOrBundled('./src/tracking/logger');
|
|
3991
4536
|
logRun({
|
|
3992
4537
|
version: VERSION,
|
|
3993
4538
|
fileCount: result.fileCount,
|
|
@@ -4009,10 +4554,24 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
4009
4554
|
// Monorepo support
|
|
4010
4555
|
// ---------------------------------------------------------------------------
|
|
4011
4556
|
const MONO_ROOTS = ['packages', 'apps', 'services', 'libs'];
|
|
4012
|
-
const PKG_MANIFESTS = ['package.json', 'pyproject.toml', 'Cargo.toml', 'go.mod', 'build.gradle', 'pom.xml'];
|
|
4557
|
+
const PKG_MANIFESTS = ['package.json', 'pyproject.toml', 'Cargo.toml', 'go.mod', 'build.gradle', 'pom.xml', 'requirements.txt'];
|
|
4013
4558
|
|
|
4014
|
-
function detectMonorepoPackages(cwd) {
|
|
4559
|
+
function detectMonorepoPackages(cwd, config) {
|
|
4015
4560
|
const packages = [];
|
|
4561
|
+
const seen = new Set();
|
|
4562
|
+
|
|
4563
|
+
const addIfPackage = (pkgPath) => {
|
|
4564
|
+
if (seen.has(pkgPath)) return;
|
|
4565
|
+
seen.add(pkgPath);
|
|
4566
|
+
for (const manifest of PKG_MANIFESTS) {
|
|
4567
|
+
if (fs.existsSync(path.join(pkgPath, manifest))) {
|
|
4568
|
+
packages.push(pkgPath);
|
|
4569
|
+
return;
|
|
4570
|
+
}
|
|
4571
|
+
}
|
|
4572
|
+
};
|
|
4573
|
+
|
|
4574
|
+
// Strategy 1: classic JS monorepo roots (packages/, apps/, services/, libs/)
|
|
4016
4575
|
for (const monoDir of MONO_ROOTS) {
|
|
4017
4576
|
const abs = path.join(cwd, monoDir);
|
|
4018
4577
|
if (!fs.existsSync(abs)) continue;
|
|
@@ -4020,20 +4579,25 @@ function detectMonorepoPackages(cwd) {
|
|
|
4020
4579
|
try { entries = fs.readdirSync(abs, { withFileTypes: true }); } catch (_) { continue; }
|
|
4021
4580
|
for (const entry of entries) {
|
|
4022
4581
|
if (!entry.isDirectory()) continue;
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4582
|
+
addIfPackage(path.join(abs, entry.name));
|
|
4583
|
+
}
|
|
4584
|
+
}
|
|
4585
|
+
|
|
4586
|
+
// Strategy 2: treat each srcDir that has a manifest as its own package
|
|
4587
|
+
// (covers server/web/desktop-style layouts)
|
|
4588
|
+
if (packages.length === 0 && config && config.srcDirs) {
|
|
4589
|
+
for (const srcDir of config.srcDirs) {
|
|
4590
|
+
const abs = path.join(cwd, srcDir);
|
|
4591
|
+
if (!fs.existsSync(abs)) continue;
|
|
4592
|
+
addIfPackage(abs);
|
|
4030
4593
|
}
|
|
4031
4594
|
}
|
|
4595
|
+
|
|
4032
4596
|
return packages;
|
|
4033
4597
|
}
|
|
4034
4598
|
|
|
4035
4599
|
function runMonorepo(cwd, config) {
|
|
4036
|
-
const packages = detectMonorepoPackages(cwd);
|
|
4600
|
+
const packages = detectMonorepoPackages(cwd, config);
|
|
4037
4601
|
if (packages.length === 0) {
|
|
4038
4602
|
console.warn('[sigmap] no monorepo packages found — checked packages/, apps/, services/, libs/');
|
|
4039
4603
|
return;
|
|
@@ -4131,6 +4695,7 @@ Usage:
|
|
|
4131
4695
|
node gen-context.js --health Print composite health score
|
|
4132
4696
|
node gen-context.js --health --json Machine-readable health score
|
|
4133
4697
|
node gen-context.js --diff Generate context for git-changed files only
|
|
4698
|
+
node gen-context.js --diff <base-ref> Generate context + structural diff vs base ref (e.g. main)
|
|
4134
4699
|
node gen-context.js --diff --staged Generate context for staged files only
|
|
4135
4700
|
node gen-context.js --init Write example config + .contextignore scaffold
|
|
4136
4701
|
node gen-context.js --help Show this message
|
|
@@ -4305,7 +4870,10 @@ function main() {
|
|
|
4305
4870
|
}
|
|
4306
4871
|
|
|
4307
4872
|
if (args.includes('--diff')) {
|
|
4308
|
-
|
|
4873
|
+
const idx = args.indexOf('--diff');
|
|
4874
|
+
const maybeBase = args[idx + 1];
|
|
4875
|
+
const baseRef = (maybeBase && !maybeBase.startsWith('--')) ? maybeBase : null;
|
|
4876
|
+
runDiff(cwd, config, args.includes('--staged'), baseRef);
|
|
4309
4877
|
process.exit(0);
|
|
4310
4878
|
}
|
|
4311
4879
|
|