sigmap 6.5.0 → 6.5.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/AGENTS.md +106 -103
- package/CHANGELOG.md +15 -0
- package/README.md +6 -6
- package/gen-context.js +2 -2
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/mcp/server.js +1 -1
- package/src/retrieval/ranker.js +88 -23
package/AGENTS.md
CHANGED
|
@@ -47,6 +47,26 @@ Use this marker block for all appendable context files:
|
|
|
47
47
|
<!-- Updated by gen-context.js -->
|
|
48
48
|
# Code signatures
|
|
49
49
|
|
|
50
|
+
## SigMap commands
|
|
51
|
+
|
|
52
|
+
| When | Command |
|
|
53
|
+
|------|---------|
|
|
54
|
+
| Before answering a question | `sigmap ask "<your question>"` |
|
|
55
|
+
| After code changes | `sigmap validate` |
|
|
56
|
+
| To query by topic | `sigmap --query "<topic>"` |
|
|
57
|
+
|
|
58
|
+
Always run `sigmap ask` or `sigmap --query` before searching for files relevant to a task.
|
|
59
|
+
## changes (last 5 commits — 6 minutes ago)
|
|
60
|
+
```
|
|
61
|
+
src/config/loader.js +_legacyDetectAutoSrcDirs ~detectAutoSrcDirs
|
|
62
|
+
src/discovery/language-detector.js +detectLanguages +_walkDepth
|
|
63
|
+
src/discovery/framework-detector.js +detectFrameworks +_readDeps +_readFile +_existsAnywhere
|
|
64
|
+
src/discovery/source-root-resolver.js +resolveSourceRoots +_detectMonorepo +_enumerateCandidates +_applySpecialRules
|
|
65
|
+
src/discovery/source-root-scorer.js +getRecentlyChangedDirs +scoreCandidate +_countSourceFiles
|
|
66
|
+
src/discovery/sigmapignore.js +loadIgnorePatterns +matchesIgnorePattern
|
|
67
|
+
src/retrieval/ranker.js +_computePenalty ~scoreFile ~rank ~buildSigIndex
|
|
68
|
+
```
|
|
69
|
+
|
|
50
70
|
## packages
|
|
51
71
|
|
|
52
72
|
### packages/cli/index.js
|
|
@@ -104,15 +124,7 @@ function score(cwd) → { * score: number, * grad
|
|
|
104
124
|
function adapt(context, adapterName, opts = {}) → string
|
|
105
125
|
```
|
|
106
126
|
|
|
107
|
-
### packages/adapters/
|
|
108
|
-
```
|
|
109
|
-
module.exports = { name, format, outputPath, write }
|
|
110
|
-
function format(context, opts = {}) → string
|
|
111
|
-
function outputPath(cwd) → string
|
|
112
|
-
function write(context, cwd, opts = {})
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### packages/adapters/claude.js
|
|
127
|
+
### packages/adapters/copilot.js
|
|
116
128
|
```
|
|
117
129
|
module.exports = { name, format, outputPath, write }
|
|
118
130
|
function format(context, opts = {}) → string
|
|
@@ -121,13 +133,12 @@ function outputPath(cwd) → string
|
|
|
121
133
|
function write(context, cwd, opts = {})
|
|
122
134
|
```
|
|
123
135
|
|
|
124
|
-
### packages/adapters/
|
|
136
|
+
### packages/adapters/cursor.js
|
|
125
137
|
```
|
|
126
|
-
module.exports = { name, format, outputPath
|
|
138
|
+
module.exports = { name, format, outputPath }
|
|
127
139
|
function format(context, opts = {}) → string
|
|
128
140
|
function _confidenceMeta(opts)
|
|
129
141
|
function outputPath(cwd) → string
|
|
130
|
-
function write(context, cwd, opts = {})
|
|
131
142
|
```
|
|
132
143
|
|
|
133
144
|
### packages/adapters/gemini.js
|
|
@@ -139,14 +150,6 @@ function write(context, cwd, opts = {})
|
|
|
139
150
|
function _confidenceMeta(opts)
|
|
140
151
|
```
|
|
141
152
|
|
|
142
|
-
### packages/adapters/cursor.js
|
|
143
|
-
```
|
|
144
|
-
module.exports = { name, format, outputPath }
|
|
145
|
-
function format(context, opts = {}) → string
|
|
146
|
-
function _confidenceMeta(opts)
|
|
147
|
-
function outputPath(cwd) → string
|
|
148
|
-
```
|
|
149
|
-
|
|
150
153
|
### packages/adapters/openai.js
|
|
151
154
|
```
|
|
152
155
|
module.exports = { name, format, outputPath }
|
|
@@ -163,74 +166,24 @@ function _confidenceMeta(opts)
|
|
|
163
166
|
function outputPath(cwd) → string
|
|
164
167
|
```
|
|
165
168
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
### src/security/patterns.js
|
|
169
|
-
```
|
|
170
|
-
module.exports = { PATTERNS }
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### src/security/scanner.js
|
|
174
|
-
```
|
|
175
|
-
module.exports = { scan }
|
|
176
|
-
function scan(signatures, filePath) → { safe: string[], redacte
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### src/extractors/cpp.js
|
|
180
|
-
```
|
|
181
|
-
module.exports = { extract }
|
|
182
|
-
function extract(src) → string[]
|
|
183
|
-
function extractBlock(src, startIndex)
|
|
184
|
-
function extractMembers(block)
|
|
185
|
-
function normalizeParams(params)
|
|
186
|
-
function normalizeType(type)
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### src/extractors/csharp.js
|
|
190
|
-
```
|
|
191
|
-
module.exports = { extract }
|
|
192
|
-
function extract(src) → string[]
|
|
193
|
-
function extractBlock(src, startIndex)
|
|
194
|
-
function extractMembers(block)
|
|
195
|
-
function normalizeParams(params)
|
|
196
|
-
function normalizeType(type)
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
### src/extractors/dart.js
|
|
200
|
-
```
|
|
201
|
-
module.exports = { extract }
|
|
202
|
-
function extract(src) → string[]
|
|
203
|
-
function extractBlock(src, startIndex)
|
|
204
|
-
function extractMembers(block)
|
|
205
|
-
function normalizeParams(params)
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
### src/extractors/deps.js
|
|
169
|
+
### packages/adapters/codex.js
|
|
209
170
|
```
|
|
210
|
-
module.exports = {
|
|
211
|
-
function
|
|
212
|
-
function
|
|
213
|
-
function
|
|
171
|
+
module.exports = { name, format, outputPath, write }
|
|
172
|
+
function format(context, opts = {}) → string
|
|
173
|
+
function outputPath(cwd) → string
|
|
174
|
+
function write(context, cwd, opts = {})
|
|
214
175
|
```
|
|
215
176
|
|
|
216
|
-
###
|
|
177
|
+
### packages/adapters/claude.js
|
|
217
178
|
```
|
|
218
|
-
module.exports = {
|
|
219
|
-
function
|
|
220
|
-
function
|
|
221
|
-
function
|
|
222
|
-
function
|
|
179
|
+
module.exports = { name, format, outputPath, write }
|
|
180
|
+
function format(context, opts = {}) → string
|
|
181
|
+
function _confidenceMeta(opts)
|
|
182
|
+
function outputPath(cwd) → string
|
|
183
|
+
function write(context, cwd, opts = {})
|
|
223
184
|
```
|
|
224
185
|
|
|
225
|
-
|
|
226
|
-
```
|
|
227
|
-
module.exports = { extract }
|
|
228
|
-
function extract(src) → string[]
|
|
229
|
-
function extractBlock(src, startIndex)
|
|
230
|
-
function extractMembers(block)
|
|
231
|
-
function normalizeParams(params)
|
|
232
|
-
function normalizeType(type)
|
|
233
|
-
```
|
|
186
|
+
## src
|
|
234
187
|
|
|
235
188
|
### src/extractors/javascript.js
|
|
236
189
|
```
|
|
@@ -622,18 +575,6 @@ function queryContext(args, cwd)
|
|
|
622
575
|
function getImpact(args, cwd)
|
|
623
576
|
```
|
|
624
577
|
|
|
625
|
-
### src/retrieval/ranker.js
|
|
626
|
-
```
|
|
627
|
-
module.exports = { rank, buildSigIndex, scoreFile, formatRankTable, formatRankJSON, DEFAULT_WEIGHTS, detectIntent }
|
|
628
|
-
function scoreFile(filePath, sigs, queryTokens, weights) → number
|
|
629
|
-
function rank(query, sigIndex, opts) → { file: string, score: nu
|
|
630
|
-
function _parseContextFile(contextPath) → Map<string, string[]>
|
|
631
|
-
function buildSigIndex(cwd, opts) → Map<string, string[]>
|
|
632
|
-
function formatRankTable(results, query) → string
|
|
633
|
-
function formatRankJSON(results, query) → object
|
|
634
|
-
function detectIntent(query)
|
|
635
|
-
```
|
|
636
|
-
|
|
637
578
|
### src/tracking/logger.js
|
|
638
579
|
```
|
|
639
580
|
module.exports = { logRun, readLog, summarize }
|
|
@@ -652,15 +593,6 @@ function extractClassMembers(block)
|
|
|
652
593
|
function normalizeParams(params)
|
|
653
594
|
```
|
|
654
595
|
|
|
655
|
-
### src/config/loader.js
|
|
656
|
-
```
|
|
657
|
-
module.exports = { loadConfig, loadBaseConfig }
|
|
658
|
-
function loadBaseConfig(extendsVal, cwd)
|
|
659
|
-
function detectAutoSrcDirs(cwd, excludeList) → string[]
|
|
660
|
-
function loadConfig(cwd) → object
|
|
661
|
-
function deepClone(obj)
|
|
662
|
-
```
|
|
663
|
-
|
|
664
596
|
### src/learning/weights.js
|
|
665
597
|
```
|
|
666
598
|
module.exports = { BASELINE, DECAY, MAX_MULT, MIN_MULT, weightsPath, clampMultiplier, normalizeFile, loadWeights, saveWeights, updateWeights, boostFiles, penalizeFiles, resetWeights, exportWeights, importWeights }
|
|
@@ -678,6 +610,77 @@ function exportWeights(cwd, outputPath)
|
|
|
678
610
|
function importWeights(cwd, importPath, replace)
|
|
679
611
|
```
|
|
680
612
|
|
|
613
|
+
### src/config/loader.js
|
|
614
|
+
```
|
|
615
|
+
module.exports = { loadConfig, loadBaseConfig }
|
|
616
|
+
function loadBaseConfig(extendsVal, cwd)
|
|
617
|
+
function detectAutoSrcDirs(cwd, excludeList) → string[]
|
|
618
|
+
function _legacyDetectAutoSrcDirs(cwd, excludeList) → string[]
|
|
619
|
+
function loadConfig(cwd) → object
|
|
620
|
+
function deepClone(obj)
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### src/discovery/language-detector.js
|
|
624
|
+
```
|
|
625
|
+
module.exports = { detectLanguages }
|
|
626
|
+
function detectLanguages(cwd)
|
|
627
|
+
function _walkDepth(dir, depth, extCount)
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### src/discovery/framework-detector.js
|
|
631
|
+
```
|
|
632
|
+
module.exports = { detectFrameworks }
|
|
633
|
+
function detectFrameworks(cwd)
|
|
634
|
+
function _readDeps(cwd)
|
|
635
|
+
function _readFile(p)
|
|
636
|
+
function _existsAnywhere(cwd, filename, maxDepth)
|
|
637
|
+
function _walkFind(dir, name, depth)
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
### src/discovery/source-root-registry.js
|
|
641
|
+
```
|
|
642
|
+
module.exports = { REGISTRY }
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
### src/discovery/source-root-resolver.js
|
|
646
|
+
```
|
|
647
|
+
module.exports = { resolveSourceRoots }
|
|
648
|
+
function resolveSourceRoots(cwd, opts = {})
|
|
649
|
+
function _detectMonorepo(cwd)
|
|
650
|
+
function _enumerateCandidates(cwd, isMonorepo, ignorePatterns, excludeList)
|
|
651
|
+
function _applySpecialRules(scored, cwd, primaryFw, fwEntry, frameworks)
|
|
652
|
+
function _dedupeNested(scored)
|
|
653
|
+
function _computeConfidence(frameworks, languages, scoredCount)
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### src/discovery/source-root-scorer.js
|
|
657
|
+
```
|
|
658
|
+
module.exports = { scoreCandidate, getRecentlyChangedDirs, ROOT_ENTRYPOINTS }
|
|
659
|
+
function getRecentlyChangedDirs(cwd)
|
|
660
|
+
function scoreCandidate(dirName, fullPath, context)
|
|
661
|
+
function _countSourceFiles(dir, depth)
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
### src/discovery/sigmapignore.js
|
|
665
|
+
```
|
|
666
|
+
module.exports = { loadIgnorePatterns, matchesIgnorePattern }
|
|
667
|
+
function loadIgnorePatterns(cwd)
|
|
668
|
+
function matchesIgnorePattern(dirName, patterns)
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
### src/retrieval/ranker.js
|
|
672
|
+
```
|
|
673
|
+
module.exports = { rank, buildSigIndex, scoreFile, formatRankTable, formatRankJSON, DEFAULT_WEIGHTS, detectIntent }
|
|
674
|
+
function _computePenalty(filePath)
|
|
675
|
+
function scoreFile(filePath, sigs, queryTokens, weights) → { score: number, signals:
|
|
676
|
+
function rank(query, sigIndex, opts) → { file: string, score: nu
|
|
677
|
+
function _parseContextFile(contextPath) → Map<string, string[]>
|
|
678
|
+
function buildSigIndex(cwd, opts) → Map<string, string[]>
|
|
679
|
+
function formatRankTable(results, query) → string
|
|
680
|
+
function formatRankJSON(results, query) → object
|
|
681
|
+
function detectIntent(query)
|
|
682
|
+
```
|
|
683
|
+
|
|
681
684
|
### src/mcp/server.js
|
|
682
685
|
```
|
|
683
686
|
module.exports = { start }
|
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,21 @@ Format: [Semantic Versioning](https://semver.org/)
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## [6.5.1] — 2026-04-25
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **Retrieval explain** — `rank()` and `scoreFile()` now return detailed signal breakdown (exactToken, symbolMatch, prefixMatch, pathMatch, penalty) for transparency in ranking decisions
|
|
18
|
+
- **7-intent ranking** — expanded intent detection from 4 to 7 patterns (debug, explain, refactor, review, test, integrate, navigate). Each intent applies tuned weights to prioritize relevant signals.
|
|
19
|
+
- **Negative-signal penalty layer** — formalized penalties for test files (0.4x), generated code (0.3x), documentation (0.2x), and node_modules (0.0x) to deprioritize non-source content
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- `formatRankTable` now shows penalty column and signals breakdown for top 3 results
|
|
24
|
+
- `formatRankJSON` now includes `intent` and `signals` fields in output for API consumers
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
13
28
|
## [6.5.0] — 2026-04-25
|
|
14
29
|
|
|
15
30
|
### Added
|
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ Works with Copilot, Claude, Cursor, Windsurf, and any LLM.
|
|
|
38
38
|
|
|
39
39
|
## Why SigMap?
|
|
40
40
|
|
|
41
|
-
- **
|
|
41
|
+
- **81.1% hit@5** — right file found in top 5 results (vs 13.6% baseline)
|
|
42
42
|
- **40–98% token reduction** — 2K–4K tokens instead of 80K+
|
|
43
43
|
- **52.2% task success rate** — up from 10% without context
|
|
44
44
|
- **1.69 prompts per task** — down from 2.84
|
|
@@ -51,7 +51,7 @@ Works with Copilot, Claude, Cursor, Windsurf, and any LLM.
|
|
|
51
51
|
|
|
52
52
|
| Without SigMap | With SigMap |
|
|
53
53
|
|---|---|
|
|
54
|
-
| ❌ Guessing which files are relevant | ✅ Right file in context —
|
|
54
|
+
| ❌ Guessing which files are relevant | ✅ Right file in context — 81% of the time |
|
|
55
55
|
| ❌ Sending the full repo to your AI | ✅ Minimal context — only what matters |
|
|
56
56
|
| ❌ Embeddings / vector DB required | ✅ Grounded answers, no infra needed |
|
|
57
57
|
|
|
@@ -75,11 +75,11 @@ Ask → Rank → Context → Validate → Judge → Learn
|
|
|
75
75
|
## Benchmark
|
|
76
76
|
|
|
77
77
|
```
|
|
78
|
-
Benchmark : sigmap-v6.
|
|
79
|
-
Date : 2026-04-
|
|
78
|
+
Benchmark : sigmap-v6.5-main
|
|
79
|
+
Date : 2026-04-25
|
|
80
80
|
|
|
81
|
-
Hit@5 :
|
|
82
|
-
Prompt reduction :
|
|
81
|
+
Hit@5 : 81.1% (baseline 13.6% — 6.0× lift)
|
|
82
|
+
Prompt reduction : 41.4%
|
|
83
83
|
Task success : 52.2% (baseline 10%)
|
|
84
84
|
Prompts / task : 1.69 (baseline 2.84)
|
|
85
85
|
Token reduction: 40–98% (avg 96.9% across 18 real repos)
|
package/gen-context.js
CHANGED
|
@@ -5387,7 +5387,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
|
|
|
5387
5387
|
|
|
5388
5388
|
const SERVER_INFO = {
|
|
5389
5389
|
name: 'sigmap',
|
|
5390
|
-
version: '6.5.
|
|
5390
|
+
version: '6.5.1',
|
|
5391
5391
|
description: 'SigMap MCP server — code signatures on demand',
|
|
5392
5392
|
};
|
|
5393
5393
|
|
|
@@ -7222,7 +7222,7 @@ const path = require('path');
|
|
|
7222
7222
|
const os = require('os');
|
|
7223
7223
|
const { execSync } = require('child_process');
|
|
7224
7224
|
|
|
7225
|
-
const VERSION = '6.5.
|
|
7225
|
+
const VERSION = '6.5.1';
|
|
7226
7226
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
7227
7227
|
|
|
7228
7228
|
function requireSourceOrBundled(key) {
|
package/package.json
CHANGED
package/src/mcp/server.js
CHANGED
package/src/retrieval/ranker.js
CHANGED
|
@@ -32,19 +32,49 @@ const DEFAULT_WEIGHTS = {
|
|
|
32
32
|
graphBoost: 0.4, // additive bonus for 1-hop import neighbors of matching files
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
+
// Intent-specific weight adjustments
|
|
36
|
+
const INTENT_WEIGHTS = {
|
|
37
|
+
search: DEFAULT_WEIGHTS,
|
|
38
|
+
debug: { ...DEFAULT_WEIGHTS, exactToken: 1.2, pathMatch: 0.6 },
|
|
39
|
+
explain: { ...DEFAULT_WEIGHTS, symbolMatch: 0.8, pathMatch: 0.9 },
|
|
40
|
+
refactor: { ...DEFAULT_WEIGHTS, symbolMatch: 0.9, exactToken: 0.8 },
|
|
41
|
+
review: { ...DEFAULT_WEIGHTS, pathMatch: 1.0, exactToken: 0.9 },
|
|
42
|
+
test: { ...DEFAULT_WEIGHTS, exactToken: 0.7, symbolMatch: 0.4 },
|
|
43
|
+
integrate: { ...DEFAULT_WEIGHTS, graphBoost: 0.7, pathMatch: 1.1 },
|
|
44
|
+
navigate: { ...DEFAULT_WEIGHTS, pathMatch: 1.2, exactToken: 0.9 },
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Penalty multipliers for negative signals
|
|
48
|
+
const PENALTY_SIGNALS = {
|
|
49
|
+
testFile: 0.4, // test/spec/__tests__ in path
|
|
50
|
+
generatedCode: 0.3, // dist/build/.next in path
|
|
51
|
+
docsFile: 0.2, // docs/doc/README in path
|
|
52
|
+
nodeModules: 0.0, // node_modules (zero score)
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
function _computePenalty(filePath) {
|
|
56
|
+
const pathLower = filePath.toLowerCase();
|
|
57
|
+
if (pathLower.includes('node_modules')) return PENALTY_SIGNALS.nodeModules;
|
|
58
|
+
if (/(^|\/)(test|tests|spec|__tests__|e2e)($|\/)/.test(pathLower)) return PENALTY_SIGNALS.testFile;
|
|
59
|
+
if (/(^|\/)(dist|build|\.next|\.nuxt|out|\.venv|venv)($|\/)/.test(pathLower)) return PENALTY_SIGNALS.generatedCode;
|
|
60
|
+
if (/(^|\/)(docs|doc|readme|changelog)($|\/)/.test(pathLower)) return PENALTY_SIGNALS.docsFile;
|
|
61
|
+
return 1.0;
|
|
62
|
+
}
|
|
63
|
+
|
|
35
64
|
/**
|
|
36
|
-
* Score a single file against a query.
|
|
65
|
+
* Score a single file against a query, returning detailed signal breakdown.
|
|
37
66
|
*
|
|
38
67
|
* @param {string} filePath - relative file path (e.g. 'src/extractors/python.js')
|
|
39
68
|
* @param {string[]} sigs - signature strings for this file
|
|
40
69
|
* @param {string[]} queryTokens - pre-tokenized query
|
|
41
70
|
* @param {object} weights
|
|
42
|
-
* @returns {number}
|
|
71
|
+
* @returns {{ score: number, signals: { exactToken: number, symbolMatch: number, prefixMatch: number, pathMatch: number, penalty: number } }}
|
|
43
72
|
*/
|
|
44
73
|
function scoreFile(filePath, sigs, queryTokens, weights) {
|
|
45
|
-
if (!sigs || sigs.length === 0) return 0;
|
|
74
|
+
if (!sigs || sigs.length === 0) return { score: 0, signals: { exactToken: 0, symbolMatch: 0, prefixMatch: 0, pathMatch: 0, penalty: 1.0 } };
|
|
46
75
|
|
|
47
76
|
const w = weights || DEFAULT_WEIGHTS;
|
|
77
|
+
const signals = { exactToken: 0, symbolMatch: 0, prefixMatch: 0, pathMatch: 0, penalty: _computePenalty(filePath) };
|
|
48
78
|
|
|
49
79
|
// Build token set from all signatures
|
|
50
80
|
const sigText = sigs.join(' ');
|
|
@@ -60,14 +90,19 @@ function scoreFile(filePath, sigs, queryTokens, weights) {
|
|
|
60
90
|
|
|
61
91
|
// Exact token match in sigs
|
|
62
92
|
if (sigTokenSet.has(qt)) {
|
|
63
|
-
|
|
93
|
+
const bonus = w.exactToken;
|
|
94
|
+
score += bonus;
|
|
95
|
+
signals.exactToken += bonus;
|
|
64
96
|
|
|
65
97
|
// Bonus: appears directly in a function/class/method name line
|
|
66
98
|
const nameLineMatch = sigs.some((sig) => {
|
|
67
99
|
const nt = tokenize(sig.replace(/[^a-zA-Z0-9_\s]/g, ' '));
|
|
68
100
|
return nt.includes(qt);
|
|
69
101
|
});
|
|
70
|
-
if (nameLineMatch)
|
|
102
|
+
if (nameLineMatch) {
|
|
103
|
+
score += w.symbolMatch;
|
|
104
|
+
signals.symbolMatch += w.symbolMatch;
|
|
105
|
+
}
|
|
71
106
|
}
|
|
72
107
|
|
|
73
108
|
// Prefix match (e.g. query "python" matches "pythonDeps")
|
|
@@ -75,6 +110,7 @@ function scoreFile(filePath, sigs, queryTokens, weights) {
|
|
|
75
110
|
for (const st of sigTokenSet) {
|
|
76
111
|
if (st !== qt && st.startsWith(qt)) {
|
|
77
112
|
score += w.prefixMatch;
|
|
113
|
+
signals.prefixMatch += w.prefixMatch;
|
|
78
114
|
break; // one bonus per query token
|
|
79
115
|
}
|
|
80
116
|
}
|
|
@@ -83,10 +119,14 @@ function scoreFile(filePath, sigs, queryTokens, weights) {
|
|
|
83
119
|
// Path token match
|
|
84
120
|
if (pathTokenSet.has(qt)) {
|
|
85
121
|
score += w.pathMatch;
|
|
122
|
+
signals.pathMatch += w.pathMatch;
|
|
86
123
|
}
|
|
87
124
|
}
|
|
88
125
|
|
|
89
|
-
|
|
126
|
+
// Apply penalty multiplier
|
|
127
|
+
score *= signals.penalty;
|
|
128
|
+
|
|
129
|
+
return { score, signals };
|
|
90
130
|
}
|
|
91
131
|
|
|
92
132
|
/**
|
|
@@ -101,7 +141,7 @@ function scoreFile(filePath, sigs, queryTokens, weights) {
|
|
|
101
141
|
* @param {object} [opts.weights] - override scoring weights
|
|
102
142
|
* @param {string} [opts.cwd] - project root for learned ranking weights
|
|
103
143
|
* @param {{ forward: Map<string,string[]> }} [opts.graph] - dependency graph for neighbor boost
|
|
104
|
-
* @returns {{ file: string, score: number, sigs: string[], tokens: number }[]}
|
|
144
|
+
* @returns {{ file: string, score: number, sigs: string[], tokens: number, intent: string, signals: object }[]}
|
|
105
145
|
*/
|
|
106
146
|
function rank(query, sigIndex, opts) {
|
|
107
147
|
if (!query || typeof query !== 'string') return [];
|
|
@@ -110,17 +150,21 @@ function rank(query, sigIndex, opts) {
|
|
|
110
150
|
const topK = (opts && opts.topK) || 10;
|
|
111
151
|
const recencyMultiplier = (opts && opts.recencyBoost) || DEFAULT_WEIGHTS.recencyBoost;
|
|
112
152
|
const recencySet = (opts && opts.recencySet) || null;
|
|
113
|
-
const weights = (opts && opts.weights) ? Object.assign({}, DEFAULT_WEIGHTS, opts.weights) : DEFAULT_WEIGHTS;
|
|
114
|
-
const learnedWeights = opts && opts.cwd ? loadWeights(opts.cwd) : null;
|
|
115
153
|
const graph = (opts && opts.graph && opts.graph.forward instanceof Map) ? opts.graph : null;
|
|
116
154
|
const cwd = (opts && opts.cwd) || null;
|
|
117
155
|
|
|
156
|
+
// Detect query intent and get appropriate weights
|
|
157
|
+
const intent = detectIntent(query);
|
|
158
|
+
const intentWeights = INTENT_WEIGHTS[intent] || DEFAULT_WEIGHTS;
|
|
159
|
+
const weights = (opts && opts.weights) ? Object.assign({}, intentWeights, opts.weights) : intentWeights;
|
|
160
|
+
const learnedWeights = opts && opts.cwd ? loadWeights(opts.cwd) : null;
|
|
161
|
+
|
|
118
162
|
const queryTokens = tokenize(query);
|
|
119
163
|
if (queryTokens.length === 0) {
|
|
120
164
|
// Empty query: return top-K by file count (most signatures = most useful)
|
|
121
165
|
const all = [];
|
|
122
166
|
for (const [file, sigs] of sigIndex.entries()) {
|
|
123
|
-
all.push({ file, score: sigs.length, sigs, tokens: Math.ceil(sigs.join('\n').length / 4) });
|
|
167
|
+
all.push({ file, score: sigs.length, sigs, tokens: Math.ceil(sigs.join('\n').length / 4), intent, signals: {} });
|
|
124
168
|
}
|
|
125
169
|
all.sort((a, b) => b.score - a.score || a.file.localeCompare(b.file));
|
|
126
170
|
return all.slice(0, topK);
|
|
@@ -128,15 +172,20 @@ function rank(query, sigIndex, opts) {
|
|
|
128
172
|
|
|
129
173
|
const scored = [];
|
|
130
174
|
for (const [file, sigs] of sigIndex.entries()) {
|
|
131
|
-
|
|
175
|
+
const result = scoreFile(file, sigs, queryTokens, weights);
|
|
176
|
+
let score = result.score;
|
|
177
|
+
const signals = result.signals;
|
|
132
178
|
|
|
133
179
|
// Recency boost
|
|
134
180
|
if (recencySet && recencySet.has(file) && score > 0) {
|
|
135
181
|
score *= recencyMultiplier;
|
|
182
|
+
signals.recencyBoost = recencyMultiplier;
|
|
136
183
|
}
|
|
137
184
|
|
|
138
185
|
if (learnedWeights && score > 0) {
|
|
139
|
-
|
|
186
|
+
const multiplier = learnedWeights[file] || 1.0;
|
|
187
|
+
score *= multiplier;
|
|
188
|
+
signals.learnedWeights = multiplier;
|
|
140
189
|
}
|
|
141
190
|
|
|
142
191
|
scored.push({
|
|
@@ -144,6 +193,8 @@ function rank(query, sigIndex, opts) {
|
|
|
144
193
|
score,
|
|
145
194
|
sigs,
|
|
146
195
|
tokens: Math.ceil(sigs.join('\n').length / 4),
|
|
196
|
+
intent,
|
|
197
|
+
signals,
|
|
147
198
|
});
|
|
148
199
|
}
|
|
149
200
|
|
|
@@ -166,6 +217,7 @@ function rank(query, sigIndex, opts) {
|
|
|
166
217
|
const idx = relToIdx.get(neighborRel);
|
|
167
218
|
if (idx !== undefined) {
|
|
168
219
|
scored[idx].score += weights.graphBoost;
|
|
220
|
+
scored[idx].signals.graphBoost = (scored[idx].signals.graphBoost || 0) + weights.graphBoost;
|
|
169
221
|
}
|
|
170
222
|
}
|
|
171
223
|
}
|
|
@@ -286,7 +338,7 @@ function buildSigIndex(cwd, opts) {
|
|
|
286
338
|
/**
|
|
287
339
|
* Format ranked results as a markdown table string.
|
|
288
340
|
*
|
|
289
|
-
* @param {{ file: string, score: number, sigs: string[], tokens: number }[]} results
|
|
341
|
+
* @param {{ file: string, score: number, sigs: string[], tokens: number, intent: string, signals: object }[]} results
|
|
290
342
|
* @param {string} query
|
|
291
343
|
* @returns {string}
|
|
292
344
|
*/
|
|
@@ -295,14 +347,17 @@ function formatRankTable(results, query) {
|
|
|
295
347
|
return `No matching files found for query: "${query}"\n`;
|
|
296
348
|
}
|
|
297
349
|
|
|
350
|
+
const intent = (results[0] && results[0].intent) || 'search';
|
|
298
351
|
const lines = [
|
|
299
352
|
`## Query: ${query}`,
|
|
353
|
+
`Intent: ${intent}`,
|
|
300
354
|
'',
|
|
301
|
-
'| Rank | File | Score | Sigs |
|
|
302
|
-
'
|
|
303
|
-
...results.map((r, i) =>
|
|
304
|
-
|
|
305
|
-
|
|
355
|
+
'| Rank | File | Score | Sigs | Penalty |',
|
|
356
|
+
'|------|------|-------|------|---------|',
|
|
357
|
+
...results.map((r, i) => {
|
|
358
|
+
const penalty = r.signals && r.signals.penalty ? r.signals.penalty.toFixed(2) : '1.00';
|
|
359
|
+
return `| ${i + 1} | ${r.file} | ${r.score.toFixed(2)} | ${r.sigs.length} | ${penalty} |`;
|
|
360
|
+
}),
|
|
306
361
|
'',
|
|
307
362
|
];
|
|
308
363
|
|
|
@@ -310,6 +365,10 @@ function formatRankTable(results, query) {
|
|
|
310
365
|
for (const r of results.slice(0, 3)) {
|
|
311
366
|
if (r.sigs.length > 0) {
|
|
312
367
|
lines.push(`### ${r.file}`);
|
|
368
|
+
if (r.signals) {
|
|
369
|
+
const sig = r.signals;
|
|
370
|
+
lines.push(`Signals: exactToken=${(sig.exactToken || 0).toFixed(2)} symbolMatch=${(sig.symbolMatch || 0).toFixed(2)} prefixMatch=${(sig.prefixMatch || 0).toFixed(2)} pathMatch=${(sig.pathMatch || 0).toFixed(2)} penalty=${(sig.penalty || 1).toFixed(2)}`);
|
|
371
|
+
}
|
|
313
372
|
lines.push('```');
|
|
314
373
|
lines.push(...r.sigs.slice(0, 10));
|
|
315
374
|
if (r.sigs.length > 10) lines.push(`... (${r.sigs.length - 10} more)`);
|
|
@@ -324,32 +383,38 @@ function formatRankTable(results, query) {
|
|
|
324
383
|
/**
|
|
325
384
|
* Format ranked results as a structured JSON-serialisable object.
|
|
326
385
|
*
|
|
327
|
-
* @param {{ file: string, score: number, sigs: string[], tokens: number }[]} results
|
|
386
|
+
* @param {{ file: string, score: number, sigs: string[], tokens: number, intent: string, signals: object }[]} results
|
|
328
387
|
* @param {string} query
|
|
329
388
|
* @returns {object}
|
|
330
389
|
*/
|
|
331
390
|
function formatRankJSON(results, query) {
|
|
391
|
+
const intent = (results && results[0] && results[0].intent) || 'search';
|
|
332
392
|
return {
|
|
333
393
|
query,
|
|
394
|
+
intent,
|
|
334
395
|
results: (results || []).map((r, i) => ({
|
|
335
396
|
rank: i + 1,
|
|
336
397
|
file: r.file,
|
|
337
398
|
score: r.score,
|
|
338
399
|
sigs: r.sigs,
|
|
339
400
|
tokens: r.tokens,
|
|
401
|
+
signals: r.signals || {},
|
|
340
402
|
})),
|
|
341
403
|
totalResults: (results || []).length,
|
|
342
404
|
};
|
|
343
405
|
}
|
|
344
406
|
|
|
345
407
|
// ---------------------------------------------------------------------------
|
|
346
|
-
// Intent detection
|
|
408
|
+
// Intent detection — 7 intents
|
|
347
409
|
// ---------------------------------------------------------------------------
|
|
348
410
|
const INTENT_PATTERNS = {
|
|
349
411
|
debug: /\b(bug|fix|error|crash|exception|broken|failing|issue|problem|regression)\b/i,
|
|
350
|
-
explain: /\b(explain|how does|what is|understand|overview|architecture|describe|walk me)\b/i,
|
|
351
|
-
refactor: /\b(refactor|restructure|redesign|clean up|extract|move|rename|simplify)\b/i,
|
|
352
|
-
review: /\b(review|check|audit|security|pr|pull request|assess)\b/i,
|
|
412
|
+
explain: /\b(explain|how does|what is|understand|overview|architecture|describe|walk me|teach)\b/i,
|
|
413
|
+
refactor: /\b(refactor|restructure|redesign|clean up|extract|move|rename|simplify|optimize)\b/i,
|
|
414
|
+
review: /\b(review|check|audit|security|pr|pull request|assess|validate)\b/i,
|
|
415
|
+
test: /\b(test|unit test|integration test|testing|spec|assert|mock)\b/i,
|
|
416
|
+
integrate:/\b(import|integrate|connect|wire|bind|require|export|depend|graph)\b|require[ds]\b/i,
|
|
417
|
+
navigate: /\b(find|locate|where|search|look for|show me|navigate|browse|list)\b/i,
|
|
353
418
|
};
|
|
354
419
|
|
|
355
420
|
function detectIntent(query) {
|