sigmap 6.4.0 → 6.5.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
@@ -47,18 +47,6 @@ Use this marker block for all appendable context files:
47
47
  <!-- Updated by gen-context.js -->
48
48
  # Code signatures
49
49
 
50
- ## changes (last 5 commits — 0 seconds ago)
51
- ```
52
- src/learning/weights.js +exportWeights +importWeights ~resetWeights
53
- packages/adapters/codex.js ~write ~format
54
- packages/adapters/claude.js ~format ~write
55
- packages/adapters/gemini.js ~format
56
- packages/adapters/copilot.js ~format
57
- packages/adapters/cursor.js ~format
58
- packages/adapters/openai.js ~format
59
- packages/adapters/windsurf.js ~format
60
- ```
61
-
62
50
  ## packages
63
51
 
64
52
  ### packages/cli/index.js
@@ -133,22 +121,22 @@ function outputPath(cwd) → string
133
121
  function write(context, cwd, opts = {})
134
122
  ```
135
123
 
136
- ### packages/adapters/gemini.js
124
+ ### packages/adapters/copilot.js
137
125
  ```
138
126
  module.exports = { name, format, outputPath, write }
139
127
  function format(context, opts = {}) → string
128
+ function _confidenceMeta(opts)
140
129
  function outputPath(cwd) → string
141
130
  function write(context, cwd, opts = {})
142
- function _confidenceMeta(opts)
143
131
  ```
144
132
 
145
- ### packages/adapters/copilot.js
133
+ ### packages/adapters/gemini.js
146
134
  ```
147
135
  module.exports = { name, format, outputPath, write }
148
136
  function format(context, opts = {}) → string
149
- function _confidenceMeta(opts)
150
137
  function outputPath(cwd) → string
151
138
  function write(context, cwd, opts = {})
139
+ function _confidenceMeta(opts)
152
140
  ```
153
141
 
154
142
  ### packages/adapters/cursor.js
@@ -547,15 +535,6 @@ function formatAnalysisJSON(stats) → object
547
535
  module.exports = { DEFAULTS }
548
536
  ```
549
537
 
550
- ### src/config/loader.js
551
- ```
552
- module.exports = { loadConfig, loadBaseConfig }
553
- function loadBaseConfig(extendsVal, cwd)
554
- function detectAutoSrcDirs(cwd, excludeList) → string[]
555
- function loadConfig(cwd) → object
556
- function deepClone(obj)
557
- ```
558
-
559
538
  ### src/format/dashboard.js
560
539
  ```
561
540
  module.exports = { generateDashboardHtml, renderHistoryCharts, computeExtractorCoverage, percentile, overBudgetStreak }
@@ -673,6 +652,15 @@ function extractClassMembers(block)
673
652
  function normalizeParams(params)
674
653
  ```
675
654
 
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
+
676
664
  ### src/learning/weights.js
