sigmap 7.31.0 → 8.1.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/gen-context.js CHANGED
@@ -4609,7 +4609,14 @@ __factories["./src/evidence/pack"] = function(module, exports) {
4609
4609
  const GENERATED_RE = /(^|\/)(dist|build|out|vendor|node_modules)\/|\.(generated|min|bundle)\.|\.(pb|_pb)\.|\.pb\.go$|_pb2\.py$/;
4610
4610
  const TEST_RE = /(^|\/)(tests?|__tests__|spec|specs)\/|\.(test|spec)\.[a-z]+$|(^|\/)test_[^/]+\.py$|_test\.(go|py|rb)$/;
4611
4611
  const CONFIG_RE = /\.(json|ya?ml|toml|ini|conf|config|properties|env)$|(^|\/)(\.?[a-z]+rc)$|\.config\.[a-z]+$/i;
4612
- const SECURITY_RE = /(^|\/|[._-])(auth|authn|authz|login|password|passwd|secret|credential|token|session|crypto|cipher|payment|billing|checkout|oauth|jwt|permission|acl|rbac)([._-]|\/|$)/i;
4612
+ // DB migrations: framework dirs (Rails/Alembic/Prisma), Flyway `V1__x.sql`,
4613
+ // timestamped migration files, and `*_migration.*` naming.
4614
+ const MIGRATION_RE = /(^|\/)(migrations?|alembic\/versions|prisma\/migrations)(\/|$)|(^|\/)db\/migrate\/|(^|\/)V\d+(_\d+)*__[^/]+\.(sql|java)$|(^|\/)\d{8,}[_-][^/]+\.(sql|rb|py|js|ts)$|[._-]migration[s]?[._-]/i;
4615
+ const PAYMENT_RE = /(^|\/|[._-])(payment|payments|billing|checkout|invoice|invoicing|subscription|stripe|paypal|braintree|charge|refund|payout)([._-]|\/|$)/i;
4616
+ const AUTH_RE = /(^|\/|[._-])(auth|authn|authz|login|logout|signin|signup|password|passwd|session|oauth|jwt|permission|permissions|acl|rbac|credential|credentials)([._-]|\/|$)/i;
4617
+ const SECURITY_RE = /(^|\/|[._-])(secret|secrets|crypto|cipher|encrypt|decrypt|token|signing|keystore|vault)([._-]|\/|$)/i;
4618
+ // Public API surface: `api/` dirs, `public-api`, and module barrel entrypoints.
4619
+ const PUBLIC_API_RE = /(^|\/)api(\/|$)|(^|\/)public[-_]?api(\/|$)|(^|\/)index\.(js|ts|mjs|cjs)$/i;
4613
4620
 
4614
4621
  /**
4615
4622
  * Split a signature's ` :start-end` line anchor from its symbol text.
@@ -4627,17 +4634,25 @@ __factories["./src/evidence/pack"] = function(module, exports) {
4627
4634
  }
4628
4635
 
4629
4636
  /**
4630
- * Classify a file into a coarse risk label. Path-based heuristic (v1) — the
4631
- * richer label set (C3) lands in v8.5.
4637
+ * Classify a file into a risk label (C3, v8.5). Path-based, deterministic.
4638
+ * Precedence is strict, most-specific-risk first: a migration touching payments
4639
+ * is labeled `migration` (a schema change is the dominant risk), payment/auth
4640
+ * outrank the generic `security` bucket, and `config`/`public-api` resolve
4641
+ * before the `source` fallback. `test`/`generated` semantics are preserved so
4642
+ * existing consumers (findRelatedTests, verifier) keep working.
4632
4643
  * @param {string} relPath
4633
- * @returns {'generated'|'test'|'config'|'security'|'source'}
4644
+ * @returns {'generated'|'test'|'migration'|'payment'|'auth'|'security'|'config'|'public-api'|'source'}
4634
4645
  */
4635
4646
  function riskLabelFor(relPath) {
4636
4647
  const p = relPath.replace(/\\/g, '/');
4637
4648
  if (GENERATED_RE.test(p)) return 'generated';
4638
4649
  if (TEST_RE.test(p)) return 'test';
4650
+ if (MIGRATION_RE.test(p)) return 'migration';
4651
+ if (PAYMENT_RE.test(p)) return 'payment';
4652
+ if (AUTH_RE.test(p)) return 'auth';
4639
4653
  if (SECURITY_RE.test(p)) return 'security';
4640
4654
  if (CONFIG_RE.test(p)) return 'config';
4655
+ if (PUBLIC_API_RE.test(p)) return 'public-api';
4641
4656
  return 'source';
4642
4657
  }
4643
4658
 
@@ -4648,9 +4663,28 @@ __factories["./src/evidence/pack"] = function(module, exports) {
4648
4663
  }
4649
4664
 
4650
4665
  /**
4651
- * Best-effort impl→test discovery (v1). Matches test files whose stem equals
4652
- * the implementation file's stem, by common convention. Deterministic. The
4653
- * accuracy-measured discovery (C2) lands in v8.5.
4666
+ * Infer the implementation stem a test file targets, by stripping the
4667
+ * conventional test affixes across languages (measured in the C2 benchmark):
4668
+ * foo.test.js / foo.spec.ts → foo (JS/TS)
4669
+ * test_foo.py → foo (Python / pytest)
4670
+ * foo_test.go / foo_test.py → foo (Go, unittest)
4671
+ * FooTest.java / BarSpec.scala → Foo (JVM, PascalCase)
4672
+ * @param {string} relPath
4673
+ * @returns {string}
4674
+ */
4675
+ function testTargetStem(relPath) {
4676
+ let s = stemOf(relPath); // strips ext + trailing .test/.spec
4677
+ s = s.replace(/^test[_-]/i, ''); // Python: test_foo
4678
+ s = s.replace(/[_-]test$/i, ''); // Go / unittest: foo_test
4679
+ s = s.replace(/(Tests?|Specs?)$/, ''); // JVM PascalCase: FooTest, BarSpec
4680
+ return s;
4681
+ }
4682
+
4683
+ /**
4684
+ * Impl→test discovery (C2, v8.5). Matches test files back to their
4685
+ * implementation by normalizing conventional test affixes, so JS/TS, Python,
4686
+ * Go, and JVM naming conventions all resolve. Deterministic; accuracy is
4687
+ * measured by `scripts/run-test-discovery-benchmark.mjs`.
4654
4688
  * @param {string} relPath
4655
4689
  * @param {string[]} allFiles - universe of indexed files (relative paths)
4656
4690
  * @returns {string[]}
@@ -4663,7 +4697,7 @@ __factories["./src/evidence/pack"] = function(module, exports) {
4663
4697
  for (const f of allFiles) {
4664
4698
  if (f === relPath) continue;
4665
4699
  if (riskLabelFor(f) !== 'test') continue;
4666
- if (stemOf(f).toLowerCase() === stem) out.push(f);
4700
+ if (testTargetStem(f).toLowerCase() === stem) out.push(f);
4667
4701
  }
4668
4702
  return out.sort();
4669
4703
  }
@@ -11179,6 +11213,101 @@ __factories["./src/learning/weights"] = function(module, exports) {
11179
11213
 
11180
11214
  };
11181
11215
 
11216
+ // ── ./src/map/build-ci ──
11217
+ __factories["./src/map/build-ci"] = function(module, exports) {
11218
+
11219
+ /**
11220
+ * Build & CI extractor (v8.5 C1).
11221
+ *
11222
+ * Surfaces how the project is built and validated: npm/pnpm/yarn scripts
11223
+ * (package.json), GitHub Actions workflows (.github/workflows/*.yml), and
11224
+ * Makefile targets. Pure, zero-dependency, deterministic.
11225
+ *
11226
+ * @param {string[]} files — absolute file paths (unused; roots are read directly)
11227
+ * @param {string} cwd — project root
11228
+ * @returns {string} formatted markdown table (empty string if none found)
11229
+ */
11230
+
11231
+ const fs = require('fs');
11232
+ const path = require('path');
11233
+
11234
+ const MAX_ROWS = 120;
11235
+
11236
+ function readJson(p) {
11237
+ try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch (_) { return null; }
11238
+ }
11239
+
11240
+ function npmScripts(cwd, rows) {
11241
+ const pkg = readJson(path.join(cwd, 'package.json'));
11242
+ if (!pkg || !pkg.scripts || typeof pkg.scripts !== 'object') return;
11243
+ for (const name of Object.keys(pkg.scripts).sort()) {
11244
+ rows.push({ kind: 'script', name, detail: 'npm run ' + name });
11245
+ }
11246
+ }
11247
+
11248
+ function ciWorkflows(cwd, rows) {
11249
+ const dir = path.join(cwd, '.github', 'workflows');
11250
+ let entries;
11251
+ try { entries = fs.readdirSync(dir); } catch (_) { return; }
11252
+ for (const file of entries.sort()) {
11253
+ if (!/\.ya?ml$/i.test(file)) continue;
11254
+ let content;
11255
+ try { content = fs.readFileSync(path.join(dir, file), 'utf8'); } catch (_) { continue; }
11256
+ const nameMatch = content.match(/^name:\s*(.+)$/m);
11257
+ const name = nameMatch ? nameMatch[1].trim().replace(/^['"]|['"]$/g, '') : file;
11258
+ // Trigger events from an `on:` mapping or inline form.
11259
+ const onMatch = content.match(/^on:\s*(.*)$/m);
11260
+ let triggers = '';
11261
+ if (onMatch) {
11262
+ if (onMatch[1].trim()) {
11263
+ triggers = onMatch[1].replace(/[[\]{}'"]/g, '').trim();
11264
+ } else {
11265
+ const block = content.slice(onMatch.index);
11266
+ const events = [...block.matchAll(/^\s{2,}([a-z_]+):/gm)].map((m) => m[1]);
11267
+ triggers = [...new Set(events)].slice(0, 6).join(', ');
11268
+ }
11269
+ }
11270
+ rows.push({ kind: 'ci', name, detail: `${file}${triggers ? ' — ' + triggers : ''}` });
11271
+ }
11272
+ }
11273
+
11274
+ function makeTargets(cwd, rows) {
11275
+ let content;
11276
+ try { content = fs.readFileSync(path.join(cwd, 'Makefile'), 'utf8'); } catch (_) { return; }
11277
+ const targets = [];
11278
+ for (const line of content.split('\n')) {
11279
+ const m = line.match(/^([a-zA-Z0-9_][a-zA-Z0-9_.-]*)\s*:(?!=)/);
11280
+ if (m && m[1] !== '.PHONY') targets.push(m[1]);
11281
+ }
11282
+ for (const t of [...new Set(targets)].sort()) {
11283
+ rows.push({ kind: 'make', name: t, detail: 'make ' + t });
11284
+ }
11285
+ }
11286
+
11287
+ function analyze(files, cwd) {
11288
+ const rows = [];
11289
+ npmScripts(cwd, rows);
11290
+ ciWorkflows(cwd, rows);
11291
+ makeTargets(cwd, rows);
11292
+ if (rows.length === 0) return '';
11293
+
11294
+ const lines = [
11295
+ '| Kind | Name | Detail |',
11296
+ '|------|------|--------|',
11297
+ ];
11298
+ for (const r of rows.slice(0, MAX_ROWS)) {
11299
+ lines.push(`| ${r.kind} | ${r.name} | ${r.detail} |`);
11300
+ }
11301
+ if (rows.length > MAX_ROWS) {
11302
+ lines.push(`| … | | +${rows.length - MAX_ROWS} more |`);
11303
+ }
11304
+ return lines.join('\n');
11305
+ }
11306
+
11307
+ module.exports = { analyze };
11308
+
11309
+ };
11310
+
11182
11311
  // ── ./src/map/class-hierarchy ──
11183
11312
  __factories["./src/map/class-hierarchy"] = function(module, exports) {
11184
11313
 
@@ -11300,6 +11429,205 @@ __factories["./src/map/class-hierarchy"] = function(module, exports) {
11300
11429
 
11301
11430
  };
11302
11431
 
11432
+ // ── ./src/map/config-manifest ──
11433
+ __factories["./src/map/config-manifest"] = function(module, exports) {
11434
+
11435
+ /**
11436
+ * Config & package-manifest extractor (v8.5 C1).
11437
+ *
11438
+ * Surfaces the project's package manifests (name / version / dependency counts)
11439
+ * across ecosystems and the notable root config files present. Pure,
11440
+ * zero-dependency, deterministic.
11441
+ *
11442
+ * @param {string[]} files — absolute file paths (unused; roots are read directly)
11443
+ * @param {string} cwd — project root
11444
+ * @returns {string} formatted markdown table (empty string if none found)
11445
+ */
11446
+
11447
+ const fs = require('fs');
11448
+ const path = require('path');
11449
+
11450
+ const CONFIG_FILES = [
11451
+ 'tsconfig.json', 'jsconfig.json', '.eslintrc', '.eslintrc.json', '.eslintrc.js',
11452
+ '.prettierrc', 'babel.config.js', 'jest.config.js', 'vitest.config.ts',
11453
+ 'webpack.config.js', 'vite.config.ts', 'rollup.config.js', 'tailwind.config.js',
11454
+ 'docker-compose.yml', 'docker-compose.yaml', 'Dockerfile', '.editorconfig',
11455
+ ];
11456
+
11457
+ function readText(p) { try { return fs.readFileSync(p, 'utf8'); } catch (_) { return null; } }
11458
+ function readJson(p) { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch (_) { return null; } }
11459
+ function count(obj) { return obj && typeof obj === 'object' ? Object.keys(obj).length : 0; }
11460
+
11461
+ function manifests(cwd, rows) {
11462
+ const pkg = readJson(path.join(cwd, 'package.json'));
11463
+ if (pkg) {
11464
+ const deps = count(pkg.dependencies);
11465
+ const dev = count(pkg.devDependencies);
11466
+ const id = [pkg.name, pkg.version].filter(Boolean).join('@') || 'package.json';
11467
+ rows.push({ manifest: 'package.json (npm)', detail: `${id} · ${deps} deps, ${dev} devDeps` });
11468
+ }
11469
+
11470
+ const pyproject = readText(path.join(cwd, 'pyproject.toml'));
11471
+ if (pyproject) {
11472
+ const name = (pyproject.match(/^\s*name\s*=\s*["']([^"']+)["']/m) || [])[1];
11473
+ const ver = (pyproject.match(/^\s*version\s*=\s*["']([^"']+)["']/m) || [])[1];
11474
+ rows.push({ manifest: 'pyproject.toml (python)', detail: [name, ver].filter(Boolean).join('@') || 'present' });
11475
+ } else if (readText(path.join(cwd, 'setup.py'))) {
11476
+ rows.push({ manifest: 'setup.py (python)', detail: 'present' });
11477
+ }
11478
+ if (readText(path.join(cwd, 'requirements.txt'))) {
11479
+ rows.push({ manifest: 'requirements.txt (python)', detail: 'present' });
11480
+ }
11481
+
11482
+ const cargo = readText(path.join(cwd, 'Cargo.toml'));
11483
+ if (cargo) {
11484
+ const name = (cargo.match(/^\s*name\s*=\s*["']([^"']+)["']/m) || [])[1];
11485
+ const ver = (cargo.match(/^\s*version\s*=\s*["']([^"']+)["']/m) || [])[1];
11486
+ rows.push({ manifest: 'Cargo.toml (rust)', detail: [name, ver].filter(Boolean).join('@') || 'present' });
11487
+ }
11488
+
11489
+ const gomod = readText(path.join(cwd, 'go.mod'));
11490
+ if (gomod) {
11491
+ const mod = (gomod.match(/^module\s+(\S+)/m) || [])[1];
11492
+ const go = (gomod.match(/^go\s+(\S+)/m) || [])[1];
11493
+ rows.push({ manifest: 'go.mod (go)', detail: [mod, go && 'go ' + go].filter(Boolean).join(' · ') || 'present' });
11494
+ }
11495
+
11496
+ if (readText(path.join(cwd, 'pom.xml'))) rows.push({ manifest: 'pom.xml (maven)', detail: 'present' });
11497
+ if (readText(path.join(cwd, 'build.gradle')) || readText(path.join(cwd, 'build.gradle.kts'))) {
11498
+ rows.push({ manifest: 'build.gradle (gradle)', detail: 'present' });
11499
+ }
11500
+ if (readText(path.join(cwd, 'Gemfile'))) rows.push({ manifest: 'Gemfile (ruby)', detail: 'present' });
11501
+ const composer = readJson(path.join(cwd, 'composer.json'));
11502
+ if (composer) {
11503
+ rows.push({ manifest: 'composer.json (php)', detail: `${composer.name || 'present'} · ${count(composer.require)} deps` });
11504
+ }
11505
+ }
11506
+
11507
+ function configFiles(cwd) {
11508
+ const present = [];
11509
+ for (const f of CONFIG_FILES) {
11510
+ if (fs.existsSync(path.join(cwd, f))) present.push(f);
11511
+ }
11512
+ return present;
11513
+ }
11514
+
11515
+ function analyze(files, cwd) {
11516
+ const rows = [];
11517
+ manifests(cwd, rows);
11518
+ const configs = configFiles(cwd);
11519
+ if (rows.length === 0 && configs.length === 0) return '';
11520
+
11521
+ const lines = [];
11522
+ if (rows.length) {
11523
+ lines.push('| Manifest | Detail |', '|----------|--------|');
11524
+ for (const r of rows) lines.push(`| ${r.manifest} | ${r.detail} |`);
11525
+ }
11526
+ if (configs.length) {
11527
+ if (lines.length) lines.push('');
11528
+ lines.push(`**Config files:** ${configs.map((c) => '`' + c + '`').join(', ')}`);
11529
+ }
11530
+ return lines.join('\n');
11531
+ }
11532
+
11533
+ module.exports = { analyze };
11534
+
11535
+ };
11536
+
11537
+ // ── ./src/map/env-schema ──
11538
+ __factories["./src/map/env-schema"] = function(module, exports) {
11539
+
11540
+ /**
11541
+ * Environment-variable schema extractor (v8.5 C1).
11542
+ *
11543
+ * Surfaces the environment the project actually reads — from source across
11544
+ * JS/TS, Python, Ruby, and Go, plus keys declared in a committed `.env.example`
11545
+ * / `.env.sample` / `.env.template`. Pure, zero-dependency, deterministic.
11546
+ *
11547
+ * @param {string[]} files — absolute file paths to analyze (srcDirs-scoped)
11548
+ * @param {string} cwd — project root
11549
+ * @returns {string} formatted markdown table (empty string if none found)
11550
+ */
11551
+
11552
+ const fs = require('fs');
11553
+ const path = require('path');
11554
+
11555
+ const SCAN_EXTS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.py', '.rb', '.go']);
11556
+ const EXAMPLE_FILES = ['.env.example', '.env.sample', '.env.template', '.env.dist'];
11557
+
11558
+ // process.env.X / process.env['X'] / import.meta.env.X / Deno.env.get('X')
11559
+ const JS_RE = /(?:process\.env|import\.meta\.env)(?:\.([A-Z_][A-Z0-9_]*)|\[\s*['"]([A-Z_][A-Z0-9_]*)['"]\s*\])|Deno\.env\.get\(\s*['"]([A-Z_][A-Z0-9_]*)['"]/g;
11560
+ // os.environ['X'] / os.environ.get('X') / os.getenv('X') / getenv('X')
11561
+ const PY_RE = /(?:os\.)?(?:environ(?:\.get)?\[?\s*['"]([A-Z_][A-Z0-9_]*)['"]|getenv\(\s*['"]([A-Z_][A-Z0-9_]*)['"])/g;
11562
+ const RB_RE = /ENV\[\s*['"]([A-Z_][A-Z0-9_]*)['"]\s*\]/g;
11563
+ const GO_RE = /os\.(?:Getenv|LookupEnv)\(\s*["`']([A-Z_][A-Z0-9_]*)["`']/g;
11564
+
11565
+ const MAX_ROWS = 200;
11566
+
11567
+ function collectMatches(re, content, into) {
11568
+ let m;
11569
+ re.lastIndex = 0;
11570
+ while ((m = re.exec(content)) !== null) {
11571
+ const name = m[1] || m[2] || m[3];
11572
+ if (name) into.add(name);
11573
+ }
11574
+ }
11575
+
11576
+ function readExampleKeys(cwd) {
11577
+ const keys = new Set();
11578
+ for (const name of EXAMPLE_FILES) {
11579
+ let content;
11580
+ try { content = fs.readFileSync(path.join(cwd, name), 'utf8'); } catch (_) { continue; }
11581
+ for (const line of content.split('\n')) {
11582
+ const t = line.trim();
11583
+ if (!t || t.startsWith('#')) continue;
11584
+ const eq = t.match(/^(?:export\s+)?([A-Z_][A-Z0-9_]*)\s*=/);
11585
+ if (eq) keys.add(eq[1]);
11586
+ }
11587
+ }
11588
+ return keys;
11589
+ }
11590
+
11591
+ function analyze(files, cwd) {
11592
+ const fromCode = new Set();
11593
+
11594
+ for (const filePath of files) {
11595
+ const ext = path.extname(filePath).toLowerCase();
11596
+ if (!SCAN_EXTS.has(ext)) continue;
11597
+ let content;
11598
+ try { content = fs.readFileSync(filePath, 'utf8'); } catch (_) { continue; }
11599
+
11600
+ if (ext === '.py') collectMatches(PY_RE, content, fromCode);
11601
+ else if (ext === '.rb') collectMatches(RB_RE, content, fromCode);
11602
+ else if (ext === '.go') collectMatches(GO_RE, content, fromCode);
11603
+ else collectMatches(JS_RE, content, fromCode);
11604
+ }
11605
+
11606
+ const fromExample = readExampleKeys(cwd);
11607
+ const all = new Set([...fromCode, ...fromExample]);
11608
+ if (all.size === 0) return '';
11609
+
11610
+ const names = [...all].sort();
11611
+ const lines = [
11612
+ '| Variable | Source |',
11613
+ '|----------|--------|',
11614
+ ];
11615
+ for (const name of names.slice(0, MAX_ROWS)) {
11616
+ const src = [];
11617
+ if (fromCode.has(name)) src.push('code');
11618
+ if (fromExample.has(name)) src.push('.env.example');
11619
+ lines.push(`| ${name} | ${src.join(', ')} |`);
11620
+ }
11621
+ if (names.length > MAX_ROWS) {
11622
+ lines.push(`| … | +${names.length - MAX_ROWS} more |`);
11623
+ }
11624
+ return lines.join('\n');
11625
+ }
11626
+
11627
+ module.exports = { analyze };
11628
+
11629
+ };
11630
+
11303
11631
  // ── ./src/map/import-graph ──
11304
11632
  __factories["./src/map/import-graph"] = function(module, exports) {
11305
11633
 
@@ -11489,6 +11817,94 @@ __factories["./src/map/import-graph"] = function(module, exports) {
11489
11817
 
11490
11818
  };
11491
11819
 
11820
+ // ── ./src/map/migrations ──
11821
+ __factories["./src/map/migrations"] = function(module, exports) {
11822
+
11823
+ /**
11824
+ * Database-migration extractor (v8.5 C1).
11825
+ *
11826
+ * Detects schema-migration files across the common frameworks — Rails
11827
+ * (db/migrate), Django/Alembic, Prisma, Flyway (`V1__name.sql`), knex/Sequelize,
11828
+ * and timestamped SQL — and surfaces them with a parsed version + name. Pure,
11829
+ * zero-dependency, deterministic.
11830
+ *
11831
+ * @param {string[]} files — absolute file paths (unused; the tree is walked)
11832
+ * @param {string} cwd — project root
11833
+ * @returns {string} formatted markdown table (empty string if none found)
11834
+ */
11835
+
11836
+ const fs = require('fs');
11837
+ const path = require('path');
11838
+
11839
+ const MAX_DEPTH = 6;
11840
+ const MAX_ROWS = 200;
11841
+ const SKIP_DIR = new Set(['.git', 'node_modules', 'vendor', 'dist', 'build', 'target', '.venv', 'venv', '__pycache__']);
11842
+ const MIG_EXT = new Set(['.sql', '.rb', '.py', '.js', '.ts']);
11843
+
11844
+ // A directory whose path marks its children as migrations.
11845
+ const MIG_DIR_RE = /(^|\/)(db\/migrate|migrations?|alembic\/versions|prisma\/migrations)$/i;
11846
+ // A filename that is itself a migration regardless of directory.
11847
+ const FLYWAY_RE = /^V\d+(?:[._]\d+)*__(.+)\.(sql|java)$/;
11848
+ const TIMESTAMP_RE = /^(\d{8,})[_-](.+)\.(sql|rb|py|js|ts)$/;
11849
+ const NAMED_RE = /[._-]migrations?[._-]/i;
11850
+
11851
+ function walk(dir, cwd, depth, out) {
11852
+ if (depth > MAX_DEPTH) return;
11853
+ let entries;
11854
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; }
11855
+ entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
11856
+
11857
+ const relDir = path.relative(cwd, dir).replace(/\\/g, '/');
11858
+ const dirIsMigration = MIG_DIR_RE.test(relDir);
11859
+
11860
+ for (const e of entries) {
11861
+ if (e.isDirectory()) {
11862
+ if (SKIP_DIR.has(e.name)) continue;
11863
+ walk(path.join(dir, e.name), cwd, depth + 1, out);
11864
+ continue;
11865
+ }
11866
+ const ext = path.extname(e.name).toLowerCase();
11867
+ if (!MIG_EXT.has(ext)) continue;
11868
+
11869
+ const rel = path.relative(cwd, path.join(dir, e.name)).replace(/\\/g, '/');
11870
+ let version = null;
11871
+ let name = null;
11872
+
11873
+ let m;
11874
+ if ((m = e.name.match(FLYWAY_RE))) { version = e.name.split('__')[0]; name = m[1].replace(/_/g, ' '); }
11875
+ else if ((m = e.name.match(TIMESTAMP_RE))) { version = m[1]; name = m[2].replace(/[_-]/g, ' '); }
11876
+ else if (dirIsMigration) { version = '—'; name = e.name.replace(ext, ''); }
11877
+ else if (NAMED_RE.test(e.name)) { version = '—'; name = e.name.replace(ext, ''); }
11878
+ else continue;
11879
+
11880
+ out.push({ version, name, file: rel });
11881
+ }
11882
+ }
11883
+
11884
+ function analyze(files, cwd) {
11885
+ const found = [];
11886
+ walk(cwd, cwd, 0, found);
11887
+ if (found.length === 0) return '';
11888
+
11889
+ found.sort((a, b) => (a.file < b.file ? -1 : a.file > b.file ? 1 : 0));
11890
+
11891
+ const lines = [
11892
+ '| Version | Migration | File |',
11893
+ '|---------|-----------|------|',
11894
+ ];
11895
+ for (const r of found.slice(0, MAX_ROWS)) {
11896
+ lines.push(`| ${r.version} | ${r.name} | ${r.file} |`);
11897
+ }
11898
+ if (found.length > MAX_ROWS) {
11899
+ lines.push(`| … | +${found.length - MAX_ROWS} more | |`);
11900
+ }
11901
+ return lines.join('\n');
11902
+ }
11903
+
11904
+ module.exports = { analyze };
11905
+
11906
+ };
11907
+
11492
11908
  // ── ./src/map/route-table ──
11493
11909
  __factories["./src/map/route-table"] = function(module, exports) {
11494
11910
 
@@ -11644,6 +12060,10 @@ __factories["./src/mcp/handlers"] = function(module, exports) {
11644
12060
  imports: '### Import graph',
11645
12061
  classes: '### Class hierarchy',
11646
12062
  routes: '### Route table',
12063
+ env: '### Environment variables',
12064
+ buildci: '### Build & CI',
12065
+ manifests: '### Config & manifests',
12066
+ migrations: '### Database migrations',
11647
12067
  };
11648
12068
 
11649
12069
  /**
@@ -11729,7 +12149,7 @@ __factories["./src/mcp/handlers"] = function(module, exports) {
11729
12149
 
11730
12150
  const header = MAP_SECTIONS[args.type];
11731
12151
  if (!header) {
11732
- return `Unknown map type: "${args.type}". Use: imports, classes, routes`;
12152
+ return `Unknown map type: "${args.type}". Use: ${Object.keys(MAP_SECTIONS).join(', ')}`;
11733
12153
  }
11734
12154
 
11735
12155
  const mapPath = path.join(cwd, 'PROJECT_MAP.md');
@@ -12643,7 +13063,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
12643
13063
 
12644
13064
  const SERVER_INFO = {
12645
13065
  name: 'sigmap',
12646
- version: '7.31.0',
13066
+ version: '8.1.0',
12647
13067
  description: 'SigMap MCP server — code signatures on demand',
12648
13068
  };
12649
13069
 
@@ -15983,6 +16403,7 @@ __factories["./src/verify/hallucination-guard"] = function(module, exports) {
15983
16403
  const path = require('path');
15984
16404
  const parsers = __require('./src/verify/parsers');
15985
16405
  const { closestMatch, buildSymbolCandidates, formatSuggestion } = __require('./src/verify/closest-match');
16406
+ const { buildLibraryIndex } = __require('./src/verify/lib-index');
15986
16407
 
15987
16408
  // A path that looks like a test file (JS/TS spec/test, Python test_/_test, or
15988
16409
  // a tests/__tests__ directory). Used to flag fake-test-file separately.
@@ -16151,6 +16572,28 @@ __factories["./src/verify/hallucination-guard"] = function(module, exports) {
16151
16572
  }
16152
16573
  if (!fileBasenames) fileBasenames = new Set();
16153
16574
 
16575
+ // Installed-library grounding (G5/D5, the moat): union the exported symbols of
16576
+ // the libraries actually installed in node_modules, so genuine library calls
16577
+ // stop false-flagging as fake-symbol and the summary can pin the versions the
16578
+ // answer was verified against. Auto-runs only when the caller did not override
16579
+ // the symbol set (keeps hermetic callers unchanged); disable with libIndex:false.
16580
+ let libraries = opts.libraries || [];
16581
+ {
16582
+ let libSyms = opts.libSymbols;
16583
+ if (!libSyms && opts.libIndex !== false && !opts.symbolSet) {
16584
+ try {
16585
+ const li = buildLibraryIndex(cwd, { version: opts.version });
16586
+ libSyms = li.symbols;
16587
+ if (!opts.libraries) libraries = li.libraries;
16588
+ } catch (_) { libSyms = null; }
16589
+ }
16590
+ if (libSyms && libSyms.size) {
16591
+ const merged = new Set(symbolSet);
16592
+ for (const s of libSyms) merged.add(s);
16593
+ symbolSet = merged;
16594
+ }
16595
+ }
16596
+
16154
16597
  let deps = opts.deps;
16155
16598
  let hasPkg = opts.hasPkg;
16156
16599
  if (!deps) {
@@ -16274,6 +16717,8 @@ __factories["./src/verify/hallucination-guard"] = function(module, exports) {
16274
16717
  clean: issues.length === 0,
16275
16718
  symbolsIndexed: symbolSet.size,
16276
16719
  withSuggestion: issues.filter((i) => i.suggestion).length,
16720
+ librariesIndexed: libraries.length,
16721
+ libraries: libraries.map((l) => ({ name: l.name, version: l.version, symbols: l.symbols, typed: l.typed })),
16277
16722
  };
16278
16723
 
16279
16724
  return { issues, summary };
@@ -16283,6 +16728,174 @@ __factories["./src/verify/hallucination-guard"] = function(module, exports) {
16283
16728
 
16284
16729
  };
16285
16730
 
16731
+ // ── ./src/verify/lib-index ──
16732
+ __factories["./src/verify/lib-index"] = function(module, exports) {
16733
+
16734
+ /**
16735
+ * Local-library signature index (v9.0 G5/D5 — the private-API grounding moat).
16736
+ *
16737
+ * Context7 knows only *public* library docs. SigMap can do something no
16738
+ * competitor can: index the signatures of the libraries **actually installed**
16739
+ * in `node_modules` and verify AI suggestions against repo + private +
16740
+ * installed-lib symbols. This module builds the installed-lib half.
16741
+ *
16742
+ * For each **direct** dependency declared in `package.json`, it locates the
16743
+ * package under `node_modules/<dep>`, reads its version (D8 version pinning),
16744
+ * and extracts the exported symbol names from its TypeScript declaration entry
16745
+ * (`types`/`typings`, else `index.d.ts`). Pure, zero-dependency, deterministic:
16746
+ * byte-stable given a fixed installed tree. Bounded (per-file read cap + dep
16747
+ * cap) and cached via `src/cache/sig-cache.js` so repeat builds are near-free.
16748
+ */
16749
+
16750
+ const fs = require('fs');
16751
+ const path = require('path');
16752
+ const { loadCache, saveCache, getChangedFiles, updateCacheEntries } = __require('./src/cache/sig-cache');
16753
+
16754
+ const MAX_DTS_BYTES = 512 * 1024; // per-file read cap
16755
+ const MAX_DEPS = 1000; // dep count cap
16756
+ const DEP_KEYS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
16757
+
16758
+ /**
16759
+ * Extract exported symbol names from a `.d.ts` declaration file. Deterministic,
16760
+ * regex-based (declaration files are already normalized, so this is robust
16761
+ * without a full TS parser and stays zero-dependency).
16762
+ * @param {string} src
16763
+ * @returns {string[]} sorted unique exported names
16764
+ */
16765
+ function extractDtsExports(src) {
16766
+ const names = new Set();
16767
+ if (!src) return [];
16768
+
16769
+ // export [declare] [default] function|const|let|var|class|interface|type|enum|namespace Name
16770
+ const declRe = /\bexport\s+(?:declare\s+)?(?:default\s+)?(?:abstract\s+)?(?:async\s+)?(?:function|const|let|var|class|interface|type|enum|namespace|module)\s+([A-Za-z_$][\w$]*)/g;
16771
+ let m;
16772
+ while ((m = declRe.exec(src)) !== null) names.add(m[1]);
16773
+
16774
+ // export { a, b as c, default as d }
16775
+ const listRe = /\bexport\s*(?:type\s*)?\{([^}]*)\}/g;
16776
+ while ((m = listRe.exec(src)) !== null) {
16777
+ for (const part of m[1].split(',')) {
16778
+ const name = part.trim().split(/\s+as\s+/).pop().trim();
16779
+ if (/^[A-Za-z_$][\w$]*$/.test(name) && name !== 'default') names.add(name);
16780
+ }
16781
+ }
16782
+
16783
+ // export as namespace Name / export = Name
16784
+ const nsRe = /\bexport\s+as\s+namespace\s+([A-Za-z_$][\w$]*)/g;
16785
+ while ((m = nsRe.exec(src)) !== null) names.add(m[1]);
16786
+ const assignRe = /\bexport\s*=\s*([A-Za-z_$][\w$]*)/g;
16787
+ while ((m = assignRe.exec(src)) !== null) names.add(m[1]);
16788
+
16789
+ return [...names].sort();
16790
+ }
16791
+
16792
+ /** Read direct dependency names declared in the project's package.json. */
16793
+ function directDeps(cwd) {
16794
+ const names = new Set();
16795
+ try {
16796
+ const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
16797
+ for (const k of DEP_KEYS) {
16798
+ if (pkg[k] && typeof pkg[k] === 'object') {
16799
+ for (const n of Object.keys(pkg[k])) names.add(n);
16800
+ }
16801
+ }
16802
+ } catch (_) { /* no/invalid package.json → no deps */ }
16803
+ return [...names].sort();
16804
+ }
16805
+
16806
+ /**
16807
+ * Resolve an installed dependency's version + entry `.d.ts` path.
16808
+ * @returns {{ version: string|null, dtsPath: string|null }|null} null if not installed
16809
+ */
16810
+ function resolveEntry(cwd, dep) {
16811
+ const pkgDir = path.join(cwd, 'node_modules', dep);
16812
+ let pkg;
16813
+ try { pkg = JSON.parse(fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8')); } catch (_) { return null; }
16814
+ const version = typeof pkg.version === 'string' ? pkg.version : null;
16815
+
16816
+ const candidates = [];
16817
+ const typesField = pkg.types || pkg.typings;
16818
+ if (typeof typesField === 'string') {
16819
+ candidates.push(typesField);
16820
+ candidates.push(path.join(typesField, 'index.d.ts')); // typesField may be a dir
16821
+ }
16822
+ candidates.push('index.d.ts');
16823
+ if (typeof pkg.main === 'string') candidates.push(pkg.main.replace(/\.(js|cjs|mjs)$/, '.d.ts'));
16824
+
16825
+ for (const c of candidates) {
16826
+ const p = path.join(pkgDir, c);
16827
+ try { if (fs.statSync(p).isFile()) return { version, dtsPath: p }; } catch (_) { /* next */ }
16828
+ }
16829
+ return { version, dtsPath: null }; // installed but untyped
16830
+ }
16831
+
16832
+ /**
16833
+ * Build the installed-library signature index for `cwd`.
16834
+ *
16835
+ * @param {string} cwd
16836
+ * @param {object} [opts]
16837
+ * @param {string} [opts.version='0'] sigmap version, for cache busting
16838
+ * @param {boolean} [opts.cache=true] use the on-disk sig-cache
16839
+ * @returns {{ symbols: Set<string>, libraries: Array<{name,version,symbols,typed}>, count: number }}
16840
+ */
16841
+ function buildLibraryIndex(cwd, opts = {}) {
16842
+ const version = opts.version || '0';
16843
+ const useCache = opts.cache !== false;
16844
+ const deps = directDeps(cwd).slice(0, MAX_DEPS);
16845
+
16846
+ const entries = [];
16847
+ for (const dep of deps) {
16848
+ const r = resolveEntry(cwd, dep);
16849
+ if (r) entries.push({ dep, version: r.version, dtsPath: r.dtsPath });
16850
+ }
16851
+
16852
+ const cache = useCache ? loadCache(cwd, version) : new Map();
16853
+ const dtsFiles = entries.filter((e) => e.dtsPath).map((e) => e.dtsPath);
16854
+ const { unchanged } = getChangedFiles(dtsFiles, cache);
16855
+ const unchangedSet = new Set(unchanged);
16856
+
16857
+ const symbols = new Set();
16858
+ const libraries = [];
16859
+ const fresh = [];
16860
+
16861
+ for (const e of entries) {
16862
+ let names;
16863
+ if (!e.dtsPath) {
16864
+ names = [];
16865
+ } else if (unchangedSet.has(e.dtsPath) && cache.get(e.dtsPath)) {
16866
+ names = cache.get(e.dtsPath).sigs || [];
16867
+ } else {
16868
+ let src = '';
16869
+ try {
16870
+ if (fs.statSync(e.dtsPath).size <= MAX_DTS_BYTES) src = fs.readFileSync(e.dtsPath, 'utf8');
16871
+ } catch (_) { /* unreadable → empty */ }
16872
+ names = extractDtsExports(src);
16873
+ fresh.push({ file: e.dtsPath, sigs: names });
16874
+ }
16875
+ for (const n of names) symbols.add(n);
16876
+ libraries.push({ name: e.dep, version: e.version, symbols: names.length, typed: !!e.dtsPath });
16877
+ }
16878
+
16879
+ if (useCache && fresh.length) {
16880
+ updateCacheEntries(cache, fresh);
16881
+ saveCache(cwd, version, cache);
16882
+ }
16883
+
16884
+ libraries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
16885
+ return { symbols, libraries, count: symbols.size };
16886
+ }
16887
+
16888
+ /** D8: render `name@version` pins for the typed/installed libraries. */
16889
+ function formatVersionPins(libraries) {
16890
+ return (libraries || [])
16891
+ .filter((l) => l.version)
16892
+ .map((l) => `${l.name}@${l.version}`);
16893
+ }
16894
+
16895
+ module.exports = { buildLibraryIndex, extractDtsExports, directDeps, resolveEntry, formatVersionPins };
16896
+
16897
+ };
16898
+
16286
16899
  // ── ./src/verify/parsers ──
16287
16900
  __factories["./src/verify/parsers"] = function(module, exports) {
16288
16901
 
@@ -16612,7 +17225,7 @@ function __tryGit(args, opts = {}) {
16612
17225
  catch (_) { return ''; }
16613
17226
  }
16614
17227
 
16615
- const VERSION = '7.31.0';
17228
+ const VERSION = '8.1.0';
16616
17229
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
16617
17230
 
16618
17231
  function requireSourceOrBundled(key) {