sigmap 6.9.0 → 6.10.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 CHANGED
@@ -56,6 +56,19 @@ 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
+ ## deps
60
+ ```
61
+ src/extractors/python_ast.py ← ast
62
+ ```
63
+
64
+ ## changes (last 5 commits — 0 seconds ago)
65
+ ```
66
+ src/discovery/language-detector.js ~detectLanguages
67
+ src/extractors/python.js +tryNativeExtract +extract ~extract ~extractDocHint
68
+ src/extractors/python_ast.py +annotation_to_str +format_args +arguments +get_decorator_names
69
+ src/extractors/r.js +extract +definitions +readBalancedParens +normalizeParams
70
+ ```
71
+
59
72
  ## packages
60
73
 
61
74
  ### packages/cli/index.js
@@ -101,18 +114,6 @@ code-fence js
101
114
  code-fence ---
102
115
  ```
103
116
 
104
- ### packages/core/index.js
105
- ```
106
- module.exports = { extract, rank, buildSigIndex, scan, score, adapt }
107
- function _resolveExtractor(language)
108
- function extract(src, language) → string[]
109
- function rank(query, sigIndex, opts) → { file: string, score: nu
110
- function buildSigIndex(cwd) → Map<string, string[]>
111
- function scan(sigs, filePath) → { safe: string[], redacte
112
- function score(cwd) → { * score: number, * grad
113
- function adapt(context, adapterName, opts = {}) → string
114
- ```
115
-
116
117
  ### packages/adapters/copilot.js
117
118
  ```
118
119
  module.exports = { name, format, outputPath, write }
@@ -172,109 +173,19 @@ function outputPath(cwd) → string
172
173
  function write(context, cwd, opts = {})
173
174
  ```
174
175
 
175
- ## src
176
-
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
- ### src/extractors/python.js
195
- ```
196
- module.exports = { extract }
197
- function extract(src) → string[]
198
- function extractClassMethods(stripped, startIndex)
199
- function tryExtractDataclassFields(stripped, classIndex)
200
- function tryExtractBaseModelFields(stripped, bodyStart)
201
- function extractClassConstants(stripped, startIndex)
202
- function extractReturnType(sigLine)
203
- function normalizeParams(params)
204
- function extractDocHint(src, fnName, fnSigLine)
205
- ```
206
-
207
- ### src/extractors/ruby.js
208
- ```
209
- module.exports = { extract }
210
- function extract(src) → string[]
211
- function normalizeParams(params)
212
- function extractReturnHint(stripped, index)
213
- ```
214
-
215
- ### src/extractors/rust.js
216
- ```
217
- module.exports = { extract }
218
- function extract(src) → string[]
219
- function extractBlock(src, startIndex)
220
- function extractMethods(block)
221
- function normalizeParams(params)
222
- function extractReturnType(afterParen)
223
- ```
224
-
225
- ### src/extractors/scala.js
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
- ```
234
-
235
- ### src/extractors/svelte.js
236
- ```
237
- module.exports = { extract }
238
- function extract(src) → string[]
239
- function normalizeParams(params)
240
- function normalizeType(type)
241
- ```
242
-
243
- ### src/extractors/swift.js
244
- ```
245
- module.exports = { extract }
246
- function extract(src) → string[]
247
- function extractBlock(src, startIndex)
248
- function extractMembers(block)
249
- function normalizeParams(params)
250
- function extractArrowType(str)
251
- ```
252
-
253
- ### src/extractors/todos.js
254
- ```
255
- module.exports = { extractTodos }
256
- function extractTodos(src) → {line:number, tag:string,
257
- ```
258
-
259
- ### src/extractors/vue.js
176
+ ### packages/core/index.js
260
177
  ```
261
- module.exports = { extract }
262
- function extract(src) → string[]
263
- function normalizeParams(params)
264
- function normalizeType(type)
178
+ module.exports = { extract, rank, buildSigIndex, scan, score, adapt }
179
+ function _resolveExtractor(language)
180
+ function extract(src, language) → string[]
181
+ function rank(query, sigIndex, opts) → { file: string, score: nu
182
+ function buildSigIndex(cwd) → Map<string, string[]>
183
+ function scan(sigs, filePath) → { safe: string[], redacte
184
+ function score(cwd) → { * score: number, * grad
185
+ function adapt(context, adapterName, opts = {}) → string
265
186
  ```
266
187
 
267
- ### src/eval/scorer.js
268
- ```
269
- module.exports = { hitAtK, reciprocalRank, precisionAtK, aggregate, firstRank }
270
- function firstRank(ranked, expected) → number
271
- function normalizePath(p) → string
272
- function hitAtK(ranked, expected, k = 5) → 0|1
273
- function reciprocalRank(ranked, expected) → number
274
- function precisionAtK(ranked, expected, k = 5) → number
275
- function aggregate(results, k = 5) → { * hitAt5: number, // fr
276
- function round(x)
277
- ```
188
+ ## src
278
189
 
279
190
  ### src/eval/runner.js
280
191
  ```
@@ -438,19 +349,6 @@ function detectVersion(cwd)
438
349
  function format(context, cwd, writtenFiles, sigmapVersion)
439
350
  ```
440
351
 
441
- ### src/eval/analyzer.js
442
- ```
443
- module.exports = { analyzeFiles, formatAnalysisTable, formatAnalysisJSON }
444
- function isDockerfile(name)
445
- function getExtractorName(filePath)
446
- function tokenCount(sigs)
447
- function hasCoverage(filePath, cwd)
448
- function loadExtractor(name, cwd)
449
- function analyzeFiles(files, cwd, opts) → object[]
450
- function formatAnalysisTable(stats, showSlow) → string
451
- function formatAnalysisJSON(stats) → object
452
- ```
453
-
454
352
  ### src/format/dashboard.js
455
353
  ```
456
354
  module.exports = { generateDashboardHtml, renderHistoryCharts, computeExtractorCoverage, percentile, overBudgetStreak }
@@ -593,13 +491,6 @@ function _existsAnywhere(cwd, filename, maxDepth)
593
491
  function _walkFind(dir, name, depth)
594
492
  ```
595
493
 
596
- ### src/discovery/language-detector.js
597
- ```
598
- module.exports = { detectLanguages }
599
- function detectLanguages(cwd)
600
- function _walkDepth(dir, depth, extCount)
601
- ```
602
-
603
494
  ### src/discovery/sigmapignore.js
604
495
  ```
605
496
  module.exports = { loadIgnorePatterns, matchesIgnorePattern }
@@ -607,11 +498,6 @@ function loadIgnorePatterns(cwd)
607
498
  function matchesIgnorePattern(dirName, patterns)
608
499
  ```
609
500
 
610
- ### src/discovery/source-root-registry.js
611
- ```
612
- module.exports = { REGISTRY }
613
- ```
614
-
615
501
  ### src/retrieval/ranker.js
616
502
  ```
617
503
  module.exports = { rank, buildSigIndex, scoreFile, formatRankTable, formatRankJSON, DEFAULT_WEIGHTS, GRAPH_BOOST_AMOUNTS, detectIntent }
@@ -656,6 +542,22 @@ function scoreCandidate(dirName, fullPath, context)
656
542
  function _countSourceFiles(dir, depth)
657
543
  ```
658
544
 
545
+ ### src/eval/usefulness-scorer.js
546
+ ```
547
+ module.exports = { scoreUsefulness, computeUsefulnessStats }
548
+ function scoreUsefulness(taskResult, rankingScore)
549
+ function computeUsefulnessStats(taskResults)
550
+ ```
551
+
552
+ ### src/workspace/detector.js
553
+ ```
554
+ module.exports = { detectWorkspaces, inferPackage, scopeToPackage }
555
+ function detectWorkspaces(cwd)
556
+ function inferPackage(query, workspaceDirs, cwd)
557
+ function _getMatchLength(name, token)
558
+ function scopeToPackage(filePath, packageDir)
559
+ ```
560
+
659
561
  ### src/discovery/source-root-resolver.js
660
562
  ```
661
563
  module.exports = { resolveSourceRoots }
@@ -667,6 +569,72 @@ function _dedupeNested(scored)
667
569
  function _computeConfidence(frameworks, languages, scoredCount)
668
570
  ```
669
571
 
572
+ ### src/discovery/source-root-registry.js
573
+ ```
574
+ module.exports = { REGISTRY }
575
+ ```
576
+
577
+ ### src/discovery/language-detector.js
578
+ ```
579
+ module.exports = { detectLanguages }
580
+ function detectLanguages(cwd)
581
+ function _walkDepth(dir, depth, extCount)
582
+ ```
583
+
584
+ ### src/eval/analyzer.js
585
+ ```
586
+ module.exports = { analyzeFiles, formatAnalysisTable, formatAnalysisJSON }
587
+ function isDockerfile(name)
588
+ function getExtractorName(filePath)
589
+ function tokenCount(sigs)
590
+ function hasCoverage(filePath, cwd)
591
+ function loadExtractor(name, cwd)
592
+ function analyzeFiles(files, cwd, opts) → object[]
593
+ function formatAnalysisTable(stats, showSlow) → string
594
+ function formatAnalysisJSON(stats) → object
595
+ ```
596
+
597
+ ### src/extractors/python.js
598
+ ```
599
+ module.exports = { extract, tryNativeExtract }
600
+ function tryNativeExtract(filePath) → string[]|null
601
+ function extract(src, filePath) → string[]
602
+ function extractClassMethods(stripped, startIndex)
603
+ function tryExtractDataclassFields(stripped, classIndex)
604
+ function tryExtractBaseModelFields(stripped, bodyStart)
605
+ function extractClassConstants(stripped, startIndex)
606
+ function extractReturnType(sigLine)
607
+ function normalizeParams(params)
608
+ function extractDocHint(src, fnName, fnSigLine)
609
+ ```
610
+
611
+ ### src/extractors/python_ast.py
612
+ ```
613
+ def annotation_to_str(node) # Convert an AST annotation node to a string representation
614
+ def format_args(args_node) # Format a function arguments node into a compact signature st
615
+ def get_decorator_names(node) # Return a list of decorator name strings for a function/class
616
+ def is_dataclass(node)
617
+ def is_basemodel(bases) # Check if class bases include BaseModel or BaseSettings
618
+ def is_optional_annotation(annotation) # Check if an annotation represents an Optional type
619
+ def get_docstring_hint(node) # Extract first sentence of docstring, if present
620
+ def extract_dataclass_fields(class_node) # Return a collapsed fields string for a @dataclass class
621
+ def extract_basemodel_fields(class_node) # Return a compact {required*, optional
622
+ def extract_class_constants(class_node) # Yield ALL_CAPS constant assignments from class body
623
+ def extract_method_sig(func_node) # Format a method signature string (already indented by caller
624
+ def extract_function_sig(func_node, src_lines) # Format a top-level function signature string
625
+ def extract_fastapi_routes(tree, src_lines) # Extract FastAPI route signatures from top-level decorated fu
626
+ def extract(filepath)
627
+ def main()
628
+ ```
629
+
630
+ ### src/extractors/r.js
631
+ ```
632
+ module.exports = { extract }
633
+ function extract(src) → string[]
634
+ function readBalancedParens(src, openIdx, cap = 4096)
635
+ function normalizeParams(raw)
636
+ ```
637
+
670
638
  ### src/mcp/server.js
671
639
  ```
672
640
  module.exports = { start }
package/CHANGELOG.md CHANGED
@@ -10,6 +10,30 @@ Format: [Semantic Versioning](https://semver.org/)
10
10
 
11
11
  ---
12
12
 
13
+ ## [6.10.1] — 2026-05-10
14
+
15
+ ### Added
16
+
17
+ - **R language support (Phase 1)** — Extract function signatures from `.r` and `.R` files with support for function definitions (`<-`, `=`, `<<-` forms), multi-line arguments with string-literal protection, S4 patterns (setGeneric, setMethod, setClass), and private function filtering. Shiny framework detection via `app.R`/`ui.R`/`server.R` triplet.
18
+ - **Native Python AST extractor** — Fallback to `python_ast.py` using `ast.parse()` for accurate extraction of complex signatures (multiline args, stacked decorators, complex generics). Preserves regex fallback for Python 2 / no-Python3 environments. Zero breaking changes to output format.
19
+
20
+ ### Fixed
21
+
22
+ - **ReferenceError in `--query`** — Fixed variable scope issue where `adpIdx` was undefined when no context file present. Moved variable declaration to proper scope before conditional block.
23
+ - **Windows path handling** — Normalized path separators in nested path deduplication. Windows backslashes no longer cause false negatives when matching nested source roots.
24
+ - **.contextignore patterns** — Fixed bracket character classes (`[Bb]in/`) being treated as literals. Fixed trailing slashes on directory patterns not matching nested paths. Added error handling for malformed bracket syntax.
25
+ - **Claude adapter in per-module and hot-cold strategies** — Fixed adapter not being written to output in per-module and hot-cold context strategies.
26
+
27
+ ---
28
+
29
+ ## [6.10.0] — 2026-05-05
30
+
31
+ ### Added
32
+
33
+ - **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.
34
+
35
+ ---
36
+
13
37
  ## [6.9.0] — 2026-05-03
14
38
 
15
39
  ### Added
package/README.md CHANGED
@@ -12,7 +12,8 @@
12
12
  [![Zero deps](https://img.shields.io/badge/dependencies-zero-22c55e)](package.json)
13
13
  [![License: MIT](https://img.shields.io/badge/License-MIT-7c6af7.svg)](LICENSE)
14
14
  [![GitHub Stars](https://img.shields.io/github/stars/manojmallick/sigmap?style=flat&color=f59e0b&logo=github)](https://github.com/manojmallick/sigmap/stargazers)
15
- [![Hacker News](https://img.shields.io/badge/Hacker%20News-Discussion-orange?logo=ycombinator)](https://news.ycombinator.com/item?id=47956790)
15
+ [![Star History Chart](https://api.star-history.com/svg?repos=manojmallick/sigmap&type=Date)](https://star-history.com/#manojmallick/sigmap&Date)
16
+ [![Discover on ShyPD](https://img.shields.io/badge/ShyPD-Discover-7c6af7?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij48Y2lyY2xlIGN4PSI4IiBjeT0iOCIgcj0iOCIgZmlsbD0id2hpdGUiLz48L3N2Zz4=&logoColor=7c6af7)](https://shypd.ai/tools/sigmap)
16
17
 
17
18
  </div>
18
19
 
@@ -20,9 +21,11 @@
20
21
 
21
22
  ## Try it now
22
23
 
24
+ **No install required.** Run instantly on any machine:
25
+
23
26
  ```bash
24
27
  npx sigmap
25
- sigmap ask "Where is auth handled?"
28
+ npx sigmap ask "Where is auth handled?"
26
29
  ```
27
30
 
28
31
  Zero config. Zero dependencies. Under 10 seconds.
@@ -76,8 +79,8 @@ Ask → Rank → Context → Validate → Judge → Learn
76
79
  ## Benchmark
77
80
 
78
81
  ```
79
- Benchmark : sigmap-v6.8-main
80
- Date : 2026-04-30
82
+ Benchmark : sigmap-v6.10-main
83
+ Date : 2026-05-05
81
84
 
82
85
  Hit@5 : 80.0% (baseline 13.6% — 5.9× lift)
83
86
  Prompt reduction : 41.0%
@@ -228,6 +231,20 @@ If SigMap saves you context or API spend, a ⭐ on [GitHub](https://github.com/m
228
231
 
229
232
  ---
230
233
 
234
+ ## Contributing
235
+
236
+ SigMap welcomes contributions!
237
+
238
+ **Before submitting a PR:**
239
+ 1. Read [CONTRIBUTING.md](CONTRIBUTING.md)
240
+ 2. Check [Discussions → Announcements](../../discussions) for workflow setup
241
+ 3. Target the `develop` branch (not main)
242
+ 4. Follow the [contributor checklist](.github/CONTRIBUTOR_CHECKLIST.txt)
243
+
244
+ See [.github/PULL_REQUEST_TEMPLATE.md](.github/PULL_REQUEST_TEMPLATE.md) for the PR checklist. All contributors are credited in the CHANGELOG and release notes.
245
+
246
+ ---
247
+
231
248
  ## Why not embeddings?
232
249
 
233
250
  | | Embeddings | SigMap |
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.9.0',
5390
+ version: '6.10.1',
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) {
@@ -7843,6 +7913,95 @@ __factories["./src/discovery/source-root-resolver"] = function(module, exports)
7843
7913
  module.exports = { resolveSourceRoots };
7844
7914
  };
7845
7915
 
7916
+ // ── ./src/workspace/detector ──
7917
+ __factories["./src/workspace/detector"] = function(module, exports) {
7918
+ 'use strict';
7919
+ const fs = require('fs');
7920
+ const path = require('path');
7921
+ module.exports = { detectWorkspaces, inferPackage, scopeToPackage };
7922
+
7923
+ function detectWorkspaces(cwd) {
7924
+ const pkgPath = path.join(cwd, 'package.json');
7925
+ if (!fs.existsSync(pkgPath)) return [];
7926
+
7927
+ let pkg;
7928
+ try {
7929
+ pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
7930
+ } catch {
7931
+ return [];
7932
+ }
7933
+
7934
+ const patterns = pkg.workspaces || [];
7935
+ const dirs = [];
7936
+
7937
+ // Handle both flat array and object with packages field (Yarn v2 format)
7938
+ const patternArray = Array.isArray(patterns) ? patterns : (patterns.packages || []);
7939
+
7940
+ for (const p of patternArray) {
7941
+ const base = p.replace(/\/\*\*?$/, '');
7942
+ const resolved = path.join(cwd, base);
7943
+ if (fs.existsSync(resolved)) {
7944
+ try {
7945
+ for (const entry of fs.readdirSync(resolved, { withFileTypes: true })) {
7946
+ if (entry.isDirectory()) dirs.push(path.join(resolved, entry.name));
7947
+ }
7948
+ } catch (_) {}
7949
+ }
7950
+ }
7951
+
7952
+ return dirs;
7953
+ }
7954
+
7955
+ // Infer package from query tokens: "add rate limiting to payments" → "packages/payments"
7956
+ function inferPackage(query, workspaceDirs, cwd) {
7957
+ const tokens = query.toLowerCase().split(/\W+/).filter(t => t.length > 2);
7958
+
7959
+ // Find longest matching package name
7960
+ let bestMatch = null;
7961
+ let bestLen = 0;
7962
+ let bestMatchLen = 0;
7963
+
7964
+ for (const dir of workspaceDirs) {
7965
+ const name = path.basename(dir).toLowerCase();
7966
+ for (const token of tokens) {
7967
+ const matchLen = _getMatchLength(name, token);
7968
+ // Only consider matches; use longest match, and break ties by longest package name
7969
+ if (matchLen > 0 && (matchLen > bestLen || (matchLen === bestLen && name.length > bestMatchLen))) {
7970
+ bestMatch = dir;
7971
+ bestLen = matchLen;
7972
+ bestMatchLen = name.length;
7973
+ }
7974
+ }
7975
+ }
7976
+
7977
+ return bestMatch;
7978
+ }
7979
+
7980
+ function _getMatchLength(name, token) {
7981
+ if (name === token) return 1000 + name.length; // Exact match is best
7982
+ if (name.startsWith(token) && token.length >= 3) return 100 + token.length;
7983
+ if (token.startsWith(name) && name.length >= 3) return name.length;
7984
+ return 0;
7985
+ }
7986
+
7987
+ // Return boost multiplier for files inside the inferred package
7988
+ function scopeToPackage(filePath, packageDir) {
7989
+ const normalized = filePath.replace(/\\/g, '/');
7990
+ const normalizedPkg = packageDir.replace(/\\/g, '/');
7991
+
7992
+ // Ensure we match the directory boundary, not just a prefix
7993
+ // e.g., packages/payment should not match packages/payment-old
7994
+ if (normalized.startsWith(normalizedPkg)) {
7995
+ const afterPrefix = normalized.slice(normalizedPkg.length);
7996
+ // Check if next char is / or if it's the exact match
7997
+ if (afterPrefix === '' || afterPrefix[0] === '/') {
7998
+ return 0.30;
7999
+ }
8000
+ }
8001
+ return 0;
8002
+ }
8003
+ };
8004
+
7846
8005
  /**
7847
8006
  * SigMap — gen-context.js v1.2.0
7848
8007
  * Zero-dependency AI context engine.
@@ -7855,7 +8014,7 @@ const path = require('path');
7855
8014
  const os = require('os');
7856
8015
  const { execSync } = require('child_process');
7857
8016
 
7858
- const VERSION = '6.9.0';
8017
+ const VERSION = '6.10.1';
7859
8018
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
7860
8019
 
7861
8020
  function requireSourceOrBundled(key) {
@@ -7935,14 +8094,22 @@ function loadIgnorePatterns(cwd) {
7935
8094
  function matchesIgnore(relPath, patterns) {
7936
8095
  for (const pat of patterns) {
7937
8096
  const normalized = pat.replace(/\\/g, '/');
7938
- // Simple glob: support * and ** and trailing /
7939
- const regexStr = normalized
7940
- .replace(/[.+^${}()|[\]\\]/g, '\\$&')
8097
+ // Strip trailing slash (gitignore style directory patterns)
8098
+ const patternToUse = normalized.endsWith('/')
8099
+ ? normalized.slice(0, -1)
8100
+ : normalized;
8101
+ // Escape regex special chars but NOT brackets (keep them for character classes)
8102
+ const regexStr = patternToUse
8103
+ .replace(/[.+^${}()|\\]/g, '\\$&')
7941
8104
  .replace(/\*\*/g, '___DOUBLE___')
7942
8105
  .replace(/\*/g, '[^/]*')
7943
8106
  .replace(/___DOUBLE___/g, '.*');
7944
- const regex = new RegExp(`(^|/)${regexStr}($|/)`);
7945
- if (regex.test(relPath)) return true;
8107
+ try {
8108
+ const regex = new RegExp(`(^|/)${regexStr}($|/)`);
8109
+ if (regex.test(relPath)) return true;
8110
+ } catch (_) {
8111
+ // Malformed bracket syntax or invalid regex — skip this pattern
8112
+ }
7946
8113
  }
7947
8114
  return false;
7948
8115
  }
