sigmap 6.4.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.
@@ -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.1',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24