sigmap 8.33.0 → 8.34.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/CHANGELOG.md +11 -0
- package/README.md +4 -4
- package/gen-context.js +317 -8
- package/llms-full.txt +5 -5
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/index.js +1 -0
- package/packages/core/package.json +1 -1
- package/src/config/loader.js +1 -1
- package/src/config/tune.js +1 -1
- package/src/discovery/language-detector.js +1 -0
- package/src/discovery/source-root-registry.js +10 -1
- package/src/discovery/source-root-scorer.js +1 -1
- package/src/extractors/deps.js +23 -1
- package/src/extractors/dispatch.js +2 -0
- package/src/extractors/lua.js +152 -0
- package/src/graph/call-graph.js +81 -1
- package/src/mcp/server.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,17 @@ Format: [Semantic Versioning](https://semver.org/)
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## [8.34.0] — 2026-09-13
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- **Lua extractor** (Tier 3) — global and local functions, module-table functions (`function M.name`, `function M:name`), assigned functions, `require` module hints, and LDoc `---` doc comments as first-sentence hints (#540, PR #550) — thanks **@zerone0x**. The contribution shipped its own fixture and expected output, which is exactly what the #588 fixture guard now requires, contributed before that guard existed. Brought up to the disclosure convention it predates: its caps now report the true overflow rather than truncating silently, with the ceiling unchanged
|
|
17
|
+
- Call-graph definitions for **Kotlin and Scala** (#586, PR #602). `extractDefs` returned null for `.kt`/`.scala` and the walk did not collect them, so the graph was empty for both — and an empty graph returns no error, so `--impact` and blast radius silently read zero. That is the failure mode that cost a release in #561, and akka (Scala) sits in the gated JVM corpus, so CI exercised Scala in a way that could never detect it. Adds `jvmBodyRange` for expression bodies (`fun f() = expr`), which Java does not have. On a small Kotlin+Scala tree: 0 symbols / 0 edges → 8 / 2. Scope is pinned by test — same-file calls resolve, cross-file *receiver* calls do not yet, because receiver typing is Java-shaped
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- Generated `context-*.md` splits from a previous `strategy` no longer pollute retrieval (#555, PR #601). Splits are discovered by filename pattern at read time, not by consulting the config, so a file left by an earlier strategy — or by a module since dropped from `srcDirs` — kept being merged into the index and steering every query. On a 524-file Java repo a stale 376 KB split held ranks 1, 3 and 4 with generated entities, one unrelated to the query, while both files implementing the feature fell outside the top 6 — with `sig-index.json` correctly holding zero entries for it. Each strategy now declares what it wrote and the rest is pruned, and the prune is reported rather than silent
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
13
24
|
## [8.33.0] — 2026-09-13
|
|
14
25
|
|
|
15
26
|
### Added
|
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ That map is exactly what agentic grep is worst at: reproducible, auditable conte
|
|
|
64
64
|
- **62.9% task-success proxy** — modeled from retrieval tiers, not measured LLM sessions
|
|
65
65
|
- **1.58 prompts per task** — down from 2.84 (44.4% fewer retries, modeled)
|
|
66
66
|
<!--/SM:whyMetrics-->
|
|
67
|
-
- **<!--SM:languages-->
|
|
67
|
+
- **<!--SM:languages-->33<!--/SM:languages--> languages supported** — TypeScript, Python, Go, Rust, Java, R, and more
|
|
68
68
|
- **No vendor lock-in** — works with any AI assistant or local LLM
|
|
69
69
|
- **No API costs** — use local models (Ollama, llama.cpp, vLLM) with zero token fees
|
|
70
70
|
- **Full privacy** — keep your code and context on your machine
|
|
@@ -122,7 +122,7 @@ Ask → Rank → Context → Validate → Judge → Learn
|
|
|
122
122
|
|
|
123
123
|
<!--SM:benchmarkBlock-->
|
|
124
124
|
```
|
|
125
|
-
Benchmark : sigmap-v8.
|
|
125
|
+
Benchmark : sigmap-v8.34-main (21 repositories, including R language)
|
|
126
126
|
Date : 2026-09-13
|
|
127
127
|
|
|
128
128
|
Hit@5 : 78.9% (grep-agent baseline 44.0% — 1.73× lift)
|
|
@@ -332,7 +332,7 @@ sigmap --health
|
|
|
332
332
|
| Benchmark methodology | [benchmark.html](https://sigmap.io/guide/benchmark.html) |
|
|
333
333
|
| Config reference | [config.html](https://sigmap.io/guide/config.html) |
|
|
334
334
|
| Roadmap | [roadmap.html](https://sigmap.io/guide/roadmap.html) |
|
|
335
|
-
| <!--SM:languages-->
|
|
335
|
+
| <!--SM:languages-->33<!--/SM:languages--> languages | [generalization.html](https://sigmap.io/guide/generalization.html) |
|
|
336
336
|
|
|
337
337
|
---
|
|
338
338
|
|
|
@@ -389,7 +389,7 @@ See [.github/PULL_REQUEST_TEMPLATE.md](.github/PULL_REQUEST_TEMPLATE.md) for the
|
|
|
389
389
|
|
|
390
390
|
---
|
|
391
391
|
|
|
392
|
-
## <!--SM:languages-->
|
|
392
|
+
## <!--SM:languages-->33<!--/SM:languages--> languages
|
|
393
393
|
|
|
394
394
|
TypeScript · JavaScript · Python · Java · Kotlin · Go · Rust · C# · C/C++ · Ruby · PHP · Swift · Dart · Scala · Vue · Svelte · HTML · CSS/SCSS · YAML · Shell · SQL · GraphQL · Terraform · Protobuf · Dockerfile · TOML · XML · Properties · Markdown · R · GDScript
|
|
395
395
|
|
package/gen-context.js
CHANGED
|
@@ -1644,7 +1644,7 @@ __factories["./src/config/loader"] = function(module, exports) {
|
|
|
1644
1644
|
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
|
|
1645
1645
|
'.py', '.pyw', '.java', '.kt', '.kts', '.go', '.rs', '.cs',
|
|
1646
1646
|
'.cpp', '.c', '.h', '.hpp', '.cc', '.rb', '.rake', '.php',
|
|
1647
|
-
'.swift', '.dart', '.scala', '.sc', '.vue', '.svelte',
|
|
1647
|
+
'.swift', '.dart', '.scala', '.sc', '.lua', '.vue', '.svelte',
|
|
1648
1648
|
'.html', '.htm', '.css', '.scss', '.sass', '.less',
|
|
1649
1649
|
'.yml', '.yaml', '.sh', '.bash', '.zsh', '.fish',
|
|
1650
1650
|
'.sql', '.graphql', '.gql', '.tf', '.tfvars', '.proto',
|
|
@@ -1968,7 +1968,7 @@ __factories["./src/config/tune"] = function(module, exports) {
|
|
|
1968
1968
|
const SOURCE_EXTS = new Set([
|
|
1969
1969
|
'.js', '.mjs', '.cjs', '.jsx', '.ts', '.tsx', '.py', '.rb', '.go', '.rs',
|
|
1970
1970
|
'.java', '.kt', '.cs', '.cpp', '.c', '.h', '.hpp', '.swift', '.dart',
|
|
1971
|
-
'.scala', '.php', '.gd', '.r', '.R',
|
|
1971
|
+
'.scala', '.php', '.lua', '.gd', '.r', '.R',
|
|
1972
1972
|
]);
|
|
1973
1973
|
|
|
1974
1974
|
/** Raw user config file content, or null when absent/unparsable. */
|
|
@@ -3128,6 +3128,7 @@ __factories["./src/discovery/language-detector"] = function(module, exports) {
|
|
|
3128
3128
|
'.java': 'java', '.kt': 'kotlin', '.cs': 'csharp', '.cpp': 'cpp',
|
|
3129
3129
|
'.c': 'cpp', '.h': 'cpp', '.hpp': 'cpp', '.swift': 'swift',
|
|
3130
3130
|
'.dart': 'dart', '.scala': 'scala', '.php': 'php',
|
|
3131
|
+
'.lua': 'lua',
|
|
3131
3132
|
'.gd': 'gdscript',
|
|
3132
3133
|
'.r': 'r', '.R': 'r',
|
|
3133
3134
|
};
|
|
@@ -3558,12 +3559,21 @@ __factories["./src/discovery/source-root-registry"] = function(module, exports)
|
|
|
3558
3559
|
akka: { detectionFiles: [], detectionDeps: ['akka'], srcDirs: ['src/main/scala','src'] },
|
|
3559
3560
|
play: { detectionFiles: [], detectionDeps: ['play'], srcDirs: ['app','conf'] },
|
|
3560
3561
|
spark: { detectionFiles: [], detectionDeps: ['spark'],srcDirs: ['src/main/scala'] },
|
|
3561
|
-
zio: { detectionFiles: [], detectionDeps: ['zio'],
|
|
3562
|
+
zio: { detectionFiles: [], detectionDeps: ['zio'], srcDirs: ['src/main/scala'] },
|
|
3562
3563
|
},
|
|
3563
3564
|
srcDirs: ['src/main/scala','src'],
|
|
3564
3565
|
penalties: ['target'],
|
|
3565
3566
|
},
|
|
3566
3567
|
|
|
3568
|
+
lua: {
|
|
3569
|
+
manifestFiles: ['.luarc.json', 'selene.toml', 'stylua.toml'],
|
|
3570
|
+
frameworks: {
|
|
3571
|
+
luarocks: { detectionFiles: ['*.rockspec'], srcDirs: ['src','lua','lib'] },
|
|
3572
|
+
},
|
|
3573
|
+
srcDirs: ['src','lua','lib'],
|
|
3574
|
+
penalties: ['.luarocks','luarocks_modules'],
|
|
3575
|
+
},
|
|
3576
|
+
|
|
3567
3577
|
r: {
|
|
3568
3578
|
manifestFiles: ['DESCRIPTION','renv.lock'],
|
|
3569
3579
|
frameworks: {
|
|
@@ -3797,7 +3807,7 @@ __factories["./src/discovery/source-root-scorer"] = function(module, exports) {
|
|
|
3797
3807
|
const CODE_EXTS = new Set([
|
|
3798
3808
|
'.js','.mjs','.cjs','.ts','.tsx','.jsx',
|
|
3799
3809
|
'.py','.rb','.go','.rs','.java','.kt',
|
|
3800
|
-
'.cs','.cpp','.c','.h','.swift','.dart','.scala','.php',
|
|
3810
|
+
'.cs','.cpp','.c','.h','.swift','.dart','.scala','.php','.lua',
|
|
3801
3811
|
]);
|
|
3802
3812
|
|
|
3803
3813
|
const AUTO_SKIP = new Set([
|
|
@@ -5882,6 +5892,28 @@ __factories["./src/extractors/deps"] = function(module, exports) {
|
|
|
5882
5892
|
return [...deps].slice(0, 5);
|
|
5883
5893
|
}
|
|
5884
5894
|
|
|
5895
|
+
/**
|
|
5896
|
+
* Extract Lua require() module dependencies.
|
|
5897
|
+
* Captures `require "mod"` and `require("mod")`, returning compact module
|
|
5898
|
+
* names as they appear in source.
|
|
5899
|
+
* @param {string} src
|
|
5900
|
+
* @returns {string[]}
|
|
5901
|
+
*/
|
|
5902
|
+
function extractLuaDeps(src) {
|
|
5903
|
+
const deps = new Set();
|
|
5904
|
+
const stripped = stripLuaComments(src || '');
|
|
5905
|
+
for (const m of stripped.matchAll(/\brequire\s*(?:\(\s*)?["']([A-Za-z0-9_.\/-]+)["']\s*\)?/g)) {
|
|
5906
|
+
if (m[1]) deps.add(m[1]);
|
|
5907
|
+
}
|
|
5908
|
+
return [...deps].slice(0, 5);
|
|
5909
|
+
}
|
|
5910
|
+
|
|
5911
|
+
function stripLuaComments(src) {
|
|
5912
|
+
return String(src || '')
|
|
5913
|
+
.replace(/--\[\[[\s\S]*?\]\]/g, '')
|
|
5914
|
+
.replace(/--.*$/gm, '');
|
|
5915
|
+
}
|
|
5916
|
+
|
|
5885
5917
|
/**
|
|
5886
5918
|
* Build reverse dependency map from forward map.
|
|
5887
5919
|
* @param {Map<string, string[]>} forwardMap
|
|
@@ -5900,7 +5932,7 @@ __factories["./src/extractors/deps"] = function(module, exports) {
|
|
|
5900
5932
|
return reverse;
|
|
5901
5933
|
}
|
|
5902
5934
|
|
|
5903
|
-
module.exports = { extractPythonDeps, extractTSDeps, extractRDeps, buildReverseDepMap };
|
|
5935
|
+
module.exports = { extractPythonDeps, extractTSDeps, extractRDeps, extractLuaDeps, buildReverseDepMap };
|
|
5904
5936
|
|
|
5905
5937
|
};
|
|
5906
5938
|
|
|
@@ -5935,6 +5967,7 @@ __factories["./src/extractors/dispatch"] = function(module, exports) {
|
|
|
5935
5967
|
swift: __require('./src/extractors/swift'),
|
|
5936
5968
|
dart: __require('./src/extractors/dart'),
|
|
5937
5969
|
scala: __require('./src/extractors/scala'),
|
|
5970
|
+
lua: __require('./src/extractors/lua'),
|
|
5938
5971
|
gdscript: __require('./src/extractors/gdscript'),
|
|
5939
5972
|
r: __require('./src/extractors/r'),
|
|
5940
5973
|
vue_sfc: __require('./src/extractors/vue_sfc'),
|
|
@@ -5985,6 +6018,7 @@ __factories["./src/extractors/dispatch"] = function(module, exports) {
|
|
|
5985
6018
|
'.swift': 'swift',
|
|
5986
6019
|
'.dart': 'dart',
|
|
5987
6020
|
'.scala': 'scala', '.sc': 'scala',
|
|
6021
|
+
'.lua': 'lua',
|
|
5988
6022
|
'.gd': 'gdscript',
|
|
5989
6023
|
'.r': 'r', '.R': 'r',
|
|
5990
6024
|
'.vue': 'vue_sfc',
|
|
@@ -7012,6 +7046,162 @@ __factories["./src/extractors/line-anchor"] = function(module, exports) {
|
|
|
7012
7046
|
|
|
7013
7047
|
};
|
|
7014
7048
|
|
|
7049
|
+
// ── ./src/extractors/lua ──
|
|
7050
|
+
__factories["./src/extractors/lua"] = function(module, exports) {
|
|
7051
|
+
|
|
7052
|
+
const { capWithNotice } = __require('./src/util/truncate');
|
|
7053
|
+
|
|
7054
|
+
// Ceiling discloses what it drops rather than truncating silently (#583).
|
|
7055
|
+
// Collection runs to completion so the marker reports the true overflow —
|
|
7056
|
+
// stopping early made r.js report "+1 more" where 50 were hidden (#584).
|
|
7057
|
+
const PER_FILE_LIMIT = 30;
|
|
7058
|
+
|
|
7059
|
+
/**
|
|
7060
|
+
* Extract signatures from Lua source code.
|
|
7061
|
+
*
|
|
7062
|
+
* Recognised constructs:
|
|
7063
|
+
* - Global functions: `function name(args)`
|
|
7064
|
+
* - Module-table functions: `function M.name(args)` / `function M:name(args)`
|
|
7065
|
+
* - Local functions: `local function name(args)`
|
|
7066
|
+
* - Assigned functions: `name = function(args)` / `M.name = function(args)`
|
|
7067
|
+
* - Module imports: `local mod = require("mod")` as compact hints
|
|
7068
|
+
* - LDoc-style doc comments (`---`) as first-sentence hints
|
|
7069
|
+
*
|
|
7070
|
+
* The extractor is regex-only and zero-dependency, matching SigMap's Tier-3
|
|
7071
|
+
* language extractor style.
|
|
7072
|
+
*
|
|
7073
|
+
* @param {string} src - Raw file content
|
|
7074
|
+
* @returns {string[]} Array of signature strings
|
|
7075
|
+
*/
|
|
7076
|
+
function extract(src) {
|
|
7077
|
+
if (!src || typeof src !== 'string') return [];
|
|
7078
|
+
const sigs = [];
|
|
7079
|
+
const hints = collectDocHints(src);
|
|
7080
|
+
const stripped = stripLuaComments(src);
|
|
7081
|
+
const seen = new Set();
|
|
7082
|
+
|
|
7083
|
+
// local foo = require('bar.baz') — useful module-surface hint, capped low.
|
|
7084
|
+
for (const m of stripped.matchAll(/^\s*(?:local\s+)?([A-Za-z_]\w*)\s*=\s*require\s*\(\s*['"]([A-Za-z0-9_.\/-]+)['"]\s*\)/gm)) {
|
|
7085
|
+
pushUnique(sigs, seen, `require ${m[2]} as ${m[1]}`);
|
|
7086
|
+
}
|
|
7087
|
+
|
|
7088
|
+
// local function name(args)
|
|
7089
|
+
for (const m of stripped.matchAll(/^\s*local\s+function\s+([A-Za-z_]\w*)\s*\(([^)]*)\)/gm)) {
|
|
7090
|
+
if (m[1].startsWith('_')) continue;
|
|
7091
|
+
pushUnique(sigs, seen, `local function ${m[1]}(${normalizeParams(m[2])})${applyHint(hints, m[1])}`);
|
|
7092
|
+
}
|
|
7093
|
+
|
|
7094
|
+
// function name(args), function M.name(args), function M:name(args)
|
|
7095
|
+
for (const m of stripped.matchAll(/^\s*function\s+([A-Za-z_]\w*(?:(?:\.|:)[A-Za-z_]\w*)*)\s*\(([^)]*)\)/gm)) {
|
|
7096
|
+
const name = m[1];
|
|
7097
|
+
if (name.startsWith('_')) continue;
|
|
7098
|
+
pushUnique(sigs, seen, `function ${name}(${normalizeParams(m[2])})${applyHint(hints, name)}`);
|
|
7099
|
+
}
|
|
7100
|
+
|
|
7101
|
+
// name = function(args), M.name = function(args), M:name = function(args)
|
|
7102
|
+
for (const m of stripped.matchAll(/^\s*(?:local\s+)?([A-Za-z_]\w*(?:(?:\.|:)[A-Za-z_]\w*)*)\s*=\s*function\s*\(([^)]*)\)/gm)) {
|
|
7103
|
+
const name = m[1];
|
|
7104
|
+
if (name.startsWith('_')) continue;
|
|
7105
|
+
pushUnique(sigs, seen, `${name} = function(${normalizeParams(m[2])})${applyHint(hints, name)}`);
|
|
7106
|
+
}
|
|
7107
|
+
|
|
7108
|
+
return capWithNotice(sigs, PER_FILE_LIMIT, 'signatures');
|
|
7109
|
+
}
|
|
7110
|
+
|
|
7111
|
+
function pushUnique(out, seen, sig) {
|
|
7112
|
+
if (!sig || seen.has(sig)) return;
|
|
7113
|
+
seen.add(sig);
|
|
7114
|
+
out.push(sig);
|
|
7115
|
+
}
|
|
7116
|
+
|
|
7117
|
+
function normalizeParams(params) {
|
|
7118
|
+
return String(params || '')
|
|
7119
|
+
.replace(/--.*$/gm, '')
|
|
7120
|
+
.split(',')
|
|
7121
|
+
.map((p) => p.trim().replace(/\s+/g, ' '))
|
|
7122
|
+
.filter(Boolean)
|
|
7123
|
+
.join(', ');
|
|
7124
|
+
}
|
|
7125
|
+
|
|
7126
|
+
function applyHint(hints, name) {
|
|
7127
|
+
const h = hints.get(name);
|
|
7128
|
+
return h ? ` # ${h}` : '';
|
|
7129
|
+
}
|
|
7130
|
+
|
|
7131
|
+
/**
|
|
7132
|
+
* Attach each contiguous `---` doc block to the next function-like declaration
|
|
7133
|
+
* by its extracted symbol name.
|
|
7134
|
+
*/
|
|
7135
|
+
function collectDocHints(src) {
|
|
7136
|
+
const hints = new Map();
|
|
7137
|
+
const lines = src.split('\n');
|
|
7138
|
+
let block = [];
|
|
7139
|
+
for (const line of lines) {
|
|
7140
|
+
const doc = line.match(/^\s*---\s?(.*)$/);
|
|
7141
|
+
if (doc) {
|
|
7142
|
+
block.push(doc[1]);
|
|
7143
|
+
} else if (block.length > 0) {
|
|
7144
|
+
const decl = line.match(/^\s*local\s+function\s+([A-Za-z_]\w*)\s*\(/)
|
|
7145
|
+
|| line.match(/^\s*function\s+([A-Za-z_]\w*(?:(?:\.|:)[A-Za-z_]\w*)*)\s*\(/)
|
|
7146
|
+
|| line.match(/^\s*(?:local\s+)?([A-Za-z_]\w*(?:(?:\.|:)[A-Za-z_]\w*)*)\s*=\s*function\s*\(/);
|
|
7147
|
+
if (decl) {
|
|
7148
|
+
const hint = firstDocSentence(block);
|
|
7149
|
+
if (hint) hints.set(decl[1], hint);
|
|
7150
|
+
}
|
|
7151
|
+
block = [];
|
|
7152
|
+
}
|
|
7153
|
+
}
|
|
7154
|
+
return hints;
|
|
7155
|
+
}
|
|
7156
|
+
|
|
7157
|
+
function firstDocSentence(block) {
|
|
7158
|
+
for (const raw of block) {
|
|
7159
|
+
const line = String(raw || '').trim();
|
|
7160
|
+
if (!line || line.startsWith('@')) continue;
|
|
7161
|
+
return line.replace(/\s+/g, ' ').slice(0, 60).replace(/[.,;:!?]+$/, '').trim();
|
|
7162
|
+
}
|
|
7163
|
+
return '';
|
|
7164
|
+
}
|
|
7165
|
+
|
|
7166
|
+
/** Strip Lua line and long comments while preserving strings enough for regex scans. */
|
|
7167
|
+
function stripLuaComments(src) {
|
|
7168
|
+
const out = src.split('');
|
|
7169
|
+
const blank = (a, b) => { for (let i = a; i < b; i++) if (out[i] !== '\n') out[i] = ' '; };
|
|
7170
|
+
let i = 0;
|
|
7171
|
+
while (i < src.length) {
|
|
7172
|
+
const ch = src[i];
|
|
7173
|
+
if (ch === '"' || ch === "'") {
|
|
7174
|
+
const quote = ch;
|
|
7175
|
+
i++;
|
|
7176
|
+
while (i < src.length) {
|
|
7177
|
+
if (src[i] === '\\') { i += 2; continue; }
|
|
7178
|
+
if (src[i] === quote) { i++; break; }
|
|
7179
|
+
if (src[i] === '\n') break;
|
|
7180
|
+
i++;
|
|
7181
|
+
}
|
|
7182
|
+
continue;
|
|
7183
|
+
}
|
|
7184
|
+
if (src.startsWith('--[[', i)) {
|
|
7185
|
+
const end = src.indexOf(']]', i + 4);
|
|
7186
|
+
blank(i, end === -1 ? src.length : end + 2);
|
|
7187
|
+
i = end === -1 ? src.length : end + 2;
|
|
7188
|
+
continue;
|
|
7189
|
+
}
|
|
7190
|
+
if (src.startsWith('--', i)) {
|
|
7191
|
+
const end = src.indexOf('\n', i + 2);
|
|
7192
|
+
blank(i, end === -1 ? src.length : end);
|
|
7193
|
+
i = end === -1 ? src.length : end;
|
|
7194
|
+
continue;
|
|
7195
|
+
}
|
|
7196
|
+
i++;
|
|
7197
|
+
}
|
|
7198
|
+
return out.join('');
|
|
7199
|
+
}
|
|
7200
|
+
|
|
7201
|
+
module.exports = { extract };
|
|
7202
|
+
|
|
7203
|
+
};
|
|
7204
|
+
|
|
7015
7205
|
// ── ./src/extractors/markdown ──
|
|
7016
7206
|
__factories["./src/extractors/markdown"] = function(module, exports) {
|
|
7017
7207
|
|
|
@@ -11977,6 +12167,8 @@ __factories["./src/graph/call-graph"] = function(module, exports) {
|
|
|
11977
12167
|
const JAVA_EXTS = new Set(['.java']);
|
|
11978
12168
|
const GO_EXTS = new Set(['.go']);
|
|
11979
12169
|
const RS_EXTS = new Set(['.rs']);
|
|
12170
|
+
const KT_EXTS = new Set(['.kt', '.kts']);
|
|
12171
|
+
const SCALA_EXTS = new Set(['.scala', '.sc']);
|
|
11980
12172
|
|
|
11981
12173
|
// Tokens that look like `name(` calls or definition headers but are language
|
|
11982
12174
|
// keywords, not user symbols — never treated as a call or a definition.
|
|
@@ -12300,6 +12492,81 @@ __factories["./src/graph/call-graph"] = function(module, exports) {
|
|
|
12300
12492
|
|
|
12301
12493
|
// Pick the masker whose comment/string syntax matches the language.
|
|
12302
12494
|
// Java and Go share JS syntax (Go raw strings mask like template literals).
|
|
12495
|
+
/**
|
|
12496
|
+
* Body range for a JVM-family member that may use either a brace body or an
|
|
12497
|
+
* expression body (`fun f() = expr` / `def f = expr`), which Java has not.
|
|
12498
|
+
* An expression body runs to the end of its line — enough to capture the calls
|
|
12499
|
+
* it makes, which is all the graph needs.
|
|
12500
|
+
* @returns {{bodyStart:number, bodyEnd:number}|null} null when neither form follows
|
|
12501
|
+
*/
|
|
12502
|
+
function jvmBodyRange(masked, from) {
|
|
12503
|
+
let k = from;
|
|
12504
|
+
while (k < masked.length && masked[k] !== '{' && masked[k] !== '=' && masked[k] !== '\n' && masked[k] !== ';') k++;
|
|
12505
|
+
if (masked[k] === '{') return { bodyStart: k, bodyEnd: matchDelim(masked, k, '{', '}') };
|
|
12506
|
+
if (masked[k] === '=') {
|
|
12507
|
+
// `= {` is still a brace body, just written with an assignment.
|
|
12508
|
+
let j = k + 1;
|
|
12509
|
+
while (j < masked.length && /\s/.test(masked[j])) j++;
|
|
12510
|
+
if (masked[j] === '{') return { bodyStart: j, bodyEnd: matchDelim(masked, j, '{', '}') };
|
|
12511
|
+
const eol = masked.indexOf('\n', k);
|
|
12512
|
+
return { bodyStart: k, bodyEnd: eol === -1 ? masked.length : eol };
|
|
12513
|
+
}
|
|
12514
|
+
return null; // abstract member or interface declaration
|
|
12515
|
+
}
|
|
12516
|
+
|
|
12517
|
+
/**
|
|
12518
|
+
* Kotlin `fun` definitions, including extension functions (`fun Foo.bar()`,
|
|
12519
|
+
* recorded as `bar`) and expression bodies.
|
|
12520
|
+
*/
|
|
12521
|
+
function ktDefs(masked) {
|
|
12522
|
+
const defs = [];
|
|
12523
|
+
const seen = new Set();
|
|
12524
|
+
const re = /(?:^|\n)[ \t]*((?:(?:public|private|protected|internal|open|override|abstract|final|suspend|inline|operator|infix|tailrec|external|expect|actual|inner|companion)\s+)*)fun\s+(?:<[^>\n]{0,80}>\s*)?(?:[A-Za-z_][\w.<>]*\.)?([A-Za-z_][\w]*)\s*\(/g;
|
|
12525
|
+
let m;
|
|
12526
|
+
while ((m = re.exec(masked)) !== null) {
|
|
12527
|
+
const name = m[2];
|
|
12528
|
+
if (NON_CALL.has(name)) continue;
|
|
12529
|
+
const paren = masked.indexOf('(', m.index + m[0].length - 1);
|
|
12530
|
+
const close = matchDelim(masked, paren, '(', ')');
|
|
12531
|
+
if (close < 0) continue;
|
|
12532
|
+
const range = jvmBodyRange(masked, close + 1);
|
|
12533
|
+
const line = lineAt(masked, m.index + 1);
|
|
12534
|
+
const key = name + ':' + (range ? range.bodyStart : line);
|
|
12535
|
+
if (seen.has(key)) continue;
|
|
12536
|
+
seen.add(key);
|
|
12537
|
+
// An abstract/interface member owns no body: it can receive edges but
|
|
12538
|
+
// never produces them, the same treatment javaDefs gives declarations.
|
|
12539
|
+
defs.push(range ? { name, line, ...range } : { name, line, bodyStart: close, bodyEnd: close });
|
|
12540
|
+
}
|
|
12541
|
+
return defs;
|
|
12542
|
+
}
|
|
12543
|
+
|
|
12544
|
+
/**
|
|
12545
|
+
* Scala `def` definitions. Handles parameterless members (`def foo: Int = 1`),
|
|
12546
|
+
* type parameters, and expression bodies.
|
|
12547
|
+
*/
|
|
12548
|
+
function scalaDefs(masked) {
|
|
12549
|
+
const defs = [];
|
|
12550
|
+
const seen = new Set();
|
|
12551
|
+
const re = /(?:^|\n)[ \t]*((?:(?:private|protected|override|implicit|final|lazy|sealed|abstract|inline)(?:\s*\[[^\]\n]{0,60}\])?\s+)*)def\s+([A-Za-z_][\w]*)\s*(?=[\[\(:=])/g;
|
|
12552
|
+
let m;
|
|
12553
|
+
while ((m = re.exec(masked)) !== null) {
|
|
12554
|
+
const name = m[2];
|
|
12555
|
+
if (NON_CALL.has(name)) continue;
|
|
12556
|
+
// Step past an optional type-parameter list, then an optional value list.
|
|
12557
|
+
let k = m.index + m[0].length;
|
|
12558
|
+
if (masked[k] === '[') { const e = matchDelim(masked, k, '[', ']'); if (e < 0) continue; k = e + 1; }
|
|
12559
|
+
while (masked[k] === '(') { const e = matchDelim(masked, k, '(', ')'); if (e < 0) break; k = e + 1; }
|
|
12560
|
+
const range = jvmBodyRange(masked, k);
|
|
12561
|
+
const line = lineAt(masked, m.index + 1);
|
|
12562
|
+
const key = name + ':' + (range ? range.bodyStart : line);
|
|
12563
|
+
if (seen.has(key)) continue;
|
|
12564
|
+
seen.add(key);
|
|
12565
|
+
defs.push(range ? { name, line, ...range } : { name, line, bodyStart: k, bodyEnd: k });
|
|
12566
|
+
}
|
|
12567
|
+
return defs;
|
|
12568
|
+
}
|
|
12569
|
+
|
|
12303
12570
|
function maskFor(filePath, src) {
|
|
12304
12571
|
const ext = path.extname(filePath).toLowerCase();
|
|
12305
12572
|
if (PY_EXTS.has(ext)) return maskPy(src);
|
|
@@ -12314,6 +12581,8 @@ __factories["./src/graph/call-graph"] = function(module, exports) {
|
|
|
12314
12581
|
if (JAVA_EXTS.has(ext)) return javaDefs(maskJs(src));
|
|
12315
12582
|
if (GO_EXTS.has(ext)) return goDefs(maskJs(src));
|
|
12316
12583
|
if (RS_EXTS.has(ext)) return rustDefs(maskRust(src));
|
|
12584
|
+
if (KT_EXTS.has(ext)) return ktDefs(maskJs(src));
|
|
12585
|
+
if (SCALA_EXTS.has(ext)) return scalaDefs(maskJs(src));
|
|
12317
12586
|
return null; // unsupported language
|
|
12318
12587
|
}
|
|
12319
12588
|
|
|
@@ -12403,7 +12672,8 @@ __factories["./src/graph/call-graph"] = function(module, exports) {
|
|
|
12403
12672
|
if (e.isDirectory()) _walk(full, excludeSet, out, depth + 1, maxDepth);
|
|
12404
12673
|
else if (e.isFile()) {
|
|
12405
12674
|
const ext = path.extname(e.name).toLowerCase();
|
|
12406
|
-
if (JS_EXTS.has(ext) || PY_EXTS.has(ext) || JAVA_EXTS.has(ext) || GO_EXTS.has(ext)
|
|
12675
|
+
if (JS_EXTS.has(ext) || PY_EXTS.has(ext) || JAVA_EXTS.has(ext) || GO_EXTS.has(ext)
|
|
12676
|
+
|| RS_EXTS.has(ext) || KT_EXTS.has(ext) || SCALA_EXTS.has(ext)) out.push(full);
|
|
12407
12677
|
}
|
|
12408
12678
|
}
|
|
12409
12679
|
}
|
|
@@ -15806,7 +16076,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
|
|
|
15806
16076
|
|
|
15807
16077
|
const SERVER_INFO = {
|
|
15808
16078
|
name: 'sigmap',
|
|
15809
|
-
version: '8.
|
|
16079
|
+
version: '8.34.0',
|
|
15810
16080
|
description: 'SigMap MCP server — code signatures on demand',
|
|
15811
16081
|
};
|
|
15812
16082
|
|
|
@@ -22100,7 +22370,7 @@ function __tryGit(args, opts = {}) {
|
|
|
22100
22370
|
catch (_) { return ''; }
|
|
22101
22371
|
}
|
|
22102
22372
|
|
|
22103
|
-
const VERSION = '8.
|
|
22373
|
+
const VERSION = '8.34.0';
|
|
22104
22374
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
22105
22375
|
|
|
22106
22376
|
function requireSourceOrBundled(key) {
|
|
@@ -22469,6 +22739,38 @@ function computeEffectiveMaxTokens(fileEntries, config) {
|
|
|
22469
22739
|
return effective;
|
|
22470
22740
|
}
|
|
22471
22741
|
|
|
22742
|
+
/**
|
|
22743
|
+
* Delete `.github/context-*.md` split files this run did not write (#555).
|
|
22744
|
+
*
|
|
22745
|
+
* Splits are discovered by filename pattern at read time, not by consulting
|
|
22746
|
+
* the config, so a file left behind by a previous `strategy` — or by a module
|
|
22747
|
+
* since dropped from `srcDirs` — keeps being merged into the retrieval index
|
|
22748
|
+
* and silently steers every query. Observed on a 524-file Java repo: a stale
|
|
22749
|
+
* 376 KB `context-mall-mbg.md` kept generated entities at ranks 1, 3 and 4
|
|
22750
|
+
* while both files implementing the feature fell outside the top 6, even
|
|
22751
|
+
* though `sig-index.json` correctly held zero entries for that module.
|
|
22752
|
+
*
|
|
22753
|
+
* @param {string} cwd
|
|
22754
|
+
* @param {string[]} keep basenames this run wrote, e.g. ['context-core.md']
|
|
22755
|
+
* @returns {string[]} basenames removed
|
|
22756
|
+
*/
|
|
22757
|
+
function pruneStaleContextSplits(cwd, keep) {
|
|
22758
|
+
const ghDir = path.join(cwd, '.github');
|
|
22759
|
+
const kept = new Set(keep);
|
|
22760
|
+
const removed = [];
|
|
22761
|
+
let entries;
|
|
22762
|
+
try { entries = fs.readdirSync(ghDir); } catch (_) { return removed; }
|
|
22763
|
+
for (const f of entries) {
|
|
22764
|
+
if (!/^context-[\w.-]+\.md$/.test(f) || kept.has(f)) continue;
|
|
22765
|
+
try { fs.unlinkSync(path.join(ghDir, f)); removed.push(f); } catch (_) { /* best effort */ }
|
|
22766
|
+
}
|
|
22767
|
+
if (removed.length) {
|
|
22768
|
+
console.warn(`[sigmap] pruned ${removed.length} stale context split(s) from a previous `
|
|
22769
|
+
+ `strategy/srcDirs: ${removed.join(', ')}`);
|
|
22770
|
+
}
|
|
22771
|
+
return removed;
|
|
22772
|
+
}
|
|
22773
|
+
|
|
22472
22774
|
function applyTokenBudget(fileEntries, maxTokens) {
|
|
22473
22775
|
// fileEntries: [{ filePath, sigs, mtime }]
|
|
22474
22776
|
// Per-file rendered overhead: "### <path>", code fences, trailing blank line.
|
|
@@ -23276,6 +23578,7 @@ function runPerModuleStrategy(cwd, config, fileEntries, inputTokenTotal) {
|
|
|
23276
23578
|
];
|
|
23277
23579
|
|
|
23278
23580
|
let totalOut = 0;
|
|
23581
|
+
const writtenSplits = [];
|
|
23279
23582
|
for (const mod of moduleNames) {
|
|
23280
23583
|
const outName = `context-${mod}.md`;
|
|
23281
23584
|
const outPath = path.join(cwd, '.github', outName);
|
|
@@ -23294,7 +23597,9 @@ function runPerModuleStrategy(cwd, config, fileEntries, inputTokenTotal) {
|
|
|
23294
23597
|
console.warn(`[sigmap] per-module: wrote .github/${outName} (~${modTokens} tokens, ${budgeted.length} files)`);
|
|
23295
23598
|
|
|
23296
23599
|
overviewLines.push(`| \`${mod}\` | \`.github/${outName}\` |`);
|
|
23600
|
+
writtenSplits.push(outName);
|
|
23297
23601
|
}
|
|
23602
|
+
pruneStaleContextSplits(cwd, writtenSplits);
|
|
23298
23603
|
|
|
23299
23604
|
overviewLines.push('');
|
|
23300
23605
|
overviewLines.push('> Inject the relevant module file into your IDE context window.');
|
|
@@ -23333,6 +23638,7 @@ function runHotColdStrategy(cwd, config, fileEntries, recentFiles, inputTokenTot
|
|
|
23333
23638
|
const coldContent = coldHeader + formatOutput(coldEntries, cwd, false, config, null);
|
|
23334
23639
|
ensureDir(coldPath);
|
|
23335
23640
|
fs.writeFileSync(coldPath, coldContent, 'utf8');
|
|
23641
|
+
pruneStaleContextSplits(cwd, ['context-cold.md']);
|
|
23336
23642
|
const coldTokens = estimateTokens(coldContent);
|
|
23337
23643
|
|
|
23338
23644
|
console.warn('[sigmap] hot-cold:');
|
|
@@ -23657,6 +23963,9 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
23657
23963
|
result = runHotColdStrategy(cwd, configWithBudget, fileEntries, recentFiles, inputTokenTotal);
|
|
23658
23964
|
} else {
|
|
23659
23965
|
// 'full' — original behaviour
|
|
23966
|
+
// 'full' writes no split files — anything left from a previous
|
|
23967
|
+
// strategy would still be merged into the index at read time (#555).
|
|
23968
|
+
pruneStaleContextSplits(cwd, []);
|
|
23660
23969
|
fileEntries = applyTokenBudget(fileEntries, effectiveMaxTokens);
|
|
23661
23970
|
const droppedCount = beforeCount - fileEntries.length;
|
|
23662
23971
|
const routingEnabled = !!(config.routing || process.argv.includes('--routing'));
|
package/llms-full.txt
CHANGED
|
@@ -11,13 +11,13 @@ ranking keeps the relevant context in scope (cutting tokens ~97% as a side
|
|
|
11
11
|
effect), with no LLM calls, embeddings, or vector database. Works with Claude,
|
|
12
12
|
Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
13
13
|
|
|
14
|
-
# Version: 8.
|
|
14
|
+
# Version: 8.34.0 | Benchmark: sigmap-v8.34-main (2026-09-13)
|
|
15
15
|
# Source: auto-generated from package.json, version.json, benchmarks/latest.json, src/mcp/tools.js, src/config/defaults.js
|
|
16
16
|
# Regenerate: npm run generate:llms | Validate: npm run validate:llms
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
20
|
-
## Core metrics (benchmark: sigmap-v8.
|
|
20
|
+
## Core metrics (benchmark: sigmap-v8.34-main, 2026-09-13)
|
|
21
21
|
|
|
22
22
|
| Metric | Without SigMap | With SigMap |
|
|
23
23
|
|--------|----------------|-------------|
|
|
@@ -25,7 +25,7 @@ Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
|
25
25
|
| Token reduction | — | 96.8% average |
|
|
26
26
|
| Task-success proxy (modeled) | — | 62.9% |
|
|
27
27
|
| Prompts per task | 2.84 | 1.58 (44.4% fewer) |
|
|
28
|
-
| Supported languages | — |
|
|
28
|
+
| Supported languages | — | 33 |
|
|
29
29
|
| MCP tools | — | 21 |
|
|
30
30
|
| npm runtime dependencies | — | 0 |
|
|
31
31
|
|
|
@@ -370,9 +370,9 @@ impact = {"depth":3,"includeSigs":true}
|
|
|
370
370
|
|
|
371
371
|
---
|
|
372
372
|
|
|
373
|
-
## Supported languages (
|
|
373
|
+
## Supported languages (33 extractors)
|
|
374
374
|
|
|
375
|
-
cpp, csharp, css, dart, dockerfile, gdscript, go, graphql, html, java, javascript, kotlin, markdown, php, properties, protobuf, python, r, ruby, rust, scala, shell, sql, svelte, swift, terraform, toml, typescript, typescript_react, vue_sfc, xml, yaml
|
|
375
|
+
cpp, csharp, css, dart, dockerfile, gdscript, go, graphql, html, java, javascript, kotlin, lua, markdown, php, properties, protobuf, python, r, ruby, rust, scala, shell, sql, svelte, swift, terraform, toml, typescript, typescript_react, vue_sfc, xml, yaml
|
|
376
376
|
|
|
377
377
|
---
|
|
378
378
|
|
package/llms.txt
CHANGED
|
@@ -11,7 +11,7 @@ ranking keeps the relevant context in scope (cutting tokens ~97% as a side
|
|
|
11
11
|
effect), with no LLM calls, embeddings, or vector database. Works with Claude,
|
|
12
12
|
Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
13
13
|
|
|
14
|
-
# Version: 8.
|
|
14
|
+
# Version: 8.34.0 | Benchmark: sigmap-v8.34-main (2026-09-13)
|
|
15
15
|
# Source: auto-generated from package.json, version.json, benchmarks/latest.json, src/mcp/tools.js, src/config/defaults.js
|
|
16
16
|
# Regenerate: npm run generate:llms | Validate: npm run validate:llms
|
|
17
17
|
|
|
@@ -23,13 +23,13 @@ Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
|
23
23
|
- No blast-radius awareness before editing a hub file — `--impact` shows every file a change touches.
|
|
24
24
|
- Pasted stack traces, CI logs, and JSON bloat the prompt — `squeeze` minimizes them and enriches the top frame from the symbol index.
|
|
25
25
|
|
|
26
|
-
## Core metrics (benchmark: sigmap-v8.
|
|
26
|
+
## Core metrics (benchmark: sigmap-v8.34-main, 2026-09-13)
|
|
27
27
|
|
|
28
28
|
- hit@5 retrieval: 78.9% vs 44.0% single-shot grep baseline (1.73× lift)
|
|
29
29
|
- Token reduction: 96.8% average across benchmark repos
|
|
30
30
|
- Task-success proxy: 62.9% (modeled from retrieval tiers, not measured LLM sessions)
|
|
31
31
|
- Prompts per task: 1.58 vs 2.84 baseline (44.4% fewer, modeled)
|
|
32
|
-
- Languages:
|
|
32
|
+
- Languages: 33 supported · MCP tools: 21
|
|
33
33
|
- Dependencies: zero npm runtime dependencies · fully offline
|
|
34
34
|
|
|
35
35
|
## Quick start
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sigmap",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.34.0",
|
|
4
4
|
"description": "The deterministic, verifiable grounding layer for AI code work — a zero-dependency signature-and-evidence map that grounds Claude, Cursor, Copilot, Aider, Windsurf, local LLMs & MCP agents against your real code (repo + installed libraries) so they stop hallucinating files, imports & APIs. Runs offline via npx; byte-stable output; ~97% token reduction as proof.",
|
|
5
5
|
"main": "packages/core/index.js",
|
|
6
6
|
"exports": {
|
package/packages/core/index.js
CHANGED
package/src/config/loader.js
CHANGED
|
@@ -81,7 +81,7 @@ const SUPPORTED_CODE_EXTS = new Set([
|
|
|
81
81
|
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
|
|
82
82
|
'.py', '.pyw', '.java', '.kt', '.kts', '.go', '.rs', '.cs',
|
|
83
83
|
'.cpp', '.c', '.h', '.hpp', '.cc', '.rb', '.rake', '.php',
|
|
84
|
-
'.swift', '.dart', '.scala', '.sc', '.vue', '.svelte',
|
|
84
|
+
'.swift', '.dart', '.scala', '.sc', '.lua', '.vue', '.svelte',
|
|
85
85
|
'.html', '.htm', '.css', '.scss', '.sass', '.less',
|
|
86
86
|
'.yml', '.yaml', '.sh', '.bash', '.zsh', '.fish',
|
|
87
87
|
'.sql', '.graphql', '.gql', '.tf', '.tfvars', '.proto',
|
package/src/config/tune.js
CHANGED
|
@@ -38,7 +38,7 @@ const TOKENS_PER_FILE = 25;
|
|
|
38
38
|
const SOURCE_EXTS = new Set([
|
|
39
39
|
'.js', '.mjs', '.cjs', '.jsx', '.ts', '.tsx', '.py', '.rb', '.go', '.rs',
|
|
40
40
|
'.java', '.kt', '.cs', '.cpp', '.c', '.h', '.hpp', '.swift', '.dart',
|
|
41
|
-
'.scala', '.php', '.gd', '.r', '.R',
|
|
41
|
+
'.scala', '.php', '.lua', '.gd', '.r', '.R',
|
|
42
42
|
]);
|
|
43
43
|
|
|
44
44
|
/** Raw user config file content, or null when absent/unparsable. */
|
|
@@ -19,6 +19,7 @@ const EXT_TO_LANG = {
|
|
|
19
19
|
'.java': 'java', '.kt': 'kotlin', '.cs': 'csharp', '.cpp': 'cpp',
|
|
20
20
|
'.c': 'cpp', '.h': 'cpp', '.hpp': 'cpp', '.swift': 'swift',
|
|
21
21
|
'.dart': 'dart', '.scala': 'scala', '.php': 'php',
|
|
22
|
+
'.lua': 'lua',
|
|
22
23
|
'.gd': 'gdscript',
|
|
23
24
|
'.r': 'r', '.R': 'r',
|
|
24
25
|
};
|
|
@@ -156,12 +156,21 @@ const REGISTRY = {
|
|
|
156
156
|
akka: { detectionFiles: [], detectionDeps: ['akka'], srcDirs: ['src/main/scala','src'] },
|
|
157
157
|
play: { detectionFiles: [], detectionDeps: ['play'], srcDirs: ['app','conf'] },
|
|
158
158
|
spark: { detectionFiles: [], detectionDeps: ['spark'],srcDirs: ['src/main/scala'] },
|
|
159
|
-
zio: { detectionFiles: [], detectionDeps: ['zio'],
|
|
159
|
+
zio: { detectionFiles: [], detectionDeps: ['zio'], srcDirs: ['src/main/scala'] },
|
|
160
160
|
},
|
|
161
161
|
srcDirs: ['src/main/scala','src'],
|
|
162
162
|
penalties: ['target'],
|
|
163
163
|
},
|
|
164
164
|
|
|
165
|
+
lua: {
|
|
166
|
+
manifestFiles: ['.luarc.json', 'selene.toml', 'stylua.toml'],
|
|
167
|
+
frameworks: {
|
|
168
|
+
luarocks: { detectionFiles: ['*.rockspec'], srcDirs: ['src','lua','lib'] },
|
|
169
|
+
},
|
|
170
|
+
srcDirs: ['src','lua','lib'],
|
|
171
|
+
penalties: ['.luarocks','luarocks_modules'],
|
|
172
|
+
},
|
|
173
|
+
|
|
165
174
|
r: {
|
|
166
175
|
manifestFiles: ['DESCRIPTION','renv.lock'],
|
|
167
176
|
frameworks: {
|
|
@@ -7,7 +7,7 @@ const { git } = require('../util/git');
|
|
|
7
7
|
const CODE_EXTS = new Set([
|
|
8
8
|
'.js','.mjs','.cjs','.ts','.tsx','.jsx',
|
|
9
9
|
'.py','.rb','.go','.rs','.java','.kt',
|
|
10
|
-
'.cs','.cpp','.c','.h','.swift','.dart','.scala','.php',
|
|
10
|
+
'.cs','.cpp','.c','.h','.swift','.dart','.scala','.php','.lua',
|
|
11
11
|
]);
|
|
12
12
|
|
|
13
13
|
const AUTO_SKIP = new Set([
|
package/src/extractors/deps.js
CHANGED
|
@@ -89,6 +89,28 @@ function extractRDeps(src) {
|
|
|
89
89
|
return [...deps].slice(0, 5);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Extract Lua require() module dependencies.
|
|
94
|
+
* Captures `require "mod"` and `require("mod")`, returning compact module
|
|
95
|
+
* names as they appear in source.
|
|
96
|
+
* @param {string} src
|
|
97
|
+
* @returns {string[]}
|
|
98
|
+
*/
|
|
99
|
+
function extractLuaDeps(src) {
|
|
100
|
+
const deps = new Set();
|
|
101
|
+
const stripped = stripLuaComments(src || '');
|
|
102
|
+
for (const m of stripped.matchAll(/\brequire\s*(?:\(\s*)?["']([A-Za-z0-9_.\/-]+)["']\s*\)?/g)) {
|
|
103
|
+
if (m[1]) deps.add(m[1]);
|
|
104
|
+
}
|
|
105
|
+
return [...deps].slice(0, 5);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function stripLuaComments(src) {
|
|
109
|
+
return String(src || '')
|
|
110
|
+
.replace(/--\[\[[\s\S]*?\]\]/g, '')
|
|
111
|
+
.replace(/--.*$/gm, '');
|
|
112
|
+
}
|
|
113
|
+
|
|
92
114
|
/**
|
|
93
115
|
* Build reverse dependency map from forward map.
|
|
94
116
|
* @param {Map<string, string[]>} forwardMap
|
|
@@ -107,4 +129,4 @@ function buildReverseDepMap(forwardMap) {
|
|
|
107
129
|
return reverse;
|
|
108
130
|
}
|
|
109
131
|
|
|
110
|
-
module.exports = { extractPythonDeps, extractTSDeps, extractRDeps, buildReverseDepMap };
|
|
132
|
+
module.exports = { extractPythonDeps, extractTSDeps, extractRDeps, extractLuaDeps, buildReverseDepMap };
|
|
@@ -28,6 +28,7 @@ const EXTRACTORS = {
|
|
|
28
28
|
swift: require('./swift'),
|
|
29
29
|
dart: require('./dart'),
|
|
30
30
|
scala: require('./scala'),
|
|
31
|
+
lua: require('./lua'),
|
|
31
32
|
gdscript: require('./gdscript'),
|
|
32
33
|
r: require('./r'),
|
|
33
34
|
vue_sfc: require('./vue_sfc'),
|
|
@@ -78,6 +79,7 @@ const EXT_MAP = {
|
|
|
78
79
|
'.swift': 'swift',
|
|
79
80
|
'.dart': 'dart',
|
|
80
81
|
'.scala': 'scala', '.sc': 'scala',
|
|
82
|
+
'.lua': 'lua',
|
|
81
83
|
'.gd': 'gdscript',
|
|
82
84
|
'.r': 'r', '.R': 'r',
|
|
83
85
|
'.vue': 'vue_sfc',
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { capWithNotice } = require('../util/truncate');
|
|
4
|
+
|
|
5
|
+
// Ceiling discloses what it drops rather than truncating silently (#583).
|
|
6
|
+
// Collection runs to completion so the marker reports the true overflow —
|
|
7
|
+
// stopping early made r.js report "+1 more" where 50 were hidden (#584).
|
|
8
|
+
const PER_FILE_LIMIT = 30;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Extract signatures from Lua source code.
|
|
12
|
+
*
|
|
13
|
+
* Recognised constructs:
|
|
14
|
+
* - Global functions: `function name(args)`
|
|
15
|
+
* - Module-table functions: `function M.name(args)` / `function M:name(args)`
|
|
16
|
+
* - Local functions: `local function name(args)`
|
|
17
|
+
* - Assigned functions: `name = function(args)` / `M.name = function(args)`
|
|
18
|
+
* - Module imports: `local mod = require("mod")` as compact hints
|
|
19
|
+
* - LDoc-style doc comments (`---`) as first-sentence hints
|
|
20
|
+
*
|
|
21
|
+
* The extractor is regex-only and zero-dependency, matching SigMap's Tier-3
|
|
22
|
+
* language extractor style.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} src - Raw file content
|
|
25
|
+
* @returns {string[]} Array of signature strings
|
|
26
|
+
*/
|
|
27
|
+
function extract(src) {
|
|
28
|
+
if (!src || typeof src !== 'string') return [];
|
|
29
|
+
const sigs = [];
|
|
30
|
+
const hints = collectDocHints(src);
|
|
31
|
+
const stripped = stripLuaComments(src);
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
|
|
34
|
+
// local foo = require('bar.baz') — useful module-surface hint, capped low.
|
|
35
|
+
for (const m of stripped.matchAll(/^\s*(?:local\s+)?([A-Za-z_]\w*)\s*=\s*require\s*\(\s*['"]([A-Za-z0-9_.\/-]+)['"]\s*\)/gm)) {
|
|
36
|
+
pushUnique(sigs, seen, `require ${m[2]} as ${m[1]}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// local function name(args)
|
|
40
|
+
for (const m of stripped.matchAll(/^\s*local\s+function\s+([A-Za-z_]\w*)\s*\(([^)]*)\)/gm)) {
|
|
41
|
+
if (m[1].startsWith('_')) continue;
|
|
42
|
+
pushUnique(sigs, seen, `local function ${m[1]}(${normalizeParams(m[2])})${applyHint(hints, m[1])}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// function name(args), function M.name(args), function M:name(args)
|
|
46
|
+
for (const m of stripped.matchAll(/^\s*function\s+([A-Za-z_]\w*(?:(?:\.|:)[A-Za-z_]\w*)*)\s*\(([^)]*)\)/gm)) {
|
|
47
|
+
const name = m[1];
|
|
48
|
+
if (name.startsWith('_')) continue;
|
|
49
|
+
pushUnique(sigs, seen, `function ${name}(${normalizeParams(m[2])})${applyHint(hints, name)}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// name = function(args), M.name = function(args), M:name = function(args)
|
|
53
|
+
for (const m of stripped.matchAll(/^\s*(?:local\s+)?([A-Za-z_]\w*(?:(?:\.|:)[A-Za-z_]\w*)*)\s*=\s*function\s*\(([^)]*)\)/gm)) {
|
|
54
|
+
const name = m[1];
|
|
55
|
+
if (name.startsWith('_')) continue;
|
|
56
|
+
pushUnique(sigs, seen, `${name} = function(${normalizeParams(m[2])})${applyHint(hints, name)}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return capWithNotice(sigs, PER_FILE_LIMIT, 'signatures');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function pushUnique(out, seen, sig) {
|
|
63
|
+
if (!sig || seen.has(sig)) return;
|
|
64
|
+
seen.add(sig);
|
|
65
|
+
out.push(sig);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function normalizeParams(params) {
|
|
69
|
+
return String(params || '')
|
|
70
|
+
.replace(/--.*$/gm, '')
|
|
71
|
+
.split(',')
|
|
72
|
+
.map((p) => p.trim().replace(/\s+/g, ' '))
|
|
73
|
+
.filter(Boolean)
|
|
74
|
+
.join(', ');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function applyHint(hints, name) {
|
|
78
|
+
const h = hints.get(name);
|
|
79
|
+
return h ? ` # ${h}` : '';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Attach each contiguous `---` doc block to the next function-like declaration
|
|
84
|
+
* by its extracted symbol name.
|
|
85
|
+
*/
|
|
86
|
+
function collectDocHints(src) {
|
|
87
|
+
const hints = new Map();
|
|
88
|
+
const lines = src.split('\n');
|
|
89
|
+
let block = [];
|
|
90
|
+
for (const line of lines) {
|
|
91
|
+
const doc = line.match(/^\s*---\s?(.*)$/);
|
|
92
|
+
if (doc) {
|
|
93
|
+
block.push(doc[1]);
|
|
94
|
+
} else if (block.length > 0) {
|
|
95
|
+
const decl = line.match(/^\s*local\s+function\s+([A-Za-z_]\w*)\s*\(/)
|
|
96
|
+
|| line.match(/^\s*function\s+([A-Za-z_]\w*(?:(?:\.|:)[A-Za-z_]\w*)*)\s*\(/)
|
|
97
|
+
|| line.match(/^\s*(?:local\s+)?([A-Za-z_]\w*(?:(?:\.|:)[A-Za-z_]\w*)*)\s*=\s*function\s*\(/);
|
|
98
|
+
if (decl) {
|
|
99
|
+
const hint = firstDocSentence(block);
|
|
100
|
+
if (hint) hints.set(decl[1], hint);
|
|
101
|
+
}
|
|
102
|
+
block = [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return hints;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function firstDocSentence(block) {
|
|
109
|
+
for (const raw of block) {
|
|
110
|
+
const line = String(raw || '').trim();
|
|
111
|
+
if (!line || line.startsWith('@')) continue;
|
|
112
|
+
return line.replace(/\s+/g, ' ').slice(0, 60).replace(/[.,;:!?]+$/, '').trim();
|
|
113
|
+
}
|
|
114
|
+
return '';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Strip Lua line and long comments while preserving strings enough for regex scans. */
|
|
118
|
+
function stripLuaComments(src) {
|
|
119
|
+
const out = src.split('');
|
|
120
|
+
const blank = (a, b) => { for (let i = a; i < b; i++) if (out[i] !== '\n') out[i] = ' '; };
|
|
121
|
+
let i = 0;
|
|
122
|
+
while (i < src.length) {
|
|
123
|
+
const ch = src[i];
|
|
124
|
+
if (ch === '"' || ch === "'") {
|
|
125
|
+
const quote = ch;
|
|
126
|
+
i++;
|
|
127
|
+
while (i < src.length) {
|
|
128
|
+
if (src[i] === '\\') { i += 2; continue; }
|
|
129
|
+
if (src[i] === quote) { i++; break; }
|
|
130
|
+
if (src[i] === '\n') break;
|
|
131
|
+
i++;
|
|
132
|
+
}
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (src.startsWith('--[[', i)) {
|
|
136
|
+
const end = src.indexOf(']]', i + 4);
|
|
137
|
+
blank(i, end === -1 ? src.length : end + 2);
|
|
138
|
+
i = end === -1 ? src.length : end + 2;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (src.startsWith('--', i)) {
|
|
142
|
+
const end = src.indexOf('\n', i + 2);
|
|
143
|
+
blank(i, end === -1 ? src.length : end);
|
|
144
|
+
i = end === -1 ? src.length : end;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
i++;
|
|
148
|
+
}
|
|
149
|
+
return out.join('');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = { extract };
|
package/src/graph/call-graph.js
CHANGED
|
@@ -26,6 +26,8 @@ const PY_EXTS = new Set(['.py', '.pyw']);
|
|
|
26
26
|
const JAVA_EXTS = new Set(['.java']);
|
|
27
27
|
const GO_EXTS = new Set(['.go']);
|
|
28
28
|
const RS_EXTS = new Set(['.rs']);
|
|
29
|
+
const KT_EXTS = new Set(['.kt', '.kts']);
|
|
30
|
+
const SCALA_EXTS = new Set(['.scala', '.sc']);
|
|
29
31
|
|
|
30
32
|
// Tokens that look like `name(` calls or definition headers but are language
|
|
31
33
|
// keywords, not user symbols — never treated as a call or a definition.
|
|
@@ -349,6 +351,81 @@ function rustDefs(masked) {
|
|
|
349
351
|
|
|
350
352
|
// Pick the masker whose comment/string syntax matches the language.
|
|
351
353
|
// Java and Go share JS syntax (Go raw strings mask like template literals).
|
|
354
|
+
/**
|
|
355
|
+
* Body range for a JVM-family member that may use either a brace body or an
|
|
356
|
+
* expression body (`fun f() = expr` / `def f = expr`), which Java has not.
|
|
357
|
+
* An expression body runs to the end of its line — enough to capture the calls
|
|
358
|
+
* it makes, which is all the graph needs.
|
|
359
|
+
* @returns {{bodyStart:number, bodyEnd:number}|null} null when neither form follows
|
|
360
|
+
*/
|
|
361
|
+
function jvmBodyRange(masked, from) {
|
|
362
|
+
let k = from;
|
|
363
|
+
while (k < masked.length && masked[k] !== '{' && masked[k] !== '=' && masked[k] !== '\n' && masked[k] !== ';') k++;
|
|
364
|
+
if (masked[k] === '{') return { bodyStart: k, bodyEnd: matchDelim(masked, k, '{', '}') };
|
|
365
|
+
if (masked[k] === '=') {
|
|
366
|
+
// `= {` is still a brace body, just written with an assignment.
|
|
367
|
+
let j = k + 1;
|
|
368
|
+
while (j < masked.length && /\s/.test(masked[j])) j++;
|
|
369
|
+
if (masked[j] === '{') return { bodyStart: j, bodyEnd: matchDelim(masked, j, '{', '}') };
|
|
370
|
+
const eol = masked.indexOf('\n', k);
|
|
371
|
+
return { bodyStart: k, bodyEnd: eol === -1 ? masked.length : eol };
|
|
372
|
+
}
|
|
373
|
+
return null; // abstract member or interface declaration
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Kotlin `fun` definitions, including extension functions (`fun Foo.bar()`,
|
|
378
|
+
* recorded as `bar`) and expression bodies.
|
|
379
|
+
*/
|
|
380
|
+
function ktDefs(masked) {
|
|
381
|
+
const defs = [];
|
|
382
|
+
const seen = new Set();
|
|
383
|
+
const re = /(?:^|\n)[ \t]*((?:(?:public|private|protected|internal|open|override|abstract|final|suspend|inline|operator|infix|tailrec|external|expect|actual|inner|companion)\s+)*)fun\s+(?:<[^>\n]{0,80}>\s*)?(?:[A-Za-z_][\w.<>]*\.)?([A-Za-z_][\w]*)\s*\(/g;
|
|
384
|
+
let m;
|
|
385
|
+
while ((m = re.exec(masked)) !== null) {
|
|
386
|
+
const name = m[2];
|
|
387
|
+
if (NON_CALL.has(name)) continue;
|
|
388
|
+
const paren = masked.indexOf('(', m.index + m[0].length - 1);
|
|
389
|
+
const close = matchDelim(masked, paren, '(', ')');
|
|
390
|
+
if (close < 0) continue;
|
|
391
|
+
const range = jvmBodyRange(masked, close + 1);
|
|
392
|
+
const line = lineAt(masked, m.index + 1);
|
|
393
|
+
const key = name + ':' + (range ? range.bodyStart : line);
|
|
394
|
+
if (seen.has(key)) continue;
|
|
395
|
+
seen.add(key);
|
|
396
|
+
// An abstract/interface member owns no body: it can receive edges but
|
|
397
|
+
// never produces them, the same treatment javaDefs gives declarations.
|
|
398
|
+
defs.push(range ? { name, line, ...range } : { name, line, bodyStart: close, bodyEnd: close });
|
|
399
|
+
}
|
|
400
|
+
return defs;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Scala `def` definitions. Handles parameterless members (`def foo: Int = 1`),
|
|
405
|
+
* type parameters, and expression bodies.
|
|
406
|
+
*/
|
|
407
|
+
function scalaDefs(masked) {
|
|
408
|
+
const defs = [];
|
|
409
|
+
const seen = new Set();
|
|
410
|
+
const re = /(?:^|\n)[ \t]*((?:(?:private|protected|override|implicit|final|lazy|sealed|abstract|inline)(?:\s*\[[^\]\n]{0,60}\])?\s+)*)def\s+([A-Za-z_][\w]*)\s*(?=[\[\(:=])/g;
|
|
411
|
+
let m;
|
|
412
|
+
while ((m = re.exec(masked)) !== null) {
|
|
413
|
+
const name = m[2];
|
|
414
|
+
if (NON_CALL.has(name)) continue;
|
|
415
|
+
// Step past an optional type-parameter list, then an optional value list.
|
|
416
|
+
let k = m.index + m[0].length;
|
|
417
|
+
if (masked[k] === '[') { const e = matchDelim(masked, k, '[', ']'); if (e < 0) continue; k = e + 1; }
|
|
418
|
+
while (masked[k] === '(') { const e = matchDelim(masked, k, '(', ')'); if (e < 0) break; k = e + 1; }
|
|
419
|
+
const range = jvmBodyRange(masked, k);
|
|
420
|
+
const line = lineAt(masked, m.index + 1);
|
|
421
|
+
const key = name + ':' + (range ? range.bodyStart : line);
|
|
422
|
+
if (seen.has(key)) continue;
|
|
423
|
+
seen.add(key);
|
|
424
|
+
defs.push(range ? { name, line, ...range } : { name, line, bodyStart: k, bodyEnd: k });
|
|
425
|
+
}
|
|
426
|
+
return defs;
|
|
427
|
+
}
|
|
428
|
+
|
|
352
429
|
function maskFor(filePath, src) {
|
|
353
430
|
const ext = path.extname(filePath).toLowerCase();
|
|
354
431
|
if (PY_EXTS.has(ext)) return maskPy(src);
|
|
@@ -363,6 +440,8 @@ function extractDefs(filePath, src) {
|
|
|
363
440
|
if (JAVA_EXTS.has(ext)) return javaDefs(maskJs(src));
|
|
364
441
|
if (GO_EXTS.has(ext)) return goDefs(maskJs(src));
|
|
365
442
|
if (RS_EXTS.has(ext)) return rustDefs(maskRust(src));
|
|
443
|
+
if (KT_EXTS.has(ext)) return ktDefs(maskJs(src));
|
|
444
|
+
if (SCALA_EXTS.has(ext)) return scalaDefs(maskJs(src));
|
|
366
445
|
return null; // unsupported language
|
|
367
446
|
}
|
|
368
447
|
|
|
@@ -452,7 +531,8 @@ function _walk(dir, excludeSet, out, depth, maxDepth) {
|
|
|
452
531
|
if (e.isDirectory()) _walk(full, excludeSet, out, depth + 1, maxDepth);
|
|
453
532
|
else if (e.isFile()) {
|
|
454
533
|
const ext = path.extname(e.name).toLowerCase();
|
|
455
|
-
if (JS_EXTS.has(ext) || PY_EXTS.has(ext) || JAVA_EXTS.has(ext) || GO_EXTS.has(ext)
|
|
534
|
+
if (JS_EXTS.has(ext) || PY_EXTS.has(ext) || JAVA_EXTS.has(ext) || GO_EXTS.has(ext)
|
|
535
|
+
|| RS_EXTS.has(ext) || KT_EXTS.has(ext) || SCALA_EXTS.has(ext)) out.push(full);
|
|
456
536
|
}
|
|
457
537
|
}
|
|
458
538
|
}
|
package/src/mcp/server.js
CHANGED