677
665
  ```
678
666
  module.exports = { BASELINE, DECAY, MAX_MULT, MIN_MULT, weightsPath, clampMultiplier, normalizeFile, loadWeights, saveWeights, updateWeights, boostFiles, penalizeFiles, resetWeights, exportWeights, importWeights }
package/CHANGELOG.md CHANGED
@@ -10,6 +10,23 @@ Format: [Semantic Versioning](https://semver.org/)
10
10
 
11
11
  ---
12
12
 
13
+ ## [6.5.0] — 2026-04-25
14
+
15
+ ### Added
16
+
17
+ - **Source Root Resolver (v6.5)** — intelligent auto-detection of source directories for 17 languages and 50+ frameworks (Next.js, Django, Rails, Spring Boot, Flutter, Go, Rust, etc.). Uses multi-signal scoring: manifest files, language/framework detection, file density, git activity, and framework-specific srcDirs. Returns confidence level (high/medium/low) and detailed explanation. Integrated into `loadConfig()` with graceful fallback to legacy heuristics.
18
+ - **`.sigmapignore` pattern matching** — new `.sigmapignore` file support (fallback to `.contextignore`) for excluding directories. Supports simple patterns like `legacy/` and globs like `src/**`.
19
+ - **`sigmap roots` CLI command** — three modes: `--explain` (default, shows detected languages/frameworks and scores), `--json` (structured output), `--fix` (interactive prompt to correct srcDirs and write to config).
20
+ - **Monorepo detection and enumeration** — auto-detects monorepos via pnpm-workspace.yaml, turbo.json, nx.json, lerna.json, and package.json workspaces. Enumerates all sub-packages and common deep paths.
21
+
22
+ ### Fixed
23
+
24
+ - **Framework-discovery tests** — updated registry entries to include all framework-specific srcDirs expected by legacy detector (Rails: db/spec/test, Laravel: resources/tests, Angular: projects/apps/libs, Next: hooks/utils).
25
+ - **Scoring penalty for framework srcDirs** — test directories (spec, test, tests) no longer penalized when explicitly in framework's srcDirs list.
26
+ - **CLI command ordering** — `roots` command handler now executes before `explain` to prevent flag conflict.
27
+
28
+ ---
29
+
13
30
  ## [6.4.0] — 2026-04-23
14
31
 
15
32
  ### Changed
package/README.md CHANGED
@@ -7,6 +7,7 @@
7
7
  **SigMap finds the right files before your AI answers.**
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/sigmap?color=7c6af7&label=latest&logo=npm)](https://www.npmjs.com/package/sigmap)
10
+ [![npm downloads](https://img.shields.io/npm/dt/sigmap?color=22c55e&label=downloads&logo=npm)](https://www.npmjs.com/package/sigmap)
10
11
  [![CI](https://github.com/manojmallick/sigmap/actions/workflows/ci.yml/badge.svg)](https://github.com/manojmallick/sigmap/actions/workflows/ci.yml)
11
12
  [![Zero deps](https://img.shields.io/badge/dependencies-zero-22c55e)](package.json)
12
13
  [![License: MIT](https://img.shields.io/badge/License-MIT-7c6af7.svg)](LICENSE)
@@ -37,10 +38,10 @@ Works with Copilot, Claude, Cursor, Windsurf, and any LLM.
37
38
 
38
39
  ## Why SigMap?
39
40
 
40
- - **80.0% hit@5** — right file found in top 5 results (vs 13.6% baseline)
41
+ - **78.9% hit@5** — right file found in top 5 results (vs 13.6% baseline)
41
42
  - **40–98% token reduction** — 2K–4K tokens instead of 80K+
42
43
  - **52.2% task success rate** — up from 10% without context
43
- - **1.68 prompts per task** — down from 2.84
44
+ - **1.69 prompts per task** — down from 2.84
44
45
  - **Works with any LLM** — no API key, no cloud, no accounts
45
46
  - **Zero npm dependencies** — `npx sigmap` on any machine
46
47
 
@@ -50,7 +51,7 @@ Works with Copilot, Claude, Cursor, Windsurf, and any LLM.
50
51
 
51
52
  | Without SigMap | With SigMap |
52
53
  |---|---|
53
- | ❌ Guessing which files are relevant | ✅ Right file in context — 80% of the time |
54
+ | ❌ Guessing which files are relevant | ✅ Right file in context — 79% of the time |
54
55
  | ❌ Sending the full repo to your AI | ✅ Minimal context — only what matters |
55
56
  | ❌ Embeddings / vector DB required | ✅ Grounded answers, no infra needed |
56
57
 
@@ -74,13 +75,13 @@ Ask → Rank → Context → Validate → Judge → Learn
74
75
  ## Benchmark
75
76
 
76
77
  ```
77
- Benchmark : sigmap-v6.0-main
78
- Date : 2026-04-19
78
+ Benchmark : sigmap-v6.4-main
79
+ Date : 2026-04-23
79
80
 
80
- Hit@5 : 80.0% (baseline 13.6% — 5.8× lift)
81
- Prompt reduction : 40.8%
81
+ Hit@5 : 78.9% (baseline 13.6% — 5.8× lift)
82
+ Prompt reduction : 40.6%
82
83
  Task success : 52.2% (baseline 10%)
83
- Prompts / task : 1.68 (baseline 2.84)
84
+ Prompts / task : 1.69 (baseline 2.84)
84
85
  Token reduction: 40–98% (avg 96.9% across 18 real repos)
85
86
  ```
86
87
 
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.4.0',
5390
+ version: '6.5.0',
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.4.0';
7225
+ const VERSION = '6.5.0';
7226
7226
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
7227
7227
 
7228
7228
  function requireSourceOrBundled(key) {
@@ -8477,6 +8477,15 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
8477
8477
  fileEntries.push({ filePath, sigs, deps: extractFileDeps(filePath, content, config), content, mtime });
8478
8478
  }
8479
8479
 
8480
+ // v6.5: Coverage feedback loop — warn if symbol density is suspiciously low
8481
+ const symbolsFound = fileEntries.reduce((n, f) => n + (f.sigs?.length || 0), 0);
8482
+ const filesFound = fileEntries.length;
8483
+ const coverageRatio = filesFound > 0 ? symbolsFound / filesFound : 1;
8484
+ if (coverageRatio < 0.25 && !config.srcDirs?.length) {
8485
+ process.stderr.write('[sigmap] ⚠ low symbol coverage — source roots may be wrong\n');
8486
+ process.stderr.write('[sigmap] run: sigmap roots --explain\n');
8487
+ }
8488
+
8480
8489
  const strategy = config.strategy || 'full';
8481
8490
  const beforeCount = fileEntries.length;
8482
8491
 
@@ -9797,6 +9806,54 @@ function main() {
9797
9806
  process.exit(0);
9798
9807
  }
9799
9808
 
9809
+ // v6.5: `sigmap roots` — detect source roots
9810
+ if (args[0] === 'roots') {
9811
+ const { resolveSourceRoots } = requireSourceOrBundled('./src/discovery/source-root-resolver');
9812
+ const result = resolveSourceRoots(cwd);
9813
+
9814
+ if (args.includes('--json')) {
9815
+ console.log(JSON.stringify(result, null, 2));
9816
+ process.exit(0);
9817
+ }
9818
+
9819
+ if (args.includes('--fix')) {
9820
+ console.log('[sigmap] Current detected roots:', result.roots.join(', '));
9821
+ const readline = require('readline');
9822
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
9823
+ rl.question('Enter correct srcDirs (comma-separated): ', answer => {
9824
+ const dirs = answer.split(',').map(d => d.trim()).filter(Boolean);
9825
+ const cfgPath = path.join(cwd, 'gen-context.config.json');
9826
+ const cfg = fs.existsSync(cfgPath) ? JSON.parse(fs.readFileSync(cfgPath, 'utf8')) : {};
9827
+ cfg.srcDirs = dirs;
9828
+ fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n');
9829
+ console.log(`[sigmap] Saved srcDirs: ${dirs.join(', ')} → gen-context.config.json`);
9830
+ rl.close();
9831
+ process.exit(0);
9832
+ });
9833
+ return;
9834
+ }
9835
+
9836
+ // --explain (default)
9837
+ console.log('\nDetected languages:');
9838
+ for (const l of result.languages.slice(0, 4)) {
9839
+ console.log(` ${l.name.padEnd(16)} ${l.weight.toFixed(2)}`);
9840
+ }
9841
+ console.log('\nDetected frameworks:');
9842
+ if (result.frameworks.length === 0) console.log(' (none)');
9843
+ for (const f of result.frameworks.slice(0, 3)) {
9844
+ console.log(` ${f.name.padEnd(16)} ${f.confidence.toFixed(2)}`);
9845
+ }
9846
+ console.log('\nChosen source roots:');
9847
+ if (result.roots.length === 0) console.log(' (none — legacy fallback used)');
9848
+ result.roots.forEach((r, i) => {
9849
+ const exp = result.explanation?.find(e => e.dir === r);
9850
+ console.log(` ${i + 1}. ${r.padEnd(20)} ${exp ? 'score ' + exp.score : ''}`);
9851
+ });
9852
+ console.log('\nMonorepo:', result.isMonorepo ? 'yes' : 'no');
9853
+ console.log('Confidence:', result.confidence);
9854
+ process.exit(0);
9855
+ }
9856
+
9800
9857
  // Feature 1: `sigmap explain <file>` — why a file is included or excluded
9801
9858
  if (args[0] === 'explain' || args.includes('--explain')) {
9802
9859
  const target = args[0] === 'explain'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "6.4.0",
3
+ "version": "6.5.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.4.0",
3
+ "version": "6.5.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.4.0",
3
+ "version": "6.5.0",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -89,14 +89,38 @@ const SUPPORTED_CODE_EXTS = new Set([
89
89
  ]);
90
90
 
91
91
  /**
92
- * Detect source directories for the given project root by reading manifest
93
- * files and scanning top-level directories for code files.
92
+ * Detect source directories for the given project root.
93
+ * Uses smart resolver (v6.5+) with fallback to legacy heuristics.
94
94
  *
95
95
  * @param {string} cwd - Project root
96
96
  * @param {string[]} excludeList - Folders to skip
97
97
  * @returns {string[]}
98
98
  */
