sigmap 3.3.2 → 3.3.4

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,37 @@ Format: [Semantic Versioning](https://semver.org/)
10
10
 
11
11
  ---
12
12
 
13
+ ## [3.3.4] — 2026-04-14 — Binary Bundle Fix
14
+
15
+ ### Fixed
16
+ - **Standalone binary pre-flight now passes for new P1 extractors**
17
+ - Added missing bundled `__factories` entries in `gen-context.js` for:
18
+ - `./src/extractors/graphql`
19
+ - `./src/extractors/protobuf`
20
+ - `./src/extractors/sql`
21
+ - `./src/extractors/terraform`
22
+ - Resolves CI/build failure in `scripts/build-binary.mjs` reporting missing `src/` modules in bundle.
23
+
24
+ ---
25
+
26
+ ## [3.3.3] — 2026-04-14 — Auto srcDirs + P1 Extractors
27
+
28
+ ### Added
29
+ - **P1 extractor support** for additional high-value formats:
30
+ - SQL: `.sql`
31
+ - GraphQL: `.graphql`, `.gql`
32
+ - Terraform: `.tf`, `.tfvars`
33
+ - Protobuf: `.proto`
34
+ - **Extractor registration updates** across runtime and core mapping so the new file types are parsed consistently.
35
+
36
+ ### Changed
37
+ - **Auto source directory detection** (framework- and manifest-aware) in config loading and strategy auditing.
38
+ - **Auto maxDepth tuning** in strategy audit based on repository file-depth distribution.
39
+ - **Benchmark strategy-audit reports refreshed** to reflect improved source discovery and coverage.
40
+ - **Language support count updated from 21 to 25** across core README and extension README.
41
+
42
+ ---
43
+
13
44
  ## [3.3.1] — 2026-04-10 — Patch: `--each --adapter` flag combination
14
45
 
15
46
  ### Fixed
package/README.md CHANGED
@@ -19,6 +19,12 @@
19
19
  npx sigmap # 10 seconds. zero config. your AI never reads the wrong file again.
20
20
  ```
21
21
 
22
+ > Latest: **v3.3.4** includes a standalone binary bundle fix for newly added P1 extractors.
23
+
24
+ <div align="center">
25
+ <img src="demo.gif" alt="SigMap demo — reducing 80K tokens to 4K in under 10 seconds" width="760" />
26
+ </div>
27
+
22
28
  <details>
23
29
  <summary><strong>Trust signals</strong></summary>
24
30
 
@@ -68,7 +74,7 @@ npx sigmap # 10 seconds. zero config. your AI never reads the wrong file again
68
74
  | [Standalone binaries](docs/readmes/binaries.md) | macOS, Linux, Windows — no Node required |
69
75
  | [VS Code extension](#-vs-code-extension) | Status bar, stale alerts, commands |
70
76
  | [JetBrains plugin](#-jetbrains-plugin) | IntelliJ IDEA, WebStorm, PyCharm support |
71
- | [Languages supported](#-languages-supported) | 21 languages |
77
+ | [Languages supported](#-languages-supported) | 25 languages |
72
78
  | [Context strategies](#-context-strategies) | full / per-module / hot-cold |
73
79
  | [MCP server](#-mcp-server) | 8 on-demand tools |
74
80
  | [CLI reference](#-cli-reference) | All flags |
@@ -91,7 +97,7 @@ SigMap scans your source files and extracts only the **function and class signat
91
97
  Your codebase
92
98
 
93
99
 
94
- sigmap ─────────► extracts signatures from 21 languages
100
+ sigmap ─────────► extracts signatures from 25 languages
95
101
 
96
102
 
97
103
  .github/copilot-instructions.md ◄── auto-read by Copilot / Claude / Cursor
@@ -433,6 +439,11 @@ The official SigMap VS Code extension keeps your context fresh without any manua
433
439
  | **Open context command** | `SigMap: Open Context File` — opens `.github/copilot-instructions.md` |
434
440
  | **Script path setting** | `sigmap.scriptPath` — override the path to the `sigmap` binary or `gen-context.js` |
435
441
 
442
+ <div align="center">
443
+ <img src="vscode.gif" alt="SigMap VS Code extension — status bar, stale notification, regenerate context" width="760" />
444
+ </div>
445
+
446
+
436
447
  Activates on startup (`onStartupFinished`) — loads within 3 s, never blocks editor startup.
437
448
 
438
449
  **Install:** [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=manojmallick.sigmap) | [Open VSX Registry](https://open-vsx.org/extension/manojmallick/sigmap)
@@ -459,10 +470,10 @@ Compatible with **IntelliJ IDEA 2024.1+** (Community & Ultimate), **WebStorm**,
459
470
 
460
471
  ## 🌐 Languages supported
461
472
 
462
- > 21 languages. All implemented with zero external dependencies — pure regex + Node built-ins.
473
+ > 25 languages. All implemented with zero external dependencies — pure regex + Node built-ins.
463
474
 
464
475
  <details>
465
- <summary><strong>Show all 21 languages</strong></summary>
476
+ <summary><strong>Show all 25 languages</strong></summary>
466
477
 
467
478
  | Language | Extensions | Extracts |
468
479
  |---|---|---|
@@ -486,6 +497,10 @@ Compatible with **IntelliJ IDEA 2024.1+** (Community & Ultimate), **WebStorm**,
486
497
  | CSS/SCSS | `.css` `.scss` `.sass` `.less` | custom properties and keyframes |
487
498
  | YAML | `.yml` `.yaml` | top-level keys and pipeline jobs |
488
499
  | Shell | `.sh` `.bash` `.zsh` `.fish` | function declarations |
500
+ | SQL | `.sql` | tables, views, indexes, functions, procedures |
501
+ | GraphQL | `.graphql` `.gql` | types, interfaces, enums, operations, fragments |
502
+ | Terraform | `.tf` `.tfvars` | resources, modules, variables, outputs |
503
+ | Protobuf | `.proto` | messages, services, rpc, enums |
489
504
  | Dockerfile | `Dockerfile` `Dockerfile.*` | stages and key instructions |
490
505
 
491
506
  </details>
package/gen-context.js CHANGED
@@ -1680,6 +1680,326 @@ __factories["./src/extractors/typescript"] = function(module, exports) {
1680
1680
 
1681
1681
  };
1682
1682
 
1683
+ // ── ./src/extractors/graphql ──
1684
+ __factories["./src/extractors/graphql"] = function(module, exports) {
1685
+
1686
+ 'use strict';
1687
+
1688
+ /**
1689
+ * Extract signatures from GraphQL schema / operation files.
1690
+ * Captures type, interface, enum, input, union, scalar, query, mutation,
1691
+ * subscription, fragment definitions.
1692
+ *
1693
+ * @param {string} src - Raw GraphQL content
1694
+ * @returns {string[]} Array of signature strings
1695
+ */
1696
+ function extract(src) {
1697
+ if (!src || typeof src !== 'string') return [];
1698
+ const sigs = [];
1699
+
1700
+ // Strip comments (# style)
1701
+ const stripped = src.replace(/#[^\n]*/g, '');
1702
+
1703
+ // Schema type definitions: type Foo [implements Bar] { ... }
1704
+ for (const m of stripped.matchAll(
1705
+ /\b(type|interface|input)\s+(\w+)(?:\s+implements\s+([\w\s&]+))?\s*\{/g
1706
+ )) {
1707
+ const implements_ = m[3] ? ` implements ${m[3].trim().replace(/\s+/g, ' ')}` : '';
1708
+ sigs.push(`${m[1]} ${m[2]}${implements_}`);
1709
+ }
1710
+
1711
+ // enum
1712
+ for (const m of stripped.matchAll(/\benum\s+(\w+)\s*\{/g)) {
1713
+ sigs.push(`enum ${m[1]}`);
1714
+ }
1715
+
1716
+ // union
1717
+ for (const m of stripped.matchAll(/\bunion\s+(\w+)\s*=/g)) {
1718
+ sigs.push(`union ${m[1]}`);
1719
+ }
1720
+
1721
+ // scalar
1722
+ for (const m of stripped.matchAll(/\bscalar\s+(\w+)/g)) {
1723
+ sigs.push(`scalar ${m[1]}`);
1724
+ }
1725
+
1726
+ // extend type / extend interface
1727
+ for (const m of stripped.matchAll(/\bextend\s+(type|interface)\s+(\w+)/g)) {
1728
+ sigs.push(`extend ${m[1]} ${m[2]}`);
1729
+ }
1730
+
1731
+ // Query / Mutation / Subscription operations
1732
+ for (const m of stripped.matchAll(
1733
+ /\b(query|mutation|subscription)\s+(\w+)\s*(?:\([^)]*\))?\s*\{/g
1734
+ )) {
1735
+ sigs.push(`${m[1]} ${m[2]}`);
1736
+ }
1737
+
1738
+ // Named fragments
1739
+ for (const m of stripped.matchAll(/\bfragment\s+(\w+)\s+on\s+(\w+)/g)) {
1740
+ sigs.push(`fragment ${m[1]} on ${m[2]}`);
1741
+ }
1742
+
1743
+ // Top-level schema { query: ... }
1744
+ if (/\bschema\s*\{/.test(stripped)) {
1745
+ sigs.push('schema { ... }');
1746
+ }
1747
+
1748
+ return sigs;
1749
+ }
1750
+
1751
+ module.exports = { extract };
1752
+
1753
+ };
1754
+
1755
+ // ── ./src/extractors/protobuf ──
1756
+ __factories["./src/extractors/protobuf"] = function(module, exports) {
1757
+
1758
+ 'use strict';
1759
+
1760
+ /**
1761
+ * Extract signatures from Protocol Buffer (.proto) files.
1762
+ * Captures message, enum, service, rpc, oneof, extend definitions.
1763
+ *
1764
+ * @param {string} src - Raw .proto content
1765
+ * @returns {string[]} Array of signature strings
1766
+ */
1767
+ function extract(src) {
1768
+ if (!src || typeof src !== 'string') return [];
1769
+ const sigs = [];
1770
+
1771
+ // Strip single-line and block comments
1772
+ const stripped = src
1773
+ .replace(/\/\/[^\n]*/g, '')
1774
+ .replace(/\/\*[\s\S]*?\*\//g, '');
1775
+
1776
+ // syntax / package / option (top-level metadata)
1777
+ const syntaxM = stripped.match(/\bsyntax\s*=\s*"([^"]+)"/);
1778
+ if (syntaxM) sigs.push(`syntax = "${syntaxM[1]}"`);
1779
+
1780
+ const pkgM = stripped.match(/\bpackage\s+([\w.]+)\s*;/);
1781
+ if (pkgM) sigs.push(`package ${pkgM[1]}`);
1782
+
1783
+ // message <Name> { ... }
1784
+ for (const m of stripped.matchAll(/\bmessage\s+(\w+)\s*\{/g)) {
1785
+ sigs.push(`message ${m[1]}`);
1786
+ }
1787
+
1788
+ // enum <Name> { ... }
1789
+ for (const m of stripped.matchAll(/\benum\s+(\w+)\s*\{/g)) {
1790
+ sigs.push(`enum ${m[1]}`);
1791
+ }
1792
+
1793
+ // service <Name> { ... }
1794
+ for (const m of stripped.matchAll(/\bservice\s+(\w+)\s*\{/g)) {
1795
+ sigs.push(`service ${m[1]}`);
1796
+ }
1797
+
1798
+ // rpc <Name>(<Request>) returns (<Response>)
1799
+ for (const m of stripped.matchAll(
1800
+ /\brpc\s+(\w+)\s*\(\s*(stream\s+)?(\w+)\s*\)\s+returns\s*\(\s*(stream\s+)?(\w+)\s*\)/g
1801
+ )) {
1802
+ const req = `${m[2] || ''}${m[3]}`.trim();
1803
+ const res = `${m[4] || ''}${m[5]}`.trim();
1804
+ sigs.push(`rpc ${m[1]}(${req}) returns (${res})`);
1805
+ }
1806
+
1807
+ // oneof <name>
1808
+ for (const m of stripped.matchAll(/\boneof\s+(\w+)\s*\{/g)) {
1809
+ sigs.push(`oneof ${m[1]}`);
1810
+ }
1811
+
1812
+ // extend <TypeName>
1813
+ for (const m of stripped.matchAll(/\bextend\s+([\w.]+)\s*\{/g)) {
1814
+ sigs.push(`extend ${m[1]}`);
1815
+ }
1816
+
1817
+ return sigs;
1818
+ }
1819
+
1820
+ module.exports = { extract };
1821
+
1822
+ };
1823
+
1824
+ // ── ./src/extractors/sql ──
1825
+ __factories["./src/extractors/sql"] = function(module, exports) {
1826
+
1827
+ 'use strict';
1828
+
1829
+ /**
1830
+ * Extract signatures from SQL source files.
1831
+ * Captures CREATE TABLE, VIEW, INDEX, FUNCTION, PROCEDURE, TRIGGER, TYPE, SEQUENCE.
1832
+ *
1833
+ * @param {string} src - Raw SQL content
1834
+ * @returns {string[]} Array of signature strings
1835
+ */
1836
+ function extract(src) {
1837
+ if (!src || typeof src !== 'string') return [];
1838
+ const sigs = [];
1839
+
1840
+ // Strip single-line comments and block comments
1841
+ const stripped = src
1842
+ .replace(/--[^\n]*/g, '')
1843
+ .replace(/\/\*[\s\S]*?\*\//g, '');
1844
+
1845
+ // CREATE TABLE [IF NOT EXISTS] <name> / CREATE [TEMP] TABLE ...
1846
+ for (const m of stripped.matchAll(
1847
+ /CREATE\s+(?:OR\s+REPLACE\s+)?(?:TEMP(?:ORARY)?\s+)?TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([`"[\w.]+)/gi
1848
+ )) {
1849
+ sigs.push(`TABLE ${_cleanName(m[1])}`);
1850
+ }
1851
+
1852
+ // CREATE VIEW / MATERIALIZED VIEW
1853
+ for (const m of stripped.matchAll(
1854
+ /CREATE\s+(?:OR\s+REPLACE\s+)?(?:MATERIALIZED\s+)?VIEW\s+(?:IF\s+NOT\s+EXISTS\s+)?([`"[\w.]+)/gi
1855
+ )) {
1856
+ sigs.push(`VIEW ${_cleanName(m[1])}`);
1857
+ }
1858
+
1859
+ // CREATE INDEX / UNIQUE INDEX
1860
+ for (const m of stripped.matchAll(
1861
+ /CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:CONCURRENTLY\s+)?(?:IF\s+NOT\s+EXISTS\s+)?([`"[\w.]+)\s+ON\s+([`"[\w.]+)/gi
1862
+ )) {
1863
+ sigs.push(`INDEX ${_cleanName(m[1])} ON ${_cleanName(m[2])}`);
1864
+ }
1865
+
1866
+ // CREATE FUNCTION / CREATE OR REPLACE FUNCTION
1867
+ for (const m of stripped.matchAll(
1868
+ /CREATE\s+(?:OR\s+REPLACE\s+)?FUNCTION\s+([`"[\w.]+)\s*\(([^)]*)\)/gi
1869
+ )) {
1870
+ const params = _normalizeParams(m[2]);
1871
+ sigs.push(`FUNCTION ${_cleanName(m[1])}(${params})`);
1872
+ }
1873
+
1874
+ // CREATE PROCEDURE
1875
+ for (const m of stripped.matchAll(
1876
+ /CREATE\s+(?:OR\s+REPLACE\s+)?PROCEDURE\s+([`"[\w.]+)\s*\(([^)]*)\)/gi
1877
+ )) {
1878
+ const params = _normalizeParams(m[2]);
1879
+ sigs.push(`PROCEDURE ${_cleanName(m[1])}(${params})`);
1880
+ }
1881
+
1882
+ // CREATE TRIGGER
1883
+ for (const m of stripped.matchAll(
1884
+ /CREATE\s+(?:OR\s+REPLACE\s+)?(?:CONSTRAINT\s+)?TRIGGER\s+([`"[\w.]+)/gi
1885
+ )) {
1886
+ sigs.push(`TRIGGER ${_cleanName(m[1])}`);
1887
+ }
1888
+
1889
+ // CREATE TYPE (composite, enum, domain)
1890
+ for (const m of stripped.matchAll(
1891
+ /CREATE\s+(?:OR\s+REPLACE\s+)?TYPE\s+([`"[\w.]+)/gi
1892
+ )) {
1893
+ sigs.push(`TYPE ${_cleanName(m[1])}`);
1894
+ }
1895
+
1896
+ // CREATE SEQUENCE
1897
+ for (const m of stripped.matchAll(
1898
+ /CREATE\s+(?:OR\s+REPLACE\s+)?SEQUENCE\s+(?:IF\s+NOT\s+EXISTS\s+)?([`"[\w.]+)/gi
1899
+ )) {
1900
+ sigs.push(`SEQUENCE ${_cleanName(m[1])}`);
1901
+ }
1902
+
1903
+ return sigs;
1904
+ }
1905
+
1906
+ function _cleanName(raw) {
1907
+ return raw.replace(/^[`"[]|[`"\]]+$/g, '').trim();
1908
+ }
1909
+
1910
+ function _normalizeParams(raw) {
1911
+ if (!raw || !raw.trim()) return '';
1912
+ return raw.trim()
1913
+ .split(',')
1914
+ .map((p) => p.trim().replace(/\s+/g, ' ').split(' ').slice(0, 2).join(' '))
1915
+ .filter(Boolean)
1916
+ .join(', ');
1917
+ }
1918
+
1919
+ module.exports = { extract };
1920
+
1921
+ };
1922
+
1923
+ // ── ./src/extractors/terraform ──
1924
+ __factories["./src/extractors/terraform"] = function(module, exports) {
1925
+
1926
+ 'use strict';
1927
+
1928
+ /**
1929
+ * Extract signatures from Terraform (.tf / .tfvars) configuration files.
1930
+ * Captures resource, data, module, variable, output, locals, provider,
1931
+ * terraform blocks, and moved/import blocks.
1932
+ *
1933
+ * @param {string} src - Raw Terraform content
1934
+ * @returns {string[]} Array of signature strings
1935
+ */
1936
+ function extract(src) {
1937
+ if (!src || typeof src !== 'string') return [];
1938
+ const sigs = [];
1939
+
1940
+ // Strip single-line comments
1941
+ const stripped = src
1942
+ .replace(/\/\/[^\n]*/g, '')
1943
+ .replace(/#[^\n]*/g, '')
1944
+ .replace(/\/\*[\s\S]*?\*\//g, '');
1945
+
1946
+ // resource "<type>" "<name>" { ... }
1947
+ for (const m of stripped.matchAll(/\bresource\s+"([^"]+)"\s+"([^"]+)"\s*\{/g)) {
1948
+ sigs.push(`resource "${m[1]}" "${m[2]}"`);
1949
+ }
1950
+
1951
+ // data "<type>" "<name>" { ... }
1952
+ for (const m of stripped.matchAll(/\bdata\s+"([^"]+)"\s+"([^"]+)"\s*\{/g)) {
1953
+ sigs.push(`data "${m[1]}" "${m[2]}"`);
1954
+ }
1955
+
1956
+ // module "<name>" { ... }
1957
+ for (const m of stripped.matchAll(/\bmodule\s+"([^"]+)"\s*\{/g)) {
1958
+ sigs.push(`module "${m[1]}"`);
1959
+ }
1960
+
1961
+ // variable "<name>" { ... }
1962
+ for (const m of stripped.matchAll(/\bvariable\s+"([^"]+)"\s*\{/g)) {
1963
+ sigs.push(`variable "${m[1]}"`);
1964
+ }
1965
+
1966
+ // output "<name>" { ... }
1967
+ for (const m of stripped.matchAll(/\boutput\s+"([^"]+)"\s*\{/g)) {
1968
+ sigs.push(`output "${m[1]}"`);
1969
+ }
1970
+
1971
+ // provider "<name>" { ... }
1972
+ for (const m of stripped.matchAll(/\bprovider\s+"([^"]+)"\s*\{/g)) {
1973
+ sigs.push(`provider "${m[1]}"`);
1974
+ }
1975
+
1976
+ // locals { ... } (just mark presence; key names too noisy to enumerate)
1977
+ if (/\blocals\s*\{/.test(stripped)) {
1978
+ sigs.push('locals { ... }');
1979
+ }
1980
+
1981
+ // terraform { required_providers / backend }
1982
+ if (/\bterraform\s*\{/.test(stripped)) {
1983
+ sigs.push('terraform { ... }');
1984
+ }
1985
+
1986
+ // moved block
1987
+ for (const m of stripped.matchAll(/\bmoved\s*\{[\s\S]*?from\s*=\s*([^\n]+)/g)) {
1988
+ sigs.push(`moved from ${m[1].trim()}`);
1989
+ }
1990
+
1991
+ // import block (Terraform 1.5+)
1992
+ for (const m of stripped.matchAll(/\bimport\s*\{[\s\S]*?to\s*=\s*([^\n]+)/g)) {
1993
+ sigs.push(`import to ${m[1].trim()}`);
1994
+ }
1995
+
1996
+ return sigs;
1997
+ }
1998
+
1999
+ module.exports = { extract };
2000
+
2001
+ };
2002
+
1683
2003
  // ── ./src/extractors/deps ──
1684
2004
  __factories["./src/extractors/deps"] = function(module, exports) {
1685
2005
 
@@ -3700,7 +4020,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
3700
4020
 
3701
4021
  const SERVER_INFO = {
3702
4022
  name: 'sigmap',
3703
- version: '3.3.2',
4023
+ version: '3.3.4',
3704
4024
  description: 'SigMap MCP server — code signatures on demand',
3705
4025
  };
3706
4026
 
@@ -4619,6 +4939,11 @@ __factories["./src/eval/analyzer"] = function(module, exports) {
4619
4939
  '.css': 'css', '.scss': 'css', '.sass': 'css', '.less': 'css',
4620
4940
  '.yml': 'yaml', '.yaml': 'yaml',
4621
4941
  '.sh': 'shell', '.bash': 'shell', '.zsh': 'shell', '.fish': 'shell',
4942
+ // P1 languages
4943
+ '.sql': 'sql',
4944
+ '.graphql': 'graphql', '.gql': 'graphql',
4945
+ '.tf': 'terraform', '.tfvars': 'terraform',
4946
+ '.proto': 'protobuf',
4622
4947
  };
4623
4948
 
4624
4949
  function isDockerfile(name) { return name === 'Dockerfile' || name.startsWith('Dockerfile.'); }
@@ -5105,7 +5430,7 @@ const path = require('path');
5105
5430
  const os = require('os');
5106
5431
  const { execSync } = require('child_process');
5107
5432
 
5108
- const VERSION = '3.3.2';
5433
+ const VERSION = '3.3.4';
5109
5434
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
5110
5435
 
5111
5436
  function requireSourceOrBundled(key) {
@@ -5147,6 +5472,11 @@ const EXT_MAP = {
5147
5472
  '.css': 'css', '.scss': 'css', '.sass': 'css', '.less': 'css',
5148
5473
  '.yml': 'yaml', '.yaml': 'yaml',
5149
5474
  '.sh': 'shell', '.bash': 'shell', '.zsh': 'shell', '.fish': 'shell',
5475
+ // P1 languages
5476
+ '.sql': 'sql',
5477
+ '.graphql': 'graphql', '.gql': 'graphql',
5478
+ '.tf': 'terraform', '.tfvars': 'terraform',
5479
+ '.proto': 'protobuf',
5150
5480
  };
5151
5481
 
5152
5482
  // Dockerfile handled separately (no extension)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "3.3.2",
3
+ "version": "3.3.4",
4
4
  "description": "Zero-dependency AI context engine — 97% token reduction. No npm install. Runs on Node 18+.",
5
5
  "main": "gen-context.js",
6
6
  "exports": {
@@ -23,6 +23,7 @@
23
23
  "setup": "node gen-context.js --setup",
24
24
  "init": "node gen-context.js --init",
25
25
  "report": "node gen-context.js --report",
26
+ "audit:strategies": "node scripts/run-strategy-audit.mjs",
26
27
  "health": "node gen-context.js --health",
27
28
  "map": "node gen-project-map.js",
28
29
  "mcp": "node gen-context.js --mcp",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-cli",
3
- "version": "3.3.2",
3
+ "version": "3.3.4",
4
4
  "description": "SigMap CLI wrapper — thin adapter for programmatic CLI invocation",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -35,6 +35,11 @@ const EXT_MAP = {
35
35
  '.css': 'css', '.scss': 'css', '.sass': 'css', '.less': 'css',
36
36
  '.yml': 'yaml', '.yaml': 'yaml',
37
37
  '.sh': 'shell', '.bash': 'shell', '.zsh': 'shell', '.fish': 'shell',
38
+ // P1 languages
39
+ '.sql': 'sql',
40
+ '.graphql': 'graphql', '.gql': 'graphql',
41
+ '.tf': 'terraform', '.tfvars': 'terraform',
42
+ '.proto': 'protobuf',
38
43
  };
39
44
 
40
45
  const SRC_ROOT = path.resolve(__dirname, '..', '..', 'src');
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-core",
3
- "version": "3.3.2",
3
+ "version": "3.3.4",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -7,6 +7,142 @@ const { DEFAULTS } = require('./defaults');
7
7
  // Keys that are valid in gen-context.config.json
8
8
  const KNOWN_KEYS = new Set(Object.keys(DEFAULTS));
9
9
 
10
+ // Common top-level folder names that reliably hold source code
11
+ const COMMON_CODE_DIRS = new Set([
12
+ 'src', 'app', 'lib', 'packages', 'services', 'api', 'core', 'cmd',
13
+ 'internal', 'pkg', 'handlers', 'controllers', 'models', 'views',
14
+ 'components', 'pages', 'routes', 'middleware', 'utils', 'helpers',
15
+ 'modules', 'plugins', 'extensions', 'adapters', 'drivers',
16
+ 'examples', 'sample', 'demo', 'tests', 'test', 'spec', '__tests__',
17
+ 'hooks', 'composables', 'stores', 'features', 'domain', 'infra',
18
+ 'infrastructure', 'application', 'data', 'Sources', 'Tests',
19
+ ]);
20
+
21
+ const SUPPORTED_CODE_EXTS = new Set([
22
+ '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
23
+ '.py', '.pyw', '.java', '.kt', '.kts', '.go', '.rs', '.cs',
24
+ '.cpp', '.c', '.h', '.hpp', '.cc', '.rb', '.rake', '.php',
25
+ '.swift', '.dart', '.scala', '.sc', '.vue', '.svelte',
26
+ '.html', '.htm', '.css', '.scss', '.sass', '.less',
27
+ '.yml', '.yaml', '.sh', '.bash', '.zsh', '.fish',
28
+ '.sql', '.graphql', '.gql', '.tf', '.tfvars', '.proto',
29
+ ]);
30
+
31
+ /**
32
+ * Detect source directories for the given project root by reading manifest
33
+ * files and scanning top-level directories for code files.
34
+ *
35
+ * @param {string} cwd - Project root
36
+ * @param {string[]} excludeList - Folders to skip
37
+ * @returns {string[]}
38
+ */
39
+ function detectAutoSrcDirs(cwd, excludeList) {
40
+ const excludeSet = new Set(excludeList || []);
41
+ const candidates = new Set(DEFAULTS.srcDirs);
42
+
43
+ // ── Manifest-based detection ──────────────────────────────────────────────
44
+ const pkgPath = path.join(cwd, 'package.json');
45
+ if (fs.existsSync(pkgPath)) {
46
+ try {
47
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
48
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies, ...pkg.peerDependencies };
49
+ if (allDeps.react || allDeps.next) {
50
+ for (const d of ['src', 'app', 'pages', 'components', 'hooks', 'lib', 'utils']) candidates.add(d);
51
+ }
52
+ if (allDeps['@angular/core']) {
53
+ for (const d of ['src', 'projects', 'apps', 'libs']) candidates.add(d);
54
+ }
55
+ if (allDeps['@nestjs/core']) {
56
+ for (const d of ['src', 'libs', 'apps']) candidates.add(d);
57
+ }
58
+ if (allDeps.vue) {
59
+ for (const d of ['src', 'components', 'views', 'stores', 'composables', 'plugins']) candidates.add(d);
60
+ }
61
+ if (allDeps.svelte || allDeps['@sveltejs/kit']) {
62
+ for (const d of ['src', 'lib', 'routes']) candidates.add(d);
63
+ }
64
+ if (allDeps.nx || allDeps.turbo || allDeps.lerna || pkg.workspaces) {
65
+ for (const d of ['packages', 'apps', 'libs', 'services']) candidates.add(d);
66
+ }
67
+ } catch (_) {}
68
+ }
69
+
70
+ const hasPyproject = fs.existsSync(path.join(cwd, 'pyproject.toml'));
71
+ const hasRequirements = fs.existsSync(path.join(cwd, 'requirements.txt'));
72
+ const hasSetupPy = fs.existsSync(path.join(cwd, 'setup.py'));
73
+ if (hasPyproject || hasRequirements || hasSetupPy) {
74
+ for (const d of ['src', 'app', 'tests', 'examples']) candidates.add(d);
75
+ }
76
+
77
+ if (fs.existsSync(path.join(cwd, 'Gemfile'))) {
78
+ for (const d of ['app', 'lib', 'config', 'db', 'spec', 'test']) candidates.add(d);
79
+ }
80
+
81
+ if (fs.existsSync(path.join(cwd, 'composer.json'))) {
82
+ for (const d of ['app', 'resources', 'routes', 'database', 'tests']) candidates.add(d);
83
+ }
84
+
85
+ if (fs.existsSync(path.join(cwd, 'go.mod'))) {
86
+ for (const d of ['cmd', 'internal', 'pkg', 'api', 'handler', 'handlers', 'middleware', 'service']) candidates.add(d);
87
+ }
88
+
89
+ if (fs.existsSync(path.join(cwd, 'Cargo.toml'))) {
90
+ for (const d of ['src', 'crates', 'examples', 'tests', 'benches']) candidates.add(d);
91
+ }
92
+
93
+ const hasGradle = fs.existsSync(path.join(cwd, 'build.gradle')) ||
94
+ fs.existsSync(path.join(cwd, 'build.gradle.kts'));
95
+ const hasMaven = fs.existsSync(path.join(cwd, 'pom.xml'));
96
+ if (hasGradle || hasMaven) {
97
+ for (const d of [
98
+ 'src/main/java', 'src/main/kotlin', 'src/main/scala',
99
+ 'src/main/resources', 'src/test/java', 'src/test/kotlin',
100
+ ]) candidates.add(d);
101
+ }
102
+
103
+ if (fs.existsSync(path.join(cwd, 'pubspec.yaml'))) {
104
+ for (const d of ['lib', 'test', 'integration_test', 'example', 'bin']) candidates.add(d);
105
+ }
106
+
107
+ if (fs.existsSync(path.join(cwd, 'Package.swift'))) {
108
+ for (const d of ['Sources', 'Tests']) candidates.add(d);
109
+ }
110
+
111
+ // ── Top-level directory scan ──────────────────────────────────────────────
112
+ try {
113
+ const entries = fs.readdirSync(cwd, { withFileTypes: true });
114
+ for (const entry of entries) {
115
+ if (!entry.isDirectory()) continue;
116
+ if (entry.name.startsWith('.')) continue;
117
+ if (excludeSet.has(entry.name)) continue;
118
+
119
+ const lname = entry.name.toLowerCase();
120
+ if (COMMON_CODE_DIRS.has(entry.name) || COMMON_CODE_DIRS.has(lname)) {
121
+ candidates.add(entry.name);
122
+ continue;
123
+ }
124
+ // Unknown dir: add if it directly contains source files
125
+ const dirPath = path.join(cwd, entry.name);
126
+ try {
127
+ const subs = fs.readdirSync(dirPath, { withFileTypes: true });
128
+ const hasSrc = subs.some((s) => {
129
+ if (!s.isFile()) return false;
130
+ return SUPPORTED_CODE_EXTS.has(path.extname(s.name).toLowerCase()) || s.name === 'Dockerfile';
131
+ });
132
+ if (hasSrc) { candidates.add(entry.name); continue; }
133
+ const hasSrcSub = subs.some((s) =>
134
+ s.isDirectory() && ['src', 'lib', 'main', 'java', 'kotlin', 'scala', 'python'].includes(s.name));
135
+ if (hasSrcSub) candidates.add(entry.name);
136
+ } catch (_) {}
137
+ }
138
+ } catch (_) {}
139
+
140
+ // Only return those that exist
141
+ return Array.from(candidates).filter((d) => {
142
+ try { return fs.statSync(path.join(cwd, d)).isDirectory(); } catch (_) { return false; }
143
+ });
144
+ }
145
+
10
146
  /**
11
147
  * Load and merge configuration for a given working directory.
12
148
  *
@@ -16,7 +152,10 @@ const KNOWN_KEYS = new Set(Object.keys(DEFAULTS));
16
152
  function loadConfig(cwd) {
17
153
  const configPath = path.join(cwd, 'gen-context.config.json');
18
154
  if (!fs.existsSync(configPath)) {
19
- return deepClone(DEFAULTS);
155
+ const cfg = deepClone(DEFAULTS);
156
+ const detected = detectAutoSrcDirs(cwd, cfg.exclude);
157
+ if (detected.length > 0) cfg.srcDirs = detected;
158
+ return cfg;
20
159
  }
21
160
 
22
161
  let userConfig;
@@ -25,7 +164,10 @@ function loadConfig(cwd) {
25
164
  userConfig = JSON.parse(raw);
26
165
  } catch (err) {
27
166
  console.warn(`[sigmap] config parse error in ${configPath}: ${err.message}`);
28
- return deepClone(DEFAULTS);
167
+ const cfg = deepClone(DEFAULTS);
168
+ const detected = detectAutoSrcDirs(cwd, cfg.exclude);
169
+ if (detected.length > 0) cfg.srcDirs = detected;
170
+ return cfg;
29
171
  }
30
172
 
31
173
  // Warn on unknown keys (helps catch typos)
@@ -50,6 +192,13 @@ function loadConfig(cwd) {
50
192
  merged[key] = val;
51
193
  }
52
194
  }
195
+
196
+ // If user didn't specify srcDirs, auto-detect; fall back to DEFAULTS if nothing found
197
+ if (!Array.isArray(userConfig.srcDirs)) {
198
+ const detected = detectAutoSrcDirs(cwd, merged.exclude);
199
+ merged.srcDirs = detected.length > 0 ? detected : deepClone(DEFAULTS.srcDirs);
200
+ }
201
+
53
202
  // Backward compat (v3.0+): mirror outputs ↔ adapters
54
203
  if (merged.adapters && !Array.isArray(merged.adapters)) merged.adapters = null;
55
204
  if (!merged.adapters && Array.isArray(merged.outputs)) {
@@ -0,0 +1,66 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Extract signatures from GraphQL schema / operation files.
5
+ * Captures type, interface, enum, input, union, scalar, query, mutation,
6
+ * subscription, fragment definitions.
7
+ *
8
+ * @param {string} src - Raw GraphQL content
9
+ * @returns {string[]} Array of signature strings
10
+ */
11
+ function extract(src) {
12
+ if (!src || typeof src !== 'string') return [];
13
+ const sigs = [];
14
+
15
+ // Strip comments (# style)
16
+ const stripped = src.replace(/#[^\n]*/g, '');
17
+
18
+ // Schema type definitions: type Foo [implements Bar] { ... }
19
+ for (const m of stripped.matchAll(
20
+ /\b(type|interface|input)\s+(\w+)(?:\s+implements\s+([\w\s&]+))?\s*\{/g
21
+ )) {
22
+ const implements_ = m[3] ? ` implements ${m[3].trim().replace(/\s+/g, ' ')}` : '';
23
+ sigs.push(`${m[1]} ${m[2]}${implements_}`);
24
+ }
25
+
26
+ // enum
27
+ for (const m of stripped.matchAll(/\benum\s+(\w+)\s*\{/g)) {
28
+ sigs.push(`enum ${m[1]}`);
29
+ }
30
+
31
+ // union
32
+ for (const m of stripped.matchAll(/\bunion\s+(\w+)\s*=/g)) {
33
+ sigs.push(`union ${m[1]}`);
34
+ }
35
+
36
+ // scalar
37
+ for (const m of stripped.matchAll(/\bscalar\s+(\w+)/g)) {
38
+ sigs.push(`scalar ${m[1]}`);
39
+ }
40
+
41
+ // extend type / extend interface
42
+ for (const m of stripped.matchAll(/\bextend\s+(type|interface)\s+(\w+)/g)) {
43
+ sigs.push(`extend ${m[1]} ${m[2]}`);
44
+ }
45
+
46
+ // Query / Mutation / Subscription operations
47
+ for (const m of stripped.matchAll(
48
+ /\b(query|mutation|subscription)\s+(\w+)\s*(?:\([^)]*\))?\s*\{/g
49
+ )) {
50
+ sigs.push(`${m[1]} ${m[2]}`);
51
+ }
52
+
53
+ // Named fragments
54
+ for (const m of stripped.matchAll(/\bfragment\s+(\w+)\s+on\s+(\w+)/g)) {
55
+ sigs.push(`fragment ${m[1]} on ${m[2]}`);
56
+ }
57
+
58
+ // Top-level schema { query: ... }
59
+ if (/\bschema\s*\{/.test(stripped)) {
60
+ sigs.push('schema { ... }');
61
+ }
62
+
63
+ return sigs;
64
+ }
65
+
66
+ module.exports = { extract };
@@ -0,0 +1,63 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Extract signatures from Protocol Buffer (.proto) files.
5
+ * Captures message, enum, service, rpc, oneof, extend definitions.
6
+ *
7
+ * @param {string} src - Raw .proto content
8
+ * @returns {string[]} Array of signature strings
9
+ */
10
+ function extract(src) {
11
+ if (!src || typeof src !== 'string') return [];
12
+ const sigs = [];
13
+
14
+ // Strip single-line and block comments
15
+ const stripped = src
16
+ .replace(/\/\/[^\n]*/g, '')
17
+ .replace(/\/\*[\s\S]*?\*\//g, '');
18
+
19
+ // syntax / package / option (top-level metadata)
20
+ const syntaxM = stripped.match(/\bsyntax\s*=\s*"([^"]+)"/);
21
+ if (syntaxM) sigs.push(`syntax = "${syntaxM[1]}"`);
22
+
23
+ const pkgM = stripped.match(/\bpackage\s+([\w.]+)\s*;/);
24
+ if (pkgM) sigs.push(`package ${pkgM[1]}`);
25
+
26
+ // message <Name> { ... }
27
+ for (const m of stripped.matchAll(/\bmessage\s+(\w+)\s*\{/g)) {
28
+ sigs.push(`message ${m[1]}`);
29
+ }
30
+
31
+ // enum <Name> { ... }
32
+ for (const m of stripped.matchAll(/\benum\s+(\w+)\s*\{/g)) {
33
+ sigs.push(`enum ${m[1]}`);
34
+ }
35
+
36
+ // service <Name> { ... }
37
+ for (const m of stripped.matchAll(/\bservice\s+(\w+)\s*\{/g)) {
38
+ sigs.push(`service ${m[1]}`);
39
+ }
40
+
41
+ // rpc <Name>(<Request>) returns (<Response>)
42
+ for (const m of stripped.matchAll(
43
+ /\brpc\s+(\w+)\s*\(\s*(stream\s+)?(\w+)\s*\)\s+returns\s*\(\s*(stream\s+)?(\w+)\s*\)/g
44
+ )) {
45
+ const req = `${m[2] || ''}${m[3]}`.trim();
46
+ const res = `${m[4] || ''}${m[5]}`.trim();
47
+ sigs.push(`rpc ${m[1]}(${req}) returns (${res})`);
48
+ }
49
+
50
+ // oneof <name>
51
+ for (const m of stripped.matchAll(/\boneof\s+(\w+)\s*\{/g)) {
52
+ sigs.push(`oneof ${m[1]}`);
53
+ }
54
+
55
+ // extend <TypeName>
56
+ for (const m of stripped.matchAll(/\bextend\s+([\w.]+)\s*\{/g)) {
57
+ sigs.push(`extend ${m[1]}`);
58
+ }
59
+
60
+ return sigs;
61
+ }
62
+
63
+ module.exports = { extract };
@@ -0,0 +1,93 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Extract signatures from SQL source files.
5
+ * Captures CREATE TABLE, VIEW, INDEX, FUNCTION, PROCEDURE, TRIGGER, TYPE, SEQUENCE.
6
+ *
7
+ * @param {string} src - Raw SQL content
8
+ * @returns {string[]} Array of signature strings
9
+ */
10
+ function extract(src) {
11
+ if (!src || typeof src !== 'string') return [];
12
+ const sigs = [];
13
+
14
+ // Strip single-line comments and block comments
15
+ const stripped = src
16
+ .replace(/--[^\n]*/g, '')
17
+ .replace(/\/\*[\s\S]*?\*\//g, '');
18
+
19
+ // CREATE TABLE [IF NOT EXISTS] <name> / CREATE [TEMP] TABLE ...
20
+ for (const m of stripped.matchAll(
21
+ /CREATE\s+(?:OR\s+REPLACE\s+)?(?:TEMP(?:ORARY)?\s+)?TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([`"[\w.]+)/gi
22
+ )) {
23
+ sigs.push(`TABLE ${_cleanName(m[1])}`);
24
+ }
25
+
26
+ // CREATE VIEW / MATERIALIZED VIEW
27
+ for (const m of stripped.matchAll(
28
+ /CREATE\s+(?:OR\s+REPLACE\s+)?(?:MATERIALIZED\s+)?VIEW\s+(?:IF\s+NOT\s+EXISTS\s+)?([`"[\w.]+)/gi
29
+ )) {
30
+ sigs.push(`VIEW ${_cleanName(m[1])}`);
31
+ }
32
+
33
+ // CREATE INDEX / UNIQUE INDEX
34
+ for (const m of stripped.matchAll(
35
+ /CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:CONCURRENTLY\s+)?(?:IF\s+NOT\s+EXISTS\s+)?([`"[\w.]+)\s+ON\s+([`"[\w.]+)/gi
36
+ )) {
37
+ sigs.push(`INDEX ${_cleanName(m[1])} ON ${_cleanName(m[2])}`);
38
+ }
39
+
40
+ // CREATE FUNCTION / CREATE OR REPLACE FUNCTION
41
+ for (const m of stripped.matchAll(
42
+ /CREATE\s+(?:OR\s+REPLACE\s+)?FUNCTION\s+([`"[\w.]+)\s*\(([^)]*)\)/gi
43
+ )) {
44
+ const params = _normalizeParams(m[2]);
45
+ sigs.push(`FUNCTION ${_cleanName(m[1])}(${params})`);
46
+ }
47
+
48
+ // CREATE PROCEDURE
49
+ for (const m of stripped.matchAll(
50
+ /CREATE\s+(?:OR\s+REPLACE\s+)?PROCEDURE\s+([`"[\w.]+)\s*\(([^)]*)\)/gi
51
+ )) {
52
+ const params = _normalizeParams(m[2]);
53
+ sigs.push(`PROCEDURE ${_cleanName(m[1])}(${params})`);
54
+ }
55
+
56
+ // CREATE TRIGGER
57
+ for (const m of stripped.matchAll(
58
+ /CREATE\s+(?:OR\s+REPLACE\s+)?(?:CONSTRAINT\s+)?TRIGGER\s+([`"[\w.]+)/gi
59
+ )) {
60
+ sigs.push(`TRIGGER ${_cleanName(m[1])}`);
61
+ }
62
+
63
+ // CREATE TYPE (composite, enum, domain)
64
+ for (const m of stripped.matchAll(
65
+ /CREATE\s+(?:OR\s+REPLACE\s+)?TYPE\s+([`"[\w.]+)/gi
66
+ )) {
67
+ sigs.push(`TYPE ${_cleanName(m[1])}`);
68
+ }
69
+
70
+ // CREATE SEQUENCE
71
+ for (const m of stripped.matchAll(
72
+ /CREATE\s+(?:OR\s+REPLACE\s+)?SEQUENCE\s+(?:IF\s+NOT\s+EXISTS\s+)?([`"[\w.]+)/gi
73
+ )) {
74
+ sigs.push(`SEQUENCE ${_cleanName(m[1])}`);
75
+ }
76
+
77
+ return sigs;
78
+ }
79
+
80
+ function _cleanName(raw) {
81
+ return raw.replace(/^[`"[]|[`"\]]+$/g, '').trim();
82
+ }
83
+
84
+ function _normalizeParams(raw) {
85
+ if (!raw || !raw.trim()) return '';
86
+ return raw.trim()
87
+ .split(',')
88
+ .map((p) => p.trim().replace(/\s+/g, ' ').split(' ').slice(0, 2).join(' '))
89
+ .filter(Boolean)
90
+ .join(', ');
91
+ }
92
+
93
+ module.exports = { extract };
@@ -0,0 +1,74 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Extract signatures from Terraform (.tf / .tfvars) configuration files.
5
+ * Captures resource, data, module, variable, output, locals, provider,
6
+ * terraform blocks, and moved/import blocks.
7
+ *
8
+ * @param {string} src - Raw Terraform content
9
+ * @returns {string[]} Array of signature strings
10
+ */
11
+ function extract(src) {
12
+ if (!src || typeof src !== 'string') return [];
13
+ const sigs = [];
14
+
15
+ // Strip single-line comments
16
+ const stripped = src
17
+ .replace(/\/\/[^\n]*/g, '')
18
+ .replace(/#[^\n]*/g, '')
19
+ .replace(/\/\*[\s\S]*?\*\//g, '');
20
+
21
+ // resource "<type>" "<name>" { ... }
22
+ for (const m of stripped.matchAll(/\bresource\s+"([^"]+)"\s+"([^"]+)"\s*\{/g)) {
23
+ sigs.push(`resource "${m[1]}" "${m[2]}"`);
24
+ }
25
+
26
+ // data "<type>" "<name>" { ... }
27
+ for (const m of stripped.matchAll(/\bdata\s+"([^"]+)"\s+"([^"]+)"\s*\{/g)) {
28
+ sigs.push(`data "${m[1]}" "${m[2]}"`);
29
+ }
30
+
31
+ // module "<name>" { ... }
32
+ for (const m of stripped.matchAll(/\bmodule\s+"([^"]+)"\s*\{/g)) {
33
+ sigs.push(`module "${m[1]}"`);
34
+ }
35
+
36
+ // variable "<name>" { ... }
37
+ for (const m of stripped.matchAll(/\bvariable\s+"([^"]+)"\s*\{/g)) {
38
+ sigs.push(`variable "${m[1]}"`);
39
+ }
40
+
41
+ // output "<name>" { ... }
42
+ for (const m of stripped.matchAll(/\boutput\s+"([^"]+)"\s*\{/g)) {
43
+ sigs.push(`output "${m[1]}"`);
44
+ }
45
+
46
+ // provider "<name>" { ... }
47
+ for (const m of stripped.matchAll(/\bprovider\s+"([^"]+)"\s*\{/g)) {
48
+ sigs.push(`provider "${m[1]}"`);
49
+ }
50
+
51
+ // locals { ... } (just mark presence; key names too noisy to enumerate)
52
+ if (/\blocals\s*\{/.test(stripped)) {
53
+ sigs.push('locals { ... }');
54
+ }
55
+
56
+ // terraform { required_providers / backend }
57
+ if (/\bterraform\s*\{/.test(stripped)) {
58
+ sigs.push('terraform { ... }');
59
+ }
60
+
61
+ // moved block
62
+ for (const m of stripped.matchAll(/\bmoved\s*\{[\s\S]*?from\s*=\s*([^\n]+)/g)) {
63
+ sigs.push(`moved from ${m[1].trim()}`);
64
+ }
65
+
66
+ // import block (Terraform 1.5+)
67
+ for (const m of stripped.matchAll(/\bimport\s*\{[\s\S]*?to\s*=\s*([^\n]+)/g)) {
68
+ sigs.push(`import to ${m[1].trim()}`);
69
+ }
70
+
71
+ return sigs;
72
+ }
73
+
74
+ module.exports = { extract };