@@ -8870,7 +9037,7 @@ function runPerModuleStrategy(cwd, config, fileEntries, inputTokenTotal) {
8870
9037
  overviewLines.push('> Inject the relevant module file into your IDE context window.');
8871
9038
  overviewLines.push('> For cross-module questions load both files.');
8872
9039
  const overviewContent = overviewLines.join('\n') + '\n';
8873
- const primaryTargets = (config.outputs || ['copilot']).filter((t) => t !== 'claude');
9040
+ const primaryTargets = config.outputs || ['copilot'];
8874
9041
  writeOutputs(overviewContent, primaryTargets, cwd, config);
8875
9042
 
8876
9043
  const overviewTokens = estimateTokens(overviewContent);
@@ -8889,7 +9056,7 @@ function runHotColdStrategy(cwd, config, fileEntries, recentFiles, inputTokenTot
8889
9056
  const hotContent = hotEntries.length > 0
8890
9057
  ? formatOutput(hotEntries, cwd, false, config, null)
8891
9058
  : '<!-- Generated by SigMap — no recently changed files -->\n';
8892
- const primaryTargets = (config.outputs || ['copilot']).filter((t) => t !== 'claude');
9059
+ const primaryTargets = config.outputs || ['copilot'];
8893
9060
  writeOutputs(hotContent, primaryTargets, cwd, config);
8894
9061
  const hotTokens = estimateTokens(hotContent);
8895
9062
 
@@ -9919,6 +10086,7 @@ function main() {
9919
10086
  const { detectIntent, buildSigIndex, rank } = requireSourceOrBundled('./src/retrieval/ranker');
9920
10087
  const { coverageScore } = requireSourceOrBundled('./src/analysis/coverage-score');
9921
10088
  const { loadSession, saveSession, mergeSessionContext } = requireSourceOrBundled('./src/session/memory');
10089
+ const { detectWorkspaces, inferPackage, scopeToPackage } = requireSourceOrBundled('./src/workspace/detector');
9922
10090
 
9923
10091
  const intent = detectIntent(query);
9924
10092
  const intentWeights = getIntentWeights(intent);
@@ -9931,6 +10099,34 @@ function main() {
9931
10099
 
9932
10100
  let ranked = rank(query, sigIndex, { topK: 5, weights: intentWeights, cwd });
9933
10101
 
10102
+ // v6.10: Workspace scoping — infer package from query and apply boost
10103
+ const workspaces = detectWorkspaces(cwd);
10104
+ const packageFlag = args[args.indexOf('--package') + 1];
10105
+ const globalFlag = args.includes('--global');
10106
+
10107
+ let inferredPackage = null;
10108
+ if (!globalFlag && workspaces.length > 0) {
10109
+ if (packageFlag) {
10110
+ inferredPackage = workspaces.find(d => path.basename(d) === packageFlag) || null;
10111
+ if (!packageFlag || !inferredPackage) {
10112
+ process.stderr.write(`[sigmap] ⚠ package "${packageFlag}" not found — searching entire repo\n`);
10113
+ }
10114
+ } else {
10115
+ inferredPackage = inferPackage(query, workspaces, cwd);
10116
+ }
10117
+ }
10118
+
10119
+ if (inferredPackage) {
10120
+ ranked = ranked.map(r => ({
10121
+ ...r,
10122
+ score: r.score + scopeToPackage(r.file, inferredPackage),
10123
+ })).sort((a, b) => b.score - a.score);
10124
+
10125
+ if (!args.includes('--json') && !globalFlag) {
10126
+ process.stderr.write(`[sigmap] 📦 package scope: ${path.relative(cwd, inferredPackage)}\n`);
10127
+ }
10128
+ }
10129
+
9934
10130
  // v6.8: --followup support — carry session context forward
9935
10131
  const isFollowup = args.includes('--followup');
9936
10132
  const session = isFollowup ? loadSession(cwd) : null;
@@ -10994,6 +11190,7 @@ function main() {
10994
11190
  // Priority: --output flag > --adapter flag > buildSigIndex probe order
10995
11191
  // (customOutput from config is handled inside buildSigIndex itself)
10996
11192
  let queryOpts;
11193
+ const adpIdx = args.indexOf('--adapter');
10997
11194
 
10998
11195
  // 1. --output <file> pins to an explicit path
10999
11196
  if (config.customOutput) {
@@ -11001,17 +11198,14 @@ function main() {
11001
11198
  }
11002
11199
 
11003
11200
  // 2. --adapter <name> pins to that adapter's output path (if --output not given)
11004
- if (!queryOpts) {
11005
- const adpIdx = args.indexOf('--adapter');
11006
- if (adpIdx >= 0) {
11007
- const adapterName = (args[adpIdx + 1] || '').trim().toLowerCase();
11008
- const VALID_ADAPTERS = ['copilot', 'claude', 'cursor', 'windsurf', 'openai', 'gemini', 'codex'];
11009
- if (VALID_ADAPTERS.includes(adapterName)) {
11010
- try {
11011
- const adapterMod = __require('./packages/adapters/' + adapterName);
11012
- queryOpts = { contextPath: adapterMod.outputPath(cwd) };
11013
- } catch (_) {}
11014
- }
11201
+ if (!queryOpts && adpIdx >= 0) {
11202
+ const adapterName = (args[adpIdx + 1] || '').trim().toLowerCase();
11203
+ const VALID_ADAPTERS = ['copilot', 'claude', 'cursor', 'windsurf', 'openai', 'gemini', 'codex'];
11204
+ if (VALID_ADAPTERS.includes(adapterName)) {
11205
+ try {
11206
+ const adapterMod = __require('./packages/adapters/' + adapterName);
11207
+ queryOpts = { contextPath: adapterMod.outputPath(cwd) };
11208
+ } catch (_) {}
11015
11209
  }
11016
11210
  }
11017
11211