sigmap 8.11.0 → 8.12.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 +32 -17
- package/CHANGELOG.md +9 -0
- package/README.md +1 -1
- package/gen-context.js +281 -2
- package/llms-full.txt +3 -2
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/mcp/server.js +1 -1
- package/src/wiki/generate.js +250 -0
package/AGENTS.md
CHANGED
|
@@ -33,11 +33,12 @@ Always run `sigmap ask` (or `sigmap --query`) before searching for files relevan
|
|
|
33
33
|
src/extractors/python_ast.py ← ast
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
## changes (last 5 commits —
|
|
36
|
+
## changes (last 5 commits — 0 seconds ago)
|
|
37
37
|
```
|
|
38
|
+
src/format/terse.js +splitAnchor +encodeTerseSig +encodeTerseSigs +_tokens
|
|
39
|
+
src/wiki/generate.js +_rel +_pct +_identity +_modules
|
|
38
40
|
src/conventions/extract.js ~classifyNaming ~scoreConvention
|
|
39
41
|
src/conventions/fix.js ~buildFixList
|
|
40
|
-
src/daemon/daemon.js +daemonDir +pidFile +logFile +isAlive
|
|
41
42
|
src/extractors/javascript.js ~extract ~extractClassMembers ~extractBlock ~buildReturnHints
|
|
42
43
|
src/extractors/typescript.js ~extract ~extractInterfaceMembers ~extractClassMembers ~extractBlock
|
|
43
44
|
src/graph/builder.js +probeJs +resolveJsPath +stripJsonc +loadAliasMap
|
|
@@ -179,6 +180,21 @@ code-fence ---
|
|
|
179
180
|
|
|
180
181
|
## src
|
|
181
182
|
|
|
183
|
+
### src/config/defaults.js
|
|
184
|
+
```
|
|
185
|
+
module.exports = { DEFAULTS } :161-161
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### src/format/terse.js
|
|
189
|
+
```
|
|
190
|
+
module.exports = { encodeTerseSig, encodeTerseSigs, measureTerse, splitAnchor } :86-86
|
|
191
|
+
function splitAnchor(sig) → { text: string, suffix: s :25-30
|
|
192
|
+
function encodeTerseSig(sig) → string :37-50
|
|
193
|
+
function encodeTerseSigs(sigs) → string[] :57-59
|
|
194
|
+
function _tokens(sigs) :62-64
|
|
195
|
+
function measureTerse(sigsList) → { beforeTokens: number, a :72-84
|
|
196
|
+
```
|
|
197
|
+
|
|
182
198
|
### src/mcp/server.js
|
|
183
199
|
```
|
|
184
200
|
module.exports = { start } :140-140
|
|
@@ -188,6 +204,20 @@ function dispatch(msg, cwd) :41-107
|
|
|
188
204
|
function start(cwd) :112-138
|
|
189
205
|
```
|
|
190
206
|
|
|
207
|
+
### src/wiki/generate.js
|
|
208
|
+
```
|
|
209
|
+
module.exports = { buildWiki, renderWikiMarkdown } :250-250
|
|
210
|
+
function _rel(cwd, f) :22-24
|
|
211
|
+
function _pct(fraction) :26-28
|
|
212
|
+
function _identity(cwd) :31-37
|
|
213
|
+
function _modules(index) :40-67
|
|
214
|
+
function _flow(cwd) :70-97
|
|
215
|
+
function _conventions(cwd, index) :100-117
|
|
216
|
+
function _health(cwd) :119-127
|
|
217
|
+
function buildWiki(cwd, opts = {}) → { data: object, markdown: :137-162
|
|
218
|
+
function renderWikiMarkdown(data, sigmapVersion) → string :171-248
|
|
219
|
+
```
|
|
220
|
+
|
|
191
221
|
### src/analysis/coverage-score.js
|
|
192
222
|
```
|
|
193
223
|
module.exports = { coverageScore, CODE_EXTS } :105-105
|
|
@@ -225,11 +255,6 @@ function getChangedFiles(files, cache) → { changed: string[], unch :71-88
|
|
|
225
255
|
function updateCacheEntries(cache, extracted) :96-103
|
|
226
256
|
```
|
|
227
257
|
|
|
228
|
-
### src/config/defaults.js
|
|
229
|
-
```
|
|
230
|
-
module.exports = { DEFAULTS } :161-161
|
|
231
|
-
```
|
|
232
|
-
|
|
233
258
|
### src/config/loader.js
|
|
234
259
|
```
|
|
235
260
|
module.exports = { loadConfig, loadBaseConfig } :313-313
|
|
@@ -904,16 +929,6 @@ function detectVersion(cwd) :13-19
|
|
|
904
929
|
function format(context, cwd, writtenFiles, sigmapVersion) :21-69
|
|
905
930
|
```
|
|
906
931
|
|
|
907
|
-
### src/format/terse.js
|
|
908
|
-
```
|
|
909
|
-
module.exports = { encodeTerseSig, encodeTerseSigs, measureTerse, splitAnchor } :86-86
|
|
910
|
-
function splitAnchor(sig) → { text: string, suffix: s :25-30
|
|
911
|
-
function encodeTerseSig(sig) → string :37-50
|
|
912
|
-
function encodeTerseSigs(sigs) → string[] :57-59
|
|
913
|
-
function _tokens(sigs) :62-64
|
|
914
|
-
function measureTerse(sigsList) → { beforeTokens: number, a :72-84
|
|
915
|
-
```
|
|
916
|
-
|
|
917
932
|
### src/format/usage-guidance.js
|
|
918
933
|
```
|
|
919
934
|
module.exports = { usageBlock } :28-28
|
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,15 @@ Format: [Semantic Versioning](https://semver.org/)
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## [8.12.0] — 2026-07-11
|
|
14
|
+
|
|
15
|
+
Minor release — **`sigmap wiki`** (D9, the final unstarted in-boundary item from the master plan §3.5 backlog). With this, every in-boundary initiative D1–D9 has shipped.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **`sigmap wiki` — deterministic architecture narrative (#465, PR #466):** writes `.context/WIKI.md` composed entirely from data SigMap already computes — overview (indexed files, modules, signature tokens, health grade), module rollup with key files, dependency flow (hub files, entry points, cycle count from the import graph), conventions summary, and navigation pointers. Template prose only: **no LLM, no network, no timestamps** — two consecutive runs on an unchanged repo are byte-identical (regression-tested). `--out <path>` overrides the target; `--json` emits the structured data instead. Graph paths are relativized against the builder's normalized base so hubs/entries render repo-relative on macOS tmpdirs.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
13
22
|
## [8.11.0] — 2026-07-11
|
|
14
23
|
|
|
15
24
|
Minor release — the **terse signature encoder** (D7 from the master plan), shipped under its measure-first gate: the public reduction number comes from a benchmark run on this repo, never borrowed from another tool's prose-compression claims.
|
package/README.md
CHANGED
|
@@ -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.12-main (21 repositories, including R language)
|
|
126
126
|
Date : 2026-07-11
|
|
127
127
|
|
|
128
128
|
Hit@5 : 87.8% (baseline 13.6% — 6.5× lift)
|
package/gen-context.js
CHANGED
|
@@ -14043,7 +14043,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
|
|
|
14043
14043
|
|
|
14044
14044
|
const SERVER_INFO = {
|
|
14045
14045
|
name: 'sigmap',
|
|
14046
|
-
version: '8.
|
|
14046
|
+
version: '8.12.0',
|
|
14047
14047
|
description: 'SigMap MCP server — code signatures on demand',
|
|
14048
14048
|
};
|
|
14049
14049
|
|
|
@@ -18625,6 +18625,260 @@ __factories["./src/verify/parsers"] = function(module, exports) {
|
|
|
18625
18625
|
|
|
18626
18626
|
};
|
|
18627
18627
|
|
|
18628
|
+
// ── ./src/wiki/generate ──
|
|
18629
|
+
__factories["./src/wiki/generate"] = function(module, exports) {
|
|
18630
|
+
|
|
18631
|
+
/**
|
|
18632
|
+
* Wiki generation (D9) — `sigmap wiki`.
|
|
18633
|
+
*
|
|
18634
|
+
* Deterministic architecture narrative composed from data SigMap already
|
|
18635
|
+
* computes: the signature index, the dependency graph, conventions, and the
|
|
18636
|
+
* health score. Template prose only — no LLM, no network, no timestamps —
|
|
18637
|
+
* so two runs on an unchanged repo produce byte-identical markdown.
|
|
18638
|
+
*/
|
|
18639
|
+
|
|
18640
|
+
const fs = require('fs');
|
|
18641
|
+
const path = require('path');
|
|
18642
|
+
|
|
18643
|
+
const HUB_LIMIT = 8;
|
|
18644
|
+
const ENTRY_LIMIT = 8;
|
|
18645
|
+
const MODULE_LIMIT = 20;
|
|
18646
|
+
const KEY_FILE_LIMIT = 3;
|
|
18647
|
+
|
|
18648
|
+
// Graph keys come from src/graph/builder's normalizePath (normalized +
|
|
18649
|
+
// lowercased), so relativize against the same normalization of cwd.
|
|
18650
|
+
function _rel(cwd, f) {
|
|
18651
|
+
return path.relative(path.normalize(cwd).toLowerCase(), f).replace(/\\/g, '/');
|
|
18652
|
+
}
|
|
18653
|
+
|
|
18654
|
+
function _pct(fraction) {
|
|
18655
|
+
return Math.round(fraction * 100);
|
|
18656
|
+
}
|
|
18657
|
+
|
|
18658
|
+
/** Project name + version from package.json, falling back to the dir name. */
|
|
18659
|
+
function _identity(cwd) {
|
|
18660
|
+
try {
|
|
18661
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
|
|
18662
|
+
if (pkg && pkg.name) return { name: pkg.name, version: pkg.version || null };
|
|
18663
|
+
} catch (_) {}
|
|
18664
|
+
return { name: path.basename(cwd), version: null };
|
|
18665
|
+
}
|
|
18666
|
+
|
|
18667
|
+
/** Module rollup from the signature index (keys are cwd-relative paths). */
|
|
18668
|
+
function _modules(index) {
|
|
18669
|
+
const groups = new Map();
|
|
18670
|
+
let totalTokens = 0;
|
|
18671
|
+
for (const [rel, sigs] of index.entries()) {
|
|
18672
|
+
const parts = String(rel).replace(/\\/g, '/').split('/');
|
|
18673
|
+
const mod = parts.length > 1 ? parts[0] : '.';
|
|
18674
|
+
const tokens = Math.ceil((sigs || []).join('\n').length / 4);
|
|
18675
|
+
totalTokens += tokens;
|
|
18676
|
+
if (!groups.has(mod)) groups.set(mod, { name: mod, files: 0, tokens: 0, fileSigs: [] });
|
|
18677
|
+
const g = groups.get(mod);
|
|
18678
|
+
g.files++;
|
|
18679
|
+
g.tokens += tokens;
|
|
18680
|
+
g.fileSigs.push({ file: rel, sigCount: (sigs || []).length });
|
|
18681
|
+
}
|
|
18682
|
+
const modules = [...groups.values()]
|
|
18683
|
+
.sort((a, b) => b.tokens - a.tokens || a.name.localeCompare(b.name))
|
|
18684
|
+
.slice(0, MODULE_LIMIT)
|
|
18685
|
+
.map((g) => ({
|
|
18686
|
+
name: g.name,
|
|
18687
|
+
files: g.files,
|
|
18688
|
+
tokens: g.tokens,
|
|
18689
|
+
keyFiles: g.fileSigs
|
|
18690
|
+
.sort((a, b) => b.sigCount - a.sigCount || a.file.localeCompare(b.file))
|
|
18691
|
+
.slice(0, KEY_FILE_LIMIT)
|
|
18692
|
+
.map((f) => f.file),
|
|
18693
|
+
}));
|
|
18694
|
+
return { modules, totalTokens };
|
|
18695
|
+
}
|
|
18696
|
+
|
|
18697
|
+
/** Hubs, entry points, and cycle count from the dependency graph. */
|
|
18698
|
+
function _flow(cwd) {
|
|
18699
|
+
try {
|
|
18700
|
+
const { buildFromCwd } = __require('./src/graph/builder');
|
|
18701
|
+
const { detectCycles } = __require('./src/map/import-graph');
|
|
18702
|
+
const graph = buildFromCwd(cwd);
|
|
18703
|
+
if (!graph || !graph.forward || graph.forward.size === 0) return null;
|
|
18704
|
+
|
|
18705
|
+
const importersOf = (f) => (graph.reverse.get(f) || []).length;
|
|
18706
|
+
const hubs = [...graph.reverse.entries()]
|
|
18707
|
+
.map(([f, importers]) => ({ file: _rel(cwd, f), importers: importers.length }))
|
|
18708
|
+
.filter((h) => h.importers > 0)
|
|
18709
|
+
.sort((a, b) => b.importers - a.importers || a.file.localeCompare(b.file))
|
|
18710
|
+
.slice(0, HUB_LIMIT);
|
|
18711
|
+
|
|
18712
|
+
const entryPoints = [...graph.forward.entries()]
|
|
18713
|
+
.filter(([f, deps]) => deps.length > 0 && importersOf(f) === 0)
|
|
18714
|
+
.map(([f, deps]) => ({ file: _rel(cwd, f), imports: deps.length }))
|
|
18715
|
+
.sort((a, b) => b.imports - a.imports || a.file.localeCompare(b.file))
|
|
18716
|
+
.slice(0, ENTRY_LIMIT);
|
|
18717
|
+
|
|
18718
|
+
let cycles = 0;
|
|
18719
|
+
try { cycles = detectCycles(graph.forward).length; } catch (_) {}
|
|
18720
|
+
|
|
18721
|
+
return { hubs, entryPoints, cycles, edges: graph.forward.size };
|
|
18722
|
+
} catch (_) {
|
|
18723
|
+
return null;
|
|
18724
|
+
}
|
|
18725
|
+
}
|
|
18726
|
+
|
|
18727
|
+
/** Conventions summary; index keys are resolved back to absolute paths. */
|
|
18728
|
+
function _conventions(cwd, index) {
|
|
18729
|
+
try {
|
|
18730
|
+
const { extractConventions } = __require('./src/conventions/extract');
|
|
18731
|
+
const files = [...index.keys()].map((rel) => path.join(cwd, rel));
|
|
18732
|
+
const c = extractConventions(cwd, files);
|
|
18733
|
+
return {
|
|
18734
|
+
fileNaming: c.fileNaming
|
|
18735
|
+
? { dominant: c.fileNaming.dominant, pct: _pct(c.fileNaming.dominantPct || 0), tier: c.fileNaming.tier }
|
|
18736
|
+
: null,
|
|
18737
|
+
exportStyle: c.exportStyle
|
|
18738
|
+
? { dominant: c.exportStyle.dominant, pct: _pct(c.exportStyle.dominantPct || 0), tier: c.exportStyle.tier }
|
|
18739
|
+
: null,
|
|
18740
|
+
testFramework: c.testFramework || null,
|
|
18741
|
+
};
|
|
18742
|
+
} catch (_) {
|
|
18743
|
+
return null;
|
|
18744
|
+
}
|
|
18745
|
+
}
|
|
18746
|
+
|
|
18747
|
+
function _health(cwd) {
|
|
18748
|
+
try {
|
|
18749
|
+
const { score } = __require('./src/health/scorer');
|
|
18750
|
+
const h = score(cwd);
|
|
18751
|
+
return { score: h.score, grade: h.grade };
|
|
18752
|
+
} catch (_) {
|
|
18753
|
+
return null;
|
|
18754
|
+
}
|
|
18755
|
+
}
|
|
18756
|
+
|
|
18757
|
+
/**
|
|
18758
|
+
* Build the wiki. Every data source is optional — a repo with no context file
|
|
18759
|
+
* or no resolvable graph still yields a valid document.
|
|
18760
|
+
* @param {string} cwd
|
|
18761
|
+
* @param {object} [opts]
|
|
18762
|
+
* @param {string} [opts.version] SigMap version stamped in the header
|
|
18763
|
+
* @returns {{ data: object, markdown: string }}
|
|
18764
|
+
*/
|
|
18765
|
+
function buildWiki(cwd, opts = {}) {
|
|
18766
|
+
let index = new Map();
|
|
18767
|
+
try {
|
|
18768
|
+
const { buildSigIndex } = __require('./src/retrieval/ranker');
|
|
18769
|
+
index = buildSigIndex(cwd);
|
|
18770
|
+
} catch (_) {}
|
|
18771
|
+
|
|
18772
|
+
const identity = _identity(cwd);
|
|
18773
|
+
const { modules, totalTokens } = _modules(index);
|
|
18774
|
+
const flow = _flow(cwd);
|
|
18775
|
+
const conventions = index.size ? _conventions(cwd, index) : null;
|
|
18776
|
+
const health = _health(cwd);
|
|
18777
|
+
|
|
18778
|
+
const data = {
|
|
18779
|
+
name: identity.name,
|
|
18780
|
+
version: identity.version,
|
|
18781
|
+
files: index.size,
|
|
18782
|
+
modules,
|
|
18783
|
+
totalTokens,
|
|
18784
|
+
flow,
|
|
18785
|
+
conventions,
|
|
18786
|
+
health,
|
|
18787
|
+
};
|
|
18788
|
+
|
|
18789
|
+
return { data, markdown: renderWikiMarkdown(data, opts.version) };
|
|
18790
|
+
}
|
|
18791
|
+
|
|
18792
|
+
/**
|
|
18793
|
+
* Render the narrative markdown. Pure function of `data` — no clocks, no
|
|
18794
|
+
* randomness — so output is byte-stable for a fixed repo state.
|
|
18795
|
+
* @param {object} data
|
|
18796
|
+
* @param {string} [sigmapVersion]
|
|
18797
|
+
* @returns {string}
|
|
18798
|
+
*/
|
|
18799
|
+
function renderWikiMarkdown(data, sigmapVersion) {
|
|
18800
|
+
const L = [];
|
|
18801
|
+
const title = data.version ? `${data.name} v${data.version}` : data.name;
|
|
18802
|
+
L.push(`# ${title} — Architecture Wiki`);
|
|
18803
|
+
L.push('');
|
|
18804
|
+
L.push(`_Deterministically generated from signatures + dependency graph by SigMap${sigmapVersion ? ` v${sigmapVersion}` : ''} — no LLM. Regenerate: \`sigmap wiki\`._`);
|
|
18805
|
+
L.push('');
|
|
18806
|
+
|
|
18807
|
+
L.push('## Overview');
|
|
18808
|
+
if (data.files === 0) {
|
|
18809
|
+
L.push('No signature index found yet — run `sigmap` (or `node gen-context.js`) to generate context, then regenerate this wiki.');
|
|
18810
|
+
} else {
|
|
18811
|
+
const fileWord = data.files === 1 ? 'indexed file' : 'indexed files';
|
|
18812
|
+
const modWord = data.modules.length === 1 ? 'top-level module' : 'top-level modules';
|
|
18813
|
+
L.push(`The codebase spans **${data.files} ${fileWord}** across **${data.modules.length} ${modWord}**, with ~${data.totalTokens} tokens of extracted signatures.`);
|
|
18814
|
+
if (data.health) {
|
|
18815
|
+
L.push(`Context health: **${data.health.score}/100 (${data.health.grade})**.`);
|
|
18816
|
+
}
|
|
18817
|
+
}
|
|
18818
|
+
L.push('');
|
|
18819
|
+
|
|
18820
|
+
if (data.modules.length) {
|
|
18821
|
+
L.push('## Modules');
|
|
18822
|
+
L.push('| Module | Files | Sig tokens | Key files |');
|
|
18823
|
+
L.push('|--------|-------|------------|-----------|');
|
|
18824
|
+
for (const m of data.modules) {
|
|
18825
|
+
L.push(`| \`${m.name}\` | ${m.files} | ~${m.tokens} | ${m.keyFiles.map((f) => `\`${f}\``).join(', ')} |`);
|
|
18826
|
+
}
|
|
18827
|
+
const top = data.modules[0];
|
|
18828
|
+
L.push('');
|
|
18829
|
+
L.push(`The largest module by signature volume is \`${top.name}\` (${top.files} files, ~${top.tokens} tokens) — start there for the core logic.`);
|
|
18830
|
+
L.push('');
|
|
18831
|
+
}
|
|
18832
|
+
|
|
18833
|
+
if (data.flow) {
|
|
18834
|
+
L.push('## Dependency flow');
|
|
18835
|
+
if (data.flow.hubs.length) {
|
|
18836
|
+
L.push('The most depended-on files — changes here have the widest blast radius:');
|
|
18837
|
+
L.push('');
|
|
18838
|
+
L.push('| Hub file | Importers |');
|
|
18839
|
+
L.push('|----------|-----------|');
|
|
18840
|
+
for (const h of data.flow.hubs) L.push(`| \`${h.file}\` | ${h.importers} |`);
|
|
18841
|
+
L.push('');
|
|
18842
|
+
}
|
|
18843
|
+
if (data.flow.entryPoints.length) {
|
|
18844
|
+
L.push('Entry points (imported by nothing, importing the rest):');
|
|
18845
|
+
L.push('');
|
|
18846
|
+
for (const e of data.flow.entryPoints) L.push(`- \`${e.file}\` → ${e.imports} imports`);
|
|
18847
|
+
L.push('');
|
|
18848
|
+
}
|
|
18849
|
+
L.push(data.flow.cycles
|
|
18850
|
+
? `**Dependency cycles:** ${data.flow.cycles} — untangle these first when refactoring.`
|
|
18851
|
+
: '**Dependency cycles:** none detected.');
|
|
18852
|
+
L.push('');
|
|
18853
|
+
}
|
|
18854
|
+
|
|
18855
|
+
if (data.conventions) {
|
|
18856
|
+
L.push('## Conventions');
|
|
18857
|
+
const c = data.conventions;
|
|
18858
|
+
const bits = [];
|
|
18859
|
+
if (c.fileNaming && c.fileNaming.dominant) bits.push(`file naming is predominantly **${c.fileNaming.dominant}** (${c.fileNaming.pct}%, ${c.fileNaming.tier})`);
|
|
18860
|
+
if (c.exportStyle && c.exportStyle.dominant) bits.push(`exports use the **${c.exportStyle.dominant}** style (${c.exportStyle.pct}%, ${c.exportStyle.tier})`);
|
|
18861
|
+
if (c.testFramework) bits.push(`tests run on **${c.testFramework}**`);
|
|
18862
|
+
L.push(bits.length
|
|
18863
|
+
? `In this repo, ${bits.join('; ')}. New code should match.`
|
|
18864
|
+
: 'No dominant conventions detected (repo too small or styles mixed).');
|
|
18865
|
+
L.push('');
|
|
18866
|
+
}
|
|
18867
|
+
|
|
18868
|
+
L.push('## Navigating');
|
|
18869
|
+
L.push('- `sigmap ask "<question>"` — ranked, budgeted mini-context for any task');
|
|
18870
|
+
L.push('- `sigmap --impact <file>` / `--callers <symbol>` — blast radius before you change something');
|
|
18871
|
+
L.push('- `sigmap evidence "<query>"` — machine-consumable Evidence Pack (JSON) for agents/CI');
|
|
18872
|
+
L.push('- MCP: `get_architecture_overview`, `get_map`, `get_callee_signatures` for live agent access');
|
|
18873
|
+
L.push('');
|
|
18874
|
+
|
|
18875
|
+
return L.join('\n');
|
|
18876
|
+
}
|
|
18877
|
+
|
|
18878
|
+
module.exports = { buildWiki, renderWikiMarkdown };
|
|
18879
|
+
|
|
18880
|
+
};
|
|
18881
|
+
|
|
18628
18882
|
// ── ./src/workspace/detector ──
|
|
18629
18883
|
__factories["./src/workspace/detector"] = function(module, exports) {
|
|
18630
18884
|
|
|
@@ -18733,7 +18987,7 @@ function __tryGit(args, opts = {}) {
|
|
|
18733
18987
|
catch (_) { return ''; }
|
|
18734
18988
|
}
|
|
18735
18989
|
|
|
18736
|
-
const VERSION = '8.
|
|
18990
|
+
const VERSION = '8.12.0';
|
|
18737
18991
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
18738
18992
|
|
|
18739
18993
|
function requireSourceOrBundled(key) {
|
|
@@ -20607,6 +20861,7 @@ Usage:
|
|
|
20607
20861
|
${cmd} review-pr Audit a diff — scope drift, god-node edits, missing tests, security files (--staged, --base, --json, --markdown)
|
|
20608
20862
|
${cmd} review-pr --markdown PR Evidence Report — branded Markdown (signatures + blast radius + tests) to post as a PR comment
|
|
20609
20863
|
${cmd} create "<task>" Grounded-creation pipeline: scaffold → verify-plan → verify-ai-output → review-pr (--staged)
|
|
20864
|
+
${cmd} wiki Deterministic architecture wiki from signatures + graph — no LLM (--json, --out <path>)
|
|
20610
20865
|
${cmd} squeeze <file|-> Minimize a pasted stacktrace/CI-log/JSON blob (--json for stats)
|
|
20611
20866
|
${cmd} squeeze --response <file|-> Minimize an agent/tool response (same engine; also exposed as the squeeze_output MCP tool)
|
|
20612
20867
|
${cmd} ask "<query>" --squeeze Auto-accept input minimization (no prompt; for scripts/CI)
|
|
@@ -22227,6 +22482,30 @@ function main() {
|
|
|
22227
22482
|
process.exit(result.ok ? 0 : 1);
|
|
22228
22483
|
}
|
|
22229
22484
|
|
|
22485
|
+
// D9: `sigmap wiki` — deterministic architecture narrative (no LLM).
|
|
22486
|
+
if (args[0] === 'wiki') {
|
|
22487
|
+
const { buildWiki } = requireSourceOrBundled('./src/wiki/generate');
|
|
22488
|
+
const result = buildWiki(cwd, { version: VERSION });
|
|
22489
|
+
if (args.includes('--json')) {
|
|
22490
|
+
process.stdout.write(JSON.stringify(result.data, null, 2) + '\n');
|
|
22491
|
+
process.exit(0);
|
|
22492
|
+
}
|
|
22493
|
+
const outIdx = args.indexOf('--out');
|
|
22494
|
+
const outRel = outIdx >= 0 && args[outIdx + 1] && !args[outIdx + 1].startsWith('--')
|
|
22495
|
+
? args[outIdx + 1]
|
|
22496
|
+
: path.join('.context', 'WIKI.md');
|
|
22497
|
+
const outPath = path.resolve(cwd, outRel);
|
|
22498
|
+
try {
|
|
22499
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
22500
|
+
fs.writeFileSync(outPath, result.markdown, 'utf8');
|
|
22501
|
+
} catch (e) {
|
|
22502
|
+
console.error(`[sigmap] cannot write ${outPath}: ${e.message}`);
|
|
22503
|
+
process.exit(1);
|
|
22504
|
+
}
|
|
22505
|
+
console.log(`[sigmap] wiki → ${path.relative(cwd, outPath)} (${result.data.files} files · ${result.data.modules.length} modules)`);
|
|
22506
|
+
process.exit(0);
|
|
22507
|
+
}
|
|
22508
|
+
|
|
22230
22509
|
// Layer 3: `sigmap conventions` — extract & report repo coding conventions
|
|
22231
22510
|
// (file naming, export style, test framework) for TS/JS/Python so generated
|
|
22232
22511
|
// code matches the house style. Writes .context/conventions.json.
|
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.12.0 | Benchmark: sigmap-v8.12-main (2026-07-11)
|
|
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.12-main, 2026-07-11)
|
|
21
21
|
|
|
22
22
|
| Metric | Without SigMap | With SigMap |
|
|
23
23
|
|--------|----------------|-------------|
|
|
@@ -114,6 +114,7 @@ sigmap verify-plan <plan.md|-> Check a plan vs the live index — file
|
|
|
114
114
|
sigmap review-pr Audit a diff — scope drift, god-node edits, missing tests, security files (--staged, --base, --json, --markdown)
|
|
115
115
|
sigmap review-pr --markdown PR Evidence Report — branded Markdown (signatures + blast radius + tests) to post as a PR comment
|
|
116
116
|
sigmap create "<task>" Grounded-creation pipeline: scaffold → verify-plan → verify-ai-output → review-pr (--staged)
|
|
117
|
+
sigmap wiki Deterministic architecture wiki from signatures + graph — no LLM (--json, --out <path>)
|
|
117
118
|
sigmap squeeze <file|-> Minimize a pasted stacktrace/CI-log/JSON blob (--json for stats)
|
|
118
119
|
sigmap squeeze --response <file|-> Minimize an agent/tool response (same engine; also exposed as the squeeze_output MCP tool)
|
|
119
120
|
sigmap ask "<query>" --squeeze Auto-accept input minimization (no prompt; for scripts/CI)
|
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.12.0 | Benchmark: sigmap-v8.12-main (2026-07-11)
|
|
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,7 +23,7 @@ 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.12-main, 2026-07-11)
|
|
27
27
|
|
|
28
28
|
- hit@5 retrieval: 87.8% vs 13.6% random baseline (6.5× lift)
|
|
29
29
|
- Token reduction: 97.0% average across benchmark repos
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sigmap",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.12.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/src/mcp/server.js
CHANGED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wiki generation (D9) — `sigmap wiki`.
|
|
5
|
+
*
|
|
6
|
+
* Deterministic architecture narrative composed from data SigMap already
|
|
7
|
+
* computes: the signature index, the dependency graph, conventions, and the
|
|
8
|
+
* health score. Template prose only — no LLM, no network, no timestamps —
|
|
9
|
+
* so two runs on an unchanged repo produce byte-identical markdown.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const HUB_LIMIT = 8;
|
|
16
|
+
const ENTRY_LIMIT = 8;
|
|
17
|
+
const MODULE_LIMIT = 20;
|
|
18
|
+
const KEY_FILE_LIMIT = 3;
|
|
19
|
+
|
|
20
|
+
// Graph keys come from src/graph/builder's normalizePath (normalized +
|
|
21
|
+
// lowercased), so relativize against the same normalization of cwd.
|
|
22
|
+
function _rel(cwd, f) {
|
|
23
|
+
return path.relative(path.normalize(cwd).toLowerCase(), f).replace(/\\/g, '/');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function _pct(fraction) {
|
|
27
|
+
return Math.round(fraction * 100);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Project name + version from package.json, falling back to the dir name. */
|
|
31
|
+
function _identity(cwd) {
|
|
32
|
+
try {
|
|
33
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
|
|
34
|
+
if (pkg && pkg.name) return { name: pkg.name, version: pkg.version || null };
|
|
35
|
+
} catch (_) {}
|
|
36
|
+
return { name: path.basename(cwd), version: null };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Module rollup from the signature index (keys are cwd-relative paths). */
|
|
40
|
+
function _modules(index) {
|
|
41
|
+
const groups = new Map();
|
|
42
|
+
let totalTokens = 0;
|
|
43
|
+
for (const [rel, sigs] of index.entries()) {
|
|
44
|
+
const parts = String(rel).replace(/\\/g, '/').split('/');
|
|
45
|
+
const mod = parts.length > 1 ? parts[0] : '.';
|
|
46
|
+
const tokens = Math.ceil((sigs || []).join('\n').length / 4);
|
|
47
|
+
totalTokens += tokens;
|
|
48
|
+
if (!groups.has(mod)) groups.set(mod, { name: mod, files: 0, tokens: 0, fileSigs: [] });
|
|
49
|
+
const g = groups.get(mod);
|
|
50
|
+
g.files++;
|
|
51
|
+
g.tokens += tokens;
|
|
52
|
+
g.fileSigs.push({ file: rel, sigCount: (sigs || []).length });
|
|
53
|
+
}
|
|
54
|
+
const modules = [...groups.values()]
|
|
55
|
+
.sort((a, b) => b.tokens - a.tokens || a.name.localeCompare(b.name))
|
|
56
|
+
.slice(0, MODULE_LIMIT)
|
|
57
|
+
.map((g) => ({
|
|
58
|
+
name: g.name,
|
|
59
|
+
files: g.files,
|
|
60
|
+
tokens: g.tokens,
|
|
61
|
+
keyFiles: g.fileSigs
|
|
62
|
+
.sort((a, b) => b.sigCount - a.sigCount || a.file.localeCompare(b.file))
|
|
63
|
+
.slice(0, KEY_FILE_LIMIT)
|
|
64
|
+
.map((f) => f.file),
|
|
65
|
+
}));
|
|
66
|
+
return { modules, totalTokens };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Hubs, entry points, and cycle count from the dependency graph. */
|
|
70
|
+
function _flow(cwd) {
|
|
71
|
+
try {
|
|
72
|
+
const { buildFromCwd } = require('../graph/builder');
|
|
73
|
+
const { detectCycles } = require('../map/import-graph');
|
|
74
|
+
const graph = buildFromCwd(cwd);
|
|
75
|
+
if (!graph || !graph.forward || graph.forward.size === 0) return null;
|
|
76
|
+
|
|
77
|
+
const importersOf = (f) => (graph.reverse.get(f) || []).length;
|
|
78
|
+
const hubs = [...graph.reverse.entries()]
|
|
79
|
+
.map(([f, importers]) => ({ file: _rel(cwd, f), importers: importers.length }))
|
|
80
|
+
.filter((h) => h.importers > 0)
|
|
81
|
+
.sort((a, b) => b.importers - a.importers || a.file.localeCompare(b.file))
|
|
82
|
+
.slice(0, HUB_LIMIT);
|
|
83
|
+
|
|
84
|
+
const entryPoints = [...graph.forward.entries()]
|
|
85
|
+
.filter(([f, deps]) => deps.length > 0 && importersOf(f) === 0)
|
|
86
|
+
.map(([f, deps]) => ({ file: _rel(cwd, f), imports: deps.length }))
|
|
87
|
+
.sort((a, b) => b.imports - a.imports || a.file.localeCompare(b.file))
|
|
88
|
+
.slice(0, ENTRY_LIMIT);
|
|
89
|
+
|
|
90
|
+
let cycles = 0;
|
|
91
|
+
try { cycles = detectCycles(graph.forward).length; } catch (_) {}
|
|
92
|
+
|
|
93
|
+
return { hubs, entryPoints, cycles, edges: graph.forward.size };
|
|
94
|
+
} catch (_) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Conventions summary; index keys are resolved back to absolute paths. */
|
|
100
|
+
function _conventions(cwd, index) {
|
|
101
|
+
try {
|
|
102
|
+
const { extractConventions } = require('../conventions/extract');
|
|
103
|
+
const files = [...index.keys()].map((rel) => path.join(cwd, rel));
|
|
104
|
+
const c = extractConventions(cwd, files);
|
|
105
|
+
return {
|
|
106
|
+
fileNaming: c.fileNaming
|
|
107
|
+
? { dominant: c.fileNaming.dominant, pct: _pct(c.fileNaming.dominantPct || 0), tier: c.fileNaming.tier }
|
|
108
|
+
: null,
|
|
109
|
+
exportStyle: c.exportStyle
|
|
110
|
+
? { dominant: c.exportStyle.dominant, pct: _pct(c.exportStyle.dominantPct || 0), tier: c.exportStyle.tier }
|
|
111
|
+
: null,
|
|
112
|
+
testFramework: c.testFramework || null,
|
|
113
|
+
};
|
|
114
|
+
} catch (_) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function _health(cwd) {
|
|
120
|
+
try {
|
|
121
|
+
const { score } = require('../health/scorer');
|
|
122
|
+
const h = score(cwd);
|
|
123
|
+
return { score: h.score, grade: h.grade };
|
|
124
|
+
} catch (_) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Build the wiki. Every data source is optional — a repo with no context file
|
|
131
|
+
* or no resolvable graph still yields a valid document.
|
|
132
|
+
* @param {string} cwd
|
|
133
|
+
* @param {object} [opts]
|
|
134
|
+
* @param {string} [opts.version] SigMap version stamped in the header
|
|
135
|
+
* @returns {{ data: object, markdown: string }}
|
|
136
|
+
*/
|
|
137
|
+
function buildWiki(cwd, opts = {}) {
|
|
138
|
+
let index = new Map();
|
|
139
|
+
try {
|
|
140
|
+
const { buildSigIndex } = require('../retrieval/ranker');
|
|
141
|
+
index = buildSigIndex(cwd);
|
|
142
|
+
} catch (_) {}
|
|
143
|
+
|
|
144
|
+
const identity = _identity(cwd);
|
|
145
|
+
const { modules, totalTokens } = _modules(index);
|
|
146
|
+
const flow = _flow(cwd);
|
|
147
|
+
const conventions = index.size ? _conventions(cwd, index) : null;
|
|
148
|
+
const health = _health(cwd);
|
|
149
|
+
|
|
150
|
+
const data = {
|
|
151
|
+
name: identity.name,
|
|
152
|
+
version: identity.version,
|
|
153
|
+
files: index.size,
|
|
154
|
+
modules,
|
|
155
|
+
totalTokens,
|
|
156
|
+
flow,
|
|
157
|
+
conventions,
|
|
158
|
+
health,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
return { data, markdown: renderWikiMarkdown(data, opts.version) };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Render the narrative markdown. Pure function of `data` — no clocks, no
|
|
166
|
+
* randomness — so output is byte-stable for a fixed repo state.
|
|
167
|
+
* @param {object} data
|
|
168
|
+
* @param {string} [sigmapVersion]
|
|
169
|
+
* @returns {string}
|
|
170
|
+
*/
|
|
171
|
+
function renderWikiMarkdown(data, sigmapVersion) {
|
|
172
|
+
const L = [];
|
|
173
|
+
const title = data.version ? `${data.name} v${data.version}` : data.name;
|
|
174
|
+
L.push(`# ${title} — Architecture Wiki`);
|
|
175
|
+
L.push('');
|
|
176
|
+
L.push(`_Deterministically generated from signatures + dependency graph by SigMap${sigmapVersion ? ` v${sigmapVersion}` : ''} — no LLM. Regenerate: \`sigmap wiki\`._`);
|
|
177
|
+
L.push('');
|
|
178
|
+
|
|
179
|
+
L.push('## Overview');
|
|
180
|
+
if (data.files === 0) {
|
|
181
|
+
L.push('No signature index found yet — run `sigmap` (or `node gen-context.js`) to generate context, then regenerate this wiki.');
|
|
182
|
+
} else {
|
|
183
|
+
const fileWord = data.files === 1 ? 'indexed file' : 'indexed files';
|
|
184
|
+
const modWord = data.modules.length === 1 ? 'top-level module' : 'top-level modules';
|
|
185
|
+
L.push(`The codebase spans **${data.files} ${fileWord}** across **${data.modules.length} ${modWord}**, with ~${data.totalTokens} tokens of extracted signatures.`);
|
|
186
|
+
if (data.health) {
|
|
187
|
+
L.push(`Context health: **${data.health.score}/100 (${data.health.grade})**.`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
L.push('');
|
|
191
|
+
|
|
192
|
+
if (data.modules.length) {
|
|
193
|
+
L.push('## Modules');
|
|
194
|
+
L.push('| Module | Files | Sig tokens | Key files |');
|
|
195
|
+
L.push('|--------|-------|------------|-----------|');
|
|
196
|
+
for (const m of data.modules) {
|
|
197
|
+
L.push(`| \`${m.name}\` | ${m.files} | ~${m.tokens} | ${m.keyFiles.map((f) => `\`${f}\``).join(', ')} |`);
|
|
198
|
+
}
|
|
199
|
+
const top = data.modules[0];
|
|
200
|
+
L.push('');
|
|
201
|
+
L.push(`The largest module by signature volume is \`${top.name}\` (${top.files} files, ~${top.tokens} tokens) — start there for the core logic.`);
|
|
202
|
+
L.push('');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (data.flow) {
|
|
206
|
+
L.push('## Dependency flow');
|
|
207
|
+
if (data.flow.hubs.length) {
|
|
208
|
+
L.push('The most depended-on files — changes here have the widest blast radius:');
|
|
209
|
+
L.push('');
|
|
210
|
+
L.push('| Hub file | Importers |');
|
|
211
|
+
L.push('|----------|-----------|');
|
|
212
|
+
for (const h of data.flow.hubs) L.push(`| \`${h.file}\` | ${h.importers} |`);
|
|
213
|
+
L.push('');
|
|
214
|
+
}
|
|
215
|
+
if (data.flow.entryPoints.length) {
|
|
216
|
+
L.push('Entry points (imported by nothing, importing the rest):');
|
|
217
|
+
L.push('');
|
|
218
|
+
for (const e of data.flow.entryPoints) L.push(`- \`${e.file}\` → ${e.imports} imports`);
|
|
219
|
+
L.push('');
|
|
220
|
+
}
|
|
221
|
+
L.push(data.flow.cycles
|
|
222
|
+
? `**Dependency cycles:** ${data.flow.cycles} — untangle these first when refactoring.`
|
|
223
|
+
: '**Dependency cycles:** none detected.');
|
|
224
|
+
L.push('');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (data.conventions) {
|
|
228
|
+
L.push('## Conventions');
|
|
229
|
+
const c = data.conventions;
|
|
230
|
+
const bits = [];
|
|
231
|
+
if (c.fileNaming && c.fileNaming.dominant) bits.push(`file naming is predominantly **${c.fileNaming.dominant}** (${c.fileNaming.pct}%, ${c.fileNaming.tier})`);
|
|
232
|
+
if (c.exportStyle && c.exportStyle.dominant) bits.push(`exports use the **${c.exportStyle.dominant}** style (${c.exportStyle.pct}%, ${c.exportStyle.tier})`);
|
|
233
|
+
if (c.testFramework) bits.push(`tests run on **${c.testFramework}**`);
|
|
234
|
+
L.push(bits.length
|
|
235
|
+
? `In this repo, ${bits.join('; ')}. New code should match.`
|
|
236
|
+
: 'No dominant conventions detected (repo too small or styles mixed).');
|
|
237
|
+
L.push('');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
L.push('## Navigating');
|
|
241
|
+
L.push('- `sigmap ask "<question>"` — ranked, budgeted mini-context for any task');
|
|
242
|
+
L.push('- `sigmap --impact <file>` / `--callers <symbol>` — blast radius before you change something');
|
|
243
|
+
L.push('- `sigmap evidence "<query>"` — machine-consumable Evidence Pack (JSON) for agents/CI');
|
|
244
|
+
L.push('- MCP: `get_architecture_overview`, `get_map`, `get_callee_signatures` for live agent access');
|
|
245
|
+
L.push('');
|
|
246
|
+
|
|
247
|
+
return L.join('\n');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
module.exports = { buildWiki, renderWikiMarkdown };
|