sigmap 6.8.0 → 6.10.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
@@ -56,6 +56,12 @@ Use this marker block for all appendable context files:
56
56
  | To query by topic | `sigmap --query "<topic>"` |
57
57
 
58
58
  Always run `sigmap ask` or `sigmap --query` before searching for files relevant to a task.
59
+ ## changes (last 5 commits — 0 seconds ago)
60
+ ```
61
+ src/eval/usefulness-scorer.js +scoreUsefulness +computeUsefulnessStats
62
+ src/workspace/detector.js +detectWorkspaces +inferPackage +_getMatchLength +scopeToPackage
63
+ ```
64
+
59
65
  ## packages
60
66
 
61
67
  ### packages/cli/index.js
@@ -174,23 +180,6 @@ function write(context, cwd, opts = {})
174
180
 
175
181
  ## src
176
182
 
177
- ### src/extractors/php.js
178
- ```
179
- module.exports = { extract }
180
- function extract(src) → string[]
181
- function extractBlock(src, startIndex)
182
- function extractMembers(block)
183
- function normalizeParams(params)
184
- function normalizeType(type)
185
- ```
186
-
187
- ### src/extractors/prdiff.js
188
- ```
189
- module.exports = { diffSignatures, extractName }
190
- function diffSignatures(baseSigs, currentSigs) → {added:string[], removed:
191
- function extractName(sig)
192
- ```
193
-
194
183
  ### src/extractors/python.js
195
184
  ```
196
185
  module.exports = { extract }
@@ -667,6 +656,13 @@ function _dedupeNested(scored)
667
656
  function _computeConfidence(frameworks, languages, scoredCount)
668
657
  ```
669
658
 
659
+ ### src/eval/usefulness-scorer.js
660
+ ```
661
+ module.exports = { scoreUsefulness, computeUsefulnessStats }
662
+ function scoreUsefulness(taskResult, rankingScore)
663
+ function computeUsefulnessStats(taskResults)
664
+ ```
665
+
670
666
  ### src/mcp/server.js
671
667
  ```
672
668
  module.exports = { start }
@@ -675,3 +671,12 @@ function respondError(id, code, message)
675
671
  function dispatch(msg, cwd)
676
672
  function start(cwd)
677
673
  ```
