sigmap 3.3.2 → 3.3.3

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,24 @@ Format: [Semantic Versioning](https://semver.org/)
10
10
 
11
11
  ---
12
12
 
13
+ ## [3.3.3] — 2026-04-14 — Auto srcDirs + P1 Extractors
14
+
15
+ ### Added
16
+ - **P1 extractor support** for additional high-value formats:
17
+ - SQL: `.sql`
18
+ - GraphQL: `.graphql`, `.gql`
19
+ - Terraform: `.tf`, `.tfvars`
20
+ - Protobuf: `.proto`
21
+ - **Extractor registration updates** across runtime and core mapping so the new file types are parsed consistently.
22
+
23
+ ### Changed
24
+ - **Auto source directory detection** (framework- and manifest-aware) in config loading and strategy auditing.
25
+ - **Auto maxDepth tuning** in strategy audit based on repository file-depth distribution.
26
+ - **Benchmark strategy-audit reports refreshed** to reflect improved source discovery and coverage.
27
+ - **Language support count updated from 21 to 25** across core README and extension README.
28
+
29
+ ---
30
+
13
31
  ## [3.3.1] — 2026-04-10 — Patch: `--each --adapter` flag combination
14
32
 
15
33
  ### Fixed
package/README.md CHANGED
@@ -19,6 +19,10 @@
19
19
  npx sigmap # 10 seconds. zero config. your AI never reads the wrong file again.
20
20
  ```
21
21
 
22
+ <div align="center">
23
+ <img src="demo.gif" alt="SigMap demo — reducing 80K tokens to 4K in under 10 seconds" width="760" />
24
+ </div>
25
+
22
26
  <details>
23
27
  <summary><strong>Trust signals</strong></summary>
24
28
 
@@ -68,7 +72,7 @@ npx sigmap # 10 seconds. zero config. your AI never reads the wrong file again
68
72
  | [Standalone binaries](docs/readmes/binaries.md) | macOS, Linux, Windows — no Node required |
69
73
  | [VS Code extension](#-vs-code-extension) | Status bar, stale alerts, commands |
70
74
  | [JetBrains plugin](#-jetbrains-plugin) | IntelliJ IDEA, WebStorm, PyCharm support |
71
- | [Languages supported](#-languages-supported) | 21 languages |
75
+ | [Languages supported](#-languages-supported) | 25 languages |
72
76
  | [Context strategies](#-context-strategies) | full / per-module / hot-cold |
73
77
  | [MCP server](#-mcp-server) | 8 on-demand tools |
74
78
  | [CLI reference](#-cli-reference) | All flags |
@@ -91,7 +95,7 @@ SigMap scans your source files and extracts only the **function and class signat
91
95
  Your codebase
92
96
 
93
97
 
94
- sigmap ─────────► extracts signatures from 21 languages
98
+ sigmap ─────────► extracts signatures from 25 languages
95
99
 
96
100
 
97
101
  .github/copilot-instructions.md ◄── auto-read by Copilot / Claude / Cursor
@@ -433,6 +437,11 @@ The official SigMap VS Code extension keeps your context fresh without any manua
433
437
  | **Open context command** | `SigMap: Open Context File` — opens `.github/copilot-instructions.md` |
434
438
  | **Script path setting** | `sigmap.scriptPath` — override the path to the `sigmap` binary or `gen-context.js` |
435
439
 
440
+ <div align="center">
441
+ <img src="vscode.gif" alt="SigMap VS Code extension — status bar, stale notification, regenerate context" width="760" />
442
+ </div>
443
+
444
+
436
445
  Activates on startup (`onStartupFinished`) — loads within 3 s, never blocks editor startup.
437
446
 
438
447
  **Install:** [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=manojmallick.sigmap) | [Open VSX Registry](https://open-vsx.org/extension/manojmallick/sigmap)
@@ -459,10 +468,10 @@ Compatible with **IntelliJ IDEA 2024.1+** (Community & Ultimate), **WebStorm**,
459
468
 
460
469
  ## 🌐 Languages supported
461
470
 
462
- > 21 languages. All implemented with zero external dependencies — pure regex + Node built-ins.
471
+ > 25 languages. All implemented with zero external dependencies — pure regex + Node built-ins.
463
472
 
464
473
  <details>
465
- <summary><strong>Show all 21 languages</strong></summary>
474
+ <summary><strong>Show all 25 languages</strong></summary>
466
475
 
467
476
  | Language | Extensions | Extracts |
468
477
  |---|---|---|
@@ -486,6 +495,10 @@ Compatible with **IntelliJ IDEA 2024.1+** (Community & Ultimate), **WebStorm**,
486
495
  | CSS/SCSS | `.css` `.scss` `.sass` `.less` | custom properties and keyframes |
487
496
  | YAML | `.yml` `.yaml` | top-level keys and pipeline jobs |
488
497
  | Shell | `.sh` `.bash` `.zsh` `.fish` | function declarations |
498
+ | SQL | `.sql` | tables, views, indexes, functions, procedures |
499
+ | GraphQL | `.graphql` `.gql` | types, interfaces, enums, operations, fragments |
500
+ | Terraform | `.tf` `.tfvars` | resources, modules, variables, outputs |
501
+ | Protobuf | `.proto` | messages, services, rpc, enums |
489
502
  | Dockerfile | `Dockerfile` `Dockerfile.*` | stages and key instructions |
490
503
 
491
504
  </details>
package/gen-context.js CHANGED
@@ -3700,7 +3700,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
3700
3700
 
3701
3701
  const SERVER_INFO = {
3702
3702
  name: 'sigmap',
3703
- version: '3.3.2',
3703
+ version: '3.3.3',
3704
3704
  description: 'SigMap MCP server — code signatures on demand',
3705
3705
  };
3706
3706
 
@@ -4619,6 +4619,11 @@ __factories["./src/eval/analyzer"] = function(module, exports) {
4619
4619
  '.css': 'css', '.scss': 'css', '.sass': 'css', '.less': 'css',
4620
4620
  '.yml': 'yaml', '.yaml': 'yaml',
4621
4621
  '.sh': 'shell', '.bash': 'shell', '.zsh': 'shell', '.fish': 'shell',
4622
+ // P1 languages
4623
+ '.sql': 'sql',
4624
+ '.graphql': 'graphql', '.gql': 'graphql',
4625
+ '.tf': 'terraform', '.tfvars': 'terraform',
4626
+ '.proto': 'protobuf',
4622
4627
  };
4623
4628
 
4624
4629
  function isDockerfile(name) { return name === 'Dockerfile' || name.startsWith('Dockerfile.'); }
@@ -5105,7 +5110,7 @@ const path = require('path');
5105
5110
  const os = require('os');
5106
5111
  const { execSync } = require('child_process');
5107
5112
 
5108
- const VERSION = '3.3.2';
5113
+ const VERSION = '3.3.3';
5109
5114
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
5110
5115
 
5111
5116
  function requireSourceOrBundled(key) {
@@ -5147,6 +5152,11 @@ const EXT_MAP = {
5147
5152
  '.css': 'css', '.scss': 'css', '.sass': 'css', '.less': 'css',
5148
5153
  '.yml': 'yaml', '.yaml': 'yaml',
5149
5154
  '.sh': 'shell', '.bash': 'shell', '.zsh': 'shell', '.fish': 'shell',
5155
+ // P1 languages
5156
+ '.sql': 'sql',
5157
+ '.graphql': 'graphql', '.gql': 'graphql',
5158
+ '.tf': 'terraform', '.tfvars': 'terraform',
5159
+ '.proto': 'protobuf',
5150
5160
  };
5151
5161
 
5152
5162
  // 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.3",
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.3",
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.3",
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 };