sigmap 8.32.0 → 8.33.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 +317 -293
- package/CHANGELOG.md +27 -0
- package/README.md +12 -12
- package/gen-context.js +180 -170
- package/llms-full.txt +8 -8
- package/llms.txt +6 -6
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/config/loader.js +53 -3
- package/src/eval/analyzer.js +5 -40
- package/src/extractors/dispatch.js +16 -2
- package/src/extractors/markdown.js +6 -1
- package/src/extractors/properties.js +6 -1
- package/src/extractors/r.js +4 -8
- package/src/extractors/toml.js +6 -1
- package/src/extractors/typescript_react.js +6 -1
- package/src/map/route-table.js +46 -9
- package/src/mcp/server.js +1 -1
- package/src/extractors/vue.js +0 -86
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,32 @@ Format: [Semantic Versioning](https://semver.org/)
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## [8.33.0] — 2026-09-13
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- The generated artifact now **says when the token budget left files out**. The `[sigmap] budget: dropped N file(s)` warning only ever went to stderr, which an agent reading the file never sees — on flask that meant 25 of 51 files present with no indication the other 26 existed, indistinguishable from a 25-file repo. `applyTokenBudget` attaches a non-enumerable summary to the array it returns, so all four call sites (full, per-module, hot-cold) gain the footer without a signature change. The wording states that the omitted files are still in the retrieval index and reachable via `sigmap ask`, and a test asserts it does not overstate the loss — replacing a misleading silence with a misleading warning would be no improvement (#587, PR #599)
|
|
17
|
+
- A test fixture for **every** language extractor, plus a guard that fails when one is missing (#588, PR #595). The 8 languages named in the issue were only part of it: the guard immediately found a ninth, `typescript_react` — `.tsx`, every React component, and the extractor that shipped a silent truncation cap in v8.32.0. It also caught a stale `test/expected/vue.txt` that outlived the `vue.js` deleted in v8.32.1, which had been making `--diagnose-extractors` print a silent `SKIP`. That diagnostic went from 21 passing with an unnoticed SKIP to 32 passing with none
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- NestJS route paths now compose the `@Controller` prefix. `@Controller('cats')` + `@Get(':id')` emitted `:id` rather than `/cats/:id`, so the route pseudo-signature matched nothing a user would ask about — defeating the purpose of the feature, which exists so a route-worded query can reach a controller whose signatures never mention the path. The prefix is attributed per controller rather than per file, since a file may declare several. The other six claimed frameworks were verified unchanged (#585, PR #598)
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- Extractor resolution has **one source of truth**. Three places decided which extractor module to load and two had drifted: `src/eval/analyzer.js` carried a dead duplicate `.vue` key, and the `--diagnose-extractors` map still pointed at `vue.js` after that module was deleted — which is how an unreachable extractor survived unnoticed in the first place. The hand-maintained copy was also incomplete, with no `.gd` entry, so gdscript was never actually diagnosed despite having both a fixture and recorded expected output. `language-detector.js` and `dashboard.js` are deliberately **not** merged in: they map `.tsx` for language *statistics* and display *labels*, not resolution, and a test pins that distinction so a future dedup cannot quietly break language counts (#591, PR #597)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## [8.32.1] — 2026-09-13
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
- **Retraction of a v8.32.0 claim.** That release stated "every extractor now discloses what a ceiling dropped". It did not. `vue.js` was registered but unreachable (`.vue` dispatches to `vue_sfc`), so disclosure was added to dead code while the live `.vue` handler kept truncating silently; `.tsx` (every React component), `.properties`, `.toml` and `.md` still cut output with a bare `slice()`; and `r.js` called `capWithNotice` but eight inner caps stopped collection at the ceiling so it never fired — and when forced to, reported `+1 more` where 50 signatures were hidden. All fixed, ceilings unchanged, with a test that fails against the pre-fix extractors. The root cause was that three of those languages have no test fixture (#582, #583, #584, #588)
|
|
32
|
+
- The walk depth hid most code in JVM package layouts: `maxDepth: 6` indexed **6 of 47** Java files on spring-petclinic, because Java puts one directory per package segment. #561 had already raised the *graph* walk to 12, so extraction was the shallower half of an inconsistent pair. Depth now rises to 12 only for JVM layouts; an explicit `maxDepth` always wins. Gated JVM corpus 16.4% → 23.0% (#590)
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
- Published retrieval hit@5 moves 81.1% → 78.9%. One of 18 repos accounts for it: spring-petclinic 100% → 60%, previously measured against an index holding 6 of 47 Java files. The leak-free `mined` corpus is flat and the leak-free `jvm` corpus improves, so the prior figure was inflated by under-indexing rather than this being a ranking regression. The two affected tasks are tracked as a ranking weakness the missing files were concealing (#592)
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
13
39
|
## [8.32.0] — 2026-09-12
|
|
14
40
|
|
|
15
41
|
### Added
|
|
@@ -18,6 +44,7 @@ Format: [Semantic Versioning](https://semver.org/)
|
|
|
18
44
|
|
|
19
45
|
### Fixed
|
|
20
46
|
- Every extractor now **discloses what a ceiling dropped** instead of truncating silently. 20 extractors gained `… +N more` markers on their member and per-file caps (23 now carry them in total). The ceilings themselves are unchanged — raising them is a separate, measured decision (#578, #576)
|
|
47
|
+
> **Correction (2026-09-13):** "every extractor" was wrong as published. `vue.js` was dead code — `.vue` dispatches to `vue_sfc` — so one of the 20 was unreachable; four reachable extractors (`.tsx`, `.properties`, `.toml`, `.md`) still truncated silently; and `r.js`'s disclosure was defeated by eight inner caps. Corrected in #589 (#582, #583, #584).
|
|
21
48
|
- The retrieval gate reused a gitignored index, so a "regression" could be pure staleness — this produced three separate false investigations, including one re-baseline. It now regenerates every index it scores, including one per JVM repo driven from `benchmarks/config-overrides.json` rather than a hand-written config (#579)
|
|
22
49
|
- The `sigmap lines` CLI example nested a fenced block inside a text fence; the inner fence closed early and a brace expression became a Vue interpolation. This failed the Pages build *after* v8.31.0 was tagged and published (#574)
|
|
23
50
|
|
package/README.md
CHANGED
|
@@ -59,12 +59,12 @@ That map is exactly what agentic grep is worst at: reproducible, auditable conte
|
|
|
59
59
|
|
|
60
60
|
**Proof it pays off** (full benchmark below):
|
|
61
61
|
<!--SM:whyMetrics-->
|
|
62
|
-
- **
|
|
62
|
+
- **78.9% hit@5** — right file in top 5 results (vs 44.0% single-shot grep baseline — 1.73× lift)
|
|
63
63
|
- **96.8% token reduction** — average across 21 real repos
|
|
64
|
-
- **
|
|
65
|
-
- **1.
|
|
64
|
+
- **62.9% task-success proxy** — modeled from retrieval tiers, not measured LLM sessions
|
|
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-->32<!--/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
|
|
@@ -98,7 +98,7 @@ sigmap verify answer.md --report # standalone red/amber/green HTML report
|
|
|
98
98
|
| Without SigMap | With SigMap |
|
|
99
99
|
|---|---|
|
|
100
100
|
| ❌ Non-reproducible agent guesses | ✅ Deterministic map — same input, same output, every time |
|
|
101
|
-
| ❌ "Trust me" AI answers | ✅ Grounded — right file in context <!--SM:hitWhole-->
|
|
101
|
+
| ❌ "Trust me" AI answers | ✅ Grounded — right file in context <!--SM:hitWhole-->79%<!--/SM:hitWhole--> of the time, every symbol on a real line anchor |
|
|
102
102
|
| ❌ Embeddings / vector DB required | ✅ Zero deps, no infra, fully offline |
|
|
103
103
|
|
|
104
104
|
---
|
|
@@ -122,13 +122,13 @@ Ask → Rank → Context → Validate → Judge → Learn
|
|
|
122
122
|
|
|
123
123
|
<!--SM:benchmarkBlock-->
|
|
124
124
|
```
|
|
125
|
-
Benchmark : sigmap-v8.
|
|
126
|
-
Date : 2026-09-
|
|
125
|
+
Benchmark : sigmap-v8.33-main (21 repositories, including R language)
|
|
126
|
+
Date : 2026-09-13
|
|
127
127
|
|
|
128
|
-
Hit@5 :
|
|
128
|
+
Hit@5 : 78.9% (grep-agent baseline 44.0% — 1.73× lift)
|
|
129
129
|
Token reduction: 96.8% (across 21 repos)
|
|
130
|
-
Prompt reduction :
|
|
131
|
-
Task success :
|
|
130
|
+
Prompt reduction : 44.4% (2.84 → 1.58 prompts per task, modeled)
|
|
131
|
+
Task success : 62.9% (proxy — modeled from retrieval tiers)
|
|
132
132
|
Repos tested : 21 (JavaScript, Python, Go, Rust, Java, R, C++, C#, Dart, Swift, Ruby, PHP, Scala, Kotlin, and more)
|
|
133
133
|
```
|
|
134
134
|
<!--/SM:benchmarkBlock-->
|
|
@@ -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-->32<!--/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-->32<!--/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
|
@@ -1790,6 +1790,55 @@ __factories["./src/config/loader"] = function(module, exports) {
|
|
|
1790
1790
|
});
|
|
1791
1791
|
}
|
|
1792
1792
|
|
|
1793
|
+
/**
|
|
1794
|
+
* Directory depth needed to reach source under a JVM package layout.
|
|
1795
|
+
*
|
|
1796
|
+
* `maxDepth: 6` is right for the JS/Python-shaped trees it was tuned on, but a
|
|
1797
|
+
* JVM project puts one directory per package segment — real code in
|
|
1798
|
+
* `src/main/java/com/company/project/module/Class.java` sits 8-10 levels down.
|
|
1799
|
+
* At depth 6 only the top-level package was indexed: on spring-petclinic, 6 of
|
|
1800
|
+
* 47 Java files (#590).
|
|
1801
|
+
*
|
|
1802
|
+
* #561 already raised the dependency-graph walk to 12 for exactly this reason,
|
|
1803
|
+
* so extraction was the shallower half of an inconsistent pair.
|
|
1804
|
+
*
|
|
1805
|
+
* Deepening globally is not free — it adds candidates to every repo and cost
|
|
1806
|
+
* 2.2pp on the (filename-leaky) 105-task matrix corpus while the unbiased
|
|
1807
|
+
* `mined` corpus stayed flat. So the depth is raised only where the layout
|
|
1808
|
+
* demands it, leaving non-JVM repos byte-identical.
|
|
1809
|
+
*
|
|
1810
|
+
* @param {string} cwd
|
|
1811
|
+
* @returns {boolean} true when the repo looks like a Maven/Gradle/sbt project
|
|
1812
|
+
*/
|
|
1813
|
+
function _isJvmLayout(cwd) {
|
|
1814
|
+
const MARKERS = ['pom.xml', 'build.gradle', 'build.gradle.kts', 'build.sbt', 'settings.gradle', 'settings.gradle.kts'];
|
|
1815
|
+
for (const m of MARKERS) {
|
|
1816
|
+
try { if (fs.existsSync(path.join(cwd, m))) return true; } catch { /* unreadable cwd */ }
|
|
1817
|
+
}
|
|
1818
|
+
for (const d of ['src/main/java', 'src/main/kotlin', 'src/main/scala']) {
|
|
1819
|
+
try { if (fs.existsSync(path.join(cwd, d))) return true; } catch { /* ignore */ }
|
|
1820
|
+
}
|
|
1821
|
+
return false;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
/** Walk depth for a JVM package layout — matches the graph walk from #561. */
|
|
1825
|
+
const JVM_MAX_DEPTH = 12;
|
|
1826
|
+
|
|
1827
|
+
/**
|
|
1828
|
+
* Raise `maxDepth` to the JVM depth when the layout needs it (#590).
|
|
1829
|
+
* An explicit user value always wins, including a deliberately shallow one.
|
|
1830
|
+
* @param {object} cfg resolved config (mutated and returned)
|
|
1831
|
+
* @param {string} cwd
|
|
1832
|
+
* @param {boolean} userSetDepth
|
|
1833
|
+
* @returns {object} cfg
|
|
1834
|
+
*/
|
|
1835
|
+
function _applyJvmDepth(cfg, cwd, userSetDepth) {
|
|
1836
|
+
if (!userSetDepth && cfg.maxDepth < JVM_MAX_DEPTH && _isJvmLayout(cwd)) {
|
|
1837
|
+
cfg.maxDepth = JVM_MAX_DEPTH;
|
|
1838
|
+
}
|
|
1839
|
+
return cfg;
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1793
1842
|
/**
|
|
1794
1843
|
* Load and merge configuration for a given working directory.
|
|
1795
1844
|
*
|
|
@@ -1802,7 +1851,7 @@ __factories["./src/config/loader"] = function(module, exports) {
|
|
|
1802
1851
|
const cfg = deepClone(DEFAULTS);
|
|
1803
1852
|
const detected = detectAutoSrcDirs(cwd, cfg.exclude);
|
|
1804
1853
|
if (detected.length > 0) cfg.srcDirs = detected;
|
|
1805
|
-
return cfg;
|
|
1854
|
+
return _applyJvmDepth(cfg, cwd, false);
|
|
1806
1855
|
}
|
|
1807
1856
|
|
|
1808
1857
|
let userConfig;
|
|
@@ -1814,7 +1863,7 @@ __factories["./src/config/loader"] = function(module, exports) {
|
|
|
1814
1863
|
const cfg = deepClone(DEFAULTS);
|
|
1815
1864
|
const detected = detectAutoSrcDirs(cwd, cfg.exclude);
|
|
1816
1865
|
if (detected.length > 0) cfg.srcDirs = detected;
|
|
1817
|
-
return cfg;
|
|
1866
|
+
return _applyJvmDepth(cfg, cwd, false);
|
|
1818
1867
|
}
|
|
1819
1868
|
|
|
1820
1869
|
// Warn on unknown keys (helps catch typos)
|
|
@@ -1866,7 +1915,8 @@ __factories["./src/config/loader"] = function(module, exports) {
|
|
|
1866
1915
|
} else if (Array.isArray(merged.adapters) && !userConfig.outputs) {
|
|
1867
1916
|
merged.outputs = merged.adapters.filter((a) => ['copilot','claude','cursor','windsurf'].includes(a));
|
|
1868
1917
|
}
|
|
1869
|
-
|
|
1918
|
+
|
|
1919
|
+
return _applyJvmDepth(merged, cwd, userConfig.maxDepth !== undefined);
|
|
1870
1920
|
}
|
|
1871
1921
|
|
|
1872
1922
|
function deepClone(obj) {
|
|
@@ -4101,48 +4151,13 @@ __factories["./src/eval/analyzer"] = function(module, exports) {
|
|
|
4101
4151
|
const path = require('path');
|
|
4102
4152
|
|
|
4103
4153
|
// Extension → extractor name (mirrors EXT_MAP in gen-context.js)
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
'.java': 'java',
|
|
4109
|
-
'.kt': 'kotlin', '.kts': 'kotlin',
|
|
4110
|
-
'.go': 'go',
|
|
4111
|
-
'.rs': 'rust',
|
|
4112
|
-
'.cs': 'csharp',
|
|
4113
|
-
'.cpp': 'cpp', '.c': 'cpp', '.h': 'cpp', '.hpp': 'cpp', '.cc': 'cpp',
|
|
4114
|
-
'.rb': 'ruby', '.rake': 'ruby',
|
|
4115
|
-
'.php': 'php',
|
|
4116
|
-
'.swift': 'swift',
|
|
4117
|
-
'.dart': 'dart',
|
|
4118
|
-
'.scala': 'scala', '.sc': 'scala',
|
|
4119
|
-
'.gd': 'gdscript',
|
|
4120
|
-
'.r': 'r', '.R': 'r',
|
|
4121
|
-
'.vue': 'vue',
|
|
4122
|
-
'.svelte': 'svelte',
|
|
4123
|
-
'.html': 'html', '.htm': 'html',
|
|
4124
|
-
'.css': 'css', '.scss': 'css', '.sass': 'css', '.less': 'css',
|
|
4125
|
-
'.yml': 'yaml', '.yaml': 'yaml',
|
|
4126
|
-
'.sh': 'shell', '.bash': 'shell', '.zsh': 'shell', '.fish': 'shell',
|
|
4127
|
-
'.toml': 'toml',
|
|
4128
|
-
'.properties': 'properties',
|
|
4129
|
-
'.xml': 'xml',
|
|
4130
|
-
'.md': 'markdown',
|
|
4131
|
-
// Phase C specialized extractors
|
|
4132
|
-
'.tsx': 'typescript_react',
|
|
4133
|
-
'.vue': 'vue_sfc',
|
|
4134
|
-
};
|
|
4135
|
-
|
|
4136
|
-
function isDockerfile(name) {
|
|
4137
|
-
return name === 'Dockerfile' || name.startsWith('Dockerfile.');
|
|
4138
|
-
}
|
|
4154
|
+
// Extractor resolution goes through the dispatcher — the single source of
|
|
4155
|
+
// truth (#591). This file previously kept its own copy, which had drifted to
|
|
4156
|
+
// a dead duplicate `.vue` key.
|
|
4157
|
+
const { langFor } = __require('./src/extractors/dispatch');
|
|
4139
4158
|
|
|
4140
4159
|
function getExtractorName(filePath) {
|
|
4141
|
-
|
|
4142
|
-
const ext = path.extname(base).toLowerCase();
|
|
4143
|
-
if (EXT_MAP[ext]) return EXT_MAP[ext];
|
|
4144
|
-
if (isDockerfile(base)) return 'dockerfile';
|
|
4145
|
-
return null;
|
|
4160
|
+
return langFor(filePath);
|
|
4146
4161
|
}
|
|
4147
4162
|
|
|
4148
4163
|
/** Rough token estimate: chars / 4 */
|
|
@@ -5922,7 +5937,6 @@ __factories["./src/extractors/dispatch"] = function(module, exports) {
|
|
|
5922
5937
|
scala: __require('./src/extractors/scala'),
|
|
5923
5938
|
gdscript: __require('./src/extractors/gdscript'),
|
|
5924
5939
|
r: __require('./src/extractors/r'),
|
|
5925
|
-
vue: __require('./src/extractors/vue'),
|
|
5926
5940
|
vue_sfc: __require('./src/extractors/vue_sfc'),
|
|
5927
5941
|
svelte: __require('./src/extractors/svelte'),
|
|
5928
5942
|
html: __require('./src/extractors/html'),
|
|
@@ -5941,6 +5955,21 @@ __factories["./src/extractors/dispatch"] = function(module, exports) {
|
|
|
5941
5955
|
generic: __require('./src/extractors/generic'),
|
|
5942
5956
|
};
|
|
5943
5957
|
|
|
5958
|
+
/**
|
|
5959
|
+
* Extension → extractor module name. **The single source of truth for
|
|
5960
|
+
* extractor resolution** (#591).
|
|
5961
|
+
*
|
|
5962
|
+
* Anything that decides *which extractor module to load* must go through
|
|
5963
|
+
* `langFor` rather than declaring its own copy. Three copies existed and two
|
|
5964
|
+
* had drifted: `src/eval/analyzer.js` carried a dead duplicate `.vue` key, and
|
|
5965
|
+
* the `--diagnose-extractors` map pointed at `vue.js` after that module was
|
|
5966
|
+
* deleted — which is how an unreachable extractor survived unnoticed (#582).
|
|
5967
|
+
*
|
|
5968
|
+
* Not every extension map in the codebase belongs here. `language-detector.js`
|
|
5969
|
+
* maps `.tsx → typescript` for language *statistics*, and `dashboard.js` keeps
|
|
5970
|
+
* short display *labels*. Both are correct for their purpose and deliberately
|
|
5971
|
+
* differ from resolution — folding them in would miscount languages.
|
|
5972
|
+
*/
|
|
5944
5973
|
const EXT_MAP = {
|
|
5945
5974
|
'.ts': 'typescript', '.tsx': 'typescript_react',
|
|
5946
5975
|
'.js': 'javascript', '.jsx': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript',
|
|
@@ -6001,7 +6030,7 @@ __factories["./src/extractors/dispatch"] = function(module, exports) {
|
|
|
6001
6030
|
}
|
|
6002
6031
|
}
|
|
6003
6032
|
|
|
6004
|
-
module.exports = { extractFile, langFor };
|
|
6033
|
+
module.exports = { extractFile, langFor, EXT_MAP };
|
|
6005
6034
|
|
|
6006
6035
|
};
|
|
6007
6036
|
|
|
@@ -6986,6 +7015,11 @@ __factories["./src/extractors/line-anchor"] = function(module, exports) {
|
|
|
6986
7015
|
// ── ./src/extractors/markdown ──
|
|
6987
7016
|
__factories["./src/extractors/markdown"] = function(module, exports) {
|
|
6988
7017
|
|
|
7018
|
+
const { capWithNotice } = __require('./src/util/truncate');
|
|
7019
|
+
|
|
7020
|
+
// Ceiling discloses what it drops rather than truncating silently (#583).
|
|
7021
|
+
const PER_FILE_LIMIT = 40;
|
|
7022
|
+
|
|
6989
7023
|
/**
|
|
6990
7024
|
* Lightweight markdown technical indexer.
|
|
6991
7025
|
* Captures headings and fenced code block language hints only.
|
|
@@ -7010,7 +7044,7 @@ __factories["./src/extractors/markdown"] = function(module, exports) {
|
|
|
7010
7044
|
sigs.push(`code-fence ${lang}`);
|
|
7011
7045
|
}
|
|
7012
7046
|
|
|
7013
|
-
return Array.from(new Set(sigs))
|
|
7047
|
+
return capWithNotice(Array.from(new Set(sigs)), PER_FILE_LIMIT, 'headings');
|
|
7014
7048
|
}
|
|
7015
7049
|
|
|
7016
7050
|
module.exports = { extract };
|
|
@@ -7341,6 +7375,11 @@ __factories["./src/extractors/prdiff"] = function(module, exports) {
|
|
|
7341
7375
|
// ── ./src/extractors/properties ──
|
|
7342
7376
|
__factories["./src/extractors/properties"] = function(module, exports) {
|
|
7343
7377
|
|
|
7378
|
+
const { capWithNotice } = __require('./src/util/truncate');
|
|
7379
|
+
|
|
7380
|
+
// Ceiling discloses what it drops rather than truncating silently (#583).
|
|
7381
|
+
const PER_FILE_LIMIT = 50;
|
|
7382
|
+
|
|
7344
7383
|
/**
|
|
7345
7384
|
* Extract signatures from .properties configuration files.
|
|
7346
7385
|
* Captures key names, grouped by prefixes where possible.
|
|
@@ -7372,7 +7411,7 @@ __factories["./src/extractors/properties"] = function(module, exports) {
|
|
|
7372
7411
|
sigs.push(`key ${key}`);
|
|
7373
7412
|
}
|
|
7374
7413
|
|
|
7375
|
-
return Array.from(new Set(sigs))
|
|
7414
|
+
return capWithNotice(Array.from(new Set(sigs)), PER_FILE_LIMIT, 'keys');
|
|
7376
7415
|
}
|
|
7377
7416
|
|
|
7378
7417
|
module.exports = { extract };
|
|
@@ -7845,7 +7884,7 @@ __factories["./src/extractors/r"] = function(module, exports) {
|
|
|
7845
7884
|
// ClassName <- R6::R6Class(...)
|
|
7846
7885
|
const r6Re = /([\w.]+)\s*(?:<<-|<-|=)\s*(?:R6::)?R6Class\s*\(/g;
|
|
7847
7886
|
let m;
|
|
7848
|
-
while ((m = r6Re.exec(stripped)) !== null
|
|
7887
|
+
while ((m = r6Re.exec(stripped)) !== null) {
|
|
7849
7888
|
const name = m[1];
|
|
7850
7889
|
if (name.startsWith('.')) continue;
|
|
7851
7890
|
const openIdx = r6Re.lastIndex - 1;
|
|
@@ -7856,7 +7895,6 @@ __factories["./src/extractors/r"] = function(module, exports) {
|
|
|
7856
7895
|
sigs.push(`${name} <- R6Class("${classNameLit}")` + applyHint(docHints, name));
|
|
7857
7896
|
for (const memberSig of extractListMethods(body, 8)) {
|
|
7858
7897
|
sigs.push(' ' + memberSig);
|
|
7859
|
-
if (sigs.length >= 30) break;
|
|
7860
7898
|
}
|
|
7861
7899
|
consumedRanges.push([m.index, closeIdx]);
|
|
7862
7900
|
r6Re.lastIndex = closeIdx;
|
|
@@ -7866,7 +7904,7 @@ __factories["./src/extractors/r"] = function(module, exports) {
|
|
|
7866
7904
|
// ClassName <- new_class("ClassName", properties = list(...))
|
|
7867
7905
|
const s7Classes = new Set();
|
|
7868
7906
|
const s7Re = /([\w.]+)\s*(?:<<-|<-|=)\s*(?:S7::)?new_class\s*\(/g;
|
|
7869
|
-
while ((m = s7Re.exec(stripped)) !== null
|
|
7907
|
+
while ((m = s7Re.exec(stripped)) !== null) {
|
|
7870
7908
|
const name = m[1];
|
|
7871
7909
|
if (name.startsWith('.')) continue;
|
|
7872
7910
|
const openIdx = s7Re.lastIndex - 1;
|
|
@@ -7883,7 +7921,7 @@ __factories["./src/extractors/r"] = function(module, exports) {
|
|
|
7883
7921
|
|
|
7884
7922
|
// S7 method dispatch: `method(generic, ClassName) <- function(args)`
|
|
7885
7923
|
const s7MethodRe = /^[ \t]*method\s*\(\s*([\w.]+)\s*,\s*([\w.]+)\s*\)\s*(?:<<-|<-|=)\s*function\s*\(/gm;
|
|
7886
|
-
while ((m = s7MethodRe.exec(stripped)) !== null
|
|
7924
|
+
while ((m = s7MethodRe.exec(stripped)) !== null) {
|
|
7887
7925
|
if (!s7Classes.has(m[2])) continue;
|
|
7888
7926
|
const argsStart = s7MethodRe.lastIndex - 1;
|
|
7889
7927
|
const args = readBalancedParens(stripped, argsStart);
|
|
@@ -7896,7 +7934,7 @@ __factories["./src/extractors/r"] = function(module, exports) {
|
|
|
7896
7934
|
// Skip matches whose position falls inside an R6/S7 class body — those have
|
|
7897
7935
|
// already been emitted as indented members.
|
|
7898
7936
|
const funcRe = /^(?:[ \t]*)([\w.]+)\s*(?:<<-|<-|=)\s*function\s*\(/gm;
|
|
7899
|
-
while ((m = funcRe.exec(stripped)) !== null
|
|
7937
|
+
while ((m = funcRe.exec(stripped)) !== null) {
|
|
7900
7938
|
const name = m[1];
|
|
7901
7939
|
if (name.startsWith('.')) continue;
|
|
7902
7940
|
if (inAnyRange(m.index, consumedRanges)) continue;
|
|
@@ -7908,15 +7946,12 @@ __factories["./src/extractors/r"] = function(module, exports) {
|
|
|
7908
7946
|
|
|
7909
7947
|
// ── S4 ────────────────────────────────────────────────────────────────────
|
|
7910
7948
|
for (const sm of stripped.matchAll(/^[ \t]*setGeneric\s*\(\s*["']([\w.]+)["']/gm)) {
|
|
7911
|
-
if (sigs.length >= 30) break;
|
|
7912
7949
|
sigs.push(`setGeneric("${sm[1]}")`);
|
|
7913
7950
|
}
|
|
7914
7951
|
for (const sm of stripped.matchAll(/^[ \t]*setMethod\s*\(\s*["']([\w.]+)["']\s*,\s*["']([\w.]+)["']/gm)) {
|
|
7915
|
-
if (sigs.length >= 30) break;
|
|
7916
7952
|
sigs.push(`setMethod("${sm[1]}", "${sm[2]}")`);
|
|
7917
7953
|
}
|
|
7918
7954
|
for (const sm of stripped.matchAll(/^[ \t]*setClass\s*\(\s*["']([\w.]+)["']/gm)) {
|
|
7919
|
-
if (sigs.length >= 30) break;
|
|
7920
7955
|
sigs.push(`setClass("${sm[1]}")`);
|
|
7921
7956
|
}
|
|
7922
7957
|
|
|
@@ -8925,6 +8960,11 @@ __factories["./src/extractors/todos"] = function(module, exports) {
|
|
|
8925
8960
|
// ── ./src/extractors/toml ──
|
|
8926
8961
|
__factories["./src/extractors/toml"] = function(module, exports) {
|
|
8927
8962
|
|
|
8963
|
+
const { capWithNotice } = __require('./src/util/truncate');
|
|
8964
|
+
|
|
8965
|
+
// Ceiling discloses what it drops rather than truncating silently (#583).
|
|
8966
|
+
const PER_FILE_LIMIT = 40;
|
|
8967
|
+
|
|
8928
8968
|
/**
|
|
8929
8969
|
* Extract signatures from TOML configuration files.
|
|
8930
8970
|
* Focuses on section/table names and high-value keys.
|
|
@@ -8961,7 +9001,7 @@ __factories["./src/extractors/toml"] = function(module, exports) {
|
|
|
8961
9001
|
}
|
|
8962
9002
|
}
|
|
8963
9003
|
|
|
8964
|
-
return Array.from(new Set(sigs))
|
|
9004
|
+
return capWithNotice(Array.from(new Set(sigs)), PER_FILE_LIMIT, 'entries');
|
|
8965
9005
|
}
|
|
8966
9006
|
|
|
8967
9007
|
module.exports = { extract };
|
|
@@ -9301,6 +9341,11 @@ __factories["./src/extractors/typescript"] = function(module, exports) {
|
|
|
9301
9341
|
// ── ./src/extractors/typescript_react ──
|
|
9302
9342
|
__factories["./src/extractors/typescript_react"] = function(module, exports) {
|
|
9303
9343
|
|
|
9344
|
+
const { capWithNotice } = __require('./src/util/truncate');
|
|
9345
|
+
|
|
9346
|
+
// Ceiling discloses what it drops rather than truncating silently (#583).
|
|
9347
|
+
const PER_FILE_LIMIT = 50;
|
|
9348
|
+
|
|
9304
9349
|
/**
|
|
9305
9350
|
* Extract React component signatures from .tsx files.
|
|
9306
9351
|
* Captures component props interfaces, hooks usage, and exports.
|
|
@@ -9355,97 +9400,7 @@ __factories["./src/extractors/typescript_react"] = function(module, exports) {
|
|
|
9355
9400
|
sigs.push(`handler on${h}`);
|
|
9356
9401
|
}
|
|
9357
9402
|
|
|
9358
|
-
return Array.from(new Set(sigs))
|
|
9359
|
-
}
|
|
9360
|
-
|
|
9361
|
-
module.exports = { extract };
|
|
9362
|
-
|
|
9363
|
-
};
|
|
9364
|
-
|
|
9365
|
-
// ── ./src/extractors/vue ──
|
|
9366
|
-
__factories["./src/extractors/vue"] = function(module, exports) {
|
|
9367
|
-
|
|
9368
|
-
const { capWithNotice } = __require('./src/util/truncate');
|
|
9369
|
-
|
|
9370
|
-
// Ceiling sits above the default `maxSigsPerFile` so the configured budget
|
|
9371
|
-
// governs output rather than a literal buried here, and omissions are disclosed (#576).
|
|
9372
|
-
const PER_FILE_LIMIT = 25;
|
|
9373
|
-
|
|
9374
|
-
/**
|
|
9375
|
-
* Extract signatures from Vue single-file components.
|
|
9376
|
-
* @param {string} src - Raw file content
|
|
9377
|
-
* @returns {string[]} Array of signature strings
|
|
9378
|
-
*/
|
|
9379
|
-
function extract(src) {
|
|
9380
|
-
if (!src || typeof src !== 'string') return [];
|
|
9381
|
-
const sigs = [];
|
|
9382
|
-
|
|
9383
|
-
// Extract component name from filename hint if present or defineComponent
|
|
9384
|
-
const nameMatch = src.match(/name\s*:\s*['"](\w+)['"]/);
|
|
9385
|
-
if (nameMatch) sigs.push(`component ${nameMatch[1]}`);
|
|
9386
|
-
|
|
9387
|
-
// Extract <script> block
|
|
9388
|
-
const scriptMatch = src.match(/<script(?:\s[^>]*)?>(?:\s*)([\s\S]*?)<\/script>/i);
|
|
9389
|
-
if (!scriptMatch) return sigs;
|
|
9390
|
-
|
|
9391
|
-
const script = scriptMatch[1]
|
|
9392
|
-
.replace(/\/\/.*$/gm, '')
|
|
9393
|
-
.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
9394
|
-
|
|
9395
|
-
// Props
|
|
9396
|
-
const propsMatch = script.match(/props\s*:\s*(\{[\s\S]*?\})/);
|
|
9397
|
-
if (propsMatch) {
|
|
9398
|
-
const propNames = [];
|
|
9399
|
-
for (const m of propsMatch[1].matchAll(/^\s+(\w+)\s*:/gm)) {
|
|
9400
|
-
propNames.push(m[1]);
|
|
9401
|
-
}
|
|
9402
|
-
if (propNames.length > 0) sigs.push(`props: [${propNames.join(', ')}]`);
|
|
9403
|
-
}
|
|
9404
|
-
|
|
9405
|
-
// Methods in options API
|
|
9406
|
-
const methodsMatch = script.match(/methods\s*:\s*\{([\s\S]*?)\},?\s*(?:computed|watch|mounted|created|data|\})/);
|
|
9407
|
-
if (methodsMatch) {
|
|
9408
|
-
for (const m of methodsMatch[1].matchAll(/^\s+(?:async\s+)?(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^{=\n]+))?/gm)) {
|
|
9409
|
-
if (m[1].startsWith('_')) continue;
|
|
9410
|
-
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
9411
|
-
const retStr = m[3] ? ` → ${normalizeType(m[3])}` : '';
|
|
9412
|
-
sigs.push(` ${asyncKw}${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
9413
|
-
}
|
|
9414
|
-
}
|
|
9415
|
-
|
|
9416
|
-
// Top-level functions in <script> (e.g., composition API helpers)
|
|
9417
|
-
for (const m of script.matchAll(/^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^{=\n]+))?/gm)) {
|
|
9418
|
-
if (m[1].startsWith('_')) continue;
|
|
9419
|
-
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
9420
|
-
const retStr = m[3] ? ` → ${normalizeType(m[3])}` : '';
|
|
9421
|
-
sigs.push(`${asyncKw}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
9422
|
-
}
|
|
9423
|
-
|
|
9424
|
-
// defineProps (Composition API)
|
|
9425
|
-
const definePropsMatch = script.match(/defineProps(?:<[^>]*>)?\s*\(\s*(\{[\s\S]*?\})\s*\)/);
|
|
9426
|
-
if (definePropsMatch) {
|
|
9427
|
-
const propNames = [];
|
|
9428
|
-
for (const m of definePropsMatch[1].matchAll(/^\s+(\w+)\s*:/gm)) {
|
|
9429
|
-
propNames.push(m[1]);
|
|
9430
|
-
}
|
|
9431
|
-
if (propNames.length > 0) sigs.push(`defineProps: [${propNames.join(', ')}]`);
|
|
9432
|
-
}
|
|
9433
|
-
|
|
9434
|
-
// Emits
|
|
9435
|
-
const emitsMatch = script.match(/(?:defineEmits|emits)\s*(?::\s*|\(\s*)(\[[\s\S]*?\])/);
|
|
9436
|
-
if (emitsMatch) sigs.push(`emits: ${emitsMatch[1].replace(/\s+/g, ' ')}`);
|
|
9437
|
-
|
|
9438
|
-
return capWithNotice(sigs, PER_FILE_LIMIT, 'signatures');
|
|
9439
|
-
}
|
|
9440
|
-
|
|
9441
|
-
function normalizeParams(params) {
|
|
9442
|
-
if (!params) return '';
|
|
9443
|
-
return params.trim().replace(/\s+/g, ' ');
|
|
9444
|
-
}
|
|
9445
|
-
|
|
9446
|
-
function normalizeType(type) {
|
|
9447
|
-
if (!type) return '';
|
|
9448
|
-
return type.trim().replace(/[;\s]+$/g, '').replace(/\s+/g, ' ').slice(0, 25);
|
|
9403
|
+
return capWithNotice(Array.from(new Set(sigs)), PER_FILE_LIMIT, 'signatures');
|
|
9449
9404
|
}
|
|
9450
9405
|
|
|
9451
9406
|
module.exports = { extract };
|
|
@@ -14479,6 +14434,45 @@ __factories["./src/map/route-table"] = function(module, exports) {
|
|
|
14479
14434
|
return /(^|\/)(gen-context|gen-project-map)\.js$/.test(normalized);
|
|
14480
14435
|
}
|
|
14481
14436
|
|
|
14437
|
+
/**
|
|
14438
|
+
* Byte offsets and prefixes of every `@Controller(...)` in a file.
|
|
14439
|
+
* A file may declare several controllers, so each route is attributed to the
|
|
14440
|
+
* nearest one above it rather than to a single file-wide prefix.
|
|
14441
|
+
* @param {string} content
|
|
14442
|
+
* @returns {Array<{index:number, prefix:string}>} ascending by index
|
|
14443
|
+
*/
|
|
14444
|
+
function nestControllerPrefixes(content) {
|
|
14445
|
+
const out = [];
|
|
14446
|
+
const re = /@Controller\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
|
|
14447
|
+
let m;
|
|
14448
|
+
while ((m = re.exec(content)) !== null) {
|
|
14449
|
+
out.push({ index: m.index, prefix: m[1] || '' });
|
|
14450
|
+
}
|
|
14451
|
+
return out;
|
|
14452
|
+
}
|
|
14453
|
+
|
|
14454
|
+
/** Prefix of the nearest `@Controller` above `index`, or '' when there is none. */
|
|
14455
|
+
function prefixBefore(controllers, index) {
|
|
14456
|
+
let prefix = '';
|
|
14457
|
+
for (const c of controllers) {
|
|
14458
|
+
if (c.index > index) break;
|
|
14459
|
+
prefix = c.prefix;
|
|
14460
|
+
}
|
|
14461
|
+
return prefix;
|
|
14462
|
+
}
|
|
14463
|
+
|
|
14464
|
+
/**
|
|
14465
|
+
* Join a controller prefix and a method path into one route path.
|
|
14466
|
+
* Either side may be empty, absent, or carry its own slashes.
|
|
14467
|
+
* @returns {string} always slash-prefixed; never a trailing slash except '/'
|
|
14468
|
+
*/
|
|
14469
|
+
function joinRoute(prefix, methodPath) {
|
|
14470
|
+
const parts = [prefix, methodPath]
|
|
14471
|
+
.map((p) => String(p || '').trim().replace(/^\/+|\/+$/g, ''))
|
|
14472
|
+
.filter(Boolean);
|
|
14473
|
+
return parts.length ? '/' + parts.join('/') : '/';
|
|
14474
|
+
}
|
|
14475
|
+
|
|
14482
14476
|
/**
|
|
14483
14477
|
* Structured route rows across the supported frameworks — the data behind
|
|
14484
14478
|
* `analyze`, exposed for retrieval surface-enrichment (#488).
|
|
@@ -14507,16 +14501,14 @@ __factories["./src/map/route-table"] = function(module, exports) {
|
|
|
14507
14501
|
routes.push({ method: m[1].toUpperCase(), path: m[2], file: rel });
|
|
14508
14502
|
}
|
|
14509
14503
|
|
|
14510
|
-
// NestJS
|
|
14511
|
-
|
|
14504
|
+
// NestJS: @Get(':id') / @Post() — composed with the enclosing
|
|
14505
|
+
// @Controller('prefix'). Without the prefix the emitted path matches
|
|
14506
|
+
// nothing real, which defeats the point of route pseudo-signatures (#585).
|
|
14507
|
+
const controllers = nestControllerPrefixes(content);
|
|
14508
|
+
const re2 = /@(Get|Post|Put|Patch|Delete|Head|Options|All)\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g;
|
|
14512
14509
|
while ((m = re2.exec(content)) !== null) {
|
|
14513
|
-
|
|
14514
|
-
|
|
14515
|
-
|
|
14516
|
-
// NestJS: @Get() with no path
|
|
14517
|
-
const re3 = /@(Get|Post|Put|Patch|Delete)\s*\(\s*\)/g;
|
|
14518
|
-
while ((m = re3.exec(content)) !== null) {
|
|
14519
|
-
routes.push({ method: m[1].toUpperCase(), path: '/', file: rel });
|
|
14510
|
+
const prefix = prefixBefore(controllers, m.index);
|
|
14511
|
+
routes.push({ method: m[1].toUpperCase(), path: joinRoute(prefix, m[2]), file: rel });
|
|
14520
14512
|
}
|
|
14521
14513
|
}
|
|
14522
14514
|
|
|
@@ -15814,7 +15806,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
|
|
|
15814
15806
|
|
|
15815
15807
|
const SERVER_INFO = {
|
|
15816
15808
|
name: 'sigmap',
|
|
15817
|
-
version: '8.
|
|
15809
|
+
version: '8.33.0',
|
|
15818
15810
|
description: 'SigMap MCP server — code signatures on demand',
|
|
15819
15811
|
};
|
|
15820
15812
|
|
|
@@ -22108,7 +22100,7 @@ function __tryGit(args, opts = {}) {
|
|
|
22108
22100
|
catch (_) { return ''; }
|
|
22109
22101
|
}
|
|
22110
22102
|
|
|
22111
|
-
const VERSION = '8.
|
|
22103
|
+
const VERSION = '8.33.0';
|
|
22112
22104
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
22113
22105
|
|
|
22114
22106
|
function requireSourceOrBundled(key) {
|
|
@@ -22558,6 +22550,17 @@ function applyTokenBudget(fileEntries, maxTokens) {
|
|
|
22558
22550
|
// Restore the original file order for stable output.
|
|
22559
22551
|
const kept = withPriority.filter((e) => finalByPath.has(e.filePath)).map((e) => finalByPath.get(e.filePath));
|
|
22560
22552
|
|
|
22553
|
+
// Record what was omitted so the artifact itself can say so (#587). The
|
|
22554
|
+
// stderr warning below is invisible to an agent that only reads the file:
|
|
22555
|
+
// 25 sections with no notice is indistinguishable from a 25-file repo.
|
|
22556
|
+
// Non-enumerable so every existing consumer still sees a plain array.
|
|
22557
|
+
if (verboseDropped.length > 0 || collapsedCount > 0) {
|
|
22558
|
+
Object.defineProperty(kept, '__omissions', {
|
|
22559
|
+
value: { dropped: verboseDropped.length, collapsed: collapsedCount, maxTokens },
|
|
22560
|
+
enumerable: false, writable: false, configurable: true,
|
|
22561
|
+
});
|
|
22562
|
+
}
|
|
22563
|
+
|
|
22561
22564
|
if (verboseDropped.length > 0 || collapsedCount > 0) {
|
|
22562
22565
|
const parts = [];
|
|
22563
22566
|
if (verboseDropped.length) parts.push(`dropped ${verboseDropped.length} file(s)`);
|
|
@@ -22886,6 +22889,20 @@ function formatOutput(fileEntries, cwd, routingEnabled, config, extras) {
|
|
|
22886
22889
|
}
|
|
22887
22890
|
}
|
|
22888
22891
|
|
|
22892
|
+
// Say what the budget left out — an omission the reader cannot see is the
|
|
22893
|
+
// same failure as an undisclosed truncation cap (#587, cf. #576).
|
|
22894
|
+
const omitted = fileEntries && fileEntries.__omissions;
|
|
22895
|
+
if (omitted && (omitted.dropped > 0 || omitted.collapsed > 0)) {
|
|
22896
|
+
const bits = [];
|
|
22897
|
+
if (omitted.dropped) bits.push(`${omitted.dropped} file(s) omitted`);
|
|
22898
|
+
if (omitted.collapsed) bits.push(`${omitted.collapsed} collapsed to anchors`);
|
|
22899
|
+
lines.push('');
|
|
22900
|
+
lines.push(`> **Not everything is here.** ${bits.join(', ')} to stay under the `
|
|
22901
|
+
+ `${omitted.maxTokens}-token budget (tests and configs go first). `
|
|
22902
|
+
+ 'The retrieval index still has them all — run `sigmap ask "<question>"` '
|
|
22903
|
+
+ 'to pull in anything missing.');
|
|
22904
|
+
}
|
|
22905
|
+
|
|
22889
22906
|
return lines.join('\n');
|
|
22890
22907
|
}
|
|
22891
22908
|
|
|
@@ -26875,23 +26892,16 @@ function main() {
|
|
|
26875
26892
|
process.exit(1);
|
|
26876
26893
|
}
|
|
26877
26894
|
|
|
26878
|
-
|
|
26879
|
-
|
|
26880
|
-
|
|
26881
|
-
|
|
26882
|
-
'.swift': 'swift', '.dart': 'dart', '.scala': 'scala',
|
|
26883
|
-
'.r': 'r', '.R': 'r',
|
|
26884
|
-
'.vue': 'vue', '.svelte': 'svelte', '.html': 'html',
|
|
26885
|
-
'.css': 'css', '.yml': 'yaml', '.sh': 'shell',
|
|
26886
|
-
};
|
|
26887
|
-
const SPECIAL = { 'Dockerfile': 'dockerfile' };
|
|
26895
|
+
// Resolution goes through the dispatcher — the single source of truth
|
|
26896
|
+
// (#591). This map was a third copy, and it still pointed at `vue.js`
|
|
26897
|
+
// after that module was deleted, which is how a dead extractor survived.
|
|
26898
|
+
const { langFor } = requireSourceOrBundled('./src/extractors/dispatch');
|
|
26888
26899
|
|
|
26889
26900
|
let passed = 0; let failed = 0;
|
|
26890
26901
|
const entries = fs.readdirSync(fixturesDir).sort();
|
|
26891
26902
|
|
|
26892
26903
|
for (const filename of entries) {
|
|
26893
|
-
const
|
|
26894
|
-
const lang = EXT_TO_LANG[ext] || SPECIAL[filename];
|
|
26904
|
+
const lang = langFor(filename);
|
|
26895
26905
|
if (!lang) continue;
|
|
26896
26906
|
|
|
26897
26907
|
const fixturePath = path.join(fixturesDir, filename);
|