sigmap 1.5.2 → 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/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:*&<> ]+\s+(\w+)\s*\(([^)]*)\)\s*(?:const\s*)?\{/gm)) {
204
- if (m[1].startsWith('_')) continue;
205
- sigs.push(`${m[1]}(${normalizeParams(m[2])})`);
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:*&<> ]+\s+(\w+)\s*\(([^)]*)\)\s*(?:const\s*)?(?:override\s*)?(?:=\s*0\s*)?;/gm;
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[1].startsWith('_')) continue;
227
- members.push(`${m[1]}(${normalizeParams(m[2])})`);
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<>\[\]?]+\s+)+(\w+)\s*\(([^)]*)\)/gm;
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 sig = m[0].trim().split('{')[0].trim();
284
- members.push(sig);
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
- for (const m of stripped.matchAll(/^(?:Future|void|[\w<>?]+)\s+(\w+)\s*\(([^)]*)\)/gm)) {
374
- if (m[1].startsWith('_')) continue;
375
- sigs.push(`${m[1]}(${normalizeParams(m[2])})`);
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|void|[\w<>?]+)\s+(\w+)\s*\(([^)]*)\)/gm)) {
395
- if (m[1].startsWith('_')) continue;
396
- members.push(`${m[1]}(${normalizeParams(m[2])})`);
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
- for (const m of stripped.matchAll(/^func\s+(?:\((\w+)\s+[\w*]+\)\s+)?(\w+)\s*\(([^)]*)\)(?:\s*[\w*()\[\],\s]+)?\s*\{/gm)) {
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
- sigs.push(`func ${receiver}${m[2]}(${normalizeParams(m[3])})`);
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
- methods.push(`${m[1]}(${normalizeParams(m[2])})`);
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<>\[\]]+\s+)+(\w+)\s*\(([^)]*)\)/gm;
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 sig = m[0].trim().split('{')[0].trim();
615
- members.push(sig);
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
- sigs.push(`export ${asyncKw}function ${m[1]}(${normalizeParams(m[2])})`);
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
- sigs.push(`export const ${m[1]} = ${asyncKw}(${normalizeParams(m[2])}) =>`);
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
- sigs.push(`${asyncKw}function ${m[1]}(${normalizeParams(m[2])})`);
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
- members.push(`${isStatic}${isAsync}${m[1]}(${normalizeParams(m[2])})`);
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
- for (const m of stripped.matchAll(/^(?:public\s+|internal\s+)?(?:suspend\s+)?fun\s+(\w+)\s*(?:<[^(]*>)?\s*\(([^)]*)\)/gm)) {
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
- sigs.push(`${suspend}fun ${m[1]}(${normalizeParams(m[2])})`);
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*\(([^)]*)\)/gm)) {
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
- members.push(`${suspend}fun ${m[1]}(${normalizeParams(m[2])})`);
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*\(([^)]*)\)/gm)) {
805
- sigs.push(`function ${m[1]}(${normalizeParams(m[2])})`);
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*\(([^)]*)\)/gm;
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
- members.push(`${isStatic}function ${m[1]}(${normalizeParams(m[2])})`);
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
- // Strip comments and docstrings (simple approach)
855
- const stripped = src
856
- .replace(/#.*$/gm, '')
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 base = m[2] ? `(${m[2].trim()})` : '';
863
- sigs.push(`class ${m[1]}${base}`);
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*\(([^)]*)\)/gm)) {
872
- if (/^_/.test(m[1])) continue; // skip private
873
- const asyncKw = m[0].trimStart().startsWith('async') ? 'async ' : '';
874
- const params = normalizeParams(m[2]);
875
- sigs.push(`${asyncKw}def ${m[1]}(${params})`);
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(src, startIndex) {
990
+ function extractClassMethods(stripped, startIndex) {
882
991
  const methods = [];
883
- // Extract indented block
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
- methods.push(`${asyncKw}def ${m[1]}(${params})`);
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) => p.trim().split(':')[0].split('=')[0].trim())
907
- .filter(Boolean)
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
- sigs.push(` def ${selfPrefix}${m[1]}${params}`);
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
- sigs.push(`def ${m[1]}${params}`);
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
- for (const m of stripped.matchAll(/^pub(?:\s+async)?\s+fn\s+(\w+)(?:<[^(]*>)?\s*\(([^)]*)\)/gm)) {
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
- sigs.push(`pub ${asyncKw}fn ${m[1]}(${normalizeParams(m[2])})`);
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
- methods.push(`pub ${asyncKw}fn ${m[1]}(${normalizeParams(m[2])})`);
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
- sigs.push(`def ${m[1]}${params}`);
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
- members.push(`def ${m[1]}${params}`);
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*\(([^)]*)\)/gm)) {
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
- sigs.push(`export ${asyncKw}function ${m[1]}(${normalizeParams(m[2])})`);
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*\(([^)]*)\)/gm)) {
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
- sigs.push(`${asyncKw}function ${m[1]}(${normalizeParams(m[2])})`);
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
- for (const m of stripped.matchAll(/^(?:public\s+|internal\s+)?(?:static\s+)?(?:async\s+)?func\s+(\w+)(?:<[^(]*>)?\s*\(([^)]*)\)/gm)) {
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
- sigs.push(`${asyncKw}func ${m[1]}(${normalizeParams(m[2])})`);
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
- members.push(`${asyncKw}func ${m[1]}(${normalizeParams(m[2])})`);
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
- sigs.push(`export ${asyncKw}function ${m[1]}(${params})`);
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+)\??:\s*[^;]+;/gm)) {
1583
+ for (const m of block.matchAll(/^\s+(readonly\s+)?(\w+)(\??):\s*([^;]+);/gm)) {
1359
1584
  const readonly = m[1] ? 'readonly ' : '';
1360
- members.push(`${readonly}${m[2]}`);
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
- members.push(`${isStatic}${isAsync}${m[1]}(${normalizeParams(m[2])})`);
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*\(([^)]*)\)/gm)) {
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
- sigs.push(` ${asyncKw}${m[1]}(${normalizeParams(m[2])})`);
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 = '1.5.2';
3523
+ const VERSION = '2.0.0-beta.1';
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 } = __require('./src/config/loader');
3234
- const { DEFAULTS } = __require('./src/config/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 = __require(`./src/extractors/${name}`);
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 formatOutput(fileEntries, cwd, routingEnabled) {
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
- lines.push(`### ${rel}`);
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 } = __require('./src/routing/classifier');
3500
- const { formatRoutingSection } = __require('./src/routing/hints');
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 } = __require('./src/security/scanner');
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
- fileEntries.push({ filePath, sigs, mtime: Date.now() });
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 content = formatOutput(fileEntries, cwd, routingEnabled);
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 } = __require('./src/security/scanner');
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 } = __require('./src/tracking/logger');
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
- const pkgPath = path.join(abs, entry.name);
4029
- for (const manifest of PKG_MANIFESTS) {
4030
- if (fs.existsSync(path.join(pkgPath, manifest))) {
4031
- packages.push(pkgPath);
4032
- break;
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
- runDiff(cwd, config, args.includes('--staged'));
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