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 +31 -0
- package/README.md +19 -4
- package/gen-context.js +332 -2
- package/package.json +2 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/index.js +5 -0
- package/packages/core/package.json +1 -1
- package/src/config/loader.js +151 -2
- package/src/extractors/graphql.js +66 -0
- package/src/extractors/protobuf.js +63 -0
- package/src/extractors/sql.js +93 -0
- package/src/extractors/terraform.js +74 -0
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) |
|
|
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
|
|
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
|
-
>
|
|
473
|
+
> 25 languages. All implemented with zero external dependencies — pure regex + Node built-ins.
|
|
463
474
|
|
|
464
475
|
<details>
|
|
465
|
-
<summary><strong>Show all
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|
package/packages/core/index.js
CHANGED
|
@@ -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');
|
package/src/config/loader.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 };
|