sigmap 8.16.1 → 8.17.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/AGENTS.md CHANGED
@@ -33,17 +33,16 @@ Always run `sigmap ask` (or `sigmap --query`) before searching for files relevan
33
33
  src/extractors/python_ast.py ← ast
34
34
  ```
35
35
 
36
- ## changes (last 5 commits — 0 seconds ago)
36
+ ## changes (last 5 commits — 1 second ago)
37
37
  ```
38
+ src/extractors/csharp.js ~extract ~extractMembers
39
+ src/extractors/go.js ~extract ~extractInterfaceMethods
40
+ src/extractors/java.js ~extract ~extractMembers
41
+ src/extractors/rust.js ~extract ~extractMethods ~extractReturnType
38
42
  src/evidence/pack.js +riskFactorsFor ~parseAnchor ~buildEvidencePack ~formatMarkdown
39
- src/graph/call-graph.js +calls +maskRust +name +goDefs
40
- src/mcp/handlers.js +that +getMethodImpact ~queryContext ~squeezeOutput
41
- src/mcp/server.js ~dispatch
42
- src/mcp/tools.js +it
43
+ src/graph/call-graph.js +buildCallFileGraph ~buildCallGraph ~formatCallGraphJSON ~_resolveSymbol
44
+ src/mcp/handlers.js ~queryContext
43
45
  src/retrieval/ranker.js ~scoreFile ~rank
44
- src/graph/blast-radius.js +tierFor +_normRel +_bfs +methodBlastRadius
45
- src/review/pr-evidence.js ~buildPrEvidence ~formatPrEvidenceMarkdown
46
- src/review/review-pr.js ~isSource ~reviewPr
47
46
  ```
48
47
 
49
48
  ## packages
@@ -175,86 +174,43 @@ code-fence ---
175
174
 
176
175
  ## src
177
176
 
178
- ### src/config/defaults.js
177
+ ### src/extractors/csharp.js
179
178
  ```
180
- module.exports = { DEFAULTS } :163-163
179
+ module.exports = { extract } :71-71
180
+ function extract(src) → string[] :12-71
181
+ function extractBlock(src, startIndex) :35-44
182
+ function extractMembers(block) :46-59
183
+ function normalizeParams(params) :61-64
184
+ function normalizeType(type) :66-69
181
185
  ```
182
186
 
183
- ### src/evidence/pack.js
187
+ ### src/extractors/go.js
184
188
  ```
