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.
- package/.contextignore.example +34 -0
- package/CHANGELOG.md +402 -0
- package/LICENSE +21 -0
- package/README.md +601 -0
- package/gen-context.config.json.example +40 -0
- package/gen-context.js +4316 -0
- package/gen-project-map.js +172 -0
- package/package.json +67 -0
- package/src/config/defaults.js +61 -0
- package/src/config/loader.js +60 -0
- package/src/extractors/cpp.js +60 -0
- package/src/extractors/csharp.js +48 -0
- package/src/extractors/css.js +51 -0
- package/src/extractors/dart.js +58 -0
- package/src/extractors/dockerfile.js +49 -0
- package/src/extractors/go.js +61 -0
- package/src/extractors/html.js +39 -0
- package/src/extractors/java.js +49 -0
- package/src/extractors/javascript.js +82 -0
- package/src/extractors/kotlin.js +62 -0
- package/src/extractors/php.js +62 -0
- package/src/extractors/python.js +69 -0
- package/src/extractors/ruby.js +43 -0
- package/src/extractors/rust.js +72 -0
- package/src/extractors/scala.js +67 -0
- package/src/extractors/shell.js +43 -0
- package/src/extractors/svelte.js +51 -0
- package/src/extractors/swift.js +63 -0
- package/src/extractors/typescript.js +109 -0
- package/src/extractors/vue.js +66 -0
- package/src/extractors/yaml.js +59 -0
- package/src/format/cache.js +53 -0
- package/src/health/scorer.js +123 -0
- package/src/map/class-hierarchy.js +117 -0
- package/src/map/import-graph.js +148 -0
- package/src/map/route-table.js +127 -0
- package/src/mcp/handlers.js +433 -0
- package/src/mcp/server.js +128 -0
- package/src/mcp/tools.js +125 -0
- package/src/routing/classifier.js +102 -0
- package/src/routing/hints.js +103 -0
- package/src/security/patterns.js +51 -0
- package/src/security/scanner.js +36 -0
- 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 };
|