sigmap 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.contextignore.example +34 -0
  2. package/CHANGELOG.md +402 -0
  3. package/LICENSE +21 -0
  4. package/README.md +601 -0
  5. package/gen-context.config.json.example +40 -0
  6. package/gen-context.js +4316 -0
  7. package/gen-project-map.js +172 -0
  8. package/package.json +67 -0
  9. package/src/config/defaults.js +61 -0
  10. package/src/config/loader.js +60 -0
  11. package/src/extractors/cpp.js +60 -0
  12. package/src/extractors/csharp.js +48 -0
  13. package/src/extractors/css.js +51 -0
  14. package/src/extractors/dart.js +58 -0
  15. package/src/extractors/dockerfile.js +49 -0
  16. package/src/extractors/go.js +61 -0
  17. package/src/extractors/html.js +39 -0
  18. package/src/extractors/java.js +49 -0
  19. package/src/extractors/javascript.js +82 -0
  20. package/src/extractors/kotlin.js +62 -0
  21. package/src/extractors/php.js +62 -0
  22. package/src/extractors/python.js +69 -0
  23. package/src/extractors/ruby.js +43 -0
  24. package/src/extractors/rust.js +72 -0
  25. package/src/extractors/scala.js +67 -0
  26. package/src/extractors/shell.js +43 -0
  27. package/src/extractors/svelte.js +51 -0
  28. package/src/extractors/swift.js +63 -0
  29. package/src/extractors/typescript.js +109 -0
  30. package/src/extractors/vue.js +66 -0
  31. package/src/extractors/yaml.js +59 -0
  32. package/src/format/cache.js +53 -0
  33. package/src/health/scorer.js +123 -0
  34. package/src/map/class-hierarchy.js +117 -0
  35. package/src/map/import-graph.js +148 -0
  36. package/src/map/route-table.js +127 -0
  37. package/src/mcp/handlers.js +433 -0
  38. package/src/mcp/server.js +128 -0
  39. package/src/mcp/tools.js +125 -0
  40. package/src/routing/classifier.js +102 -0
  41. package/src/routing/hints.js +103 -0
  42. package/src/security/patterns.js +51 -0
  43. package/src/security/scanner.js +36 -0
  44. package/src/tracking/logger.js +115 -0
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * gen-project-map.js — SigMap v0.4
6
+ * Generates PROJECT_MAP.md with import graph, class hierarchy, and route table.
7
+ *
8
+ * Usage:
9
+ * node gen-project-map.js Generate PROJECT_MAP.md
10
+ * node gen-project-map.js --version Print version and exit
11
+ * node gen-project-map.js --help Print usage and exit
12
+ *
13
+ * Zero npm dependencies — runs on Node.js 18+ with nothing installed.
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+
19
+ const VERSION = '0.4.0';
20
+ const OUTPUT_FILE = 'PROJECT_MAP.md';
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // CLI
24
+ // ---------------------------------------------------------------------------
25
+ const args = process.argv.slice(2);
26
+
27
+ if (args.includes('--version')) {
28
+ console.log(`gen-project-map.js v${VERSION}`);
29
+ process.exit(0);
30
+ }
31
+
32
+ if (args.includes('--help')) {
33
+ console.log([
34
+ `gen-project-map.js v${VERSION} — SigMap project map generator`,
35
+ '',
36
+ 'Usage:',
37
+ ' node gen-project-map.js Generate PROJECT_MAP.md',
38
+ ' node gen-project-map.js --version Print version and exit',
39
+ ' node gen-project-map.js --help Print this message',
40
+ '',
41
+ 'Configuration: gen-context.config.json (same file as gen-context.js)',
42
+ ' srcDirs — directories to scan (default: ["src","app","lib",...])',
43
+ ' exclude — patterns to skip (default: ["node_modules",".git",...])',
44
+ ' maxDepth — recursion limit (default: 6)',
45
+ '',
46
+ 'Output: PROJECT_MAP.md (project root)',
47
+ ].join('\n'));
48
+ process.exit(0);
49
+ }
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Config — reuse src/config/loader.js
53
+ // ---------------------------------------------------------------------------
54
+ const { loadConfig } = require('./src/config/loader');
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Compact inline file walker
58
+ // ---------------------------------------------------------------------------
59
+ const DEFAULT_EXCLUDE = new Set([
60
+ 'node_modules', '.git', 'dist', 'build', 'out',
61
+ '__pycache__', '.next', 'coverage', 'target', 'vendor', '.context',
62
+ ]);
63
+
64
+ function walkDir(dir, excludeSet, maxDepth, depth, results) {
65
+ if (depth > maxDepth) return;
66
+ let entries;
67
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; }
68
+ for (const entry of entries) {
69
+ if (excludeSet.has(entry.name)) continue;
70
+ const full = path.join(dir, entry.name);
71
+ if (entry.isDirectory()) {
72
+ walkDir(full, excludeSet, maxDepth, depth + 1, results);
73
+ } else if (entry.isFile()) {
74
+ results.push(full);
75
+ }
76
+ }
77
+ }
78
+
79
+ function buildFileList(cwd, srcDirs, exclude, maxDepth) {
80
+ const excludeSet = new Set([...DEFAULT_EXCLUDE, ...exclude]);
81
+ const files = [];
82
+ for (const dir of srcDirs) {
83
+ const abs = path.isAbsolute(dir) ? dir : path.join(cwd, dir);
84
+ if (!fs.existsSync(abs)) continue;
85
+ walkDir(abs, excludeSet, maxDepth, 0, files);
86
+ }
87
+ // Also check for files directly in root (routes, config files, etc.)
88
+ // but only when srcDirs are explicit; skip to avoid noise
89
+ return files;
90
+ }
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // Analyzers (lazy-required so errors don't abort the run)
94
+ // ---------------------------------------------------------------------------
95
+ function runAnalyzer(name, files, cwd) {
96
+ try {
97
+ const mod = require(`./src/map/${name}`);
98
+ return mod.analyze(files, cwd) || '';
99
+ } catch (err) {
100
+ console.warn(`[sigmap] ${name} analyzer failed: ${err.message}`);
101
+ return '';
102
+ }
103
+ }
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // Format PROJECT_MAP.md
107
+ // ---------------------------------------------------------------------------
108
+ function formatOutput(sections) {
109
+ const now = new Date().toISOString().slice(0, 10);
110
+ const lines = [
111
+ '# Project Map',
112
+ `<!-- Generated by gen-project-map.js v${VERSION} on ${now} -->`,
113
+ '',
114
+ ];
115
+
116
+ const parts = [
117
+ { key: 'imports', header: '### Import graph', content: sections.imports },
118
+ { key: 'classes', header: '### Class hierarchy', content: sections.classes },
119
+ { key: 'routes', header: '### Route table', content: sections.routes },
120
+ ];
121
+
122
+ for (const { header, content } of parts) {
123
+ lines.push(header);
124
+ lines.push('');
125
+ if (content) {
126
+ lines.push(content);
127
+ } else {
128
+ lines.push('_No entries found._');
129
+ }
130
+ lines.push('');
131
+ }
132
+
133
+ return lines.join('\n');
134
+ }
135
+
136
+ // ---------------------------------------------------------------------------
137
+ // Main
138
+ // ---------------------------------------------------------------------------
139
+ function main() {
140
+ const cwd = process.cwd();
141
+ const config = loadConfig(cwd);
142
+ const { srcDirs, exclude, maxDepth } = config;
143
+
144
+ console.log(`[sigmap] scanning ${srcDirs.join(', ')} (maxDepth=${maxDepth}) …`);
145
+
146
+ const files = buildFileList(cwd, srcDirs, exclude, maxDepth);
147
+
148
+ if (files.length === 0) {
149
+ console.warn(`[sigmap] no source files found — check srcDirs in gen-context.config.json`);
150
+ } else {
151
+ console.log(`[sigmap] found ${files.length} source files`);
152
+ }
153
+
154
+ const sections = {
155
+ imports: runAnalyzer('import-graph', files, cwd),
156
+ classes: runAnalyzer('class-hierarchy', files, cwd),
157
+ routes: runAnalyzer('route-table', files, cwd),
158
+ };
159
+
160
+ const output = formatOutput(sections);
161
+ const outPath = path.join(cwd, OUTPUT_FILE);
162
+
163
+ try {
164
+ fs.writeFileSync(outPath, output, 'utf8');
165
+ console.log(`[sigmap] wrote ${OUTPUT_FILE} (${output.length} bytes)`);
166
+ } catch (err) {
167
+ console.error(`[sigmap] failed to write ${OUTPUT_FILE}: ${err.message}`);
168
+ process.exit(1);
169
+ }
170
+ }
171
+
172
+ main();
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "sigmap",
3
+ "version": "1.5.0",
4
+ "description": "Zero-dependency AI context engine — 97% token reduction. No npm install. Runs on Node 18+.",
5
+ "main": "gen-context.js",
6
+ "bin": {
7
+ "sigmap": "./gen-context.js",
8
+ "gen-context": "./gen-context.js",
9
+ "gen-project-map": "./gen-project-map.js"
10
+ },
11
+ "scripts": {
12
+ "test": "node test/run.js",
13
+ "test:integration": "node test/integration/strategy.test.js && node test/integration/secret-scan.test.js && node test/integration/token-budget.test.js && node test/integration/mcp-server.test.js",
14
+ "test:all": "node test/run.js && node test/integration/strategy.test.js && node test/integration/secret-scan.test.js",
15
+ "generate": "node gen-context.js",
16
+ "watch": "node gen-context.js --watch",
17
+ "setup": "node gen-context.js --setup",
18
+ "init": "node gen-context.js --init",
19
+ "report": "node gen-context.js --report",
20
+ "health": "node gen-context.js --health",
21
+ "map": "node gen-project-map.js",
22
+ "mcp": "node gen-context.js --mcp"
23
+ },
24
+ "files": [
25
+ "gen-context.js",
26
+ "gen-project-map.js",
27
+ "src/",
28
+ "README.md",
29
+ "LICENSE",
30
+ "CHANGELOG.md",
31
+ ".contextignore.example",
32
+ "gen-context.config.json.example"
33
+ ],
34
+ "keywords": [
35
+ "ai",
36
+ "context",
37
+ "copilot",
38
+ "claude",
39
+ "cursor",
40
+ "windsurf",
41
+ "llm",
42
+ "tokens",
43
+ "token-reduction",
44
+ "code-signatures",
45
+ "ai-context",
46
+ "zero-dependency",
47
+ "mcp",
48
+ "github-copilot"
49
+ ],
50
+ "author": {
51
+ "name": "Manoj Mallick",
52
+ "url": "https://github.com/manojmallick"
53
+ },
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "https://github.com/manojmallick/sigmap.git"
57
+ },
58
+ "homepage": "https://manojmallick.github.io/sigmap/",
59
+ "bugs": {
60
+ "url": "https://github.com/manojmallick/sigmap/issues"
61
+ },
62
+ "license": "MIT",
63
+ "engines": {
64
+ "node": ">=18.0.0"
65
+ },
66
+ "devDependencies": {}
67
+ }
@@ -0,0 +1,61 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Default configuration values for SigMap.
5
+ * All keys documented here. Override via gen-context.config.json.
6
+ */
7
+ const DEFAULTS = {
8
+ // Primary output file (used when outputs includes 'copilot')
9
+ output: '.github/copilot-instructions.md',
10
+
11
+ // Output targets: 'copilot' | 'claude' | 'cursor' | 'windsurf'
12
+ outputs: ['copilot'],
13
+
14
+ // Directories to scan (relative to project root)
15
+ srcDirs: ['src', 'app', 'lib', 'packages', 'services', 'api'],
16
+
17
+ // Directory/file names to exclude entirely
18
+ exclude: [
19
+ 'node_modules', '.git', 'dist', 'build', 'out',
20
+ '__pycache__', '.next', 'coverage', 'target', 'vendor',
21
+ '.context',
22
+ ],
23
+
24
+ // Maximum directory depth to recurse
25
+ maxDepth: 6,
26
+
27
+ // Maximum signatures extracted per file
28
+ maxSigsPerFile: 25,
29
+
30
+ // Maximum tokens in final output before budget enforcement kicks in
31
+ maxTokens: 6000,
32
+
33
+ // Scan signatures for secrets and redact matches
34
+ secretScan: true,
35
+
36
+ // Auto-detect monorepo packages and write per-package output files
37
+ monorepo: false,
38
+
39
+ // Sort recently git-committed files higher in output
40
+ diffPriority: true,
41
+
42
+ // Debounce delay (ms) between file-system events and regeneration in watch mode
43
+ watchDebounce: 300,
44
+
45
+ // Append model routing hints section to the context output
46
+ // Routes files to fast/balanced/powerful model tiers based on complexity
47
+ routing: false,
48
+
49
+ // Output format: 'default' (markdown only) | 'cache' (also write Anthropic prompt-cache JSON)
50
+ format: 'default',
51
+
52
+ // Append run metrics to .context/usage.ndjson after each generate
53
+ tracking: false,
54
+
55
+ // MCP server configuration
56
+ mcp: {
57
+ autoRegister: true,
58
+ },
59
+ };
60
+
61
+ module.exports = { DEFAULTS };
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { DEFAULTS } = require('./defaults');
6
+
7
+ // Keys that are valid in gen-context.config.json
8
+ const KNOWN_KEYS = new Set(Object.keys(DEFAULTS));
9
+
10
+ /**
11
+ * Load and merge configuration for a given working directory.
12
+ *
13
+ * @param {string} cwd - Project root directory
14
+ * @returns {object} Merged config (DEFAULTS + user overrides)
15
+ */
16
+ function loadConfig(cwd) {
17
+ const configPath = path.join(cwd, 'gen-context.config.json');
18
+ if (!fs.existsSync(configPath)) {
19
+ return deepClone(DEFAULTS);
20
+ }
21
+
22
+ let userConfig;
23
+ try {
24
+ const raw = fs.readFileSync(configPath, 'utf8');
25
+ userConfig = JSON.parse(raw);
26
+ } catch (err) {
27
+ console.warn(`[sigmap] config parse error in ${configPath}: ${err.message}`);
28
+ return deepClone(DEFAULTS);
29
+ }
30
+
31
+ // Warn on unknown keys (helps catch typos)
32
+ for (const key of Object.keys(userConfig)) {
33
+ if (key.startsWith('_')) continue; // allow _comment etc.
34
+ if (!KNOWN_KEYS.has(key)) {
35
+ console.warn(`[sigmap] unknown config key: "${key}" (ignored)`);
36
+ }
37
+ }
38
+
39
+ // Deep merge: top-level known keys from user override defaults
40
+ // For object values (e.g. mcp), merge one level deep
41
+ const merged = deepClone(DEFAULTS);
42
+ for (const key of Object.keys(userConfig)) {
43
+ if (key.startsWith('_')) continue;
44
+ if (!KNOWN_KEYS.has(key)) continue; // skip unknown keys
45
+ const val = userConfig[key];
46
+ if (val !== null && typeof val === 'object' && !Array.isArray(val) &&
47
+ typeof merged[key] === 'object' && !Array.isArray(merged[key])) {
48
+ merged[key] = Object.assign({}, merged[key], val);
49
+ } else {
50
+ merged[key] = val;
51
+ }
52
+ }
53
+ return merged;
54
+ }
55
+
56
+ function deepClone(obj) {
57
+ return JSON.parse(JSON.stringify(obj));
58
+ }
59
+
60
+ module.exports = { loadConfig };
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Extract signatures from C/C++ source code.
5
+ * @param {string} src - Raw file content
6
+ * @returns {string[]} Array of signature strings
7
+ */
8
+ function extract(src) {
9
+ if (!src || typeof src !== 'string') return [];
10
+ const sigs = [];
11
+
12
+ const stripped = src
13
+ .replace(/\/\/.*$/gm, '')
14
+ .replace(/\/\*[\s\S]*?\*\//g, '');
15
+
16
+ // Classes and structs
17
+ const classRe = /^(?:class|struct)\s+(\w+)(?:\s*:\s*(?:public|protected|private)\s+[\w:]+)?\s*\{/gm;
18
+ for (const m of stripped.matchAll(classRe)) {
19
+ const kind = m[0].trimStart().startsWith('class') ? 'class' : 'struct';
20
+ sigs.push(`${kind} ${m[1]}`);
21
+ const block = extractBlock(stripped, m.index + m[0].length);
22
+ for (const meth of extractMembers(block)) sigs.push(` ${meth}`);
23
+ }
24
+
25
+ // Top-level function declarations/definitions (not inside a class)
26
+ for (const m of stripped.matchAll(/^(?!class|struct|if|for|while|switch)[\w:*&<> ]+\s+(\w+)\s*\(([^)]*)\)\s*(?:const\s*)?\{/gm)) {
27
+ if (m[1].startsWith('_')) continue;
28
+ sigs.push(`${m[1]}(${normalizeParams(m[2])})`);
29
+ }
30
+
31
+ return sigs.slice(0, 25);
32
+ }
33
+
34
+ function extractBlock(src, startIndex) {
35
+ let depth = 1, i = startIndex;
36
+ const end = Math.min(src.length, startIndex + 4000);
37
+ while (i < end && depth > 0) {
38
+ if (src[i] === '{') depth++;
39
+ else if (src[i] === '}') depth--;
40
+ i++;
41
+ }
42
+ return src.slice(startIndex, i - 1);
43
+ }
44
+
45
+ function extractMembers(block) {
46
+ const members = [];
47
+ const methodRe = /^\s+(?:virtual\s+|static\s+|inline\s+)?(?!private:|protected:|public:)[\w:*&<> ]+\s+(\w+)\s*\(([^)]*)\)\s*(?:const\s*)?(?:override\s*)?(?:=\s*0\s*)?;/gm;
48
+ for (const m of block.matchAll(methodRe)) {
49
+ if (m[1].startsWith('_')) continue;
50
+ members.push(`${m[1]}(${normalizeParams(m[2])})`);
51
+ }
52
+ return members.slice(0, 8);
53
+ }
54
+
55
+ function normalizeParams(params) {
56
+ if (!params) return '';
57
+ return params.trim().replace(/\s+/g, ' ');
58
+ }
59
+
60
+ module.exports = { extract };
@@ -0,0 +1,48 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Extract signatures from C# source code.
5
+ * @param {string} src - Raw file content
6
+ * @returns {string[]} Array of signature strings
7
+ */
8
+ function extract(src) {
9
+ if (!src || typeof src !== 'string') return [];
10
+ const sigs = [];
11
+
12
+ const stripped = src
13
+ .replace(/\/\/.*$/gm, '')
14
+ .replace(/\/\*[\s\S]*?\*\//g, '');
15
+
16
+ // Classes and interfaces
17
+ const typeRe = /^\s*(?:public\s+|internal\s+|protected\s+)?(?:abstract\s+|sealed\s+|static\s+)?(class|interface|enum|record|struct)\s+(\w+)(?:<[^{]*>)?(?:\s*:\s*[\w<>, .]+)?\s*\{/gm;
18
+ for (const m of stripped.matchAll(typeRe)) {
19
+ sigs.push(`${m[1]} ${m[2]}`);
20
+ const block = extractBlock(stripped, m.index + m[0].length);
21
+ for (const meth of extractMembers(block)) sigs.push(` ${meth}`);
22
+ }
23
+
24
+ return sigs.slice(0, 25);
25
+ }
26
+
27
+ function extractBlock(src, startIndex) {
28
+ let depth = 1, i = startIndex;
29
+ const end = Math.min(src.length, startIndex + 5000);
30
+ while (i < end && depth > 0) {
31
+ if (src[i] === '{') depth++;
32
+ else if (src[i] === '}') depth--;
33
+ i++;
34
+ }
35
+ return src.slice(startIndex, i - 1);
36
+ }
37
+
38
+ function extractMembers(block) {
39
+ const members = [];
40
+ const methodRe = /^\s+(?:public|internal|protected)\s+(?:static\s+|virtual\s+|override\s+|async\s+)*(?:[\w<>\[\]?]+\s+)+(\w+)\s*\(([^)]*)\)/gm;
41
+ for (const m of block.matchAll(methodRe)) {
42
+ const sig = m[0].trim().split('{')[0].trim();
43
+ members.push(sig);
44
+ }
45
+ return members.slice(0, 8);
46
+ }
47
+
48
+ module.exports = { extract };
@@ -0,0 +1,51 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Extract signatures from CSS/SCSS/SASS/Less source code.
5
+ * @param {string} src - Raw file content
6
+ * @returns {string[]} Array of signature strings
7
+ */
8
+ function extract(src) {
9
+ if (!src || typeof src !== 'string') return [];
10
+ const sigs = [];
11
+
12
+ const stripped = src
13
+ .replace(/\/\/.*$/gm, '')
14
+ .replace(/\/\*[\s\S]*?\*\//g, '');
15
+
16
+ // CSS custom properties (variables)
17
+ const rootMatch = stripped.match(/:root\s*\{([^}]*)\}/);
18
+ if (rootMatch) {
19
+ for (const m of rootMatch[1].matchAll(/(--[\w-]+)\s*:/g)) {
20
+ sigs.push(`var ${m[1]}`);
21
+ }
22
+ }
23
+
24
+ // SCSS/Less variables
25
+ for (const m of stripped.matchAll(/^(\$[\w-]+)\s*:/gm)) {
26
+ sigs.push(`$var ${m[1]}`);
27
+ }
28
+
29
+ // SCSS mixins
30
+ for (const m of stripped.matchAll(/^@mixin\s+([\w-]+)(?:\s*\(([^)]*)\))?/gm)) {
31
+ const params = m[2] ? `(${m[2].trim()})` : '';
32
+ sigs.push(`@mixin ${m[1]}${params}`);
33
+ }
34
+
35
+ // SCSS functions
36
+ for (const m of stripped.matchAll(/^@function\s+([\w-]+)\s*\(([^)]*)\)/gm)) {
37
+ sigs.push(`@function ${m[1]}(${m[2].trim()})`);
38
+ }
39
+
40
+ // Key class names (top-level)
41
+ const classNames = new Set();
42
+ for (const m of stripped.matchAll(/^\.([\w-]+)(?=[^{]*\{)/gm)) {
43
+ classNames.add(m[1]);
44
+ if (classNames.size >= 10) break;
45
+ }
46
+ for (const name of classNames) sigs.push(`.${name}`);
47
+
48
+ return sigs.slice(0, 25);
49
+ }
50
+
51
+ module.exports = { extract };
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Extract signatures from Dart source code.
5
+ * @param {string} src - Raw file content
6
+ * @returns {string[]} Array of signature strings
7
+ */
8
+ function extract(src) {
9
+ if (!src || typeof src !== 'string') return [];
10
+ const sigs = [];
11
+
12
+ const stripped = src
13
+ .replace(/\/\/.*$/gm, '')
14
+ .replace(/\/\*[\s\S]*?\*\//g, '');
15
+
16
+ // Classes and abstract classes
17
+ for (const m of stripped.matchAll(/^(?:abstract\s+)?class\s+(\w+)(?:<[^{]*>)?(?:\s+extends\s+[\w<>, ]+)?(?:\s+(?:implements|with|on)\s+[\w<>, ]+)?\s*\{/gm)) {
18
+ const abs = m[0].trimStart().startsWith('abstract') ? 'abstract ' : '';
19
+ sigs.push(`${abs}class ${m[1]}`);
20
+ const block = extractBlock(stripped, m.index + m[0].length);
21
+ for (const meth of extractMembers(block)) sigs.push(` ${meth}`);
22
+ }
23
+
24
+ // Top-level functions
25
+ for (const m of stripped.matchAll(/^(?:Future|void|[\w<>?]+)\s+(\w+)\s*\(([^)]*)\)/gm)) {
26
+ if (m[1].startsWith('_')) continue;
27
+ sigs.push(`${m[1]}(${normalizeParams(m[2])})`);
28
+ }
29
+
30
+ return sigs.slice(0, 25);
31
+ }
32
+
33
+ function extractBlock(src, startIndex) {
34
+ let depth = 1, i = startIndex;
35
+ const end = Math.min(src.length, startIndex + 4000);
36
+ while (i < end && depth > 0) {
37
+ if (src[i] === '{') depth++;
38
+ else if (src[i] === '}') depth--;
39
+ i++;
40
+ }
41
+ return src.slice(startIndex, i - 1);
42
+ }
43
+
44
+ function extractMembers(block) {
45
+ const members = [];
46
+ for (const m of block.matchAll(/^\s+(?:@override\s+)?(?:Future|void|[\w<>?]+)\s+(\w+)\s*\(([^)]*)\)/gm)) {
47
+ if (m[1].startsWith('_')) continue;
48
+ members.push(`${m[1]}(${normalizeParams(m[2])})`);
49
+ }
50
+ return members.slice(0, 8);
51
+ }
52
+
53
+ function normalizeParams(params) {
54
+ if (!params) return '';
55
+ return params.trim().replace(/\{[^}]*\}/g, '').replace(/\s+/g, ' ').trim();
56
+ }
57
+
58
+ module.exports = { extract };
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Extract signatures from Dockerfiles.
5
+ * @param {string} src - Raw file content
6
+ * @returns {string[]} Array of signature strings
7
+ */
8
+ function extract(src) {
9
+ if (!src || typeof src !== 'string') return [];
10
+ const sigs = [];
11
+
12
+ const lines = src.split('\n').filter((l) => l.trim() && !l.trimStart().startsWith('#'));
13
+
14
+ // FROM stages
15
+ for (const line of lines) {
16
+ const m = line.match(/^FROM\s+([^\s]+)(?:\s+AS\s+(\w+))?/i);
17
+ if (m) sigs.push(`FROM ${m[1]}${m[2] ? ` AS ${m[2]}` : ''}`);
18
+ }
19
+
20
+ // EXPOSE ports
21
+ const exposePorts = [];
22
+ for (const line of lines) {
23
+ const m = line.match(/^EXPOSE\s+([\d\s/]+)/i);
24
+ if (m) exposePorts.push(...m[1].trim().split(/\s+/));
25
+ }
26
+ if (exposePorts.length > 0) sigs.push(`EXPOSE ${exposePorts.join(' ')}`);
27
+
28
+ // ENTRYPOINT and CMD
29
+ for (const line of lines) {
30
+ if (/^ENTRYPOINT\s+/i.test(line)) sigs.push(line.trim());
31
+ if (/^CMD\s+/i.test(line)) sigs.push(line.trim());
32
+ }
33
+
34
+ // ENV variables
35
+ for (const line of lines) {
36
+ const m = line.match(/^ENV\s+([\w]+)/i);
37
+ if (m) sigs.push(`ENV ${m[1]}`);
38
+ }
39
+
40
+ // ARG variables
41
+ for (const line of lines) {
42
+ const m = line.match(/^ARG\s+([\w]+)/i);
43
+ if (m) sigs.push(`ARG ${m[1]}`);
44
+ }
45
+
46
+ return sigs.slice(0, 25);
47
+ }
48
+
49
+ module.exports = { extract };
@@ -0,0 +1,61 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Extract signatures from Go source code.
5
+ * @param {string} src - Raw file content
6
+ * @returns {string[]} Array of signature strings
7
+ */
8
+ function extract(src) {
9
+ if (!src || typeof src !== 'string') return [];
10
+ const sigs = [];
11
+
12
+ const stripped = src
13
+ .replace(/\/\/.*$/gm, '')
14
+ .replace(/\/\*[\s\S]*?\*\//g, '');
15
+
16
+ // Structs
17
+ for (const m of stripped.matchAll(/^type\s+(\w+)\s+struct\s*\{/gm)) {
18
+ sigs.push(`type ${m[1]} struct`);
19
+ }
20
+
21
+ // Interfaces
22
+ for (const m of stripped.matchAll(/^type\s+(\w+)\s+interface\s*\{/gm)) {
23
+ sigs.push(`type ${m[1]} interface`);
24
+ const block = extractBlock(stripped, m.index + m[0].length);
25
+ for (const method of extractInterfaceMethods(block)) sigs.push(` ${method}`);
26
+ }
27
+
28
+ // Functions and methods
29
+ for (const m of stripped.matchAll(/^func\s+(?:\((\w+)\s+[\w*]+\)\s+)?(\w+)\s*\(([^)]*)\)(?:\s*[\w*()\[\],\s]+)?\s*\{/gm)) {
30
+ const receiver = m[1] ? `(${m[1]}) ` : '';
31
+ sigs.push(`func ${receiver}${m[2]}(${normalizeParams(m[3])})`);
32
+ }
33
+
34
+ return sigs.slice(0, 25);
35
+ }
36
+
37
+ function extractBlock(src, startIndex) {
38
+ let depth = 1, i = startIndex;
39
+ const end = Math.min(src.length, startIndex + 2000);
40
+ while (i < end && depth > 0) {
41
+ if (src[i] === '{') depth++;
42
+ else if (src[i] === '}') depth--;
43
+ i++;
44
+ }
45
+ return src.slice(startIndex, i - 1);
46
+ }
47
+
48
+ function extractInterfaceMethods(block) {
49
+ const methods = [];
50
+ for (const m of block.matchAll(/^\s+(\w+)\s*\(([^)]*)\)/gm)) {
51
+ methods.push(`${m[1]}(${normalizeParams(m[2])})`);
52
+ }
53
+ return methods.slice(0, 8);
54
+ }
55
+
56
+ function normalizeParams(params) {
57
+ if (!params) return '';
58
+ return params.trim().replace(/\s+/g, ' ');
59
+ }
60
+
61
+ module.exports = { extract };