674
+
675
+ ### src/workspace/detector.js
676
+ ```
677
+ module.exports = { detectWorkspaces, inferPackage, scopeToPackage }
678
+ function detectWorkspaces(cwd)
679
+ function inferPackage(query, workspaceDirs, cwd)
680
+ function _getMatchLength(name, token)
681
+ function scopeToPackage(filePath, packageDir)
682
+ ```
package/CHANGELOG.md CHANGED
@@ -10,6 +10,24 @@ Format: [Semantic Versioning](https://semver.org/)
10
10
 
11
11
  ---
12
12
 
13
+ ## [6.10.0] — 2026-05-05
14
+
15
+ ### Added
16
+
17
+ - **Workspace-scoped retrieval for monorepos** — New `src/workspace/detector.js` module detects workspace packages from `package.json` workspaces field (npm array and Yarn v2 `packages` format). Automatically infers target package from query tokens (e.g., "rate limiting payments" → `packages/payments/`). Flags `--package <name>` (explicit) and `--global` (disable scoping) control retrieval scope. Files inside inferred package receive +0.30 score boost for tighter context.
18
+
19
+ ---
20
+
21
+ ## [6.9.0] — 2026-05-03
22
+
23
+ ### Added
24
+
25
+ - **Task metadata for segmentation** — All 18 benchmark repositories now tagged with language, repo type (framework/library/tool/application), and size class (small/medium/large) to enable segmented benchmark analysis.
26
+ - **Benchmark methodology documentation** — Comprehensive guide explaining what SigMap measures (retrieval accuracy, task success, prompt reduction, token reduction), why these metrics matter, and how the 90-task test set was selected and evaluated.
27
+ - **Answer usefulness evaluation** — New metric tracking whether retrieved context actually enabled correct answers, scored in three tiers: fully-useful (rank 1), partially-useful (ranks 2-5), not-useful (not retrieved). Complements task success proxy with granular answer quality assessment.
28
+
29
+ ---
30
+
13
31
  ## [6.8.0] — 2026-05-03
14
32
 
15
33
  ### Added
package/README.md CHANGED
@@ -52,7 +52,7 @@ Works with Copilot, Claude, Cursor, Windsurf, and any LLM.
52
52
 
53
53
  | Without SigMap | With SigMap |
54
54
  |---|---|
55
- | ❌ Guessing which files are relevant | ✅ Right file in context — 81% of the time |
55
+ | ❌ Guessing which files are relevant | ✅ Right file in context — 80% of the time |
56
56
  | ❌ Sending the full repo to your AI | ✅ Minimal context — only what matters |
57
57
  | ❌ Embeddings / vector DB required | ✅ Grounded answers, no infra needed |
58
58
 
@@ -76,8 +76,8 @@ Ask → Rank → Context → Validate → Judge → Learn
76
76
  ## Benchmark
77
77
 
78
78
  ```
79
- Benchmark : sigmap-v6.8-main
80
- Date : 2026-04-30
79
+ Benchmark : sigmap-v6.10-main
80
+ Date : 2026-05-05
81
81
 
82
82
  Hit@5 : 80.0% (baseline 13.6% — 5.9× lift)
83
83
  Prompt reduction : 41.0%
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.8.0',
5390
+ version: '6.10.0',
5391
5391
  description: 'SigMap MCP server — code signatures on demand',
5392
5392
  };
5393
5393
 
@@ -6855,6 +6855,76 @@ __factories["./src/eval/runner"] = function(module, exports) {
6855
6855
  module.exports = { run, rank, loadTasks, buildSigIndex, formatTable, formatMetrics, tokenize };
6856
6856
  };
6857
6857
 
6858
+ // ── ./src/eval/usefulness-scorer ──
6859
+ __factories["./src/eval/usefulness-scorer"] = function(module, exports) {
6860
+ 'use strict';
6861
+
6862
+ /**
6863
+ * Score answer usefulness based on:
6864
+ * 1. Whether right file was retrieved (retrieval hit)
6865
+ * 2. Whether retrieved context covered the answer (coverage)
6866
+ * 3. Confidence in answer quality (from ranking score)
6867
+ */
6868
+ function scoreUsefulness(taskResult, rankingScore) {
6869
+ const { hitRank } = taskResult;
6870
+
6871
+ // Tier 1: File not retrieved — context cannot be useful
6872
+ if (hitRank === -1 || hitRank > 5) {
6873
+ return {
6874
+ tier: 'not-useful',
6875
+ score: 0.0,
6876
+ reason: 'expected file not in top 5'
6877
+ };
6878
+ }
6879
+
6880
+ // Tier 2: File retrieved but not top ranking — partially useful
6881
+ if (hitRank > 1) {
6882
+ return {
6883
+ tier: 'partially-useful',
6884
+ score: rankingScore * 0.5, // Partial usefulness
6885
+ reason: `file ranked #${hitRank}`
6886
+ };
6887
+ }
6888
+
6889
+ // Tier 3: File at top of ranking — fully useful
6890
+ return {
6891
+ tier: 'fully-useful',
6892
+ score: rankingScore, // Full usefulness
6893
+ reason: 'file ranked first'
6894
+ };
6895
+ }
6896
+
6897
+ function computeUsefulnessStats(taskResults) {
6898
+ const tiers = {
6899
+ 'fully-useful': 0,
6900
+ 'partially-useful': 0,
6901
+ 'not-useful': 0
6902
+ };
6903
+
6904
+ let totalScore = 0;
6905
+ let count = 0;
6906
+
6907
+ taskResults.forEach(result => {
6908
+ const usefulness = scoreUsefulness(result, result.rankingScore || 1.0);
6909
+ tiers[usefulness.tier]++;
6910
+ totalScore += usefulness.score;
6911
+ count++;
6912
+ });
6913
+
6914
+ return {
6915
+ fully_useful: tiers['fully-useful'],
6916
+ partially_useful: tiers['partially-useful'],
6917
+ not_useful: tiers['not-useful'],
6918
+ fully_useful_pct: count > 0 ? (tiers['fully-useful'] / count * 100).toFixed(1) : 0,
6919
+ partially_useful_pct: count > 0 ? (tiers['partially-useful'] / count * 100).toFixed(1) : 0,
6920
+ not_useful_pct: count > 0 ? (tiers['not-useful'] / count * 100).toFixed(1) : 0,
6921
+ average_usefulness_score: count > 0 ? (totalScore / count).toFixed(3) : 0
6922
+ };
6923
+ }
6924
+
6925
+ module.exports = { scoreUsefulness, computeUsefulnessStats };
6926
+ };
6927
+
6858
6928
 
6859
6929
  // ── ./packages/adapters/copilot (bundled) ──