185
- module.exports = { buildEvidencePack, formatJSON, formatMarkdown, parseAnchor, riskLabelFor, riskFactorsFor, findRelatedTests, SCHEMA_VERSION, SCHEMA_URL, TEST_DISCOVERY } :326-337
186
- function parseAnchor(sig) → { symbol: string, start: :64-72
187
- function riskLabelFor(relPath) → 'generated'|'test'|'migra :84-86
188
- function riskFactorsFor(relPath) → string[] :96-108
189
- function stemOf(relPath) :111-114
190
- function testTargetStem(relPath) → string :126-132
191
- function findRelatedTests(relPath, allFiles) → string[] :143-154
192
- function reasonFor(signals) :157-168
193
- function sigTokens(sigs) :171-173
194
- function canonicalize(value) → string :180-182
195
- function sortKeys(value) :184-192
196
- function buildEvidencePack(query, cwd, opts = {}) → object :205-281
197
- function ranked0Empty(query) :284-286
198
- function formatJSON(pack) :289-291
199
- function formatMarkdown(pack) :294-324
189
+ module.exports = { extract } :81-81
190
+ function extract(src) → string[] :12-81
191
+ function extractBlock(src, startIndex) :51-60
192
+ function extractInterfaceMethods(block) :62-74
193
+ function normalizeParams(params) :76-79
200
194
  ```
201
195
 
202
- ### src/graph/call-graph.js
196
+ ### src/extractors/java.js
203
197
  ```
204
- module.exports = { buildCallGraph, buildCallFileGraph, methodImpact, methodCallees, formatCallGraph, formatCallGraphJSON, extractDefs, maskJs, maskPy, maskRust } :563-567
205
- function normalizePath(p) :41-41
206
- function toRel(cwd, f) :42-42
207
- function symId(cwd, absFile, name) :43-43
208
- function maskJs(src) :49-65
209
- function maskRust(src) :70-91
210
- function maskPy(src) :93-110
211
- function matchDelim(masked, openIdx, open, close) :113-120
212
- function lineAt(src, idx) :122-127
213
- function jsDefs(masked) :132-237
214
- function pyDefs(masked) :200-220
215
- function goDefs(masked) :224-344
216
- function javaDefs(masked) :250-367
217
- function rustDefs(masked) :278-401
218
- function maskFor(filePath, src) :300-305
219
- function extractDefs(filePath, src) :307-315
220
- function callsInRange(masked, start, end) :318-330
221
- function _walk(dir, excludeSet, out, depth) :334-347
222
- function buildCallGraph(cwd, opts = {}) → { * forward: Map<string,s :363-446
223
- function buildCallFileGraph(cwd, opts = {}) → { forward: Map<string,str :459-482
224
- function _resolveSymbol(symbol, defs) :485-490
225
- function _bfs(seedIds, graph, maxDepth) :493-506
226
- function methodImpact(symbol, cwd, opts = {}) → { symbol:string, resolved :516-522
227
- function methodCallees(symbol, cwd, opts = {}) → { symbol:string, resolved :528-534
228
- function formatCallGraph(result, kind) :537-549
229
- … +1 more signatures
198
+ module.exports = { extract } :71-71
199
+ function extract(src) → string[] :12-71
200
+ function extractBlock(src, startIndex) :34-44
201
+ function extractMembers(block) :46-59
202
+ function normalizeParams(params) :61-64
203
+ function normalizeType(type) :66-69
230
204
  ```
231
205
 
232
- ### src/mcp/handlers.js
206
+ ### src/extractors/rust.js
233
207
  ```
234
- module.exports = { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getMethodImpact, getImpact, getLines, readMemory, getCalleeSignatures, notifyFileCreated, notifySymbolAdded, notifyFileDeleted, getDiffContext, getArchitectureOverview, verifySuggestion, squeezeOutput } :975-975
235
- function _readContextFiles(cwd) :10-17
236
- function readContext(args, cwd) :36-66
237
- function searchSignatures(args, cwd) :74-100
238
- function getMap(args, cwd) :108-131
239
- function createCheckpoint(args, cwd) :143-215
240
- function getRouting(args, cwd) :224-261
241
- function explainFile(args, cwd) :269-356
242
- function listModules(args, cwd) :364-403
243
- function queryContext(args, cwd) :411-438
244
- function getMethodImpact(args, cwd) :446-460
245
- function getImpact(args, cwd) :468-480
246
- function getLines(args, cwd) :489-537
247
- function readMemory(args, cwd) :545-580
248
- function getCalleeSignatures(args, cwd) :589-634
249
- function _pkgVersion(cwd) :641-644
250
- function notifyFileCreated(args, cwd) :648-670
251
- function notifySymbolAdded(args, cwd) :673-693
252
- function notifyFileDeleted(args, cwd) :696-710
253
- function _changedFiles(cwd, args) :716-728
254
- function getDiffContext(args, cwd) :737-808
255
- function getArchitectureOverview(args, cwd) :817-885
256
- function verifySuggestion(args, cwd) :895-930
257
- function squeezeOutput(args, cwd) :940-973
208
+ module.exports = { extract } :110-110
209
+ function extract(src) → string[] :12-110
210
+ function extractBlock(src, startIndex) :72-81
211
+ function extractMethods(block) :83-110
212
+ function normalizeParams(params) :97-100
213
+ function extractReturnType(afterParen) :102-110
258
214
  ```
259
215
 
260
216
  ### src/mcp/server.js
@@ -266,29 +222,6 @@ function dispatch(msg, cwd) :41-108
266
222
  function start(cwd) :113-139
267
223
  ```
268
224
 
269
- ### src/mcp/tools.js
270
- ```
271
- module.exports = { TOOLS } :392-392
272
- ```
273
-
274
- ### src/retrieval/ranker.js
275
- ```
276
- module.exports = { rank, buildSigIndex, scoreFile, formatRankTable, formatRankJSON, DEFAULT_WEIGHTS, GRAPH_BOOST_AMOUNTS, detectIntent } :598-598
277
- function _computePenalty(filePath) :63-70
278
- function _computeHubs(graph) :73-84
279
- function _isHub(filePath) :87-91
280
- function scoreFile(filePath, sigs, queryTokens, weights) → { score: number, signals: :102-159
281
- function rank(query, sigIndex, opts) → { file: string, score: nu :177-302
282
- function _parseContextFile(contextPath) → Map<string, string[]> :372-404
283
- function _mergeSigIndex(target, source) :407-415
284
- function _buildSigIndexFromCache(cwd) → Map<string, string[]> :422-442
285
- function _enrichSigIndexFromStrategy(cwd, index) → Map<string, string[]> :450-456
286
- function buildSigIndex(cwd, opts) → Map<string, string[]> :473-506
287
- function formatRankTable(results, query) → string :515-551
288
- function formatRankJSON(results, query) → object :560-575
289
- function detectIntent(query) :590-596
290
- ```
291
-
292
225
  ### src/analysis/coverage-score.js
293
226
  ```
294
227
  module.exports = { coverageScore, CODE_EXTS } :105-105
@@ -326,6 +259,11 @@ function getChangedFiles(files, cache) → { changed: string[], unch :71-88
326
259
  function updateCacheEntries(cache, extracted) :96-103
327
260
  ```
328
261
 
262
+ ### src/config/defaults.js
263
+ ```
264
+ module.exports = { DEFAULTS } :163-163
265
+ ```
266
+
329
267
  ### src/config/loader.js
330
268
  ```
331
269
  module.exports = { loadConfig, loadBaseConfig } :313-313
@@ -539,6 +477,25 @@ function scoreUsefulness(taskResult, rankingScore) :11-38
539
477
  function computeUsefulnessStats(taskResults) :40-66
540
478
  ```
541
479
 
480
+ ### src/evidence/pack.js
481
+ ```
482
+ module.exports = { buildEvidencePack, formatJSON, formatMarkdown, parseAnchor, riskLabelFor, riskFactorsFor, findRelatedTests, SCHEMA_VERSION, SCHEMA_URL, TEST_DISCOVERY } :326-337
483
+ function parseAnchor(sig) → { symbol: string, start: :64-72
484
+ function riskLabelFor(relPath) → 'generated'|'test'|'migra :84-86
485
+ function riskFactorsFor(relPath) → string[] :96-108
486
+ function stemOf(relPath) :111-114
487
+ function testTargetStem(relPath) → string :126-132
488
+ function findRelatedTests(relPath, allFiles) → string[] :143-154
489
+ function reasonFor(signals) :157-168
490
+ function sigTokens(sigs) :171-173
491
+ function canonicalize(value) → string :180-182
492
+ function sortKeys(value) :184-192
493
+ function buildEvidencePack(query, cwd, opts = {}) → object :205-281
494
+ function ranked0Empty(query) :284-286
495
+ function formatJSON(pack) :289-291
496
+ function formatMarkdown(pack) :294-324
497
+ ```
498
+
542
499
  ### src/extractors/coverage.js
543
500
  ```
544
501
  module.exports = { buildTestIndex, isTested } :79-79
@@ -557,16 +514,6 @@ function normalizeParams(params) :59-62
557
514
  function normalizeType(type) :64-67
558
515
  ```
559
516
 
560
- ### src/extractors/csharp.js
561
- ```
562
- module.exports = { extract } :59-59
563
- function extract(src) → string[] :8-59
564
- function extractBlock(src, startIndex) :27-36
565
- function extractMembers(block) :38-47
566
- function normalizeParams(params) :49-52
567
- function normalizeType(type) :54-57
568
- ```
569
-
570
517
  ### src/extractors/css.js
571
518
  ```
572
519
  module.exports = { extract } :69-69
@@ -618,15 +565,6 @@ module.exports = { extract } :2-2
618
565
  function extract(src) :14-26
619
566
  ```
620
567
 
621
- ### src/extractors/go.js
622
- ```
623
- module.exports = { extract } :65-65
624
- function extract(src) → string[] :8-65
625
- function extractBlock(src, startIndex) :39-48
626
- function extractInterfaceMethods(block) :50-58
627
- function normalizeParams(params) :60-63
628
- ```
629
-
630
568
  ### src/extractors/graphql.js
631
569
  ```
632
570
  module.exports = { extract } :66-66
@@ -639,16 +577,6 @@ module.exports = { extract } :39-39
639
577
  function extract(src) → string[] :9-37
640
578
  ```
641
579
 
642
- ### src/extractors/java.js
643
- ```
644
- module.exports = { extract } :60-60
645
- function extract(src) → string[] :8-60
646
- function extractBlock(src, startIndex) :27-37
647
- function extractMembers(block) :39-48
648
- function normalizeParams(params) :50-53
649
- function normalizeType(type) :55-58
650
- ```
651
-
652
580
  ### src/extractors/javascript.js
653
581
  ```
654
582
  module.exports = { extract } :147-147
@@ -781,16 +709,6 @@ function normalizeParams(params) :40-43
781
709
  function extractReturnHint(stripped, index) :45-52
782
710
  ```
783
711
 
784
- ### src/extractors/rust.js
785
- ```
786
- module.exports = { extract } :82-82
787
- function extract(src) → string[] :8-82
788
- function extractBlock(src, startIndex) :48-57
789
- function extractMethods(block) :59-82
790
- function normalizeParams(params) :69-72
791
- function extractReturnType(afterParen) :74-82
792
- ```
793
-
794
712
  ### src/extractors/scala.js
795
713
  ```
796
714
  module.exports = { extract } :76-76
@@ -1033,6 +951,36 @@ function build(files, cwd, ctx) → { forward: Map<string,str :387-426
1033
951
  function buildFromCwd(cwd, opts) → { forward: Map<string,str :438-492
1034
952
  ```
1035
953
 
954
+ ### src/graph/call-graph.js
955
+ ```
956
+ module.exports = { buildCallGraph, buildCallFileGraph, methodImpact, methodCallees, formatCallGraph, formatCallGraphJSON, extractDefs, maskJs, maskPy, maskRust } :563-567
957
+ function normalizePath(p) :41-41
958
+ function toRel(cwd, f) :42-42
959
+ function symId(cwd, absFile, name) :43-43
960
+ function maskJs(src) :49-65
961
+ function maskRust(src) :70-91
962
+ function maskPy(src) :93-110
963
+ function matchDelim(masked, openIdx, open, close) :113-120
964
+ function lineAt(src, idx) :122-127
965
+ function jsDefs(masked) :132-237
966
+ function pyDefs(masked) :200-220
967
+ function goDefs(masked) :224-344
968
+ function javaDefs(masked) :250-367
969
+ function rustDefs(masked) :278-401
970
+ function maskFor(filePath, src) :300-305
971
+ function extractDefs(filePath, src) :307-315
972
+ function callsInRange(masked, start, end) :318-330
973
+ function _walk(dir, excludeSet, out, depth) :334-347
974
+ function buildCallGraph(cwd, opts = {}) → { * forward: Map<string,s :363-446
975
+ function buildCallFileGraph(cwd, opts = {}) → { forward: Map<string,str :459-482
976
+ function _resolveSymbol(symbol, defs) :485-490
977
+ function _bfs(seedIds, graph, maxDepth) :493-506
978
+ function methodImpact(symbol, cwd, opts = {}) → { symbol:string, resolved :516-522
979
+ function methodCallees(symbol, cwd, opts = {}) → { symbol:string, resolved :528-534
980
+ function formatCallGraph(result, kind) :537-549
981
+ … +1 more signatures
982
+ ```
983
+
1036
984
  ### src/graph/impact.js
1037
985
  ```
1038
986
  module.exports = { getImpact, analyzeImpact, formatImpact, formatImpactJSON } :240-240
@@ -1147,6 +1095,34 @@ function shouldSkipFile(rel) :18-21
1147
1095
  function analyze(files, cwd) :23-125
1148
1096
  ```
1149
1097
 
1098
+ ### src/mcp/handlers.js
1099
+ ```
1100
+ module.exports = { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getMethodImpact, getImpact, getLines, readMemory, getCalleeSignatures, notifyFileCreated, notifySymbolAdded, notifyFileDeleted, getDiffContext, getArchitectureOverview, verifySuggestion, squeezeOutput } :975-975
1101
+ function _readContextFiles(cwd) :10-17
1102
+ function readContext(args, cwd) :36-66
1103
+ function searchSignatures(args, cwd) :74-100
1104
+ function getMap(args, cwd) :108-131
1105
+ function createCheckpoint(args, cwd) :143-215
1106
+ function getRouting(args, cwd) :224-261
1107
+ function explainFile(args, cwd) :269-356
1108
+ function listModules(args, cwd) :364-403
1109
+ function queryContext(args, cwd) :411-438
1110
+ function getMethodImpact(args, cwd) :446-460
1111
+ function getImpact(args, cwd) :468-480
1112
+ function getLines(args, cwd) :489-537
1113
+ function readMemory(args, cwd) :545-580
1114
+ function getCalleeSignatures(args, cwd) :589-634
1115
+ function _pkgVersion(cwd) :641-644
1116
+ function notifyFileCreated(args, cwd) :648-670
1117
+ function notifySymbolAdded(args, cwd) :673-693
1118
+ function notifyFileDeleted(args, cwd) :696-710
1119
+ function _changedFiles(cwd, args) :716-728
1120
+ function getDiffContext(args, cwd) :737-808
1121
+ function getArchitectureOverview(args, cwd) :817-885
1122
+ function verifySuggestion(args, cwd) :895-930
1123
+ function squeezeOutput(args, cwd) :940-973
1124
+ ```
1125
+
1150
1126
  ### src/mcp/install.js
1151
1127
  ```
1152
1128
  module.exports = { CLIENTS, listClients, installClient, resolveTarget } :142-142
@@ -1159,6 +1135,11 @@ function _installYaml(filePath, scriptPath) :99-117
1159
1135
  function installClient(client, opts = {}) → client, label, path, stat :124-140
1160
1136
  ```
1161
1137
 
1138
+ ### src/mcp/tools.js
1139
+ ```
1140
+ module.exports = { TOOLS } :392-392
1141
+ ```
1142
+
1162
1143
  ### src/nudge.js
1163
1144
  ```
1164
1145
  module.exports = { checkStarNudge, readUsage, usagePath, showStarNudge, RUN_THRESHOLD, SUCCESS_THRESHOLD } :92-92
@@ -1192,6 +1173,24 @@ function expandQuery(qToks) → Map<string, number> :132-141
1192
1173
  function bm25rank(query, candidates) → Array<object & { score: n :154-193
1193
1174
  ```
1194
1175
 
1176
+ ### src/retrieval/ranker.js
1177
+ ```
1178
+ module.exports = { rank, buildSigIndex, scoreFile, formatRankTable, formatRankJSON, DEFAULT_WEIGHTS, GRAPH_BOOST_AMOUNTS, detectIntent } :598-598
1179
+ function _computePenalty(filePath) :63-70
1180
+ function _computeHubs(graph) :73-84
1181
+ function _isHub(filePath) :87-91
1182
+ function scoreFile(filePath, sigs, queryTokens, weights) → { score: number, signals: :102-159
1183
+ function rank(query, sigIndex, opts) → { file: string, score: nu :177-302
1184
+ function _parseContextFile(contextPath) → Map<string, string[]> :372-404
1185
+ function _mergeSigIndex(target, source) :407-415
1186
+ function _buildSigIndexFromCache(cwd) → Map<string, string[]> :422-442
1187
+ function _enrichSigIndexFromStrategy(cwd, index) → Map<string, string[]> :450-456
1188
+ function buildSigIndex(cwd, opts) → Map<string, string[]> :473-506
1189
+ function formatRankTable(results, query) → string :515-551
1190
+ function formatRankJSON(results, query) → object :560-575
1191
+ function detectIntent(query) :590-596
1192
+ ```
1193
+
1195
1194
  ### src/retrieval/tokenizer.js
1196
1195
  ```
1197
1196
  module.exports = { tokenize, STOP_WORDS } :54-54
package/CHANGELOG.md CHANGED
@@ -10,6 +10,15 @@ Format: [Semantic Versioning](https://semver.org/)
10
10
 
11
11
  ---
12
12
 
13
+ ## [8.17.0] — 2026-07-12
14
+
15
+ Minor release — **line anchors for Java, Go, Rust, and C#** (§7.4 Lang ceiling, the roadmap's long-named "anchors for the remaining extractors" item).
16
+
17
+ ### Added
18
+ - **Extractor line anchors for Java/Go/Rust/C# (#483, PR #484):** signatures from these languages now carry `:start-end` anchors — types/functions with bodies get real brace-matched ranges, single-line members get `:n-n`, Rust bodyless items (unit structs, trait declarations) get single-line anchors. Prerequisite fix folded in: the four extractors' comment strips are now **newline-preserving** (block comments previously collapsed outright, making true line numbers impossible — regression-tested with a multi-line license header above declarations). Downstream, verified: Evidence Pack `anchorCoverage` goes **0 → 1.0** on Go/Java fixtures with populated `sourceLines`, and `get_lines` (Surgical Context) works on all four languages. Expected extractor fixtures regenerated; `parseAnchor` round-trips every emitted anchor.
19
+
20
+ ---
21
+
13
22
  ## [8.16.1] — 2026-07-12
14
23
 
15
24
  Patch release — **benchmark hygiene**; the published package is unchanged (scripts + tests only).
package/README.md CHANGED
@@ -122,8 +122,8 @@ Ask → Rank → Context → Validate → Judge → Learn
122
122
 
123
123
  <!--SM:benchmarkBlock-->
124
124
  ```
125
- Benchmark : sigmap-v8.16-main (21 repositories, including R language)
126
- Date : 2026-07-11
125
+ Benchmark : sigmap-v8.17-main (21 repositories, including R language)
126
+ Date : 2026-07-12
127
127
 
128
128
  Hit@5 : 87.8% (baseline 13.6% — 6.5× lift)
129
129
  Token reduction: 97.0% (across 21 repos)
package/gen-context.js CHANGED
@@ -5229,8 +5229,12 @@ __factories["./src/extractors/cpp"] = function(module, exports) {
5229
5229
  // ── ./src/extractors/csharp ──
5230
5230
  __factories["./src/extractors/csharp"] = function(module, exports) {
5231
5231
 
5232
+ const { lineAt, withAnchor } = __require('./src/extractors/line-anchor');
5233
+
5232
5234
  /**
5233
5235
  * Extract signatures from C# source code.
5236
+ * Signatures carry `:start-end` line anchors (Surgical Context); the comment
5237
+ * strip below is newline-preserving so anchor lines match the original file.
5234
5238
  * @param {string} src - Raw file content
5235
5239
  * @returns {string[]} Array of signature strings
5236
5240
  */
@@ -5240,14 +5244,18 @@ __factories["./src/extractors/csharp"] = function(module, exports) {
5240
5244
 
5241
5245
  const stripped = src
5242
5246
  .replace(/\/\/.*$/gm, '')
5243
- .replace(/\/\*[\s\S]*?\*\//g, '');
5247
+ .replace(/\/\*[\s\S]*?\*\//g, (s) => s.replace(/[^\n]/g, ' '));
5244
5248
 
5245
5249
  // Classes and interfaces
5246
5250
  const typeRe = /^\s*(?:public\s+|internal\s+|protected\s+)?(?:abstract\s+|sealed\s+|static\s+)?(class|interface|enum|record|struct)\s+(\w+)(?:<[^{]*>)?(?:\s*:\s*[\w<>, .]+)?\s*\{/gm;
5247
5251
  for (const m of stripped.matchAll(typeRe)) {
5248
- sigs.push(`${m[1]} ${m[2]}`);
5249
- const block = extractBlock(stripped, m.index + m[0].length);
5250
- for (const meth of extractMembers(block)) sigs.push(` ${meth}`);
5252
+ const declIdx = m.index + (m[0].length - m[0].trimStart().length);
5253
+ const bodyStart = m.index + m[0].length;
5254
+ const block = extractBlock(stripped, bodyStart);
5255
+ sigs.push(withAnchor(`${m[1]} ${m[2]}`, lineAt(stripped, declIdx), lineAt(stripped, bodyStart + block.length)));
5256
+ for (const meth of extractMembers(block)) {
5257
+ sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + meth.declIdx), lineAt(stripped, bodyStart + meth.endIdx)));
5258
+ }
5251
5259
  }
5252
5260
 
5253
5261
  return sigs.slice(0, 25);
@@ -5270,7 +5278,11 @@ __factories["./src/extractors/csharp"] = function(module, exports) {
5270
5278
  for (const m of block.matchAll(methodRe)) {
5271
5279
  const ret = normalizeType(m[1]);
5272
5280
  const retStr = ret ? ` → ${ret}` : '';
5273
- members.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
5281
+ members.push({
5282
+ text: `${m[2]}(${normalizeParams(m[3])})${retStr}`,
5283
+ declIdx: m.index + (m[0].length - m[0].trimStart().length),
5284
+ endIdx: m.index + m[0].length,
5285
+ });
5274
5286
  }
5275
5287
  return members.slice(0, 8);
5276
5288
  }
@@ -5878,8 +5890,12 @@ __factories["./src/extractors/generic"] = function(module, exports) {
5878
5890
  // ── ./src/extractors/go ──
5879
5891
  __factories["./src/extractors/go"] = function(module, exports) {
5880
5892
 
5893
+ const { lineAt, withAnchor } = __require('./src/extractors/line-anchor');
5894
+
5881
5895
  /**
5882
5896
  * Extract signatures from Go source code.
5897
+ * Signatures carry `:start-end` line anchors (Surgical Context); the comment
5898
+ * strip below is newline-preserving so anchor lines match the original file.
5883
5899
  * @param {string} src - Raw file content
5884
5900
  * @returns {string[]} Array of signature strings
5885
5901
  */
@@ -5889,26 +5905,34 @@ __factories["./src/extractors/go"] = function(module, exports) {
5889
5905
 
5890
5906
  const stripped = src
5891
5907
  .replace(/\/\/.*$/gm, '')
5892
- .replace(/\/\*[\s\S]*?\*\//g, '');
5908
+ .replace(/\/\*[\s\S]*?\*\//g, (s) => s.replace(/[^\n]/g, ' '));
5909
+
5910
+ // Index of the closing brace for a block opened just before startIndex.
5911
+ const blockEndIdx = (startIndex) => startIndex + extractBlock(stripped, startIndex).length;
5893
5912
 
5894
5913
  // Structs
5895
5914
  for (const m of stripped.matchAll(/^type\s+(\w+)\s+struct\s*\{/gm)) {
5896
- sigs.push(`type ${m[1]} struct`);
5915
+ const end = blockEndIdx(m.index + m[0].length);
5916
+ sigs.push(withAnchor(`type ${m[1]} struct`, lineAt(stripped, m.index), lineAt(stripped, end)));
5897
5917
  }
5898
5918
 
5899
5919
  // Interfaces
5900
5920
  for (const m of stripped.matchAll(/^type\s+(\w+)\s+interface\s*\{/gm)) {
5901
- sigs.push(`type ${m[1]} interface`);
5902
- const block = extractBlock(stripped, m.index + m[0].length);
5903
- for (const method of extractInterfaceMethods(block)) sigs.push(` ${method}`);
5921
+ const bodyStart = m.index + m[0].length;
5922
+ const block = extractBlock(stripped, bodyStart);
5923
+ sigs.push(withAnchor(`type ${m[1]} interface`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
5924
+ for (const meth of extractInterfaceMethods(block)) {
5925
+ sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + meth.declIdx), lineAt(stripped, bodyStart + meth.endIdx)));
5926
+ }
5904
5927
  }
5905
5928
 
5906
5929
  // Functions and methods — capture return type between ) and {
5907
5930
  for (const m of stripped.matchAll(/^func\s+(?:\((\w+)\s+[\w*]+\)\s+)?(\w+)\s*\(([^)]*)\)([^{]*)\{/gm)) {
5908
5931
  const receiver = m[1] ? `(${m[1]}) ` : '';
5909
5932
  const retType = m[4] ? m[4].trim().replace(/\s+/g, ' ') : '';
5910
- const retStr = retType ? ` \u2192 ${retType.slice(0, 30)}` : '';
5911
- sigs.push(`func ${receiver}${m[2]}(${normalizeParams(m[3])})${retStr}`);
5933
+ const retStr = retType ? ` ${retType.slice(0, 30)}` : '';
5934
+ const end = blockEndIdx(m.index + m[0].length);
5935
+ sigs.push(withAnchor(`func ${receiver}${m[2]}(${normalizeParams(m[3])})${retStr}`, lineAt(stripped, m.index), lineAt(stripped, end)));
5912
5936
  }
5913
5937
 
5914
5938
  return sigs.slice(0, 25);
@@ -5929,8 +5953,12 @@ __factories["./src/extractors/go"] = function(module, exports) {
5929
5953
  const methods = [];
5930
5954
  for (const m of block.matchAll(/^\s+(\w+)\s*\(([^)]*)\)([^\n]*)/gm)) {
5931
5955
  const retType = m[3] ? m[3].trim().replace(/\s+/g, ' ') : '';
5932
- const retStr = retType ? ` \u2192 ${retType.slice(0, 30)}` : '';
5933
- methods.push(`${m[1]}(${normalizeParams(m[2])})${retStr}`);
5956
+ const retStr = retType ? ` ${retType.slice(0, 30)}` : '';
5957
+ methods.push({
5958
+ text: `${m[1]}(${normalizeParams(m[2])})${retStr}`,
5959
+ declIdx: m.index + (m[0].length - m[0].trimStart().length),
5960
+ endIdx: m.index + m[0].length,
5961
+ });
5934
5962
  }
5935
5963
  return methods.slice(0, 8);
5936
5964
  }
@@ -6060,8 +6088,12 @@ __factories["./src/extractors/html"] = function(module, exports) {
6060
6088
  // ── ./src/extractors/java ──
6061
6089
  __factories["./src/extractors/java"] = function(module, exports) {
6062
6090
 
6091
+ const { lineAt, withAnchor } = __require('./src/extractors/line-anchor');
6092
+
6063
6093
  /**
6064
6094
  * Extract signatures from Java source code.
6095
+ * Signatures carry `:start-end` line anchors (Surgical Context); the comment
6096
+ * strip below is newline-preserving so anchor lines match the original file.
6065
6097
  * @param {string} src - Raw file content
6066
6098
  * @returns {string[]} Array of signature strings
6067
6099
  */
@@ -6071,14 +6103,17 @@ __factories["./src/extractors/java"] = function(module, exports) {
6071
6103
 
6072
6104
  const stripped = src
6073
6105
  .replace(/\/\/.*$/gm, '')
6074
- .replace(/\/\*[\s\S]*?\*\//g, '');
6106
+ .replace(/\/\*[\s\S]*?\*\//g, (s) => s.replace(/[^\n]/g, ' '));
6075
6107
 
6076
6108
  // Classes and interfaces
6077
6109
  const typeRegex = /^(?:public\s+|protected\s+)?(?:abstract\s+|final\s+)?(class|interface|enum)\s+(\w+)(?:\s+extends\s+[\w<>, .]+)?(?:\s+implements\s+[\w<>, .]+)?\s*\{/gm;
6078
6110
  for (const m of stripped.matchAll(typeRegex)) {
6079
- sigs.push(`${m[1]} ${m[2]}`);
6080
- const block = extractBlock(stripped, m.index + m[0].length);
6081
- for (const meth of extractMembers(block)) sigs.push(` ${meth}`);
6111
+ const bodyStart = m.index + m[0].length;
6112
+ const block = extractBlock(stripped, bodyStart);
6113
+ sigs.push(withAnchor(`${m[1]} ${m[2]}`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
6114
+ for (const meth of extractMembers(block)) {
6115
+ sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + meth.declIdx), lineAt(stripped, bodyStart + meth.endIdx)));
6116
+ }
6082
6117
  }
6083
6118
 
6084
6119
  return sigs.slice(0, 25);
@@ -6102,7 +6137,11 @@ __factories["./src/extractors/java"] = function(module, exports) {
6102
6137
  for (const m of block.matchAll(methodRe)) {
6103
6138
  const ret = normalizeType(m[1]);
6104
6139
  const retStr = ret ? ` → ${ret}` : '';
6105
- members.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
6140
+ members.push({
6141
+ text: `${m[2]}(${normalizeParams(m[3])})${retStr}`,
6142
+ declIdx: m.index + (m[0].length - m[0].trimStart().length),
6143
+ endIdx: m.index + m[0].length,
6144
+ });
6106
6145
  }
6107
6146
  return members.slice(0, 8);
6108
6147
  }
@@ -7513,8 +7552,12 @@ __factories["./src/extractors/ruby"] = function(module, exports) {
7513
7552
  // ── ./src/extractors/rust ──
7514
7553
  __factories["./src/extractors/rust"] = function(module, exports) {
7515
7554
 
7555
+ const { lineAt, withAnchor } = __require('./src/extractors/line-anchor');
7556
+
7516
7557
  /**
7517
7558
  * Extract signatures from Rust source code.
7559
+ * Signatures carry `:start-end` line anchors (Surgical Context); the comment
7560
+ * strip below is newline-preserving so anchor lines match the original file.
7518
7561
  * @param {string} src - Raw file content
7519
7562
  * @returns {string[]} Array of signature strings
7520
7563
  */
@@ -7524,35 +7567,55 @@ __factories["./src/extractors/rust"] = function(module, exports) {
7524
7567
 
7525
7568
  const stripped = src
7526
7569
  .replace(/\/\/.*$/gm, '')
7527
- .replace(/\/\*[\s\S]*?\*\//g, '');
7570
+ .replace(/\/\*[\s\S]*?\*\//g, (s) => s.replace(/[^\n]/g, ' '));
7571
+
7572
+ // Anchor range for a declaration at declIdx whose header ends at afterIdx:
7573
+ // if a `{` body follows, range to its closing brace; else single-line.
7574
+ const rangeFor = (declIdx, afterIdx) => {
7575
+ let k = afterIdx;
7576
+ while (k < stripped.length && /[ \t]/.test(stripped[k])) k++;
7577
+ if (stripped[k] === '{') {
7578
+ const end = k + 1 + extractBlock(stripped, k + 1).length;
7579
+ return [lineAt(stripped, declIdx), lineAt(stripped, end)];
7580
+ }
7581
+ const line = lineAt(stripped, declIdx);
7582
+ return [line, line];
7583
+ };
7528
7584
 
7529
7585
  // Structs
7530
7586
  for (const m of stripped.matchAll(/^pub\s+struct\s+(\w+)(?:<[^{]*>)?/gm)) {
7531
- sigs.push(`pub struct ${m[1]}`);
7587
+ const [s, e] = rangeFor(m.index, m.index + m[0].length);
7588
+ sigs.push(withAnchor(`pub struct ${m[1]}`, s, e));
7532
7589
  }
7533
7590
 
7534
7591
  // Enums
7535
7592
  for (const m of stripped.matchAll(/^pub\s+enum\s+(\w+)(?:<[^{]*>)?/gm)) {
7536
- sigs.push(`pub enum ${m[1]}`);
7593
+ const [s, e] = rangeFor(m.index, m.index + m[0].length);
7594
+ sigs.push(withAnchor(`pub enum ${m[1]}`, s, e));
7537
7595
  }
7538
7596
 
7539
7597
  // Traits
7540
7598
  for (const m of stripped.matchAll(/^pub\s+trait\s+(\w+)(?:<[^{]*>)?/gm)) {
7541
- sigs.push(`pub trait ${m[1]}`);
7599
+ const [s, e] = rangeFor(m.index, m.index + m[0].length);
7600
+ sigs.push(withAnchor(`pub trait ${m[1]}`, s, e));
7542
7601
  }
7543
7602
 
7544
7603
  // impl blocks
7545
7604
  for (const m of stripped.matchAll(/^impl(?:<[^>]*>)?\s+(?:[\w:]+\s+for\s+)?(\w+)(?:<[^{]*>)?\s*\{/gm)) {
7546
- sigs.push(`impl ${m[1]}`);
7547
- const block = extractBlock(stripped, m.index + m[0].length);
7548
- for (const fn of extractMethods(block)) sigs.push(` ${fn}`);
7605
+ const bodyStart = m.index + m[0].length;
7606
+ const block = extractBlock(stripped, bodyStart);
7607
+ sigs.push(withAnchor(`impl ${m[1]}`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
7608
+ for (const fn of extractMethods(block)) {
7609
+ sigs.push(withAnchor(` ${fn.text}`, lineAt(stripped, bodyStart + fn.declIdx), lineAt(stripped, bodyStart + fn.endIdx)));
7610
+ }
7549
7611
  }
7550
7612
 
7551
7613
  // Top-level pub fns — capture everything after ) up to { or ; for return type
7552
7614
  for (const m of stripped.matchAll(/^pub(?:\s+async)?\s+fn\s+(\w+)(?:<[^(]*>)?\s*\(([^)]*)\)([^{;]*)/gm)) {
7553
7615
  const asyncKw = m[0].includes('async') ? 'async ' : '';
7554
7616
  const retStr = extractReturnType(m[3]);
7555
- sigs.push(`pub ${asyncKw}fn ${m[1]}(${normalizeParams(m[2])})${retStr}`);
7617
+ const [s, e] = rangeFor(m.index, m.index + m[0].length);
7618
+ sigs.push(withAnchor(`pub ${asyncKw}fn ${m[1]}(${normalizeParams(m[2])})${retStr}`, s, e));
7556
7619
  }
7557
7620
 
7558
7621
  return sigs.slice(0, 25);
@@ -7574,7 +7637,11 @@ __factories["./src/extractors/rust"] = function(module, exports) {
7574
7637
  for (const m of block.matchAll(/^\s+pub(?:\s+async)?\s+fn\s+(\w+)(?:<[^(]*>)?\s*\(([^)]*)\)([^{;]*)/gm)) {
7575
7638
  const asyncKw = m[0].includes('async') ? 'async ' : '';
7576
7639
  const retStr = extractReturnType(m[3]);
7577
- methods.push(`pub ${asyncKw}fn ${m[1]}(${normalizeParams(m[2])})${retStr}`);
7640
+ methods.push({
7641
+ text: `pub ${asyncKw}fn ${m[1]}(${normalizeParams(m[2])})${retStr}`,
7642
+ declIdx: m.index + (m[0].length - m[0].trimStart().length),
7643
+ endIdx: m.index + m[0].length,
7644
+ });
7578
7645
  }
7579
7646
  return methods.slice(0, 8);
7580
7647
  }
@@ -7589,7 +7656,7 @@ __factories["./src/extractors/rust"] = function(module, exports) {
7589
7656
  const m = afterParen.match(/->\s*([^{;]+)/);
7590
7657
  if (!m) return '';
7591
7658
  const rt = m[1].trim().replace(/\s+/g, ' ');
7592
- return ` \u2192 ${rt.length > 30 ? rt.slice(0, 27) + '...' : rt}`;
7659
+ return ` ${rt.length > 30 ? rt.slice(0, 27) + '...' : rt}`;
7593
7660
  }
7594
7661
 
7595
7662
  module.exports = { extract };
@@ -14414,7 +14481,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
14414
14481
 
14415
14482
  const SERVER_INFO = {
14416
14483
  name: 'sigmap',
14417
- version: '8.16.1',
14484
+ version: '8.17.0',
14418
14485
  description: 'SigMap MCP server — code signatures on demand',
14419
14486
  };
14420
14487
 
@@ -19455,7 +19522,7 @@ function __tryGit(args, opts = {}) {
19455
19522
  catch (_) { return ''; }
19456
19523
  }
19457
19524
 
19458
- const VERSION = '8.16.1';
19525
+ const VERSION = '8.17.0';
19459
19526
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
19460
19527
 
19461
19528
  function requireSourceOrBundled(key) {
package/llms-full.txt CHANGED
@@ -11,13 +11,13 @@ 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.16.1 | Benchmark: sigmap-v8.16-main (2026-07-11)
14
+ # Version: 8.17.0 | Benchmark: sigmap-v8.17-main (2026-07-12)
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.16-main, 2026-07-11)
20
+ ## Core metrics (benchmark: sigmap-v8.17-main, 2026-07-12)
21
21
 
22
22
  | Metric | Without SigMap | With SigMap |
23
23
  |--------|----------------|-------------|
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.16.1 | Benchmark: sigmap-v8.16-main (2026-07-11)
14
+ # Version: 8.17.0 | Benchmark: sigmap-v8.17-main (2026-07-12)
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,7 +23,7 @@ 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.16-main, 2026-07-11)
26
+ ## Core metrics (benchmark: sigmap-v8.17-main, 2026-07-12)
27
27
 
28
28
  - hit@5 retrieval: 87.8% vs 13.6% random baseline (6.5× lift)
29
29
  - Token reduction: 97.0% average across benchmark repos
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "8.16.1",
3
+ "version": "8.17.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": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-cli",
3
- "version": "8.16.1",
3
+ "version": "8.17.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.16.1",
3
+ "version": "8.17.0",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -1,7 +1,11 @@
1
1
  'use strict';
2
2
 
3
+ const { lineAt, withAnchor } = require('./line-anchor');
4
+
3
5
  /**
4
6
  * Extract signatures from C# source code.
7
+ * Signatures carry `:start-end` line anchors (Surgical Context); the comment
8
+ * strip below is newline-preserving so anchor lines match the original file.
5
9
  * @param {string} src - Raw file content
6
10
  * @returns {string[]} Array of signature strings
7
11
  */
@@ -11,14 +15,18 @@ function extract(src) {
11
15
 
12
16
  const stripped = src
13
17
  .replace(/\/\/.*$/gm, '')
14
- .replace(/\/\*[\s\S]*?\*\//g, '');
18
+ .replace(/\/\*[\s\S]*?\*\//g, (s) => s.replace(/[^\n]/g, ' '));
15
19
 
16
20
  // Classes and interfaces
17
21
  const typeRe = /^\s*(?:public\s+|internal\s+|protected\s+)?(?:abstract\s+|sealed\s+|static\s+)?(class|interface|enum|record|struct)\s+(\w+)(?:<[^{]*>)?(?:\s*:\s*[\w<>, .]+)?\s*\{/gm;
18
22
  for (const m of stripped.matchAll(typeRe)) {
19
- sigs.push(`${m[1]} ${m[2]}`);
20
- const block = extractBlock(stripped, m.index + m[0].length);
21
- for (const meth of extractMembers(block)) sigs.push(` ${meth}`);
23
+ const declIdx = m.index + (m[0].length - m[0].trimStart().length);
24
+ const bodyStart = m.index + m[0].length;
25
+ const block = extractBlock(stripped, bodyStart);
26
+ sigs.push(withAnchor(`${m[1]} ${m[2]}`, lineAt(stripped, declIdx), lineAt(stripped, bodyStart + block.length)));
27
+ for (const meth of extractMembers(block)) {
28
+ sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + meth.declIdx), lineAt(stripped, bodyStart + meth.endIdx)));
29
+ }
22
30
  }
23
31
 
24
32
  return sigs.slice(0, 25);
@@ -41,7 +49,11 @@ function extractMembers(block) {
41
49
  for (const m of block.matchAll(methodRe)) {
42
50
  const ret = normalizeType(m[1]);
43
51
  const retStr = ret ? ` → ${ret}` : '';
44
- members.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
52
+ members.push({
53
+ text: `${m[2]}(${normalizeParams(m[3])})${retStr}`,
54
+ declIdx: m.index + (m[0].length - m[0].trimStart().length),
55
+ endIdx: m.index + m[0].length,
56
+ });
45
57
  }
46
58
  return members.slice(0, 8);
47
59
  }
@@ -1,7 +1,11 @@
1
1
  'use strict';
2
2
 
3
+ const { lineAt, withAnchor } = require('./line-anchor');
4
+
3
5
  /**
4
6
  * Extract signatures from Go source code.
7
+ * Signatures carry `:start-end` line anchors (Surgical Context); the comment
8
+ * strip below is newline-preserving so anchor lines match the original file.
5
9
  * @param {string} src - Raw file content
6
10
  * @returns {string[]} Array of signature strings
7
11
  */
@@ -11,26 +15,34 @@ function extract(src) {
11
15
 
12
16
  const stripped = src
13
17
  .replace(/\/\/.*$/gm, '')
14
- .replace(/\/\*[\s\S]*?\*\//g, '');
18
+ .replace(/\/\*[\s\S]*?\*\//g, (s) => s.replace(/[^\n]/g, ' '));
19
+
20
+ // Index of the closing brace for a block opened just before startIndex.
21
+ const blockEndIdx = (startIndex) => startIndex + extractBlock(stripped, startIndex).length;
15
22
 
16
23
  // Structs
17
24
  for (const m of stripped.matchAll(/^type\s+(\w+)\s+struct\s*\{/gm)) {
18
- sigs.push(`type ${m[1]} struct`);
25
+ const end = blockEndIdx(m.index + m[0].length);
26
+ sigs.push(withAnchor(`type ${m[1]} struct`, lineAt(stripped, m.index), lineAt(stripped, end)));
19
27
  }
20
28
 
21
29
  // Interfaces
22
30
  for (const m of stripped.matchAll(/^type\s+(\w+)\s+interface\s*\{/gm)) {
23
- sigs.push(`type ${m[1]} interface`);
24
- const block = extractBlock(stripped, m.index + m[0].length);
25
- for (const method of extractInterfaceMethods(block)) sigs.push(` ${method}`);
31
+ const bodyStart = m.index + m[0].length;
32
+ const block = extractBlock(stripped, bodyStart);
33
+ sigs.push(withAnchor(`type ${m[1]} interface`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
34
+ for (const meth of extractInterfaceMethods(block)) {
35
+ sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + meth.declIdx), lineAt(stripped, bodyStart + meth.endIdx)));
36
+ }
26
37
  }
27
38
 
28
39
  // Functions and methods — capture return type between ) and {
29
40
  for (const m of stripped.matchAll(/^func\s+(?:\((\w+)\s+[\w*]+\)\s+)?(\w+)\s*\(([^)]*)\)([^{]*)\{/gm)) {
30
41
  const receiver = m[1] ? `(${m[1]}) ` : '';
31
42
  const retType = m[4] ? m[4].trim().replace(/\s+/g, ' ') : '';
32
- const retStr = retType ? ` \u2192 ${retType.slice(0, 30)}` : '';
33
- sigs.push(`func ${receiver}${m[2]}(${normalizeParams(m[3])})${retStr}`);
43
+ const retStr = retType ? ` ${retType.slice(0, 30)}` : '';
44
+ const end = blockEndIdx(m.index + m[0].length);
45
+ sigs.push(withAnchor(`func ${receiver}${m[2]}(${normalizeParams(m[3])})${retStr}`, lineAt(stripped, m.index), lineAt(stripped, end)));
34
46
  }
35
47
 
36
48
  return sigs.slice(0, 25);
@@ -51,8 +63,12 @@ function extractInterfaceMethods(block) {
51
63
  const methods = [];
52
64
  for (const m of block.matchAll(/^\s+(\w+)\s*\(([^)]*)\)([^\n]*)/gm)) {
53
65
  const retType = m[3] ? m[3].trim().replace(/\s+/g, ' ') : '';
54
- const retStr = retType ? ` \u2192 ${retType.slice(0, 30)}` : '';
55
- methods.push(`${m[1]}(${normalizeParams(m[2])})${retStr}`);
66
+ const retStr = retType ? ` ${retType.slice(0, 30)}` : '';
67
+ methods.push({
68
+ text: `${m[1]}(${normalizeParams(m[2])})${retStr}`,
69
+ declIdx: m.index + (m[0].length - m[0].trimStart().length),
70
+ endIdx: m.index + m[0].length,
71
+ });
56
72
  }
57
73
  return methods.slice(0, 8);
58
74
  }
@@ -1,7 +1,11 @@
1
1
  'use strict';
2
2
 
3
+ const { lineAt, withAnchor } = require('./line-anchor');
4
+
3
5
  /**
4
6
  * Extract signatures from Java source code.
7
+ * Signatures carry `:start-end` line anchors (Surgical Context); the comment
8
+ * strip below is newline-preserving so anchor lines match the original file.
5
9
  * @param {string} src - Raw file content
6
10
  * @returns {string[]} Array of signature strings
7
11
  */
@@ -11,14 +15,17 @@ function extract(src) {
11
15
 
12
16
  const stripped = src
13
17
  .replace(/\/\/.*$/gm, '')
14
- .replace(/\/\*[\s\S]*?\*\//g, '');
18
+ .replace(/\/\*[\s\S]*?\*\//g, (s) => s.replace(/[^\n]/g, ' '));
15
19
 
16
20
  // Classes and interfaces
17
21
  const typeRegex = /^(?:public\s+|protected\s+)?(?:abstract\s+|final\s+)?(class|interface|enum)\s+(\w+)(?:\s+extends\s+[\w<>, .]+)?(?:\s+implements\s+[\w<>, .]+)?\s*\{/gm;
18
22
  for (const m of stripped.matchAll(typeRegex)) {
19
- sigs.push(`${m[1]} ${m[2]}`);
20
- const block = extractBlock(stripped, m.index + m[0].length);
21
- for (const meth of extractMembers(block)) sigs.push(` ${meth}`);
23
+ const bodyStart = m.index + m[0].length;
24
+ const block = extractBlock(stripped, bodyStart);
25
+ sigs.push(withAnchor(`${m[1]} ${m[2]}`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
26
+ for (const meth of extractMembers(block)) {
27
+ sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + meth.declIdx), lineAt(stripped, bodyStart + meth.endIdx)));
28
+ }
22
29
  }
23
30
 
24
31
  return sigs.slice(0, 25);
@@ -42,7 +49,11 @@ function extractMembers(block) {
42
49
  for (const m of block.matchAll(methodRe)) {
43
50
  const ret = normalizeType(m[1]);
44
51
  const retStr = ret ? ` → ${ret}` : '';
45
- members.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
52
+ members.push({
53
+ text: `${m[2]}(${normalizeParams(m[3])})${retStr}`,
54
+ declIdx: m.index + (m[0].length - m[0].trimStart().length),
55
+ endIdx: m.index + m[0].length,
56
+ });
46
57
  }
47
58
  return members.slice(0, 8);
48
59
  }
@@ -1,7 +1,11 @@
1
1
  'use strict';
2
2
 
3
+ const { lineAt, withAnchor } = require('./line-anchor');
4
+
3
5
  /**
4
6
  * Extract signatures from Rust source code.
7
+ * Signatures carry `:start-end` line anchors (Surgical Context); the comment
8
+ * strip below is newline-preserving so anchor lines match the original file.
5
9
  * @param {string} src - Raw file content
6
10
  * @returns {string[]} Array of signature strings
7
11
  */
@@ -11,35 +15,55 @@ function extract(src) {
11
15
 
12
16
  const stripped = src
13
17
  .replace(/\/\/.*$/gm, '')
14
- .replace(/\/\*[\s\S]*?\*\//g, '');
18
+ .replace(/\/\*[\s\S]*?\*\//g, (s) => s.replace(/[^\n]/g, ' '));
19
+
20
+ // Anchor range for a declaration at declIdx whose header ends at afterIdx:
21
+ // if a `{` body follows, range to its closing brace; else single-line.
22
+ const rangeFor = (declIdx, afterIdx) => {
23
+ let k = afterIdx;
24
+ while (k < stripped.length && /[ \t]/.test(stripped[k])) k++;
25
+ if (stripped[k] === '{') {
26
+ const end = k + 1 + extractBlock(stripped, k + 1).length;
27
+ return [lineAt(stripped, declIdx), lineAt(stripped, end)];
28
+ }
29
+ const line = lineAt(stripped, declIdx);
30
+ return [line, line];
31
+ };
15
32
 
16
33
  // Structs
17
34
  for (const m of stripped.matchAll(/^pub\s+struct\s+(\w+)(?:<[^{]*>)?/gm)) {
18
- sigs.push(`pub struct ${m[1]}`);
35
+ const [s, e] = rangeFor(m.index, m.index + m[0].length);
36
+ sigs.push(withAnchor(`pub struct ${m[1]}`, s, e));
19
37
  }
20
38
 
21
39
  // Enums
22
40
  for (const m of stripped.matchAll(/^pub\s+enum\s+(\w+)(?:<[^{]*>)?/gm)) {
23
- sigs.push(`pub enum ${m[1]}`);
41
+ const [s, e] = rangeFor(m.index, m.index + m[0].length);
42
+ sigs.push(withAnchor(`pub enum ${m[1]}`, s, e));
24
43
  }
25
44
 
26
45
  // Traits
27
46
  for (const m of stripped.matchAll(/^pub\s+trait\s+(\w+)(?:<[^{]*>)?/gm)) {
28
- sigs.push(`pub trait ${m[1]}`);
47
+ const [s, e] = rangeFor(m.index, m.index + m[0].length);
48
+ sigs.push(withAnchor(`pub trait ${m[1]}`, s, e));
29
49
  }
30
50
 
31
51
  // impl blocks
32
52
  for (const m of stripped.matchAll(/^impl(?:<[^>]*>)?\s+(?:[\w:]+\s+for\s+)?(\w+)(?:<[^{]*>)?\s*\{/gm)) {
33
- sigs.push(`impl ${m[1]}`);
34
- const block = extractBlock(stripped, m.index + m[0].length);
35
- for (const fn of extractMethods(block)) sigs.push(` ${fn}`);
53
+ const bodyStart = m.index + m[0].length;
54
+ const block = extractBlock(stripped, bodyStart);
55
+ sigs.push(withAnchor(`impl ${m[1]}`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
56
+ for (const fn of extractMethods(block)) {
57
+ sigs.push(withAnchor(` ${fn.text}`, lineAt(stripped, bodyStart + fn.declIdx), lineAt(stripped, bodyStart + fn.endIdx)));
58
+ }
36
59
  }
37
60
 
38
61
  // Top-level pub fns — capture everything after ) up to { or ; for return type
39
62
  for (const m of stripped.matchAll(/^pub(?:\s+async)?\s+fn\s+(\w+)(?:<[^(]*>)?\s*\(([^)]*)\)([^{;]*)/gm)) {
40
63
  const asyncKw = m[0].includes('async') ? 'async ' : '';
41
64
  const retStr = extractReturnType(m[3]);
42
- sigs.push(`pub ${asyncKw}fn ${m[1]}(${normalizeParams(m[2])})${retStr}`);
65
+ const [s, e] = rangeFor(m.index, m.index + m[0].length);
66
+ sigs.push(withAnchor(`pub ${asyncKw}fn ${m[1]}(${normalizeParams(m[2])})${retStr}`, s, e));
43
67
  }
44
68
 
45
69
  return sigs.slice(0, 25);
@@ -61,7 +85,11 @@ function extractMethods(block) {
61
85
  for (const m of block.matchAll(/^\s+pub(?:\s+async)?\s+fn\s+(\w+)(?:<[^(]*>)?\s*\(([^)]*)\)([^{;]*)/gm)) {
62
86
  const asyncKw = m[0].includes('async') ? 'async ' : '';
63
87
  const retStr = extractReturnType(m[3]);
64
- methods.push(`pub ${asyncKw}fn ${m[1]}(${normalizeParams(m[2])})${retStr}`);
88
+ methods.push({
89
+ text: `pub ${asyncKw}fn ${m[1]}(${normalizeParams(m[2])})${retStr}`,
90
+ declIdx: m.index + (m[0].length - m[0].trimStart().length),
91
+ endIdx: m.index + m[0].length,
92
+ });
65
93
  }
66
94
  return methods.slice(0, 8);
67
95
  }
@@ -76,7 +104,7 @@ function extractReturnType(afterParen) {
76
104
  const m = afterParen.match(/->\s*([^{;]+)/);
77
105
  if (!m) return '';
78
106
  const rt = m[1].trim().replace(/\s+/g, ' ');
79
- return ` \u2192 ${rt.length > 30 ? rt.slice(0, 27) + '...' : rt}`;
107
+ return ` ${rt.length > 30 ? rt.slice(0, 27) + '...' : rt}`;
80
108
  }
81
109
 
82
110
  module.exports = { extract };
package/src/mcp/server.js CHANGED
@@ -18,7 +18,7 @@ const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, exp
18
18
 
19
19
  const SERVER_INFO = {
20
20
  name: 'sigmap',
21
- version: '8.16.1',
21
+ version: '8.17.0',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24