sigmap 8.28.0 → 8.28.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,19 @@ Format: [Semantic Versioning](https://semver.org/)
10
10
 
11
11
  ---
12
12
 
13
+ ## [8.28.1] — 2026-08-22
14
+
15
+ Patch release — two silent-failure bug fixes: a false "zero importers" in the Python import graph, and an empty index under the per-module strategy.
16
+
17
+ ### Fixed
18
+ - **Python absolute imports resolve through any ancestor root (#532, PR #533)** — reported and precisely diagnosed by **@ruurdboeke**, thank you: `extractFileDeps` probed only the importing file's directory and one parent for `from package.module import`, so `src/`-layout projects (source root on `sys.path`) silently produced no graph edge whenever the importer sat two or more directories below the root — and `get_impact` then reported **zero importers**, exactly the signal that says a change is safe to make. Fix: an ancestor walk from the importing file up to the project root (nearest first, 16-level cap), probing `<module>.py` and `<module>/__init__.py` per level; nearest-first preserves the old first-match semantics for same-dir/one-parent cases. 4 new tests incl. the verbatim issue repro and `get_impact` end-to-end.
19
+ - **Per-module strategy: `ask`/`query_context` find signatures again (#534, PR #535):** the per-module strategy writes one `context-<module>.md` per top-level srcDir and leaves the primary file as a thin overview — but the sig-index strategy enrichment merged only `context-cold.md` (hot-cold) plus the cache, so `sigmap ask` failed with "no context file found" and the `query_context` MCP tool returned an empty index on every per-module repo. Fix: enumerate `.github/context-*.md` (sorted, deterministic) and merge each — the cold-file merge generalized to every strategy split. 4 new tests on a real generated two-module fixture incl. the exact reported failure; hot-cold and full strategies verified unchanged.
20
+
21
+ ### Changed
22
+ - 8 new integration tests (138 test files); bundle rebuilt; zero new dependencies.
23
+
24
+ ---
25
+
13
26
  ## [8.28.0] — 2026-08-18
14
27
 
15
28
  Minor release — **"Arity Guard" (v8.28, D1)**: verification now checks not just *does this function exist* but *is it being called with a plausible number of arguments* — the payoff of the v8.27 balanced scanner.
package/README.md CHANGED
@@ -123,7 +123,7 @@ Ask → Rank → Context → Validate → Judge → Learn
123
123
  <!--SM:benchmarkBlock-->
124
124
  ```
125
125
  Benchmark : sigmap-v8.28-main (21 repositories, including R language)
126
- Date : 2026-08-18
126
+ Date : 2026-08-22
127
127
 
128
128
  Hit@5 : 81.1% (grep-agent baseline 44.0% — 1.73× lift)
129
129
  Token reduction: 96.8% (across 21 repos)
package/gen-context.js CHANGED
@@ -11573,23 +11573,31 @@ __factories["./src/graph/builder"] = function(module, exports) {
11573
11573
  }
11574
11574
  }
11575
11575
 
11576
- // Absolute imports: from package.module import ... (infer from project structure)
11576
+ // Absolute imports: from package.module import ... (infer from project
11577
+ // structure). The module is resolved against EVERY ancestor of the
11578
+ // importing file up to the project root, nearest first — any of them can
11579
+ // be the source root on sys.path (src/ layouts, pytest rootdir). The old
11580
+ // dir + one-parent probe silently dropped edges for files nested two or
11581
+ // more directories below the source root (#532), and a false "zero
11582
+ // importers" from get_impact is exactly the signal that says a change is
11583
+ // safe to make.
11577
11584
  const reAbs = /^[ \t]*from\s+([\w.]+)\s+import/gm;
11578
11585
  while ((m = reAbs.exec(content)) !== null) {
11579
11586
  const modulePath = m[1].replace(/\./g, '/');
11580
- const candidates = [
11581
- path.join(dir, modulePath + '.py'),
11582
- path.join(dir, modulePath, '__init__.py'),
11583
- path.resolve(dir, '..', modulePath + '.py'),
11584
- path.resolve(dir, '..', modulePath, '__init__.py'),
11585
- ];
11586
- for (const c of candidates) {
11587
- const normC = normalizePath(c);
11588
- if (fileSet.has(normC)) {
11589
- found.push(normC);
11590
- break;
11587
+ const normCwd = normalizePath(path.resolve(cwd));
11588
+ let base = dir;
11589
+ let hit = null;
11590
+ for (let depth = 0; depth < 16 && !hit; depth++) {
11591
+ for (const c of [path.join(base, modulePath + '.py'), path.join(base, modulePath, '__init__.py')]) {
11592
+ const normC = normalizePath(c);
11593
+ if (fileSet.has(normC)) { hit = normC; break; }
11591
11594
  }
11595
+ if (normalizePath(base) === normCwd) break;
11596
+ const parent = path.dirname(base);
11597
+ if (parent === base) break;
11598
+ base = parent;
11592
11599
  }
11600
+ if (hit) found.push(hit);
11593
11601
  }
11594
11602
  }
11595
11603
 
@@ -15385,7 +15393,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
15385
15393
 
15386
15394
  const SERVER_INFO = {
15387
15395
  name: 'sigmap',
15388
- version: '8.28.0',
15396
+ version: '8.28.1',
15389
15397
  description: 'SigMap MCP server — code signatures on demand',
15390
15398
  };
15391
15399
 
@@ -16975,9 +16983,22 @@ __factories["./src/retrieval/ranker"] = function(module, exports) {
16975
16983
  * @returns {Map<string, string[]>}
16976
16984
  */
16977
16985
  function _enrichSigIndexFromStrategy(cwd, index) {
16986
+ const fs = require('fs');
16978
16987
  const path = require('path');
16979
- const coldPath = path.join(cwd, '.github', 'context-cold.md');
16980
- _mergeSigIndex(index, _parseContextFile(coldPath));
16988
+ // Merge every strategy split file: context-cold.md (hot-cold) AND each
16989
+ // per-module context-<module>.md — the per-module strategy stores ALL
16990
+ // signatures in these, leaving the primary file as a thin overview, so
16991
+ // skipping them made ask/query_context see an empty index (#534).
16992
+ // Sorted for deterministic merge order.
16993
+ try {
16994
+ const ghDir = path.join(cwd, '.github');
16995
+ const splits = fs.readdirSync(ghDir)
16996
+ .filter((f) => /^context-[\w.-]+\.md$/.test(f))
16997
+ .sort();
16998
+ for (const f of splits) {
16999
+ _mergeSigIndex(index, _parseContextFile(path.join(ghDir, f)));
17000
+ }
17001
+ } catch (_) {}
16981
17002
  _mergeSigIndex(index, _buildSigIndexFromCache(cwd));
16982
17003
  return index;
16983
17004
  }
@@ -21214,7 +21235,7 @@ function __tryGit(args, opts = {}) {
21214
21235
  catch (_) { return ''; }
21215
21236
  }
21216
21237
 
21217
- const VERSION = '8.28.0';
21238
+ const VERSION = '8.28.1';
21218
21239
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
21219
21240
 
21220
21241
  function requireSourceOrBundled(key) {
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.28.0 | Benchmark: sigmap-v8.28-main (2026-08-18)
14
+ # Version: 8.28.1 | Benchmark: sigmap-v8.28-main (2026-08-22)
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.28-main, 2026-08-18)
20
+ ## Core metrics (benchmark: sigmap-v8.28-main, 2026-08-22)
21
21
 
22
22
  | Metric | Without SigMap | With SigMap |
23
23
  |--------|----------------|-------------|
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.28.0 | Benchmark: sigmap-v8.28-main (2026-08-18)
14
+ # Version: 8.28.1 | Benchmark: sigmap-v8.28-main (2026-08-22)
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.28-main, 2026-08-18)
26
+ ## Core metrics (benchmark: sigmap-v8.28-main, 2026-08-22)
27
27
 
28
28
  - hit@5 retrieval: 81.1% vs 44.0% single-shot grep baseline (1.73× lift)
29
29
  - Token reduction: 96.8% average across benchmark repos
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "8.28.0",
3
+ "version": "8.28.1",
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.28.0",
3
+ "version": "8.28.1",
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.28.0",
3
+ "version": "8.28.1",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -240,23 +240,31 @@ function extractFileDeps(filePath, content, fileSet, cwd, ctx) {
240
240
  }
241
241
  }
242
242
 
243
- // Absolute imports: from package.module import ... (infer from project structure)
243
+ // Absolute imports: from package.module import ... (infer from project
244
+ // structure). The module is resolved against EVERY ancestor of the
245
+ // importing file up to the project root, nearest first — any of them can
246
+ // be the source root on sys.path (src/ layouts, pytest rootdir). The old
247
+ // dir + one-parent probe silently dropped edges for files nested two or
248
+ // more directories below the source root (#532), and a false "zero
249
+ // importers" from get_impact is exactly the signal that says a change is
250
+ // safe to make.
244
251
  const reAbs = /^[ \t]*from\s+([\w.]+)\s+import/gm;
245
252
  while ((m = reAbs.exec(content)) !== null) {
246
253
  const modulePath = m[1].replace(/\./g, '/');
247
- const candidates = [
248
- path.join(dir, modulePath + '.py'),
249
- path.join(dir, modulePath, '__init__.py'),
250
- path.resolve(dir, '..', modulePath + '.py'),
251
- path.resolve(dir, '..', modulePath, '__init__.py'),
252
- ];
253
- for (const c of candidates) {
254
- const normC = normalizePath(c);
255
- if (fileSet.has(normC)) {
256
- found.push(normC);
257
- break;
254
+ const normCwd = normalizePath(path.resolve(cwd));
255
+ let base = dir;
256
+ let hit = null;
257
+ for (let depth = 0; depth < 16 && !hit; depth++) {
258
+ for (const c of [path.join(base, modulePath + '.py'), path.join(base, modulePath, '__init__.py')]) {
259
+ const normC = normalizePath(c);
260
+ if (fileSet.has(normC)) { hit = normC; break; }
258
261
  }
262
+ if (normalizePath(base) === normCwd) break;
263
+ const parent = path.dirname(base);
264
+ if (parent === base) break;
265
+ base = parent;
259
266
  }
267
+ if (hit) found.push(hit);
260
268
  }
261
269
  }
262
270
 
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.28.0',
21
+ version: '8.28.1',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24
 
@@ -473,9 +473,22 @@ function _buildSigIndexFromCache(cwd) {
473
473
  * @returns {Map<string, string[]>}
474
474
  */
475
475
  function _enrichSigIndexFromStrategy(cwd, index) {
476
+ const fs = require('fs');
476
477
  const path = require('path');
477
- const coldPath = path.join(cwd, '.github', 'context-cold.md');
478
- _mergeSigIndex(index, _parseContextFile(coldPath));
478
+ // Merge every strategy split file: context-cold.md (hot-cold) AND each
479
+ // per-module context-<module>.md — the per-module strategy stores ALL
480
+ // signatures in these, leaving the primary file as a thin overview, so
481
+ // skipping them made ask/query_context see an empty index (#534).
482
+ // Sorted for deterministic merge order.
483
+ try {
484
+ const ghDir = path.join(cwd, '.github');
485
+ const splits = fs.readdirSync(ghDir)
486
+ .filter((f) => /^context-[\w.-]+\.md$/.test(f))
487
+ .sort();
488
+ for (const f of splits) {
489
+ _mergeSigIndex(index, _parseContextFile(path.join(ghDir, f)));
490
+ }
491
+ } catch (_) {}
479
492
  _mergeSigIndex(index, _buildSigIndexFromCache(cwd));
480
493
  return index;
481
494
  }