6860
6930
  __factories["./packages/adapters/copilot"] = function(module, exports) {
@@ -7855,7 +7925,7 @@ const path = require('path');
7855
7925
  const os = require('os');
7856
7926
  const { execSync } = require('child_process');
7857
7927
 
7858
- const VERSION = '6.8.0';
7928
+ const VERSION = '6.10.0';
7859
7929
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
7860
7930
 
7861
7931
  function requireSourceOrBundled(key) {
@@ -9919,6 +9989,7 @@ function main() {
9919
9989
  const { detectIntent, buildSigIndex, rank } = requireSourceOrBundled('./src/retrieval/ranker');
9920
9990
  const { coverageScore } = requireSourceOrBundled('./src/analysis/coverage-score');
9921
9991
  const { loadSession, saveSession, mergeSessionContext } = requireSourceOrBundled('./src/session/memory');
9992
+ const { detectWorkspaces, inferPackage, scopeToPackage } = requireSourceOrBundled('./src/workspace/detector');
9922
9993
 
9923
9994
  const intent = detectIntent(query);
9924
9995
  const intentWeights = getIntentWeights(intent);
@@ -9931,6 +10002,34 @@ function main() {
9931
10002
 
9932
10003
  let ranked = rank(query, sigIndex, { topK: 5, weights: intentWeights, cwd });
9933
10004
 
10005
+ // v6.10: Workspace scoping — infer package from query and apply boost
10006
+ const workspaces = detectWorkspaces(cwd);
10007
+ const packageFlag = args[args.indexOf('--package') + 1];
10008
+ const globalFlag = args.includes('--global');
10009
+
10010
+ let inferredPackage = null;
10011
+ if (!globalFlag && workspaces.length > 0) {
10012
+ if (packageFlag) {
10013
+ inferredPackage = workspaces.find(d => path.basename(d) === packageFlag) || null;
10014
+ if (!packageFlag || !inferredPackage) {
10015
+ process.stderr.write(`[sigmap] ⚠ package "${packageFlag}" not found — searching entire repo\n`);
10016
+ }
10017
+ } else {
10018
+ inferredPackage = inferPackage(query, workspaces, cwd);
10019
+ }
10020
+ }
10021
+
10022
+ if (inferredPackage) {
10023
+ ranked = ranked.map(r => ({
10024
+ ...r,
10025
+ score: r.score + scopeToPackage(r.file, inferredPackage),
10026
+ })).sort((a, b) => b.score - a.score);
10027
+
10028
+ if (!args.includes('--json') && !globalFlag) {
10029
+ process.stderr.write(`[sigmap] 📦 package scope: ${path.relative(cwd, inferredPackage)}\n`);
10030
+ }
10031
+ }
10032
+
9934
10033
  // v6.8: --followup support — carry session context forward
9935
10034
  const isFollowup = args.includes('--followup');
9936
10035
  const session = isFollowup ? loadSession(cwd) : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "6.8.0",
3
+ "version": "6.10.0",
4
4
  "description": "Zero-dependency AI context engine — 97% token reduction. No npm install. Runs on Node 18+.",
5
5
  "main": "gen-context.js",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-cli",
3
- "version": "6.8.0",
3
+ "version": "6.10.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": "6.8.0",
3
+ "version": "6.10.0",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -0,0 +1,66 @@
1
+ 'use strict';
2
+
3
+ module.exports = { scoreUsefulness, computeUsefulnessStats };
4
+
5
+ /**
6
+ * Score answer usefulness based on:
7
+ * 1. Whether right file was retrieved (retrieval hit)
8
+ * 2. Whether retrieved context covered the answer (coverage)
9
+ * 3. Confidence in answer quality (from ranking score)
10
+ */
11
+ function scoreUsefulness(taskResult, rankingScore) {
12
+ const { hitRank } = taskResult;
13
+
14
+ // Tier 1: File not retrieved — context cannot be useful
15
+ if (hitRank === -1 || hitRank > 5) {
16
+ return {
17
+ tier: 'not-useful',
18
+ score: 0.0,
19
+ reason: 'expected file not in top 5'
20
+ };
21
+ }
22
+
23
+ // Tier 2: File retrieved but not top ranking — partially useful
24
+ if (hitRank > 1) {
25
+ return {
26
+ tier: 'partially-useful',
27
+ score: rankingScore * 0.5, // Partial usefulness
28
+ reason: `file ranked #${hitRank}`
29
+ };
30
+ }
31
+
32
+ // Tier 3: File at top of ranking — fully useful
33
+ return {
34
+ tier: 'fully-useful',
35
+ score: rankingScore, // Full usefulness
36
+ reason: 'file ranked first'
37
+ };
38
+ }
39
+
40
+ function computeUsefulnessStats(taskResults) {
41
+ const tiers = {
42
+ 'fully-useful': 0,
43
+ 'partially-useful': 0,
44
+ 'not-useful': 0
45
+ };
46
+
47
+ let totalScore = 0;
48
+ let count = 0;
49
+
50
+ taskResults.forEach(result => {
51
+ const usefulness = scoreUsefulness(result, result.rankingScore || 1.0);
52
+ tiers[usefulness.tier]++;
53
+ totalScore += usefulness.score;
54
+ count++;
55
+ });
56
+
57
+ return {
58
+ fully_useful: tiers['fully-useful'],
59
+ partially_useful: tiers['partially-useful'],
60
+ not_useful: tiers['not-useful'],
61
+ fully_useful_pct: count > 0 ? (tiers['fully-useful'] / count * 100).toFixed(1) : 0,
62
+ partially_useful_pct: count > 0 ? (tiers['partially-useful'] / count * 100).toFixed(1) : 0,
63
+ not_useful_pct: count > 0 ? (tiers['not-useful'] / count * 100).toFixed(1) : 0,
64
+ average_usefulness_score: count > 0 ? (totalScore / count).toFixed(3) : 0
65
+ };
66
+ }
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: '6.8.0',
21
+ version: '6.10.0',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24
 
@@ -0,0 +1,85 @@
1
+ 'use strict';
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ module.exports = { detectWorkspaces, inferPackage, scopeToPackage };
5
+
6
+ function detectWorkspaces(cwd) {
7
+ const pkgPath = path.join(cwd, 'package.json');
8
+ if (!fs.existsSync(pkgPath)) return [];
9
+
10
+ let pkg;
11
+ try {
12
+ pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
13
+ } catch {
14
+ return [];
15
+ }
16
+
17
+ const patterns = pkg.workspaces || [];
18
+ const dirs = [];
19
+
20
+ // Handle both flat array and object with packages field (Yarn v2 format)
21
+ const patternArray = Array.isArray(patterns) ? patterns : (patterns.packages || []);
22
+
23
+ for (const p of patternArray) {
24
+ const base = p.replace(/\/\*\*?$/, '');
25
+ const resolved = path.join(cwd, base);
26
+ if (fs.existsSync(resolved)) {
27
+ try {
28
+ for (const entry of fs.readdirSync(resolved, { withFileTypes: true })) {
29
+ if (entry.isDirectory()) dirs.push(path.join(resolved, entry.name));
30
+ }
31
+ } catch (_) {}
32
+ }
33
+ }
34
+
35
+ return dirs;
36
+ }
37
+
38
+ // Infer package from query tokens: "add rate limiting to payments" → "packages/payments"
39
+ function inferPackage(query, workspaceDirs, cwd) {
40
+ const tokens = query.toLowerCase().split(/\W+/).filter(t => t.length > 2);
41
+
42
+ // Find longest matching package name
43
+ let bestMatch = null;
44
+ let bestLen = 0;
45
+ let bestMatchLen = 0;
46
+
47
+ for (const dir of workspaceDirs) {
48
+ const name = path.basename(dir).toLowerCase();
49
+ for (const token of tokens) {
50
+ const matchLen = _getMatchLength(name, token);
51
+ // Only consider matches; use longest match, and break ties by longest package name
52
+ if (matchLen > 0 && (matchLen > bestLen || (matchLen === bestLen && name.length > bestMatchLen))) {
53
+ bestMatch = dir;
54
+ bestLen = matchLen;
55
+ bestMatchLen = name.length;
56
+ }
57
+ }
58
+ }
59
+
60
+ return bestMatch;
61
+ }
62
+
63
+ function _getMatchLength(name, token) {
64
+ if (name === token) return 1000 + name.length; // Exact match is best
65
+ if (name.startsWith(token) && token.length >= 3) return 100 + token.length;
66
+ if (token.startsWith(name) && name.length >= 3) return name.length;
67
+ return 0;
68
+ }
69
+
70
+ // Return boost multiplier for files inside the inferred package
71
+ function scopeToPackage(filePath, packageDir) {
72
+ const normalized = filePath.replace(/\\/g, '/');
73
+ const normalizedPkg = packageDir.replace(/\\/g, '/');
74
+
75
+ // Ensure we match the directory boundary, not just a prefix
76
+ // e.g., packages/payment should not match packages/payment-old
77
+ if (normalized.startsWith(normalizedPkg)) {
78
+ const afterPrefix = normalized.slice(normalizedPkg.length);
79
+ // Check if next char is / or if it's the exact match
80
+ if (afterPrefix === '' || afterPrefix[0] === '/') {
81
+ return 0.30;
82
+ }
83
+ }
84
+ return 0;
85
+ }