sigmap 8.17.0 → 8.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/gen-context.js CHANGED
@@ -1533,6 +1533,8 @@ __factories["./src/config/defaults"] = function(module, exports) {
1533
1533
  recencyBoost: 1.5,
1534
1534
  // Boost files call-graph-connected to query matches (opt-in, measure-gated)
1535
1535
  callGraphBoost: false,
1536
+ // Append route pseudo-signatures to the rankable index (opt-in, measure-gated)
1537
+ surfaceEnrichment: false,
1536
1538
  },
1537
1539
 
1538
1540
  // Impact layer settings (v2.5)
@@ -5377,8 +5379,12 @@ __factories["./src/extractors/css"] = function(module, exports) {
5377
5379
  // ── ./src/extractors/dart ──
5378
5380
  __factories["./src/extractors/dart"] = function(module, exports) {
5379
5381
 
5382
+ const { lineAt, withAnchor } = __require('./src/extractors/line-anchor');
5383
+
5380
5384
  /**
5381
5385
  * Extract signatures from Dart source code.
5386
+ * Signatures carry `:start-end` line anchors (Surgical Context); the comment
5387
+ * strip below is newline-preserving so anchor lines match the original file.
5382
5388
  * @param {string} src - Raw file content
5383
5389
  * @returns {string[]} Array of signature strings
5384
5390
  */
@@ -5388,21 +5394,37 @@ __factories["./src/extractors/dart"] = function(module, exports) {
5388
5394
 
5389
5395
  const stripped = src
5390
5396
  .replace(/\/\/.*$/gm, '')
5391
- .replace(/\/\*[\s\S]*?\*\//g, '');
5397
+ .replace(/\/\*[\s\S]*?\*\//g, (s) => s.replace(/[^\n]/g, ' '));
5398
+
5399
+ // Anchor range: scan past same-line trivia (`async`, `=>` stops) to a body `{`.
5400
+ const rangeFor = (declIdx, afterIdx) => {
5401
+ let k = afterIdx;
5402
+ while (k < stripped.length && /[ \tA-Za-z0-9_]/.test(stripped[k])) k++;
5403
+ if (stripped[k] === '{') {
5404
+ const end = k + 1 + extractBlock(stripped, k + 1).length;
5405
+ return [lineAt(stripped, declIdx), lineAt(stripped, end)];
5406
+ }
5407
+ const line = lineAt(stripped, declIdx);
5408
+ return [line, line];
5409
+ };
5392
5410
 
5393
5411
  // Classes and abstract classes
5394
5412
  for (const m of stripped.matchAll(/^(?:abstract\s+)?class\s+(\w+)(?:<[^{]*>)?(?:\s+extends\s+[\w<>, ]+)?(?:\s+(?:implements|with|on)\s+[\w<>, ]+)?\s*\{/gm)) {
5395
5413
  const abs = m[0].trimStart().startsWith('abstract') ? 'abstract ' : '';
5396
- sigs.push(`${abs}class ${m[1]}`);
5397
- const block = extractBlock(stripped, m.index + m[0].length);
5398
- for (const meth of extractMembers(block)) sigs.push(` ${meth}`);
5414
+ const bodyStart = m.index + m[0].length;
5415
+ const block = extractBlock(stripped, bodyStart);
5416
+ sigs.push(withAnchor(`${abs}class ${m[1]}`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
5417
+ for (const meth of extractMembers(block)) {
5418
+ sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + meth.declIdx), lineAt(stripped, bodyStart + meth.endIdx)));
5419
+ }
5399
5420
  }
5400
5421
 
5401
5422
  // Top-level functions — capture return type (prefix before name) and show as suffix
5402
5423
  for (const m of stripped.matchAll(/^((?:Future<[\w<>?,\s]*>|[\w<>?]+))\s+(\w+)\s*\(([^)]*)\)/gm)) {
5403
5424
  if (m[2].startsWith('_')) continue;
5404
- const retStr = (m[1] && m[1] !== 'void') ? ` \u2192 ${m[1].replace(/\s+/g, '').slice(0, 25)}` : '';
5405
- sigs.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
5425
+ const retStr = (m[1] && m[1] !== 'void') ? ` ${m[1].replace(/\s+/g, '').slice(0, 25)}` : '';
5426
+ const [s, e] = rangeFor(m.index, m.index + m[0].length);
5427
+ sigs.push(withAnchor(`${m[2]}(${normalizeParams(m[3])})${retStr}`, s, e));
5406
5428
  }
5407
5429
 
5408
5430
  return sigs.slice(0, 25);
@@ -5423,8 +5445,12 @@ __factories["./src/extractors/dart"] = function(module, exports) {
5423
5445
  const members = [];
5424
5446
  for (const m of block.matchAll(/^\s+(?:@override\s+)?(?:@\w+\s+)*((?:Future<[\w<>?,\s]*>|[\w<>?]+))\s+(\w+)\s*\(([^)]*)\)/gm)) {
5425
5447
  if (m[2].startsWith('_')) continue;
5426
- const retStr = (m[1] && m[1] !== 'void') ? ` \u2192 ${m[1].replace(/\s+/g, '').slice(0, 25)}` : '';
5427
- members.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
5448
+ const retStr = (m[1] && m[1] !== 'void') ? ` ${m[1].replace(/\s+/g, '').slice(0, 25)}` : '';
5449
+ members.push({
5450
+ text: `${m[2]}(${normalizeParams(m[3])})${retStr}`,
5451
+ declIdx: m.index + (m[0].length - m[0].trimStart().length),
5452
+ endIdx: m.index + m[0].length,
5453
+ });
5428
5454
  }
5429
5455
  return members.slice(0, 8);
5430
5456
  }
@@ -6314,8 +6340,12 @@ __factories["./src/extractors/javascript"] = function(module, exports) {
6314
6340
  // ── ./src/extractors/kotlin ──
6315
6341
  __factories["./src/extractors/kotlin"] = function(module, exports) {
6316
6342
 
6343
+ const { lineAt, withAnchor } = __require('./src/extractors/line-anchor');
6344
+
6317
6345
  /**
6318
6346
  * Extract signatures from Kotlin source code.
6347
+ * Signatures carry `:start-end` line anchors (Surgical Context); the comment
6348
+ * strip below is newline-preserving so anchor lines match the original file.
6319
6349
  * @param {string} src - Raw file content
6320
6350
  * @returns {string[]} Array of signature strings
6321
6351
  */
@@ -6325,21 +6355,37 @@ __factories["./src/extractors/kotlin"] = function(module, exports) {
6325
6355
 
6326
6356
  const stripped = src
6327
6357
  .replace(/\/\/.*$/gm, '')
6328
- .replace(/\/\*[\s\S]*?\*\//g, '');
6358
+ .replace(/\/\*[\s\S]*?\*\//g, (s) => s.replace(/[^\n]/g, ' '));
6359
+
6360
+ // Anchor range: scan past same-line modifiers to a body `{` (range) else single line.
6361
+ const rangeFor = (declIdx, afterIdx) => {
6362
+ let k = afterIdx;
6363
+ while (k < stripped.length && /[ \tA-Za-z0-9_]/.test(stripped[k])) k++;
6364
+ if (stripped[k] === '{') {
6365
+ const end = k + 1 + extractBlock(stripped, k + 1).length;
6366
+ return [lineAt(stripped, declIdx), lineAt(stripped, end)];
6367
+ }
6368
+ const line = lineAt(stripped, declIdx);
6369
+ return [line, line];
6370
+ };
6329
6371
 
6330
6372
  // Classes, objects, interfaces
6331
6373
  for (const m of stripped.matchAll(/^(?:public\s+|internal\s+)?(?:data\s+|sealed\s+|abstract\s+|open\s+)?(class|object|interface)\s+(\w+)(?:[^{]*)\{/gm)) {
6332
- sigs.push(`${m[1]} ${m[2]}`);
6333
- const block = extractBlock(stripped, m.index + m[0].length);
6334
- for (const meth of extractMembers(block)) sigs.push(` ${meth}`);
6374
+ const bodyStart = m.index + m[0].length;
6375
+ const block = extractBlock(stripped, bodyStart);
6376
+ sigs.push(withAnchor(`${m[1]} ${m[2]}`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
6377
+ for (const meth of extractMembers(block)) {
6378
+ sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + meth.declIdx), lineAt(stripped, bodyStart + meth.endIdx)));
6379
+ }
6335
6380
  }
6336
6381
 
6337
6382
  // Top-level functions — capture `: RetType` after params
6338
6383
  for (const m of stripped.matchAll(/^(?:public\s+|internal\s+)?(?:suspend\s+)?fun\s+(\w+)\s*(?:<[^(]*>)?\s*\(([^)]*)\)(?:\s*:\s*([^\n{=]+))?/gm)) {
6339
6384
  const suspend = m[0].includes('suspend') ? 'suspend ' : '';
6340
6385
  const retType = m[3] ? m[3].trim().replace(/\s+/g, ' ') : '';
6341
- const retStr = retType ? ` \u2192 ${retType.slice(0, 25)}` : '';
6342
- sigs.push(`${suspend}fun ${m[1]}(${normalizeParams(m[2])})${retStr}`);
6386
+ const retStr = retType ? ` ${retType.slice(0, 25)}` : '';
6387
+ const [s, e] = rangeFor(m.index, m.index + m[0].length);
6388
+ sigs.push(withAnchor(`${suspend}fun ${m[1]}(${normalizeParams(m[2])})${retStr}`, s, e));
6343
6389
  }
6344
6390
 
6345
6391
  return sigs.slice(0, 25);
@@ -6362,8 +6408,12 @@ __factories["./src/extractors/kotlin"] = function(module, exports) {
6362
6408
  if (m[1].startsWith('_')) continue;
6363
6409
  const suspend = m[0].includes('suspend') ? 'suspend ' : '';
6364
6410
  const retType = m[3] ? m[3].trim().replace(/\s+/g, ' ') : '';
6365
- const retStr = retType ? ` \u2192 ${retType.slice(0, 25)}` : '';
6366
- members.push(`${suspend}fun ${m[1]}(${normalizeParams(m[2])})${retStr}`);
6411
+ const retStr = retType ? ` ${retType.slice(0, 25)}` : '';
6412
+ members.push({
6413
+ text: `${suspend}fun ${m[1]}(${normalizeParams(m[2])})${retStr}`,
6414
+ declIdx: m.index + (m[0].length - m[0].trimStart().length),
6415
+ endIdx: m.index + m[0].length,
6416
+ });
6367
6417
  }
6368
6418
  return members.slice(0, 8);
6369
6419
  }
@@ -6613,8 +6663,12 @@ __factories["./src/extractors/patterns"] = function(module, exports) {
6613
6663
  // ── ./src/extractors/php ──
6614
6664
  __factories["./src/extractors/php"] = function(module, exports) {
6615
6665
 
6666
+ const { lineAt, withAnchor } = __require('./src/extractors/line-anchor');
6667
+
6616
6668
  /**
6617
6669
  * Extract signatures from PHP source code.
6670
+ * Signatures carry `:start-end` line anchors (Surgical Context); the comment
6671
+ * strips below are newline-preserving so anchor lines match the original file.
6618
6672
  * @param {string} src - Raw file content
6619
6673
  * @returns {string[]} Array of signature strings
6620
6674
  */
@@ -6625,23 +6679,40 @@ __factories["./src/extractors/php"] = function(module, exports) {
6625
6679
  const stripped = src
6626
6680
  .replace(/\/\/.*$/gm, '')
6627
6681
  .replace(/#.*$/gm, '')
6628
- .replace(/\/\*[\s\S]*?\*\//g, '');
6682
+ .replace(/\/\*[\s\S]*?\*\//g, (s) => s.replace(/[^\n]/g, ' '));
6683
+
6684
+ // Anchor range: scan past same-line trivia to a body `{` (range) else single line.
6685
+ const rangeFor = (declIdx, afterIdx) => {
6686
+ let k = afterIdx;
6687
+ while (k < stripped.length && /[ \tA-Za-z0-9_:?\\]/.test(stripped[k])) k++;
6688
+ if (stripped[k] === '{' || (stripped[k] === '\n' && stripped[k + 1] === '{')) {
6689
+ const open = stripped[k] === '{' ? k : k + 1;
6690
+ const end = open + 1 + extractBlock(stripped, open + 1).length;
6691
+ return [lineAt(stripped, declIdx), lineAt(stripped, end)];
6692
+ }
6693
+ const line = lineAt(stripped, declIdx);
6694
+ return [line, line];
6695
+ };
6629
6696
 
6630
6697
  // Classes and interfaces
6631
6698
  const typeRe = /^(?:abstract\s+)?(?:class|interface|trait)\s+(\w+)(?:\s+extends\s+\w+)?(?:\s+implements\s+[\w, ]+)?\s*\{/gm;
6632
6699
  for (const m of stripped.matchAll(typeRe)) {
6633
6700
  const kind = m[0].trimStart().startsWith('interface') ? 'interface' :
6634
6701
  m[0].trimStart().startsWith('trait') ? 'trait' : 'class';
6635
- sigs.push(`${kind} ${m[1]}`);
6636
- const block = extractBlock(stripped, m.index + m[0].length);
6637
- for (const meth of extractMembers(block)) sigs.push(` ${meth}`);
6702
+ const bodyStart = m.index + m[0].length;
6703
+ const block = extractBlock(stripped, bodyStart);
6704
+ sigs.push(withAnchor(`${kind} ${m[1]}`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
6705
+ for (const meth of extractMembers(block)) {
6706
+ sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + meth.declIdx), lineAt(stripped, bodyStart + meth.endIdx)));
6707
+ }
6638
6708
  }
6639
6709
 
6640
6710
  // Top-level functions
6641
6711
  for (const m of stripped.matchAll(/^function\s+(\w+)\s*\(([^)]*)\)\s*(?::\s*([^\n{]+))?/gm)) {
6642
6712
  const ret = normalizeType(m[3]);
6643
6713
  const retStr = ret ? ` → ${ret}` : '';
6644
- sigs.push(`function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
6714
+ const [s, e] = rangeFor(m.index, m.index + m[0].length);
6715
+ sigs.push(withAnchor(`function ${m[1]}(${normalizeParams(m[2])})${retStr}`, s, e));
6645
6716
  }
6646
6717
 
6647
6718
  return sigs.slice(0, 25);
@@ -6666,7 +6737,11 @@ __factories["./src/extractors/php"] = function(module, exports) {
6666
6737
  const isStatic = m[0].includes('static ') ? 'static ' : '';
6667
6738
  const ret = normalizeType(m[3]);
6668
6739
  const retStr = ret ? ` → ${ret}` : '';
6669
- members.push(`${isStatic}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
6740
+ members.push({
6741
+ text: `${isStatic}function ${m[1]}(${normalizeParams(m[2])})${retStr}`,
6742
+ declIdx: m.index + (m[0].length - m[0].trimStart().length),
6743
+ endIdx: m.index + m[0].length,
6744
+ });
6670
6745
  }
6671
6746
  return members.slice(0, 8);
6672
6747
  }
@@ -7666,8 +7741,12 @@ __factories["./src/extractors/rust"] = function(module, exports) {
7666
7741
  // ── ./src/extractors/scala ──
7667
7742
  __factories["./src/extractors/scala"] = function(module, exports) {
7668
7743
 
7744
+ const { lineAt, withAnchor } = __require('./src/extractors/line-anchor');
7745
+
7669
7746
  /**
7670
7747
  * Extract signatures from Scala source code.
7748
+ * Signatures carry `:start-end` line anchors (Surgical Context); the comment
7749
+ * strip below is newline-preserving so anchor lines match the original file.
7671
7750
  * @param {string} src - Raw file content
7672
7751
  * @returns {string[]} Array of signature strings
7673
7752
  */
@@ -7677,7 +7756,7 @@ __factories["./src/extractors/scala"] = function(module, exports) {
7677
7756
 
7678
7757
  const stripped = src
7679
7758
  .replace(/\/\/.*$/gm, '')
7680
- .replace(/\/\*[\s\S]*?\*\//g, '');
7759
+ .replace(/\/\*[\s\S]*?\*\//g, (s) => s.replace(/[^\n]/g, ' '));
7681
7760
 
7682
7761
  // Classes, traits, objects
7683
7762
  const typeRe = /^(?:case\s+)?(?:class|trait|object)\s+(\w+)(?:\[[\w, ]+\])?(?:[^{]*)\{/gm;
@@ -7685,9 +7764,12 @@ __factories["./src/extractors/scala"] = function(module, exports) {
7685
7764
  const kind = m[0].trimStart().startsWith('case class') ? 'case class' :
7686
7765
  m[0].trimStart().startsWith('trait') ? 'trait' :
7687
7766
  m[0].trimStart().startsWith('object') ? 'object' : 'class';
7688
- sigs.push(`${kind} ${m[1]}`);
7689
- const block = extractBlock(stripped, m.index + m[0].length);
7690
- for (const fn of extractMembers(block)) sigs.push(` ${fn}`);
7767
+ const bodyStart = m.index + m[0].length;
7768
+ const block = extractBlock(stripped, bodyStart);
7769
+ sigs.push(withAnchor(`${kind} ${m[1]}`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
7770
+ for (const fn of extractMembers(block)) {
7771
+ sigs.push(withAnchor(` ${fn.text}`, lineAt(stripped, bodyStart + fn.declIdx), lineAt(stripped, bodyStart + fn.endIdx)));
7772
+ }
7691
7773
  }
7692
7774
 
7693
7775
  // Top-level defs
@@ -7696,7 +7778,8 @@ __factories["./src/extractors/scala"] = function(module, exports) {
7696
7778
  const params = m[2] ? `(${normalizeParams(m[2])})` : '';
7697
7779
  const ret = normalizeType(m[3]);
7698
7780
  const retStr = ret ? ` → ${ret}` : '';
7699
- sigs.push(`def ${m[1]}${params}${retStr}`);
7781
+ const line = lineAt(stripped, m.index);
7782
+ sigs.push(withAnchor(`def ${m[1]}${params}${retStr}`, line, line));
7700
7783
  }
7701
7784
 
7702
7785
  return sigs.slice(0, 25);
@@ -7720,7 +7803,11 @@ __factories["./src/extractors/scala"] = function(module, exports) {
7720
7803
  const params = m[2] ? `(${normalizeParams(m[2])})` : '';
7721
7804
  const ret = normalizeType(m[3]);
7722
7805
  const retStr = ret ? ` → ${ret}` : '';
7723
- members.push(`def ${m[1]}${params}${retStr}`);
7806
+ members.push({
7807
+ text: `def ${m[1]}${params}${retStr}`,
7808
+ declIdx: m.index + (m[0].length - m[0].trimStart().length),
7809
+ endIdx: m.index + m[0].length,
7810
+ });
7724
7811
  }
7725
7812
  return members.slice(0, 8);
7726
7813
  }
@@ -7952,8 +8039,12 @@ __factories["./src/extractors/svelte"] = function(module, exports) {
7952
8039
  // ── ./src/extractors/swift ──
7953
8040
  __factories["./src/extractors/swift"] = function(module, exports) {
7954
8041
 
8042
+ const { lineAt, withAnchor } = __require('./src/extractors/line-anchor');
8043
+
7955
8044
  /**
7956
8045
  * Extract signatures from Swift source code.
8046
+ * Signatures carry `:start-end` line anchors (Surgical Context); the comment
8047
+ * strip below is newline-preserving so anchor lines match the original file.
7957
8048
  * @param {string} src - Raw file content
7958
8049
  * @returns {string[]} Array of signature strings
7959
8050
  */
@@ -7963,21 +8054,37 @@ __factories["./src/extractors/swift"] = function(module, exports) {
7963
8054
 
7964
8055
  const stripped = src
7965
8056
  .replace(/\/\/.*$/gm, '')
7966
- .replace(/\/\*[\s\S]*?\*\//g, '');
8057
+ .replace(/\/\*[\s\S]*?\*\//g, (s) => s.replace(/[^\n]/g, ' '));
8058
+
8059
+ // Anchor range: scan past same-line modifiers to a body `{` (range) else single line.
8060
+ const rangeFor = (declIdx, afterIdx) => {
8061
+ let k = afterIdx;
8062
+ while (k < stripped.length && /[ \tA-Za-z0-9_>-]/.test(stripped[k])) k++;
8063
+ if (stripped[k] === '{') {
8064
+ const end = k + 1 + extractBlock(stripped, k + 1).length;
8065
+ return [lineAt(stripped, declIdx), lineAt(stripped, end)];
8066
+ }
8067
+ const line = lineAt(stripped, declIdx);
8068
+ return [line, line];
8069
+ };
7967
8070
 
7968
8071
  // Classes, structs, protocols, enums
7969
8072
  const typeRe = /^(?:public\s+|internal\s+|open\s+)?(?:final\s+)?(class|struct|protocol|enum|actor)\s+(\w+)(?:<[^{]*>)?(?:\s*:\s*[\w, <>.]+)?\s*\{/gm;
7970
8073
  for (const m of stripped.matchAll(typeRe)) {
7971
- sigs.push(`${m[1]} ${m[2]}`);
7972
- const block = extractBlock(stripped, m.index + m[0].length);
7973
- for (const fn of extractMembers(block)) sigs.push(` ${fn}`);
8074
+ const bodyStart = m.index + m[0].length;
8075
+ const block = extractBlock(stripped, bodyStart);
8076
+ sigs.push(withAnchor(`${m[1]} ${m[2]}`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
8077
+ for (const fn of extractMembers(block)) {
8078
+ sigs.push(withAnchor(` ${fn.text}`, lineAt(stripped, bodyStart + fn.declIdx), lineAt(stripped, bodyStart + fn.endIdx)));
8079
+ }
7974
8080
  }
7975
8081
 
7976
8082
  // Top-level public functions — capture everything after ) to end of line for arrow type
7977
8083
  for (const m of stripped.matchAll(/^(?:public\s+|internal\s+)?(?:static\s+)?(?:async\s+)?func\s+(\w+)(?:<[^(]*>)?\s*\(([^)]*)\)([^{\n]*)/gm)) {
7978
8084
  const asyncKw = m[0].includes('async') ? 'async ' : '';
7979
8085
  const retStr = extractArrowType(m[3]);
7980
- sigs.push(`${asyncKw}func ${m[1]}(${normalizeParams(m[2])})${retStr}`);
8086
+ const [s, e] = rangeFor(m.index, m.index + m[0].length);
8087
+ sigs.push(withAnchor(`${asyncKw}func ${m[1]}(${normalizeParams(m[2])})${retStr}`, s, e));
7981
8088
  }
7982
8089
 
7983
8090
  return sigs.slice(0, 25);
@@ -8000,7 +8107,11 @@ __factories["./src/extractors/swift"] = function(module, exports) {
8000
8107
  if (m[1].startsWith('_')) continue;
8001
8108
  const asyncKw = m[0].includes('async') ? 'async ' : '';
8002
8109
  const retStr = extractArrowType(m[3]);
8003
- members.push(`${asyncKw}func ${m[1]}(${normalizeParams(m[2])})${retStr}`);
8110
+ members.push({
8111
+ text: `${asyncKw}func ${m[1]}(${normalizeParams(m[2])})${retStr}`,
8112
+ declIdx: m.index + (m[0].length - m[0].trimStart().length),
8113
+ endIdx: m.index + m[0].length,
8114
+ });
8004
8115
  }
8005
8116
  return members.slice(0, 8);
8006
8117
  }
@@ -8019,7 +8130,7 @@ __factories["./src/extractors/swift"] = function(module, exports) {
8019
8130
  const m = str.match(/->\s*([^\n{]+)/);
8020
8131
  if (!m) return '';
8021
8132
  const rt = m[1].trim().replace(/\s+/g, ' ');
8022
- return ` \u2192 ${rt.length > 25 ? rt.slice(0, 22) + '...' : rt}`;
8133
+ return ` ${rt.length > 25 ? rt.slice(0, 22) + '...' : rt}`;
8023
8134
  }
8024
8135
 
8025
8136
  module.exports = { extract };
@@ -13227,7 +13338,14 @@ __factories["./src/map/route-table"] = function(module, exports) {
13227
13338
  return /(^|\/)(gen-context|gen-project-map)\.js$/.test(normalized);
13228
13339
  }
13229
13340
 
13230
- function analyze(files, cwd) {
13341
+ /**
13342
+ * Structured route rows across the supported frameworks — the data behind
13343
+ * `analyze`, exposed for retrieval surface-enrichment (#488).
13344
+ * @param {string[]} files absolute paths
13345
+ * @param {string} cwd
13346
+ * @returns {{ method:string, path:string, file:string }[]} file is cwd-relative
13347
+ */
13348
+ function collectRoutes(files, cwd) {
13231
13349
  const routes = [];
13232
13350
 
13233
13351
  for (const filePath of files) {
@@ -13319,6 +13437,11 @@ __factories["./src/map/route-table"] = function(module, exports) {
13319
13437
  }
13320
13438
  }
13321
13439
 
13440
+ return routes;
13441
+ }
13442
+
13443
+ function analyze(files, cwd) {
13444
+ const routes = collectRoutes(files, cwd);
13322
13445
  if (routes.length === 0) return '';
13323
13446
 
13324
13447
  const lines = [
@@ -13331,7 +13454,7 @@ __factories["./src/map/route-table"] = function(module, exports) {
13331
13454
  return lines.join('\n');
13332
13455
  }
13333
13456
 
13334
- module.exports = { analyze };
13457
+ module.exports = { analyze, collectRoutes };
13335
13458
 
13336
13459
  };
13337
13460
 
@@ -13759,7 +13882,7 @@ __factories["./src/mcp/handlers"] = function(module, exports) {
13759
13882
  // Build dependency graph for neighbor boost — non-fatal if it fails
13760
13883
  let graph = null;
13761
13884
  try { graph = buildFromCwd(cwd); } catch (_) {}
13762
- // Opt-in call-graph neighbor boost (retrieval.callGraphBoost) — non-fatal
13885
+ // Opt-in call-graph neighbor boost + surface enrichment — non-fatal
13763
13886
  let callGraph = null;
13764
13887
  try {
13765
13888
  const { loadConfig } = __require('./src/config/loader');
@@ -13767,6 +13890,9 @@ __factories["./src/mcp/handlers"] = function(module, exports) {
13767
13890
  if (retrieval && retrieval.callGraphBoost) {
13768
13891
  callGraph = __require('./src/graph/call-graph').buildCallFileGraph(cwd);
13769
13892
  }
13893
+ if (retrieval && retrieval.surfaceEnrichment) {
13894
+ __require('./src/retrieval/enrich-from-maps').enrichWithSurfaces(index, cwd);
13895
+ }
13770
13896
  } catch (_) {}
13771
13897
  const results = rank(args.query, index, { topK, cwd, graph, callGraph });
13772
13898
  return formatRankTable(results, args.query);
@@ -14481,7 +14607,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
14481
14607
 
14482
14608
  const SERVER_INFO = {
14483
14609
  name: 'sigmap',
14484
- version: '8.17.0',
14610
+ version: '8.19.0',
14485
14611
  description: 'SigMap MCP server — code signatures on demand',
14486
14612
  };
14487
14613
 
@@ -15469,7 +15595,10 @@ __factories["./src/retrieval/bm25"] = function(module, exports) {
15469
15595
 
15470
15596
  const docs = candidates.map((c) => {
15471
15597
  const pathToks = tokenize(c.file || '');
15472
- const toks = tokenize((c.sigs || []).join(' '));
15598
+ // Ranking is anchor-invariant: `:start-end` line anchors are metadata,
15599
+ // not content — strip them before tokenizing so adding anchors to an
15600
+ // extractor never shifts BM25 length normalization or token counts.
15601
+ const toks = tokenize((c.sigs || []).map((s) => String(s).replace(/\s*:\d+(?:-\d+)?\s*$/, '')).join(' '));
15473
15602
  for (let i = 0; i < PATH_BOOST; i++) toks.push(...pathToks);
15474
15603
  const tf = new Map();
15475
15604
  for (const t of toks) tf.set(t, (tf.get(t) || 0) + 1);
@@ -15506,6 +15635,65 @@ __factories["./src/retrieval/bm25"] = function(module, exports) {
15506
15635
 
15507
15636
  };
15508
15637
 
15638
+ // ── ./src/retrieval/enrich-from-maps ──
15639
+ __factories["./src/retrieval/enrich-from-maps"] = function(module, exports) {
15640
+
15641
+ /**
15642
+ * Retrieval surface-enrichment (#488, §7.4 Retrieval ceiling — opt-in via
15643
+ * `retrieval.surfaceEnrichment`, measure-gated).
15644
+ *
15645
+ * The map analyzers extract surfaces (routes) that never reach the rankable
15646
+ * signature index — a query like "payment webhook route" cannot match a
15647
+ * controller whose signatures never mention the route path. This module
15648
+ * appends deterministic pseudo-signatures (`route GET /api/users`) to the
15649
+ * defining file's signature list so the ranker's tokenizer can see them.
15650
+ *
15651
+ * Deterministic: rows are deduped and sorted; entries are copy-on-write so
15652
+ * arrays shared with the signature cache are never mutated.
15653
+ */
15654
+
15655
+ const path = require('path');
15656
+
15657
+ /**
15658
+ * Enrich a signature index with route pseudo-signatures.
15659
+ * @param {Map<string,string[]>} index cwd-relative file → sigs (mutated: enriched entries are replaced with fresh arrays)
15660
+ * @param {string} cwd
15661
+ * @returns {number} pseudo-signatures added
15662
+ */
15663
+ function enrichWithSurfaces(index, cwd) {
15664
+ if (!(index instanceof Map) || index.size === 0) return 0;
15665
+
15666
+ let collectRoutes;
15667
+ try { ({ collectRoutes } = __require('./src/map/route-table')); } catch (_) { return 0; }
15668
+
15669
+ const rels = [...index.keys()];
15670
+ const files = rels.map((rel) => path.join(cwd, rel));
15671
+ let routes = [];
15672
+ try { routes = collectRoutes(files, cwd) || []; } catch (_) { return 0; }
15673
+
15674
+ const byFile = new Map();
15675
+ for (const r of routes) {
15676
+ const rel = String(r.file).replace(/\\/g, '/');
15677
+ if (!byFile.has(rel)) byFile.set(rel, new Set());
15678
+ byFile.get(rel).add(`route ${r.method} ${r.path}`);
15679
+ }
15680
+
15681
+ let added = 0;
15682
+ for (const [rel, extras] of [...byFile.entries()].sort()) {
15683
+ const sigs = index.get(rel);
15684
+ if (!sigs) continue;
15685
+ const fresh = [...extras].sort().filter((x) => !sigs.includes(x));
15686
+ if (!fresh.length) continue;
15687
+ index.set(rel, [...sigs, ...fresh]); // copy-on-write: never mutate cached arrays
15688
+ added += fresh.length;
15689
+ }
15690
+ return added;
15691
+ }
15692
+
15693
+ module.exports = { enrichWithSurfaces };
15694
+
15695
+ };
15696
+
15509
15697
  // ── ./src/retrieval/ranker ──
15510
15698
  __factories["./src/retrieval/ranker"] = function(module, exports) {
15511
15699
 
@@ -19522,7 +19710,7 @@ function __tryGit(args, opts = {}) {
19522
19710
  catch (_) { return ''; }
19523
19711
  }
19524
19712
 
19525
- const VERSION = '8.17.0';
19713
+ const VERSION = '8.19.0';
19526
19714
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
19527
19715
 
19528
19716
  function requireSourceOrBundled(key) {
@@ -21850,6 +22038,10 @@ function main() {
21850
22038
  if (config && config.retrieval && config.retrieval.callGraphBoost) {
21851
22039
  try { askCallGraph = requireSourceOrBundled('./src/graph/call-graph').buildCallFileGraph(cwd); } catch (_) {}
21852
22040
  }
22041
+ // Opt-in route surface-enrichment (retrieval.surfaceEnrichment) — non-fatal
22042
+ if (config && config.retrieval && config.retrieval.surfaceEnrichment) {
22043
+ try { requireSourceOrBundled('./src/retrieval/enrich-from-maps').enrichWithSurfaces(sigIndex, cwd); } catch (_) {}
22044
+ }
21853
22045
 
21854
22046
  let ranked = rank(query, sigIndex, { topK: 5, weights: intentWeights, cwd, callGraph: askCallGraph });
21855
22047
 
@@ -24028,6 +24220,10 @@ function main() {
24028
24220
  if (config && config.retrieval && config.retrieval.callGraphBoost) {
24029
24221
  try { queryCallGraph = requireSourceOrBundled('./src/graph/call-graph').buildCallFileGraph(cwd); } catch (_) {}
24030
24222
  }
24223
+ // Opt-in route surface-enrichment (retrieval.surfaceEnrichment) — non-fatal
24224
+ if (config && config.retrieval && config.retrieval.surfaceEnrichment) {
24225
+ try { requireSourceOrBundled('./src/retrieval/enrich-from-maps').enrichWithSurfaces(index, cwd); } catch (_) {}
24226
+ }
24031
24227
  const results = rank(query, index, { topK, recencyBoost, cwd, callGraph: queryCallGraph });
24032
24228
  if (args.includes('--context')) {
24033
24229
  const miniCtx = buildMiniContext(results, cwd);
package/llms-full.txt CHANGED
@@ -11,19 +11,19 @@ ranking keeps the relevant context in scope (cutting tokens ~97% as a side
11
11
  effect), with no LLM calls, embeddings, or vector database. Works with Claude,
12
12
  Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
13
13
 
14
- # Version: 8.17.0 | Benchmark: sigmap-v8.17-main (2026-07-12)
14
+ # Version: 8.19.0 | Benchmark: sigmap-v8.19-main (2026-07-19)
15
15
  # Source: auto-generated from package.json, version.json, benchmarks/latest.json, src/mcp/tools.js, src/config/defaults.js
16
16
  # Regenerate: npm run generate:llms | Validate: npm run validate:llms
17
17
 
18
18
  ---
19
19
 
20
- ## Core metrics (benchmark: sigmap-v8.17-main, 2026-07-12)
20
+ ## Core metrics (benchmark: sigmap-v8.19-main, 2026-07-19)
21
21
 
22
22
  | Metric | Without SigMap | With SigMap |
23
23
  |--------|----------------|-------------|
24
- | Retrieval hit@5 | 13.6% (random) | 87.8% (6.5× lift) |
25
- | Token reduction | — | 97.0% average |
26
- | Task success proxy | 10% | 67.8% |
24
+ | Retrieval hit@5 | 42.7% (single-shot grep) | 86.7% (2.02× lift) |
25
+ | Token reduction | — | 96.9% average |
26
+ | Task-success proxy (modeled) | | 68.9% |
27
27
  | Prompts per task | 2.84 | 1.44 (49.2% fewer) |
28
28
  | Supported languages | — | 33 |
29
29
  | MCP tools | — | 20 |
@@ -343,7 +343,7 @@ testCoverage = false
343
343
  testDirs = ["tests","test","__tests__","spec"]
344
344
  sigCache = false
345
345
  impactRadius = false
346
- retrieval = {"topK":10,"recencyBoost":1.5,"callGraphBoost":false}
346
+ retrieval = {"topK":10,"recencyBoost":1.5,"callGraphBoost":false,"surfaceEnrichment":false}
347
347
  impact = {"depth":3,"includeSigs":true}
348
348
  ```
349
349
 
package/llms.txt CHANGED
@@ -11,7 +11,7 @@ ranking keeps the relevant context in scope (cutting tokens ~97% as a side
11
11
  effect), with no LLM calls, embeddings, or vector database. Works with Claude,
12
12
  Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
13
13
 
14
- # Version: 8.17.0 | Benchmark: sigmap-v8.17-main (2026-07-12)
14
+ # Version: 8.19.0 | Benchmark: sigmap-v8.19-main (2026-07-19)
15
15
  # Source: auto-generated from package.json, version.json, benchmarks/latest.json, src/mcp/tools.js, src/config/defaults.js
16
16
  # Regenerate: npm run generate:llms | Validate: npm run validate:llms
17
17
 
@@ -23,12 +23,12 @@ Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
23
23
  - No blast-radius awareness before editing a hub file — `--impact` shows every file a change touches.
24
24
  - Pasted stack traces, CI logs, and JSON bloat the prompt — `squeeze` minimizes them and enriches the top frame from the symbol index.
25
25
 
26
- ## Core metrics (benchmark: sigmap-v8.17-main, 2026-07-12)
26
+ ## Core metrics (benchmark: sigmap-v8.19-main, 2026-07-19)
27
27
 
28
- - hit@5 retrieval: 87.8% vs 13.6% random baseline (6.5× lift)
29
- - Token reduction: 97.0% average across benchmark repos
30
- - Task success: 67.8% vs 10% without SigMap
31
- - Prompts per task: 1.44 vs 2.84 baseline (49.2% fewer)
28
+ - hit@5 retrieval: 86.7% vs 42.7% single-shot grep baseline (2.02× lift)
29
+ - Token reduction: 96.9% average across benchmark repos
30
+ - Task-success proxy: 68.9% (modeled from retrieval tiers, not measured LLM sessions)
31
+ - Prompts per task: 1.44 vs 2.84 baseline (49.2% fewer, modeled)
32
32
  - Languages: 33 supported · MCP tools: 20
33
33
  - Dependencies: zero npm runtime dependencies · fully offline
34
34
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "8.17.0",
3
+ "version": "8.19.0",
4
4
  "description": "The deterministic, verifiable grounding layer for AI code work — a zero-dependency signature-and-evidence map that grounds Claude, Cursor, Copilot, Aider, Windsurf, local LLMs & MCP agents against your real code (repo + installed libraries) so they stop hallucinating files, imports & APIs. Runs offline via npx; byte-stable output; ~97% token reduction as proof.",
5
5
  "main": "packages/core/index.js",
6
6
  "exports": {
@@ -31,6 +31,7 @@
31
31
  "benchmark:test-discovery": "node scripts/run-test-discovery-benchmark.mjs --save",
32
32
  "benchmark:terse": "node scripts/run-terse-benchmark.mjs --save",
33
33
  "benchmark:callgraph-boost": "node scripts/run-callgraph-boost-benchmark.mjs --save",
34
+ "benchmark:surface-enrichment": "node scripts/run-surface-enrichment-benchmark.mjs --save",
34
35
  "validate:squeeze": "node scripts/run-squeeze-benchmark.mjs --gate",
35
36
  "health": "node gen-context.js --health",
36
37
  "map": "node gen-project-map.js",
@@ -49,6 +50,7 @@
49
50
  "check:metrics": "node scripts/gen-benchmark-latest.mjs --check && node scripts/check-version-meta.mjs && node scripts/sync-metrics.mjs --check",
50
51
  "prepublishOnly": "node scripts/check-bundle.mjs && node scripts/build-bundle.mjs --check && node scripts/gen-benchmark-latest.mjs --check && node scripts/check-version-meta.mjs && node scripts/sync-metrics.mjs --check && node scripts/generate-llms.mjs",
51
52
  "benchmark:grounding": "node scripts/run-hallucination-benchmark.mjs",
53
+ "benchmark:honest": "node scripts/run-honest-benchmark.mjs --save",
52
54
  "benchmark:llm-ablation": "node scripts/run-llm-ablation.mjs"
53
55
  },
54
56
  "files": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-cli",
3
- "version": "8.17.0",
3
+ "version": "8.19.0",
4
4
  "description": "SigMap CLI wrapper — thin adapter for programmatic CLI invocation",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-core",
3
- "version": "8.17.0",
3
+ "version": "8.19.0",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -149,6 +149,8 @@ const DEFAULTS = {
149
149
  recencyBoost: 1.5,
150
150
  // Boost files call-graph-connected to query matches (opt-in, measure-gated)
151
151
  callGraphBoost: false,
152
+ // Append route pseudo-signatures to the rankable index (opt-in, measure-gated)
153
+ surfaceEnrichment: false,
152
154
  },
153
155
 
154
156
  // Impact layer settings (v2.5)