99
99
  function detectAutoSrcDirs(cwd, excludeList) {
100
+ try {
101
+ const { resolveSourceRoots } = require('../discovery/source-root-resolver');
102
+ const result = resolveSourceRoots(cwd, { exclude: excludeList || [] });
103
+ if (result.roots.length > 0) {
104
+ if (result.confidence === 'low') {
105
+ process.stderr.write(
106
+ '[sigmap] low confidence root detection — run "sigmap roots --explain" to verify\n'
107
+ );
108
+ }
109
+ return result.roots;
110
+ }
111
+ } catch (_) {}
112
+
113
+ return _legacyDetectAutoSrcDirs(cwd, excludeList);
114
+ }
115
+
116
+ /**
117
+ * Legacy source directory detection (fallback).
118
+ *
119
+ * @param {string} cwd - Project root
120
+ * @param {string[]} excludeList - Folders to skip
121
+ * @returns {string[]}
122
+ */
123
+ function _legacyDetectAutoSrcDirs(cwd, excludeList) {
100
124
  const excludeSet = new Set(excludeList || []);
101
125
  const candidates = new Set(DEFAULTS.srcDirs);
102
126
 
@@ -0,0 +1,88 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { REGISTRY } = require('./source-root-registry');
6
+
7
+ module.exports = { detectFrameworks };
8
+
9
+ function detectFrameworks(cwd) {
10
+ const detected = [];
11
+
12
+ for (const [lang, reg] of Object.entries(REGISTRY)) {
13
+ if (!reg.frameworks) continue;
14
+ for (const [name, fw] of Object.entries(reg.frameworks)) {
15
+ let confidence = 0;
16
+
17
+ // Detection files: +0.95 / 0.93 / 0.90 depending on specificity
18
+ for (const f of (fw.detectionFiles || [])) {
19
+ if (_existsAnywhere(cwd, f, 3)) { confidence = Math.max(confidence, 0.93); }
20
+ }
21
+
22
+ // Detection deps in package.json
23
+ if (fw.detectionDeps?.length) {
24
+ const deps = _readDeps(cwd);
25
+ for (const dep of fw.detectionDeps) {
26
+ if (deps.has(dep)) { confidence = Math.max(confidence, 0.90); }
27
+ }
28
+ }
29
+
30
+ // go.mod and Cargo.toml deps
31
+ if (lang === 'go' && fw.detectionDeps?.length) {
32
+ const goMod = _readFile(path.join(cwd, 'go.mod'));
33
+ for (const dep of fw.detectionDeps) {
34
+ if (goMod.includes(dep)) { confidence = Math.max(confidence, 0.90); }
35
+ }
36
+ }
37
+ if (lang === 'rust' && fw.detectionDeps?.length) {
38
+ const cargoToml = _readFile(path.join(cwd, 'Cargo.toml'));
39
+ for (const dep of fw.detectionDeps) {
40
+ if (cargoToml.includes(dep)) { confidence = Math.max(confidence, 0.88); }
41
+ }
42
+ }
43
+
44
+ // Special rules
45
+ if (fw.specialRule === 'django-app-dirs' && fs.existsSync(path.join(cwd, 'manage.py'))) {
46
+ confidence = Math.max(confidence, 0.95);
47
+ }
48
+ if (fw.specialRule === 'swift-project-dir' && _existsAnywhere(cwd, '.xcodeproj', 2)) {
49
+ confidence = Math.max(confidence, 0.90);
50
+ }
51
+
52
+ if (confidence > 0) detected.push({ name, language: lang, confidence });
53
+ }
54
+ }
55
+
56
+ return detected.sort((a, b) => b.confidence - a.confidence);
57
+ }
58
+
59
+ function _readDeps(cwd) {
60
+ try {
61
+ const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
62
+ return new Set([...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.devDependencies || {})]);
63
+ } catch { return new Set(); }
64
+ }
65
+
66
+ function _readFile(p) {
67
+ try { return fs.readFileSync(p, 'utf8'); } catch { return ''; }
68
+ }
69
+
70
+ function _existsAnywhere(cwd, filename, maxDepth) {
71
+ const parts = filename.split('/');
72
+ if (parts.length > 1) return fs.existsSync(path.join(cwd, filename));
73
+ return _walkFind(cwd, filename, maxDepth);
74
+ }
75
+
76
+ function _walkFind(dir, name, depth) {
77
+ if (depth <= 0) return false;
78
+ try {
79
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
80
+ for (const e of entries) {
81
+ if (e.name === name) return true;
82
+ if (e.isDirectory() && depth > 1) {
83
+ if (_walkFind(path.join(dir, e.name), name, depth - 1)) return true;
84
+ }
85
+ }
86
+ } catch (_) {}
87
+ return false;
88
+ }
@@ -0,0 +1,74 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { REGISTRY } = require('./source-root-registry');
6
+
7
+ module.exports = { detectLanguages };
8
+
9
+ const SKIP_DIRS = new Set([
10
+ 'node_modules','dist','build','.git','venv','.venv','target',
11
+ 'DerivedData','Pods','.build','Carthage','coverage','.next','.nuxt',
12
+ '__pycache__','.pytest_cache','vendor','.bundle','Carthage',
13
+ ]);
14
+
15
+ const EXT_TO_LANG = {
16
+ '.js': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript',
17
+ '.ts': 'typescript', '.tsx': 'typescript', '.jsx': 'javascript',
18
+ '.py': 'python', '.rb': 'ruby', '.go': 'go', '.rs': 'rust',
19
+ '.java': 'java', '.kt': 'kotlin', '.cs': 'csharp', '.cpp': 'cpp',
20
+ '.c': 'cpp', '.h': 'cpp', '.hpp': 'cpp', '.swift': 'swift',
21
+ '.dart': 'dart', '.scala': 'scala', '.php': 'php',
22
+ };
23
+
24
+ function detectLanguages(cwd) {
25
+ const weights = {};
26
+
27
+ // Signal 1: manifest files (+3 each)
28
+ for (const [lang, reg] of Object.entries(REGISTRY)) {
29
+ for (const mf of (reg.manifestFiles || [])) {
30
+ if (fs.existsSync(path.join(cwd, mf))) {
31
+ weights[lang] = (weights[lang] || 0) + 3;
32
+ }
33
+ }
34
+ }
35
+
36
+ // Signal 2: TypeScript dep in package.json (+2)
37
+ try {
38
+ const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
39
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
40
+ if (allDeps.typescript) { weights.typescript = (weights.typescript || 0) + 2; }
41
+ } catch (_) {}
42
+
43
+ // Signal 3: file extension count (walk depth 3, capped at +5 per language)
44
+ const extCount = {};
45
+ _walkDepth(cwd, 3, extCount);
46
+ const maxCount = Math.max(1, ...Object.values(extCount));
47
+ for (const [ext, count] of Object.entries(extCount)) {
48
+ const lang = EXT_TO_LANG[ext];
49
+ if (lang) {
50
+ weights[lang] = (weights[lang] || 0) + Math.min(5, (count / maxCount) * 5);
51
+ }
52
+ }
53
+
54
+ // Normalize to [0,1] and sort
55
+ const maxW = Math.max(1, ...Object.values(weights));
56
+ return Object.entries(weights)
57
+ .map(([name, w]) => ({ name, weight: Math.round(w / maxW * 100) / 100 }))
58
+ .sort((a, b) => b.weight - a.weight);
59
+ }
60
+
61
+ function _walkDepth(dir, depth, extCount) {
62
+ if (depth <= 0) return;
63
+ let entries;
64
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; }
65
+ for (const e of entries) {
66
+ if (SKIP_DIRS.has(e.name)) continue;
67
+ if (e.isDirectory()) {
68
+ _walkDepth(path.join(dir, e.name), depth - 1, extCount);
69
+ } else if (e.isFile()) {
70
+ const ext = path.extname(e.name).toLowerCase();
71
+ if (EXT_TO_LANG[ext]) extCount[ext] = (extCount[ext] || 0) + 1;
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ module.exports = { loadIgnorePatterns, matchesIgnorePattern };
7
+
8
+ function loadIgnorePatterns(cwd) {
9
+ for (const fname of ['.sigmapignore', '.contextignore']) {
10
+ const p = path.join(cwd, fname);
11
+ if (fs.existsSync(p)) {
12
+ return fs.readFileSync(p, 'utf8')
13
+ .split('\n')
14
+ .map(l => l.trim())
15
+ .filter(l => l && !l.startsWith('#'));
16
+ }
17
+ }
18
+ return [];
19
+ }
20
+
21
+ function matchesIgnorePattern(dirName, patterns) {
22
+ for (const pat of patterns) {
23
+ const clean = pat.replace(/\/$/, '');
24
+ if (clean === dirName) return true;
25
+ if (clean.endsWith('/**') && dirName.startsWith(clean.slice(0, -3))) return true;
26
+ if (clean.endsWith('/*') && dirName.startsWith(clean.slice(0, -2))) return true;
27
+ }
28
+ return false;
29
+ }
@@ -0,0 +1,166 @@
1
+ 'use strict';
2
+
3
+ const REGISTRY = {
4
+ javascript: {
5
+ manifestFiles: ['package.json'],
6
+ frameworks: {
7
+ nextjs: { detectionFiles: ['next.config.js','next.config.ts','next.config.mjs'], detectionDeps: ['next'], srcDirs: ['app','src/app','pages','src/pages','src','components','lib','hooks','utils'], entrypoints: ['app/page.tsx','pages/index.tsx'] },
8
+ nestjs: { detectionFiles: ['nest-cli.json'], detectionDeps: ['@nestjs/core'], srcDirs: ['src'], entrypoints: ['src/main.ts','src/app.module.ts'] },
9
+ express: { detectionFiles: [], detectionDeps: ['express'], srcDirs: ['src','routes','middleware','controllers','services'], entrypoints: ['src/index.js','server.js','app.js'] },
10
+ fastify: { detectionFiles: [], detectionDeps: ['fastify'], srcDirs: ['src','routes','plugins'], entrypoints: ['src/index.js'] },
11
+ react: { detectionFiles: [], detectionDeps: ['react'], srcDirs: ['src','components','hooks','context','pages','app','lib','utils'] },
12
+ vue: { detectionFiles: ['vue.config.js','vue.config.ts'], detectionDeps: ['vue'], srcDirs: ['src','components','composables','pages','views'] },
13
+ nuxt: { detectionFiles: ['nuxt.config.js','nuxt.config.ts'], detectionDeps: ['nuxt'], srcDirs: ['pages','components','composables','server','middleware','plugins'] },
14
+ svelte: { detectionFiles: ['svelte.config.js'], detectionDeps: ['svelte','@sveltejs/kit'], srcDirs: ['src','src/routes','src/lib'] },
15
+ angular: { detectionFiles: ['angular.json'], detectionDeps: ['@angular/core'], srcDirs: ['src','src/app','projects','apps','libs'] },
16
+ gatsby: { detectionFiles: ['gatsby-config.js','gatsby-config.ts'], detectionDeps: ['gatsby'], srcDirs: ['src','gatsby'] },
17
+ vite: { detectionFiles: ['vite.config.js','vite.config.ts'], detectionDeps: ['vite'], srcDirs: ['src'] },
18
+ remix: { detectionFiles: ['remix.config.js'], detectionDeps: ['@remix-run/react'], srcDirs: ['app'] },
19
+ trpc: { detectionFiles: [], detectionDeps: ['@trpc/server'], srcDirs: ['src','server','routers'] },
20
+ },
21
+ srcDirs: ['src','lib','index.js','server.js','app.js'],
22
+ penalties: ['dist','build','.next','.nuxt','coverage','storybook-static'],
23
+ },
24
+
25
+ typescript: {
26
+ manifestFiles: ['package.json','tsconfig.json'],
27
+ frameworks: {
28
+ nextjs: { detectionFiles: ['next.config.ts','next.config.mjs'], detectionDeps: ['next'], srcDirs: ['app','src/app','pages','src','components','lib','hooks','utils'] },
29
+ nestjs: { detectionFiles: ['nest-cli.json'], detectionDeps: ['@nestjs/core'], srcDirs: ['src'], entrypoints: ['src/main.ts'] },
30
+ angular: { detectionFiles: ['angular.json'], detectionDeps: ['@angular/core'], srcDirs: ['src','src/app','projects','apps','libs'] },
31
+ },
32
+ srcDirs: ['src','lib','packages'],
33
+ penalties: ['dist','build','.next'],
34
+ },
35
+
36
+ python: {
37
+ manifestFiles: ['requirements.txt','pyproject.toml','setup.py','Pipfile'],
38
+ frameworks: {
39
+ django: { detectionFiles: ['manage.py'], detectionDeps: ['Django'], srcDirs: [], specialRule: 'django-app-dirs', entrypoints: ['manage.py'] },
40
+ fastapi: { detectionFiles: [], detectionDeps: ['fastapi'], srcDirs: ['app','src','routers','api'], entrypoints: ['main.py','app/main.py'] },
41
+ flask: { detectionFiles: ['wsgi.py','app.py'], detectionDeps: ['Flask'], srcDirs: ['app','src'], entrypoints: ['app.py','wsgi.py'] },
42
+ celery: { detectionFiles: [], detectionDeps: ['celery'], srcDirs: ['tasks','workers','app'] },
43
+ },
44
+ srcDirs: ['.'],
45
+ penalties: ['venv','.venv','__pycache__','.pytest_cache','htmlcov'],
46
+ },
47
+
48
+ go: {
49
+ manifestFiles: ['go.mod'],
50
+ frameworks: {
51
+ gin: { detectionFiles: [], detectionDeps: ['github.com/gin-gonic/gin'], srcDirs: ['internal','cmd','pkg','api','handler','middleware'] },
52
+ echo: { detectionFiles: [], detectionDeps: ['github.com/labstack/echo'], srcDirs: ['internal','cmd','handler','middleware'] },
53
+ fiber: { detectionFiles: [], detectionDeps: ['github.com/gofiber/fiber'], srcDirs: ['internal','cmd','handler','routes'] },
54
+ grpc: { detectionFiles: [], detectionDeps: ['google.golang.org/grpc'], srcDirs: ['internal','proto','server','client'] },
55
+ chi: { detectionFiles: [], detectionDeps: ['github.com/go-chi/chi'], srcDirs: ['internal','cmd','handler'] },
56
+ },
57
+ srcDirs: ['internal','cmd','pkg','api'],
58
+ penalties: ['vendor'],
59
+ },
60
+
61
+ rust: {
62
+ manifestFiles: ['Cargo.toml'],
63
+ frameworks: {
64
+ actix: { detectionFiles: [], detectionDeps: ['actix-web'], srcDirs: ['src'] },
65
+ axum: { detectionFiles: [], detectionDeps: ['axum'], srcDirs: ['src'] },
66
+ tauri: { detectionFiles: ['src-tauri/tauri.conf.json'], detectionDeps: ['tauri'], srcDirs: ['src','src-tauri/src'] },
67
+ },
68
+ srcDirs: ['src'],
69
+ penalties: ['target'],
70
+ },
71
+
72
+ java: {
73
+ manifestFiles: ['pom.xml','build.gradle'],
74
+ frameworks: {
75
+ spring: { detectionFiles: [], detectionDeps: ['spring-boot'], srcDirs: ['src/main/java','src/main/kotlin','src/main/resources'] },
76
+ quarkus: { detectionFiles: [], detectionDeps: ['io.quarkus'], srcDirs: ['src/main/java'] },
77
+ android: { detectionFiles: ['AndroidManifest.xml'], srcDirs: ['app/src/main/java','app/src/main','src'] },
78
+ micronaut:{ detectionFiles: [], detectionDeps: ['io.micronaut'],srcDirs: ['src/main/java'] },
79
+ },
80
+ srcDirs: ['src/main/java','src'],
81
+ penalties: ['target','build'],
82
+ },
83
+
84
+ kotlin: {
85
+ manifestFiles: ['build.gradle.kts'],
86
+ frameworks: {
87
+ spring: { detectionFiles: [], detectionDeps: ['spring-boot'], srcDirs: ['src/main/kotlin'] },
88
+ android: { detectionFiles: ['AndroidManifest.xml'], srcDirs: ['app/src/main/kotlin','app/src/main/java'] },
89
+ ktor: { detectionFiles: [], detectionDeps: ['io.ktor'], srcDirs: ['src'] },
90
+ compose: { detectionFiles: [], detectionDeps: ['compose-runtime'], srcDirs: ['app/src/main/kotlin','src'] },
91
+ },
92
+ srcDirs: ['src/main/kotlin','src'],
93
+ penalties: ['build','.gradle'],
94
+ },
95
+
96
+ csharp: {
97
+ manifestFiles: ['.csproj','.sln'],
98
+ frameworks: {
99
+ aspnet: { detectionFiles: ['appsettings.json'], detectionDeps: ['Microsoft.AspNetCore'], srcDirs: ['Controllers','Services','Models','Middleware','Pages'] },
100
+ blazor: { detectionFiles: [], detectionDeps: ['Microsoft.AspNetCore.Components'], srcDirs: ['Components','Pages','Services'] },
101
+ unity: { detectionFiles: ['ProjectSettings/ProjectSettings.asset'], srcDirs: ['Assets/Scripts','Assets'] },
102
+ maui: { detectionFiles: [], detectionDeps: ['Microsoft.Maui'], srcDirs: ['src','Pages','ViewModels'] },
103
+ },
104
+ srcDirs: ['src','Controllers','Services','Models'],
105
+ penalties: ['bin','obj','.vs'],
106
+ },
107
+
108
+ php: {
109
+ manifestFiles: ['composer.json'],
110
+ frameworks: {
111
+ laravel: { detectionFiles: ['artisan'], srcDirs: ['app','routes','config','database','resources','tests'], entrypoints: ['artisan'] },
112
+ symfony: { detectionFiles: ['symfony.lock'], srcDirs: ['src','config','templates'], specialRule: 'symfony-bundle-dirs' },
113
+ wordpress: { detectionFiles: ['wp-config.php'], srcDirs: ['wp-content/themes','wp-content/plugins','wp-content/mu-plugins'] },
114
+ slim: { detectionFiles: [], detectionDeps: ['slim/slim'], srcDirs: ['src','app','routes'] },
115
+ },
116
+ srcDirs: ['src','app'],
117
+ penalties: ['vendor'],
118
+ },
119
+
120
+ ruby: {
121
+ manifestFiles: ['Gemfile'],
122
+ frameworks: {
123
+ rails: { detectionFiles: ['config/routes.rb'], srcDirs: ['app','lib','config','db','spec','test'], entrypoints: ['config/routes.rb'] },
124
+ sinatra: { detectionFiles: ['config.ru','app.rb'], srcDirs: ['.','lib'], entrypoints: ['app.rb','config.ru'] },
125
+ hanami: { detectionFiles: [], detectionDeps: ['hanami'], srcDirs: ['apps','lib','slices'] },
126
+ },
127
+ srcDirs: ['app','lib'],
128
+ penalties: ['vendor','coverage','.bundle'],
129
+ },
130
+
131
+ swift: {
132
+ manifestFiles: ['Package.swift'],
133
+ frameworks: {
134
+ vapor: { detectionFiles: [], detectionDeps: ['vapor/vapor'], srcDirs: ['Sources','App'] },
135
+ swiftui: { detectionFiles: ['.xcodeproj'], srcDirs: [], specialRule: 'swift-project-dir' },
136
+ swiftpm: { detectionFiles: ['Package.swift'],srcDirs: ['Sources'] },
137
+ },
138
+ srcDirs: ['Sources','Source'],
139
+ penalties: ['.build','DerivedData','Pods','Carthage'],
140
+ },
141
+
142
+ dart: {
143
+ manifestFiles: ['pubspec.yaml'],
144
+ frameworks: {
145
+ flutter: { detectionFiles: [], detectionDeps: ['flutter'], srcDirs: ['lib','lib/src'], entrypoints: ['lib/main.dart'] },
146
+ serverpod: { detectionFiles: [], detectionDeps: ['serverpod'], srcDirs: ['lib','endpoints','models'] },
147
+ 'dart-frog':{ detectionFiles: ['dart_frog.yaml'], srcDirs: ['routes','lib'] },
148
+ },
149
+ srcDirs: ['lib','lib/src'],
150
+ penalties: ['.dart_tool','build'],
151
+ },
152
+
153
+ scala: {
154
+ manifestFiles: ['build.sbt'],
155
+ frameworks: {
156
+ akka: { detectionFiles: [], detectionDeps: ['akka'], srcDirs: ['src/main/scala','src'] },
157
+ play: { detectionFiles: [], detectionDeps: ['play'], srcDirs: ['app','conf'] },
158
+ spark: { detectionFiles: [], detectionDeps: ['spark'],srcDirs: ['src/main/scala'] },
159
+ zio: { detectionFiles: [], detectionDeps: ['zio'], srcDirs: ['src/main/scala'] },
160
+ },
161
+ srcDirs: ['src/main/scala','src'],
162
+ penalties: ['target'],
163
+ },
164
+ };
165
+
166
+ module.exports = { REGISTRY };
@@ -0,0 +1,181 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { REGISTRY } = require('./source-root-registry');
6
+ const { detectLanguages } = require('./language-detector');
7
+ const { detectFrameworks } = require('./framework-detector');
8
+ const { scoreCandidate, getRecentlyChangedDirs, ROOT_ENTRYPOINTS } = require('./source-root-scorer');
9
+ const { loadIgnorePatterns, matchesIgnorePattern } = require('./sigmapignore');
10
+
11
+ module.exports = { resolveSourceRoots };
12
+
13
+ const MONOREPO_MARKERS = ['pnpm-workspace.yaml','turbo.json','nx.json','lerna.json'];
14
+ const MAX_ROOTS = 6;
15
+
16
+ function resolveSourceRoots(cwd, opts = {}) {
17
+ const ignorePatterns = loadIgnorePatterns(cwd);
18
+ const languages = detectLanguages(cwd);
19
+ const frameworks = detectFrameworks(cwd);
20
+ const recentDirs = getRecentlyChangedDirs(cwd);
21
+ const isMonorepo = _detectMonorepo(cwd);
22
+
23
+ const primaryLang = languages[0]?.name;
24
+ const primaryFw = frameworks[0];
25
+ const registry = primaryLang ? REGISTRY[primaryLang] : null;
26
+
27
+ // Build framework-derived context
28
+ const fwEntry = primaryFw && registry?.frameworks?.[primaryFw.name];
29
+ const frameworkSrcDirs = new Set(fwEntry?.srcDirs || registry?.srcDirs || []);
30
+ const entrypoints = fwEntry?.entrypoints || [];
31
+ const frameworkPenalties = registry?.penalties || [];
32
+
33
+ const context = { frameworks, languages, recentDirs, frameworkSrcDirs, entrypoints, frameworkPenalties };
34
+
35
+ // Enumerate candidates
36
+ const candidates = _enumerateCandidates(cwd, isMonorepo, ignorePatterns, opts.exclude || []);
37
+
38
+ // Score each candidate
39
+ const scored = candidates
40
+ .map(({ name, full }) => ({
41
+ dir: name,
42
+ full,
43
+ score: scoreCandidate(name, full, context),
44
+ }))
45
+ .filter(c => c.score > 0)
46
+ .sort((a, b) => b.score - a.score);
47
+
48
+ // Handle special rules
49
+ let roots = _applySpecialRules(scored, cwd, primaryFw, fwEntry, frameworks);
50
+
51
+ // Dedupe nested paths (prefer parent)
52
+ roots = _dedupeNested(roots);
53
+
54
+ // Cap at MAX_ROOTS
55
+ roots = roots.slice(0, MAX_ROOTS).map(r => r.dir);
56
+
57
+ // Fallback: if nothing scored, return empty (caller falls back to legacy)
58
+ const confidence = _computeConfidence(frameworks, languages, scored.length);
59
+
60
+ return {
61
+ roots,
62
+ languages,
63
+ frameworks,
64
+ confidence,
65
+ explanation: scored.slice(0, 8).map(c => ({
66
+ dir: c.dir,
67
+ score: c.score,
68
+ reason: `score: ${c.score}`,
69
+ })),
70
+ isMonorepo,
71
+ };
72
+ }
73
+
74
+ function _detectMonorepo(cwd) {
75
+ for (const m of MONOREPO_MARKERS) {
76
+ if (fs.existsSync(path.join(cwd, m))) return true;
77
+ }
78
+ try {
79
+ const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
80
+ if (pkg.workspaces) return true;
81
+ } catch (_) {}
82
+ return false;
83
+ }
84
+
85
+ function _enumerateCandidates(cwd, isMonorepo, ignorePatterns, excludeList) {
86
+ const candidates = [];
87
+ const excSet = new Set(excludeList);
88
+
89
+ // Root-level dirs
90
+ try {
91
+ for (const e of fs.readdirSync(cwd, { withFileTypes: true })) {
92
+ if (!e.isDirectory()) continue;
93
+ if (excSet.has(e.name)) continue;
94
+ if (matchesIgnorePattern(e.name, ignorePatterns)) continue;
95
+ candidates.push({ name: e.name, full: path.join(cwd, e.name) });
96
+ }
97
+ } catch (_) {}
98
+
99
+ // Monorepo sub-packages: packages/*/src, apps/*/src, services/*/src
100
+ if (isMonorepo) {
101
+ for (const top of ['packages','apps','services','modules']) {
102
+ const topFull = path.join(cwd, top);
103
+ if (!fs.existsSync(topFull)) continue;
104
+ try {
105
+ for (const pkg of fs.readdirSync(topFull, { withFileTypes: true })) {
106
+ if (!pkg.isDirectory()) continue;
107
+ const srcFull = path.join(topFull, pkg.name, 'src');
108
+ if (fs.existsSync(srcFull)) {
109
+ candidates.push({ name: `${top}/${pkg.name}/src`, full: srcFull });
110
+ }
111
+ // Also consider the package root itself
112
+ candidates.push({ name: `${top}/${pkg.name}`, full: path.join(topFull, pkg.name) });
113
+ }
114
+ } catch (_) {}
115
+ }
116
+ }
117
+
118
+ // Deep paths known by language/framework (e.g. src/main/java, src-tauri/src)
119
+ const DEEP_PATHS = [
120
+ 'src/main/java','src/main/kotlin','src/main/scala',
121
+ 'src-tauri/src','Sources/App','app/src/main/java','app/src/main/kotlin',
122
+ ];
123
+ for (const dp of DEEP_PATHS) {
124
+ const full = path.join(cwd, dp);
125
+ if (fs.existsSync(full)) candidates.push({ name: dp, full });
126
+ }
127
+
128
+ return candidates;
129
+ }
130
+
131
+ function _applySpecialRules(scored, cwd, primaryFw, fwEntry, frameworks) {
132
+ let roots = [...scored];
133
+
134
+ // Django: walk root dirs for any containing models.py or views.py
135
+ if (primaryFw?.name === 'django' || frameworks.some(f => f.name === 'django')) {
136
+ try {
137
+ for (const e of fs.readdirSync(cwd, { withFileTypes: true })) {
138
+ if (!e.isDirectory()) continue;
139
+ const d = path.join(cwd, e.name);
140
+ if (fs.existsSync(path.join(d, 'models.py')) || fs.existsSync(path.join(d, 'views.py'))) {
141
+ if (!roots.find(r => r.dir === e.name)) {
142
+ roots.push({ dir: e.name, full: d, score: 5.0 });
143
+ }
144
+ }
145
+ }
146
+ } catch (_) {}
147
+ roots.sort((a, b) => b.score - a.score);
148
+ }
149
+
150
+ // Swift project dir: dirs with ≥3 .swift files
151
+ if (frameworks.some(f => f.name === 'swiftui')) {
152
+ try {
153
+ for (const e of fs.readdirSync(cwd, { withFileTypes: true })) {
154
+ if (!e.isDirectory()) continue;
155
+ const d = path.join(cwd, e.name);
156
+ const swiftCount = (fs.readdirSync(d).filter(f => f.endsWith('.swift'))).length;
157
+ if (swiftCount >= 3 && !roots.find(r => r.dir === e.name)) {
158
+ roots.push({ dir: e.name, full: d, score: 4.0 });
159
+ }
160
+ }
161
+ } catch (_) {}
162
+ roots.sort((a, b) => b.score - a.score);
163
+ }
164
+
165
+ return roots;
166
+ }
167
+
168
+ function _dedupeNested(scored) {
169
+ const result = [];
170
+ for (const c of scored) {
171
+ const isNested = result.some(r => c.dir.startsWith(r.dir + '/'));
172
+ if (!isNested) result.push(c);
173
+ }
174
+ return result;
175
+ }
176
+
177
+ function _computeConfidence(frameworks, languages, scoredCount) {
178
+ if (frameworks.length > 0 && frameworks[0].confidence >= 0.90) return 'high';
179
+ if (languages.length > 0 && scoredCount > 0) return 'medium';
180
+ return 'low';
181
+ }
@@ -0,0 +1,98 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { execSync } = require('child_process');
6
+
7
+ const CODE_EXTS = new Set([
8
+ '.js','.mjs','.cjs','.ts','.tsx','.jsx',
9
+ '.py','.rb','.go','.rs','.java','.kt',
10
+ '.cs','.cpp','.c','.h','.swift','.dart','.scala','.php',
11
+ ]);
12
+
13
+ const AUTO_SKIP = new Set([
14
+ 'node_modules','dist','build','.git','.next','.nuxt','vendor',
15
+ 'DerivedData','Pods','target','coverage','__pycache__','.venv','venv',
16
+ '.build','Carthage','storybook-static','.gradle','bin','obj','.vs',
17
+ ]);
18
+
19
+ const PENALTY_DIRS = new Set([
20
+ 'test','tests','spec','__tests__','e2e','docs','doc','docs-vp',
21
+ 'examples','example','fixtures','mocks','__mocks__','demo','samples','migrations',
22
+ 'benchmarks','scripts',
23
+ ]);
24
+
25
+ const ROOT_ENTRYPOINTS = {
26
+ go: ['main.go'],
27
+ python: ['app.py','main.py','wsgi.py','asgi.py'],
28
+ javascript: ['index.js','server.js','app.js'],
29
+ typescript: ['index.ts','main.ts'],
30
+ rust: [],
31
+ php: ['index.php'],
32
+ };
33
+
34
+ function getRecentlyChangedDirs(cwd) {
35
+ try {
36
+ const out = execSync('git log --name-only --format="" HEAD~10 2>/dev/null', { cwd, timeout: 3000 }).toString();
37
+ return new Set(out.split('\n').filter(Boolean).map(f => f.split('/')[0]));
38
+ } catch { return new Set(); }
39
+ }
40
+
41
+ function scoreCandidate(dirName, fullPath, context) {
42
+ const { frameworks, languages, recentDirs, frameworkSrcDirs, entrypoints, frameworkPenalties } = context;
43
+
44
+ // Auto-skip noise
45
+ if (AUTO_SKIP.has(dirName)) return -99;
46
+ if (!fs.existsSync(fullPath)) return -99;
47
+
48
+ let score = 0;
49
+
50
+ // Framework match: +3.0 if this dir is in the framework's srcDirs
51
+ if (frameworkSrcDirs.has(dirName)) score += 3.0;
52
+
53
+ // Count source files in dir (depth 2)
54
+ const sourceFileCount = _countSourceFiles(fullPath, 2);
55
+ const density = Math.min(1.0, sourceFileCount / 10);
56
+
57
+ // Language density: +2.5
58
+ score += density * 2.5;
59
+
60
+ // Symbol density: +2.0 if ≥3 source files
61
+ if (sourceFileCount >= 3) score += 2.0;
62
+
63
+ // Entrypoint: +1.5 if a known entrypoint lives in this dir
64
+ if ((entrypoints || []).some(ep => ep.startsWith(dirName + '/'))) score += 1.5;
65
+
66
+ // Manifest proximity: +1.0 if a manifest file is in this dir
67
+ if (fs.existsSync(path.join(fullPath, 'package.json')) ||
68
+ fs.existsSync(path.join(fullPath, 'go.mod')) ||
69
+ fs.existsSync(path.join(fullPath, 'Cargo.toml')) ||
70
+ fs.existsSync(path.join(fullPath, 'pom.xml'))) {
71
+ score += 1.0;
72
+ }
73
+
74
+ // Git activity bonus: +2.0 if recently committed files exist here
75
+ if (recentDirs.has(dirName)) score += 2.0;
76
+
77
+ // Noise penalty: -3.0 (unless directory is in framework's srcDirs)
78
+ if (PENALTY_DIRS.has(dirName.toLowerCase()) && !frameworkSrcDirs.has(dirName)) score -= 3.0;
79
+
80
+ // Framework penalty dirs
81
+ if ((frameworkPenalties || []).includes(dirName)) score -= 3.0;
82
+
83
+ return Math.round(score * 100) / 100;
84
+ }
85
+
86
+ function _countSourceFiles(dir, depth) {
87
+ if (depth <= 0) return 0;
88
+ let count = 0;
89
+ try {
90
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
91
+ if (e.isFile() && CODE_EXTS.has(path.extname(e.name).toLowerCase())) count++;
92
+ else if (e.isDirectory() && depth > 1) count += _countSourceFiles(path.join(dir, e.name), depth - 1);
93
+ }
94
+ } catch (_) {}
95
+ return count;
96
+ }
97
+
98
+ module.exports = { scoreCandidate, getRecentlyChangedDirs, ROOT_ENTRYPOINTS };
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.4.0',
21
+ version: '6.5.0',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24