sigmap 1.5.1 → 2.0.0-beta.1

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