sigmap 1.5.2 → 2.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/README.md +2 -2
- package/gen-context.config.json.example +28 -1
- package/gen-context.js +677 -114
- package/package.json +2 -1
- package/src/config/defaults.js +36 -0
- 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 +140 -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 +48 -4
- package/src/extractors/vue.js +16 -2
- package/src/mcp/server.js +1 -1
package/gen-context.js
CHANGED
|
@@ -200,9 +200,11 @@ __factories["./src/extractors/cpp"] = function(module, exports) {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
// Top-level function declarations/definitions (not inside a class)
|
|
203
|
-
for (const m of stripped.matchAll(/^(?!class|struct|if|for|while|switch)[\w:*&<> ]
|
|
204
|
-
if (m[
|
|
205
|
-
|
|
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}`);
|
|
206
208
|
}
|
|
207
209
|
|
|
208
210
|
return sigs.slice(0, 25);
|
|
@@ -221,10 +223,12 @@ __factories["./src/extractors/cpp"] = function(module, exports) {
|
|
|
221
223
|
|
|
222
224
|
function extractMembers(block) {
|
|
223
225
|
const members = [];
|
|
224
|
-
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;
|
|
225
227
|
for (const m of block.matchAll(methodRe)) {
|
|
226
|
-
if (m[
|
|
227
|
-
|
|
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}`);
|
|
228
232
|
}
|
|
229
233
|
return members.slice(0, 8);
|
|
230
234
|
}
|
|
@@ -233,6 +237,11 @@ __factories["./src/extractors/cpp"] = function(module, exports) {
|
|
|
233
237
|
if (!params) return '';
|
|
234
238
|
return params.trim().replace(/\s+/g, ' ');
|
|
235
239
|
}
|
|
240
|
+
|
|
241
|
+
function normalizeType(type) {
|
|
242
|
+
if (!type) return '';
|
|
243
|
+
return type.trim().replace(/\s+/g, ' ').slice(0, 30);
|
|
244
|
+
}
|
|
236
245
|
|
|
237
246
|
module.exports = { extract };
|
|
238
247
|
|
|
@@ -278,13 +287,24 @@ __factories["./src/extractors/csharp"] = function(module, exports) {
|
|
|
278
287
|
|
|
279
288
|
function extractMembers(block) {
|
|
280
289
|
const members = [];
|
|
281
|
-
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;
|
|
282
291
|
for (const m of block.matchAll(methodRe)) {
|
|
283
|
-
const
|
|
284
|
-
|
|
292
|
+
const ret = normalizeType(m[1]);
|
|
293
|
+
const retStr = ret ? ` \u2192 ${ret}` : '';
|
|
294
|
+
members.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
|
|
285
295
|
}
|
|
286
296
|
return members.slice(0, 8);
|
|
287
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
|
+
}
|
|
288
308
|
|
|
289
309
|
module.exports = { extract };
|
|
290
310
|
|
|
@@ -370,9 +390,11 @@ __factories["./src/extractors/dart"] = function(module, exports) {
|
|
|
370
390
|
}
|
|
371
391
|
|
|
372
392
|
// Top-level functions
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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}`);
|
|
376
398
|
}
|
|
377
399
|
|
|
378
400
|
return sigs.slice(0, 25);
|
|
@@ -391,9 +413,10 @@ __factories["./src/extractors/dart"] = function(module, exports) {
|
|
|
391
413
|
|
|
392
414
|
function extractMembers(block) {
|
|
393
415
|
const members = [];
|
|
394
|
-
for (const m of block.matchAll(/^\s+(?:@override\s+)?(?:Future
|
|
395
|
-
if (m[
|
|
396
|
-
|
|
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}`);
|
|
397
420
|
}
|
|
398
421
|
return members.slice(0, 8);
|
|
399
422
|
}
|
|
@@ -489,9 +512,12 @@ __factories["./src/extractors/go"] = function(module, exports) {
|
|
|
489
512
|
}
|
|
490
513
|
|
|
491
514
|
// Functions and methods
|
|
492
|
-
|
|
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)) {
|
|
493
517
|
const receiver = m[1] ? `(${m[1]}) ` : '';
|
|
494
|
-
|
|
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}`);
|
|
495
521
|
}
|
|
496
522
|
|
|
497
523
|
return sigs.slice(0, 25);
|
|
@@ -510,8 +536,10 @@ __factories["./src/extractors/go"] = function(module, exports) {
|
|
|
510
536
|
|
|
511
537
|
function extractInterfaceMethods(block) {
|
|
512
538
|
const methods = [];
|
|
513
|
-
for (const m of block.matchAll(/^\s+(\w+)\s*\(([^)]*)\)/gm)) {
|
|
514
|
-
|
|
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}`);
|
|
515
543
|
}
|
|
516
544
|
return methods.slice(0, 8);
|
|
517
545
|
}
|
|
@@ -609,13 +637,24 @@ __factories["./src/extractors/java"] = function(module, exports) {
|
|
|
609
637
|
|
|
610
638
|
function extractMembers(block) {
|
|
611
639
|
const members = [];
|
|
612
|
-
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;
|
|
613
641
|
for (const m of block.matchAll(methodRe)) {
|
|
614
|
-
const
|
|
615
|
-
|
|
642
|
+
const ret = normalizeType(m[1]);
|
|
643
|
+
const retStr = ret ? ` \u2192 ${ret}` : '';
|
|
644
|
+
members.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
|
|
616
645
|
}
|
|
617
646
|
return members.slice(0, 8);
|
|
618
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
|
+
}
|
|
619
658
|
|
|
620
659
|
module.exports = { extract };
|
|
621
660
|
|
|
@@ -632,6 +671,7 @@ __factories["./src/extractors/javascript"] = function(module, exports) {
|
|
|
632
671
|
function extract(src) {
|
|
633
672
|
if (!src || typeof src !== 'string') return [];
|
|
634
673
|
const sigs = [];
|
|
674
|
+
const returnHints = buildReturnHints(src);
|
|
635
675
|
|
|
636
676
|
const stripped = src
|
|
637
677
|
.replace(/\/\/.*$/gm, '')
|
|
@@ -643,19 +683,21 @@ __factories["./src/extractors/javascript"] = function(module, exports) {
|
|
|
643
683
|
const prefix = m[1] ? m[1].trim() + ' ' : '';
|
|
644
684
|
sigs.push(`${prefix}class ${m[2]}`);
|
|
645
685
|
const block = extractBlock(stripped, m.index + m[0].length);
|
|
646
|
-
for (const meth of extractClassMembers(block)) sigs.push(` ${meth}`);
|
|
686
|
+
for (const meth of extractClassMembers(block, returnHints)) sigs.push(` ${meth}`);
|
|
647
687
|
}
|
|
648
688
|
|
|
649
689
|
// Exported named functions
|
|
650
690
|
for (const m of stripped.matchAll(/^export\s+(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/gm)) {
|
|
651
691
|
const asyncKw = /export\s+async/.test(m[0]) ? 'async ' : '';
|
|
652
|
-
|
|
692
|
+
const retStr = formatReturnHint(returnHints.get(m[1]));
|
|
693
|
+
sigs.push(`export ${asyncKw}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
653
694
|
}
|
|
654
695
|
|
|
655
696
|
// Exported arrow functions
|
|
656
697
|
for (const m of stripped.matchAll(/^export\s+const\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s*=>/gm)) {
|
|
657
698
|
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
658
|
-
|
|
699
|
+
const retStr = formatReturnHint(returnHints.get(m[1]));
|
|
700
|
+
sigs.push(`export const ${m[1]} = ${asyncKw}(${normalizeParams(m[2])}) =>${retStr}`);
|
|
659
701
|
}
|
|
660
702
|
|
|
661
703
|
// module.exports = { ... }
|
|
@@ -668,7 +710,8 @@ __factories["./src/extractors/javascript"] = function(module, exports) {
|
|
|
668
710
|
// Top-level named functions (non-exported)
|
|
669
711
|
for (const m of stripped.matchAll(/^(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/gm)) {
|
|
670
712
|
const asyncKw = m[0].startsWith('async') ? 'async ' : '';
|
|
671
|
-
|
|
713
|
+
const retStr = formatReturnHint(returnHints.get(m[1]));
|
|
714
|
+
sigs.push(`${asyncKw}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
672
715
|
}
|
|
673
716
|
|
|
674
717
|
return sigs.slice(0, 25);
|
|
@@ -686,17 +729,41 @@ __factories["./src/extractors/javascript"] = function(module, exports) {
|
|
|
686
729
|
return src.slice(startIndex, i - 1);
|
|
687
730
|
}
|
|
688
731
|
|
|
689
|
-
function extractClassMembers(block) {
|
|
732
|
+
function extractClassMembers(block, returnHints) {
|
|
690
733
|
const members = [];
|
|
691
734
|
for (const m of block.matchAll(/^\s+(?:static\s+|async\s+|get\s+|set\s+)*(\w+)\s*\(([^)]*)\)\s*\{/gm)) {
|
|
692
735
|
if (/^_/.test(m[1])) continue;
|
|
693
736
|
if (m[1] === 'constructor') { members.push(`constructor(${normalizeParams(m[2])})`); continue; }
|
|
694
737
|
const isAsync = m[0].includes('async ') ? 'async ' : '';
|
|
695
738
|
const isStatic = m[0].includes('static ') ? 'static ' : '';
|
|
696
|
-
|
|
739
|
+
const retStr = formatReturnHint(returnHints.get(m[1]));
|
|
740
|
+
members.push(`${isStatic}${isAsync}${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
697
741
|
}
|
|
698
742
|
return members.slice(0, 8);
|
|
699
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
|
+
}
|
|
700
767
|
|
|
701
768
|
function normalizeParams(params) {
|
|
702
769
|
if (!params) return '';
|
|
@@ -731,9 +798,12 @@ __factories["./src/extractors/kotlin"] = function(module, exports) {
|
|
|
731
798
|
}
|
|
732
799
|
|
|
733
800
|
// Top-level functions
|
|
734
|
-
|
|
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)) {
|
|
735
803
|
const suspend = m[0].includes('suspend') ? 'suspend ' : '';
|
|
736
|
-
|
|
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}`);
|
|
737
807
|
}
|
|
738
808
|
|
|
739
809
|
return sigs.slice(0, 25);
|
|
@@ -752,10 +822,12 @@ __factories["./src/extractors/kotlin"] = function(module, exports) {
|
|
|
752
822
|
|
|
753
823
|
function extractMembers(block) {
|
|
754
824
|
const members = [];
|
|
755
|
-
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)) {
|
|
756
826
|
if (m[1].startsWith('_')) continue;
|
|
757
827
|
const suspend = m[0].includes('suspend') ? 'suspend ' : '';
|
|
758
|
-
|
|
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}`);
|
|
759
831
|
}
|
|
760
832
|
return members.slice(0, 8);
|
|
761
833
|
}
|
|
@@ -801,8 +873,10 @@ __factories["./src/extractors/php"] = function(module, exports) {
|
|
|
801
873
|
}
|
|
802
874
|
|
|
803
875
|
// Top-level functions
|
|
804
|
-
for (const m of stripped.matchAll(/^function\s+(\w+)\s*\(([^)]*)\)
|
|
805
|
-
|
|
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}`);
|
|
806
880
|
}
|
|
807
881
|
|
|
808
882
|
return sigs.slice(0, 25);
|
|
@@ -821,11 +895,13 @@ __factories["./src/extractors/php"] = function(module, exports) {
|
|
|
821
895
|
|
|
822
896
|
function extractMembers(block) {
|
|
823
897
|
const members = [];
|
|
824
|
-
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;
|
|
825
899
|
for (const m of block.matchAll(methodRe)) {
|
|
826
900
|
if (m[1].startsWith('_')) continue;
|
|
827
901
|
const isStatic = m[0].includes('static ') ? 'static ' : '';
|
|
828
|
-
|
|
902
|
+
const ret = normalizeType(m[3]);
|
|
903
|
+
const retStr = ret ? ` \u2192 ${ret}` : '';
|
|
904
|
+
members.push(`${isStatic}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
829
905
|
}
|
|
830
906
|
return members.slice(0, 8);
|
|
831
907
|
}
|
|
@@ -834,6 +910,11 @@ __factories["./src/extractors/php"] = function(module, exports) {
|
|
|
834
910
|
if (!params) return '';
|
|
835
911
|
return params.trim().replace(/\s+/g, ' ');
|
|
836
912
|
}
|
|
913
|
+
|
|
914
|
+
function normalizeType(type) {
|
|
915
|
+
if (!type) return '';
|
|
916
|
+
return type.trim().replace(/[;\s]+$/g, '').replace(/\s+/g, ' ').slice(0, 25);
|
|
917
|
+
}
|
|
837
918
|
|
|
838
919
|
module.exports = { extract };
|
|
839
920
|
|
|
@@ -851,60 +932,152 @@ __factories["./src/extractors/python"] = function(module, exports) {
|
|
|
851
932
|
if (!src || typeof src !== 'string') return [];
|
|
852
933
|
const sigs = [];
|
|
853
934
|
|
|
854
|
-
//
|
|
855
|
-
const
|
|
856
|
-
|
|
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
|
|
857
939
|
.replace(/"""[\s\S]*?"""/g, '')
|
|
858
940
|
.replace(/'''[\s\S]*?'''/g, '');
|
|
859
941
|
|
|
860
942
|
// Classes
|
|
861
943
|
for (const m of stripped.matchAll(/^class\s+(\w+)(?:\s*\(([^)]*)\))?\s*:/gm)) {
|
|
862
|
-
const
|
|
863
|
-
|
|
864
|
-
// Get class body methods
|
|
944
|
+
const className = m[1];
|
|
945
|
+
const baseName = m[2] ? m[2].trim() : '';
|
|
865
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
|
|
866
973
|
const methods = extractClassMethods(stripped, bodyStart);
|
|
867
974
|
for (const meth of methods) sigs.push(` ${meth}`);
|
|
868
975
|
}
|
|
869
976
|
|
|
870
977
|
// Top-level functions
|
|
871
|
-
for (const m of stripped.matchAll(/^(?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)
|
|
872
|
-
if (/^_/.test(m[
|
|
873
|
-
const asyncKw = m[
|
|
874
|
-
const params = normalizeParams(m[
|
|
875
|
-
|
|
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}`);
|
|
876
985
|
}
|
|
877
986
|
|
|
878
987
|
return sigs.slice(0, 25);
|
|
879
988
|
}
|
|
880
989
|
|
|
881
|
-
function extractClassMethods(
|
|
990
|
+
function extractClassMethods(stripped, startIndex) {
|
|
882
991
|
const methods = [];
|
|
883
|
-
|
|
884
|
-
const lines = src.slice(startIndex).split('\n');
|
|
992
|
+
const lines = stripped.slice(startIndex).split('\n');
|
|
885
993
|
for (const line of lines) {
|
|
886
994
|
if (line.trim() === '') continue;
|
|
887
|
-
// End of class body: line with no leading indent that is not blank
|
|
888
995
|
const indent = line.match(/^(\s+)/);
|
|
889
996
|
if (!indent) break;
|
|
890
|
-
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*:/);
|
|
891
998
|
if (m) {
|
|
892
999
|
if (m[1].startsWith('__') && m[1] !== '__init__') continue;
|
|
893
1000
|
if (m[1].startsWith('_') && !m[1].startsWith('__')) continue;
|
|
894
1001
|
const asyncKw = line.trimStart().startsWith('async') ? 'async ' : '';
|
|
895
|
-
const params = normalizeParams(m[2]).replace(/^self,?\s*/, '');
|
|
896
|
-
|
|
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}`);
|
|
897
1006
|
}
|
|
898
1007
|
}
|
|
899
1008
|
return methods.slice(0, 8);
|
|
900
1009
|
}
|
|
901
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
|
+
|
|
902
1064
|
function normalizeParams(params) {
|
|
903
1065
|
if (!params) return '';
|
|
904
1066
|
return params.trim()
|
|
905
1067
|
.split(',')
|
|
906
|
-
.map((p) =>
|
|
907
|
-
|
|
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')
|
|
908
1081
|
.join(', ');
|
|
909
1082
|
}
|
|
910
1083
|
|
|
@@ -937,14 +1110,16 @@ __factories["./src/extractors/ruby"] = function(module, exports) {
|
|
|
937
1110
|
if (m[1].startsWith('_')) continue;
|
|
938
1111
|
const params = m[2] ? `(${normalizeParams(m[2])})` : '';
|
|
939
1112
|
const selfPrefix = m[0].includes('self.') ? 'self.' : '';
|
|
940
|
-
|
|
1113
|
+
const retStr = extractReturnHint(stripped, m.index);
|
|
1114
|
+
sigs.push(` def ${selfPrefix}${m[1]}${params}${retStr}`);
|
|
941
1115
|
}
|
|
942
1116
|
|
|
943
1117
|
// Top-level def
|
|
944
1118
|
for (const m of stripped.matchAll(/^def\s+(\w+)(?:\s*\(([^)]*)\))?/gm)) {
|
|
945
1119
|
if (m[1].startsWith('_')) continue;
|
|
946
1120
|
const params = m[2] ? `(${normalizeParams(m[2])})` : '';
|
|
947
|
-
|
|
1121
|
+
const retStr = extractReturnHint(stripped, m.index);
|
|
1122
|
+
sigs.push(`def ${m[1]}${params}${retStr}`);
|
|
948
1123
|
}
|
|
949
1124
|
|
|
950
1125
|
return sigs.slice(0, 25);
|
|
@@ -954,6 +1129,15 @@ __factories["./src/extractors/ruby"] = function(module, exports) {
|
|
|
954
1129
|
if (!params) return '';
|
|
955
1130
|
return params.trim().replace(/\s+/g, ' ');
|
|
956
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
|
+
}
|
|
957
1141
|
|
|
958
1142
|
module.exports = { extract };
|
|
959
1143
|
|
|
@@ -998,9 +1182,11 @@ __factories["./src/extractors/rust"] = function(module, exports) {
|
|
|
998
1182
|
}
|
|
999
1183
|
|
|
1000
1184
|
// Top-level pub fns
|
|
1001
|
-
|
|
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)) {
|
|
1002
1187
|
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
1003
|
-
|
|
1188
|
+
const retStr = extractReturnType(m[3]);
|
|
1189
|
+
sigs.push(`pub ${asyncKw}fn ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
1004
1190
|
}
|
|
1005
1191
|
|
|
1006
1192
|
return sigs.slice(0, 25);
|
|
@@ -1019,9 +1205,10 @@ __factories["./src/extractors/rust"] = function(module, exports) {
|
|
|
1019
1205
|
|
|
1020
1206
|
function extractMethods(block) {
|
|
1021
1207
|
const methods = [];
|
|
1022
|
-
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)) {
|
|
1023
1209
|
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
1024
|
-
|
|
1210
|
+
const retStr = extractReturnType(m[3]);
|
|
1211
|
+
methods.push(`pub ${asyncKw}fn ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
1025
1212
|
}
|
|
1026
1213
|
return methods.slice(0, 8);
|
|
1027
1214
|
}
|
|
@@ -1031,6 +1218,14 @@ __factories["./src/extractors/rust"] = function(module, exports) {
|
|
|
1031
1218
|
return params.trim().replace(/\s+/g, ' ');
|
|
1032
1219
|
}
|
|
1033
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
|
+
|
|
1034
1229
|
module.exports = { extract };
|
|
1035
1230
|
|
|
1036
1231
|
};
|
|
@@ -1063,10 +1258,12 @@ __factories["./src/extractors/scala"] = function(module, exports) {
|
|
|
1063
1258
|
}
|
|
1064
1259
|
|
|
1065
1260
|
// Top-level defs
|
|
1066
|
-
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)) {
|
|
1067
1262
|
if (m[1].startsWith('_')) continue;
|
|
1068
1263
|
const params = m[2] ? `(${normalizeParams(m[2])})` : '';
|
|
1069
|
-
|
|
1264
|
+
const ret = normalizeType(m[3]);
|
|
1265
|
+
const retStr = ret ? ` \u2192 ${ret}` : '';
|
|
1266
|
+
sigs.push(`def ${m[1]}${params}${retStr}`);
|
|
1070
1267
|
}
|
|
1071
1268
|
|
|
1072
1269
|
return sigs.slice(0, 25);
|
|
@@ -1085,10 +1282,12 @@ __factories["./src/extractors/scala"] = function(module, exports) {
|
|
|
1085
1282
|
|
|
1086
1283
|
function extractMembers(block) {
|
|
1087
1284
|
const members = [];
|
|
1088
|
-
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)) {
|
|
1089
1286
|
if (m[1].startsWith('_')) continue;
|
|
1090
1287
|
const params = m[2] ? `(${normalizeParams(m[2])})` : '';
|
|
1091
|
-
|
|
1288
|
+
const ret = normalizeType(m[3]);
|
|
1289
|
+
const retStr = ret ? ` \u2192 ${ret}` : '';
|
|
1290
|
+
members.push(`def ${m[1]}${params}${retStr}`);
|
|
1092
1291
|
}
|
|
1093
1292
|
return members.slice(0, 8);
|
|
1094
1293
|
}
|
|
@@ -1101,6 +1300,11 @@ __factories["./src/extractors/scala"] = function(module, exports) {
|
|
|
1101
1300
|
.filter(Boolean)
|
|
1102
1301
|
.join(', ');
|
|
1103
1302
|
}
|
|
1303
|
+
|
|
1304
|
+
function normalizeType(type) {
|
|
1305
|
+
if (!type) return '';
|
|
1306
|
+
return type.trim().replace(/\s+/g, ' ').slice(0, 25);
|
|
1307
|
+
}
|
|
1104
1308
|
|
|
1105
1309
|
module.exports = { extract };
|
|
1106
1310
|
|
|
@@ -1179,16 +1383,18 @@ __factories["./src/extractors/svelte"] = function(module, exports) {
|
|
|
1179
1383
|
}
|
|
1180
1384
|
|
|
1181
1385
|
// Exported functions
|
|
1182
|
-
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)) {
|
|
1183
1387
|
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
1184
|
-
|
|
1388
|
+
const retStr = m[3] ? ` \u2192 ${normalizeType(m[3])}` : '';
|
|
1389
|
+
sigs.push(`export ${asyncKw}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
1185
1390
|
}
|
|
1186
1391
|
|
|
1187
1392
|
// Top-level functions
|
|
1188
|
-
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)) {
|
|
1189
1394
|
if (m[1].startsWith('_')) continue;
|
|
1190
1395
|
const asyncKw = m[0].startsWith('async') ? 'async ' : '';
|
|
1191
|
-
|
|
1396
|
+
const retStr = m[3] ? ` \u2192 ${normalizeType(m[3])}` : '';
|
|
1397
|
+
sigs.push(`${asyncKw}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
1192
1398
|
}
|
|
1193
1399
|
|
|
1194
1400
|
// Reactive declarations $:
|
|
@@ -1203,6 +1409,11 @@ __factories["./src/extractors/svelte"] = function(module, exports) {
|
|
|
1203
1409
|
if (!params) return '';
|
|
1204
1410
|
return params.trim().replace(/\s+/g, ' ');
|
|
1205
1411
|
}
|
|
1412
|
+
|
|
1413
|
+
function normalizeType(type) {
|
|
1414
|
+
if (!type) return '';
|
|
1415
|
+
return type.trim().replace(/[;\s]+$/g, '').replace(/\s+/g, ' ').slice(0, 25);
|
|
1416
|
+
}
|
|
1206
1417
|
|
|
1207
1418
|
module.exports = { extract };
|
|
1208
1419
|
|
|
@@ -1233,9 +1444,11 @@ __factories["./src/extractors/swift"] = function(module, exports) {
|
|
|
1233
1444
|
}
|
|
1234
1445
|
|
|
1235
1446
|
// Top-level public functions
|
|
1236
|
-
|
|
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)) {
|
|
1237
1449
|
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
1238
|
-
|
|
1450
|
+
const retStr = extractArrowType(m[3]);
|
|
1451
|
+
sigs.push(`${asyncKw}func ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
1239
1452
|
}
|
|
1240
1453
|
|
|
1241
1454
|
return sigs.slice(0, 25);
|
|
@@ -1254,10 +1467,11 @@ __factories["./src/extractors/swift"] = function(module, exports) {
|
|
|
1254
1467
|
|
|
1255
1468
|
function extractMembers(block) {
|
|
1256
1469
|
const members = [];
|
|
1257
|
-
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)) {
|
|
1258
1471
|
if (m[1].startsWith('_')) continue;
|
|
1259
1472
|
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
1260
|
-
|
|
1473
|
+
const retStr = extractArrowType(m[3]);
|
|
1474
|
+
members.push(`${asyncKw}func ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
1261
1475
|
}
|
|
1262
1476
|
return members.slice(0, 8);
|
|
1263
1477
|
}
|
|
@@ -1271,6 +1485,14 @@ __factories["./src/extractors/swift"] = function(module, exports) {
|
|
|
1271
1485
|
.join(', ');
|
|
1272
1486
|
}
|
|
1273
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
|
+
|
|
1274
1496
|
module.exports = { extract };
|
|
1275
1497
|
|
|
1276
1498
|
};
|
|
@@ -1328,7 +1550,10 @@ __factories["./src/extractors/typescript"] = function(module, exports) {
|
|
|
1328
1550
|
for (const m of stripped.matchAll(/^export\s+(?:async\s+)?function\s+(\w+)\s*(?:<[^(]*>)?\s*\(([^)]*)\)(?:\s*:\s*[^{]+)?\s*\{/gm)) {
|
|
1329
1551
|
const asyncKw = /export\s+async/.test(m[0]) ? 'async ' : '';
|
|
1330
1552
|
const params = normalizeParams(m[2]);
|
|
1331
|
-
|
|
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}`);
|
|
1332
1557
|
}
|
|
1333
1558
|
|
|
1334
1559
|
// Exported arrow functions / const functions
|
|
@@ -1355,9 +1580,11 @@ __factories["./src/extractors/typescript"] = function(module, exports) {
|
|
|
1355
1580
|
|
|
1356
1581
|
function extractInterfaceMembers(block) {
|
|
1357
1582
|
const members = [];
|
|
1358
|
-
for (const m of block.matchAll(/^\s+(readonly\s+)?(\w+)
|
|
1583
|
+
for (const m of block.matchAll(/^\s+(readonly\s+)?(\w+)(\??):\s*([^;]+);/gm)) {
|
|
1359
1584
|
const readonly = m[1] ? 'readonly ' : '';
|
|
1360
|
-
|
|
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}`);
|
|
1361
1588
|
}
|
|
1362
1589
|
for (const m of block.matchAll(/^\s+(\w+)\s*(?:<[^(]*>)?\s*\(([^)]*)\)\s*:/gm)) {
|
|
1363
1590
|
members.push(`${m[1]}(${normalizeParams(m[2])})`);
|
|
@@ -1374,7 +1601,10 @@ __factories["./src/extractors/typescript"] = function(module, exports) {
|
|
|
1374
1601
|
if (m[1] === 'constructor') { members.push(`constructor(${normalizeParams(m[2])})`); continue; }
|
|
1375
1602
|
const isAsync = m[0].includes('async ') ? 'async ' : '';
|
|
1376
1603
|
const isStatic = m[0].includes('static ') ? 'static ' : '';
|
|
1377
|
-
|
|
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}`);
|
|
1378
1608
|
}
|
|
1379
1609
|
return members.slice(0, 8);
|
|
1380
1610
|
}
|
|
@@ -1388,6 +1618,58 @@ __factories["./src/extractors/typescript"] = function(module, exports) {
|
|
|
1388
1618
|
|
|
1389
1619
|
};
|
|
1390
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
|
+
|
|
1391
1673
|
// ── ./src/extractors/vue ──
|
|
1392
1674
|
__factories["./src/extractors/vue"] = function(module, exports) {
|
|
1393
1675
|
|
|
@@ -1425,12 +1707,21 @@ __factories["./src/extractors/vue"] = function(module, exports) {
|
|
|
1425
1707
|
// Methods in options API
|
|
1426
1708
|
const methodsMatch = script.match(/methods\s*:\s*\{([\s\S]*?)\},?\s*(?:computed|watch|mounted|created|data|\})/);
|
|
1427
1709
|
if (methodsMatch) {
|
|
1428
|
-
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)) {
|
|
1429
1711
|
if (m[1].startsWith('_')) continue;
|
|
1430
1712
|
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
1431
|
-
|
|
1713
|
+
const retStr = m[3] ? ` \u2192 ${normalizeType(m[3])}` : '';
|
|
1714
|
+
sigs.push(` ${asyncKw}${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
1432
1715
|
}
|
|
1433
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
|
+
}
|
|
1434
1725
|
|
|
1435
1726
|
// defineProps (Composition API)
|
|
1436
1727
|
const definePropsMatch = script.match(/defineProps(?:<[^>]*>)?\s*\(\s*(\{[\s\S]*?\})\s*\)/);
|
|
@@ -1453,6 +1744,11 @@ __factories["./src/extractors/vue"] = function(module, exports) {
|
|
|
1453
1744
|
if (!params) return '';
|
|
1454
1745
|
return params.trim().replace(/\s+/g, ' ');
|
|
1455
1746
|
}
|
|
1747
|
+
|
|
1748
|
+
function normalizeType(type) {
|
|
1749
|
+
if (!type) return '';
|
|
1750
|
+
return type.trim().replace(/[;\s]+$/g, '').replace(/\s+/g, ' ').slice(0, 25);
|
|
1751
|
+
}
|
|
1456
1752
|
|
|
1457
1753
|
module.exports = { extract };
|
|
1458
1754
|
|
|
@@ -3224,14 +3520,23 @@ const path = require('path');
|
|
|
3224
3520
|
const os = require('os');
|
|
3225
3521
|
const { execSync } = require('child_process');
|
|
3226
3522
|
|
|
3227
|
-
const VERSION = '
|
|
3523
|
+
const VERSION = '2.0.0-beta.2';
|
|
3228
3524
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
3229
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
|
+
|
|
3230
3535
|
// ---------------------------------------------------------------------------
|
|
3231
3536
|
// Config — delegate to src/config/loader.js
|
|
3232
3537
|
// ---------------------------------------------------------------------------
|
|
3233
|
-
const { loadConfig } =
|
|
3234
|
-
const { DEFAULTS } =
|
|
3538
|
+
const { loadConfig } = requireSourceOrBundled('./src/config/loader');
|
|
3539
|
+
const { DEFAULTS } = requireSourceOrBundled('./src/config/defaults');
|
|
3235
3540
|
|
|
3236
3541
|
// ---------------------------------------------------------------------------
|
|
3237
3542
|
// Language → extractor mapping (by file extension)
|
|
@@ -3340,7 +3645,7 @@ const _extractorCache = {};
|
|
|
3340
3645
|
function getExtractor(name) {
|
|
3341
3646
|
if (_extractorCache[name]) return _extractorCache[name];
|
|
3342
3647
|
try {
|
|
3343
|
-
const mod =
|
|
3648
|
+
const mod = requireSourceOrBundled(`./src/extractors/${name}`);
|
|
3344
3649
|
_extractorCache[name] = mod;
|
|
3345
3650
|
return mod;
|
|
3346
3651
|
} catch (err) {
|
|
@@ -3368,6 +3673,35 @@ function detectAndExtract(filePath, content, maxSigsPerFile) {
|
|
|
3368
3673
|
}
|
|
3369
3674
|
}
|
|
3370
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
|
+
|
|
3371
3705
|
// ---------------------------------------------------------------------------
|
|
3372
3706
|
// Token budget enforcement
|
|
3373
3707
|
// ---------------------------------------------------------------------------
|
|
@@ -3459,10 +3793,163 @@ function getDiffFiles(cwd, stagedOnly) {
|
|
|
3459
3793
|
}
|
|
3460
3794
|
}
|
|
3461
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
|
+
|
|
3462
3844
|
// ---------------------------------------------------------------------------
|
|
3463
3845
|
// Output formatter
|
|
3464
3846
|
// ---------------------------------------------------------------------------
|
|
3465
|
-
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) {
|
|
3466
3953
|
const lines = [
|
|
3467
3954
|
'<!-- Generated by SigMap gen-context.js v' + VERSION + ' -->',
|
|
3468
3955
|
'<!-- DO NOT EDIT below the marker line — run gen-context.js to regenerate -->',
|
|
@@ -3471,6 +3958,34 @@ function formatOutput(fileEntries, cwd, routingEnabled) {
|
|
|
3471
3958
|
'',
|
|
3472
3959
|
];
|
|
3473
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
|
+
|
|
3474
3989
|
// Group by top-level src dir
|
|
3475
3990
|
const groups = {};
|
|
3476
3991
|
for (const entry of fileEntries) {
|
|
@@ -3486,7 +4001,9 @@ function formatOutput(fileEntries, cwd, routingEnabled) {
|
|
|
3486
4001
|
lines.push('');
|
|
3487
4002
|
for (const { rel, sigs } of entries) {
|
|
3488
4003
|
if (sigs.length === 0) continue;
|
|
3489
|
-
|
|
4004
|
+
const usedBy = impactMap.get(rel);
|
|
4005
|
+
const usedByStr = usedBy && usedBy.length ? ` (used by: ${usedBy.join(', ')})` : '';
|
|
4006
|
+
lines.push(`### ${rel}${usedByStr}`);
|
|
3490
4007
|
lines.push('```');
|
|
3491
4008
|
lines.push(...sigs);
|
|
3492
4009
|
lines.push('```');
|
|
@@ -3496,8 +4013,8 @@ function formatOutput(fileEntries, cwd, routingEnabled) {
|
|
|
3496
4013
|
|
|
3497
4014
|
if (routingEnabled) {
|
|
3498
4015
|
try {
|
|
3499
|
-
const { classifyAll } =
|
|
3500
|
-
const { formatRoutingSection } =
|
|
4016
|
+
const { classifyAll } = requireSourceOrBundled('./src/routing/classifier');
|
|
4017
|
+
const { formatRoutingSection } = requireSourceOrBundled('./src/routing/hints');
|
|
3501
4018
|
const groups = classifyAll(fileEntries, cwd);
|
|
3502
4019
|
lines.push(formatRoutingSection(groups));
|
|
3503
4020
|
} catch (err) {
|
|
@@ -3748,7 +4265,7 @@ function runPerModuleStrategy(cwd, config, fileEntries, inputTokenTotal) {
|
|
|
3748
4265
|
const modBudget = Math.max(1000, Math.floor(config.maxTokens / moduleNames.length));
|
|
3749
4266
|
const budgeted = applyTokenBudget(modEntries, modBudget);
|
|
3750
4267
|
|
|
3751
|
-
const content = formatOutput(budgeted, cwd, false);
|
|
4268
|
+
const content = formatOutput(budgeted, cwd, false, config, null);
|
|
3752
4269
|
ensureDir(outPath);
|
|
3753
4270
|
fs.writeFileSync(outPath, content, 'utf8');
|
|
3754
4271
|
const modTokens = estimateTokens(content);
|
|
@@ -3779,7 +4296,7 @@ function runHotColdStrategy(cwd, config, fileEntries, recentFiles, inputTokenTot
|
|
|
3779
4296
|
|
|
3780
4297
|
// Hot → primary output (auto-injected by IDE)
|
|
3781
4298
|
const hotContent = hotEntries.length > 0
|
|
3782
|
-
? formatOutput(hotEntries, cwd, false)
|
|
4299
|
+
? formatOutput(hotEntries, cwd, false, config, null)
|
|
3783
4300
|
: '<!-- Generated by SigMap — no recently changed files -->\n';
|
|
3784
4301
|
const primaryTargets = (config.outputs || ['copilot']).filter((t) => t !== 'claude');
|
|
3785
4302
|
writeOutputs(hotContent, primaryTargets, cwd);
|
|
@@ -3792,7 +4309,7 @@ function runHotColdStrategy(cwd, config, fileEntries, recentFiles, inputTokenTot
|
|
|
3792
4309
|
'<!-- NOT auto-injected. Retrieve via MCP: read_context({ module: "..." }) -->',
|
|
3793
4310
|
'',
|
|
3794
4311
|
].join('\n');
|
|
3795
|
-
const coldContent = coldHeader + formatOutput(coldEntries, cwd, false);
|
|
4312
|
+
const coldContent = coldHeader + formatOutput(coldEntries, cwd, false, config, null);
|
|
3796
4313
|
ensureDir(coldPath);
|
|
3797
4314
|
fs.writeFileSync(coldPath, coldContent, 'utf8');
|
|
3798
4315
|
const coldTokens = estimateTokens(coldContent);
|
|
@@ -3810,11 +4327,11 @@ function runHotColdStrategy(cwd, config, fileEntries, recentFiles, inputTokenTot
|
|
|
3810
4327
|
// ---------------------------------------------------------------------------
|
|
3811
4328
|
// Diff-mode pipeline — context for changed files only
|
|
3812
4329
|
// ---------------------------------------------------------------------------
|
|
3813
|
-
function runDiff(cwd, config, stagedOnly) {
|
|
3814
|
-
const diffFiles = getDiffFiles(cwd, stagedOnly);
|
|
4330
|
+
function runDiff(cwd, config, stagedOnly, baseRef) {
|
|
4331
|
+
const diffFiles = baseRef ? getFilesChangedSinceBase(cwd, baseRef) : getDiffFiles(cwd, stagedOnly);
|
|
3815
4332
|
|
|
3816
4333
|
if (diffFiles.size === 0) {
|
|
3817
|
-
const scope = stagedOnly ? 'staged' : 'working tree';
|
|
4334
|
+
const scope = baseRef ? `base ${baseRef}` : (stagedOnly ? 'staged' : 'working tree');
|
|
3818
4335
|
console.warn(`[sigmap] --diff: no changed files found in ${scope} — running full generate`);
|
|
3819
4336
|
runGenerate(cwd, config, false);
|
|
3820
4337
|
return;
|
|
@@ -3838,6 +4355,15 @@ function runDiff(cwd, config, stagedOnly) {
|
|
|
3838
4355
|
|
|
3839
4356
|
let inputTokenTotal = 0;
|
|
3840
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
|
+
}
|
|
3841
4367
|
|
|
3842
4368
|
for (const filePath of diffFiltered) {
|
|
3843
4369
|
let content = '';
|
|
@@ -3853,7 +4379,7 @@ function runDiff(cwd, config, stagedOnly) {
|
|
|
3853
4379
|
inputTokenTotal += estimateTokens(content);
|
|
3854
4380
|
|
|
3855
4381
|
if (config.secretScan) {
|
|
3856
|
-
const { scan } =
|
|
4382
|
+
const { scan } = requireSourceOrBundled('./src/security/scanner');
|
|
3857
4383
|
const result = scan(sigs, filePath);
|
|
3858
4384
|
if (result.redacted) {
|
|
3859
4385
|
console.warn(`[sigmap] secrets redacted in ${path.relative(cwd, filePath)}`);
|
|
@@ -3861,7 +4387,9 @@ function runDiff(cwd, config, stagedOnly) {
|
|
|
3861
4387
|
sigs = result.safe;
|
|
3862
4388
|
}
|
|
3863
4389
|
|
|
3864
|
-
|
|
4390
|
+
sigs = annotateCoverage(sigs, testIndex, !!config.testCoverage);
|
|
4391
|
+
|
|
4392
|
+
fileEntries.push({ filePath, sigs, deps: extractFileDeps(filePath, content, config), content, mtime: Date.now() });
|
|
3865
4393
|
}
|
|
3866
4394
|
|
|
3867
4395
|
if (fileEntries.length === 0) {
|
|
@@ -3871,11 +4399,12 @@ function runDiff(cwd, config, stagedOnly) {
|
|
|
3871
4399
|
}
|
|
3872
4400
|
|
|
3873
4401
|
const routingEnabled = !!(config.routing || process.argv.includes('--routing'));
|
|
3874
|
-
const
|
|
4402
|
+
const diffSection = baseRef ? buildDiffSectionFromBase(cwd, baseRef, fileEntries, config) : [];
|
|
4403
|
+
const content = formatOutput(fileEntries, cwd, routingEnabled, config, { diffSection });
|
|
3875
4404
|
const finalTokens = estimateTokens(content);
|
|
3876
4405
|
writeOutputs(content, config.outputs, cwd);
|
|
3877
4406
|
|
|
3878
|
-
const scope = stagedOnly ? 'staged' : 'diff';
|
|
4407
|
+
const scope = baseRef ? `diff-vs-${baseRef}` : (stagedOnly ? 'staged' : 'diff');
|
|
3879
4408
|
console.warn(`[sigmap] ${scope} files: ${fileEntries.length}, diff tokens: ~${finalTokens}`);
|
|
3880
4409
|
|
|
3881
4410
|
if (process.argv.includes('--report')) {
|
|
@@ -3909,6 +4438,15 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
3909
4438
|
|
|
3910
4439
|
let inputTokenTotal = 0;
|
|
3911
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
|
+
}
|
|
3912
4450
|
|
|
3913
4451
|
for (const filePath of allFiles) {
|
|
3914
4452
|
let content = '';
|
|
@@ -3925,7 +4463,7 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
3925
4463
|
inputTokenTotal += estimateTokens(content);
|
|
3926
4464
|
|
|
3927
4465
|
if (config.secretScan) {
|
|
3928
|
-
const { scan } =
|
|
4466
|
+
const { scan } = requireSourceOrBundled('./src/security/scanner');
|
|
3929
4467
|
const result = scan(sigs, filePath);
|
|
3930
4468
|
if (result.redacted) {
|
|
3931
4469
|
console.warn(`[sigmap] secrets redacted in ${path.relative(cwd, filePath)}`);
|
|
@@ -3933,6 +4471,8 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
3933
4471
|
sigs = result.safe;
|
|
3934
4472
|
}
|
|
3935
4473
|
|
|
4474
|
+
sigs = annotateCoverage(sigs, testIndex, !!config.testCoverage);
|
|
4475
|
+
|
|
3936
4476
|
let mtime = 0;
|
|
3937
4477
|
try {
|
|
3938
4478
|
mtime = fs.statSync(filePath).mtimeMs;
|
|
@@ -3941,7 +4481,7 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
3941
4481
|
// Boost recently committed files (give them max mtime so they aren't dropped first)
|
|
3942
4482
|
if (recentFiles.has(filePath)) mtime = Date.now();
|
|
3943
4483
|
|
|
3944
|
-
fileEntries.push({ filePath, sigs, mtime });
|
|
4484
|
+
fileEntries.push({ filePath, sigs, deps: extractFileDeps(filePath, content, config), content, mtime });
|
|
3945
4485
|
}
|
|
3946
4486
|
|
|
3947
4487
|
const strategy = config.strategy || 'full';
|
|
@@ -3967,7 +4507,7 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
3967
4507
|
fileEntries = applyTokenBudget(fileEntries, config.maxTokens);
|
|
3968
4508
|
const droppedCount = beforeCount - fileEntries.length;
|
|
3969
4509
|
const routingEnabled = !!(config.routing || process.argv.includes('--routing'));
|
|
3970
|
-
const content = formatOutput(fileEntries, cwd, routingEnabled);
|
|
4510
|
+
const content = formatOutput(fileEntries, cwd, routingEnabled, config, null);
|
|
3971
4511
|
const finalTokens = estimateTokens(content);
|
|
3972
4512
|
const formatIdx = process.argv.indexOf('--format');
|
|
3973
4513
|
const formatValue = formatIdx >= 0 ? process.argv[formatIdx + 1] : (config.format || 'default');
|
|
@@ -3979,7 +4519,7 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
3979
4519
|
// report mode: always run full pipeline for accurate stats
|
|
3980
4520
|
const budgeted = applyTokenBudget([...fileEntries], config.maxTokens);
|
|
3981
4521
|
const droppedCount = beforeCount - budgeted.length;
|
|
3982
|
-
const content = formatOutput(budgeted, cwd, false);
|
|
4522
|
+
const content = formatOutput(budgeted, cwd, false, config, null);
|
|
3983
4523
|
const finalTokens = estimateTokens(content);
|
|
3984
4524
|
result = { inputTokenTotal, finalTokens, fileCount: beforeCount, droppedCount };
|
|
3985
4525
|
}
|
|
@@ -3992,7 +4532,7 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
3992
4532
|
const trackingEnabled = !!(config.tracking || process.argv.includes('--track'));
|
|
3993
4533
|
if (trackingEnabled && !reportMode) {
|
|
3994
4534
|
try {
|
|
3995
|
-
const { logRun } =
|
|
4535
|
+
const { logRun } = requireSourceOrBundled('./src/tracking/logger');
|
|
3996
4536
|
logRun({
|
|
3997
4537
|
version: VERSION,
|
|
3998
4538
|
fileCount: result.fileCount,
|
|
@@ -4014,10 +4554,24 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
4014
4554
|
// Monorepo support
|
|
4015
4555
|
// ---------------------------------------------------------------------------
|
|
4016
4556
|
const MONO_ROOTS = ['packages', 'apps', 'services', 'libs'];
|
|
4017
|
-
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'];
|
|
4018
4558
|
|
|
4019
|
-
function detectMonorepoPackages(cwd) {
|
|
4559
|
+
function detectMonorepoPackages(cwd, config) {
|
|
4020
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/)
|
|
4021
4575
|
for (const monoDir of MONO_ROOTS) {
|
|
4022
4576
|
const abs = path.join(cwd, monoDir);
|
|
4023
4577
|
if (!fs.existsSync(abs)) continue;
|
|
@@ -4025,20 +4579,25 @@ function detectMonorepoPackages(cwd) {
|
|
|
4025
4579
|
try { entries = fs.readdirSync(abs, { withFileTypes: true }); } catch (_) { continue; }
|
|
4026
4580
|
for (const entry of entries) {
|
|
4027
4581
|
if (!entry.isDirectory()) continue;
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
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);
|
|
4035
4593
|
}
|
|
4036
4594
|
}
|
|
4595
|
+
|
|
4037
4596
|
return packages;
|
|
4038
4597
|
}
|
|
4039
4598
|
|
|
4040
4599
|
function runMonorepo(cwd, config) {
|
|
4041
|
-
const packages = detectMonorepoPackages(cwd);
|
|
4600
|
+
const packages = detectMonorepoPackages(cwd, config);
|
|
4042
4601
|
if (packages.length === 0) {
|
|
4043
4602
|
console.warn('[sigmap] no monorepo packages found — checked packages/, apps/, services/, libs/');
|
|
4044
4603
|
return;
|
|
@@ -4136,6 +4695,7 @@ Usage:
|
|
|
4136
4695
|
node gen-context.js --health Print composite health score
|
|
4137
4696
|
node gen-context.js --health --json Machine-readable health score
|
|
4138
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)
|
|
4139
4699
|
node gen-context.js --diff --staged Generate context for staged files only
|
|
4140
4700
|
node gen-context.js --init Write example config + .contextignore scaffold
|
|
4141
4701
|
node gen-context.js --help Show this message
|
|
@@ -4310,7 +4870,10 @@ function main() {
|
|
|
4310
4870
|
}
|
|
4311
4871
|
|
|
4312
4872
|
if (args.includes('--diff')) {
|
|
4313
|
-
|
|
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);
|
|
4314
4877
|
process.exit(0);
|
|
4315
4878
|
}
|
|
4316
4879
|
|