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 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 — 2 days ago)
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.11-main (21 repositories, including R language)
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.11.0',
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.11.0';
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.11.0 | Benchmark: sigmap-v8.11-main (2026-07-11)
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.11-main, 2026-07-11)
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.11.0 | Benchmark: sigmap-v8.11-main (2026-07-11)
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.11-main, 2026-07-11)
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.11.0",
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": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-cli",
3
- "version": "8.11.0",
3
+ "version": "8.12.0",
4
4
  "description": "SigMap CLI wrapper — thin adapter for programmatic CLI invocation",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-core",
3
- "version": "8.11.0",
3
+ "version": "8.12.0",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
package/src/mcp/server.js CHANGED
@@ -18,7 +18,7 @@ const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, exp
18
18
 
19
19
  const SERVER_INFO = {
20
20
  name: 'sigmap',
21
- version: '8.11.0',
21
+ version: '8.12.0',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24
 
@@ -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 };