sigmap 1.5.1 → 2.0.0-beta.1
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 +30 -0
- package/README.md +3 -3
- package/gen-context.config.json.example +28 -1
- package/gen-context.js +683 -115
- package/package.json +2 -1
- package/src/config/defaults.js +43 -1
- package/src/extractors/coverage.js +60 -0
- package/src/extractors/cpp.js +15 -6
- package/src/extractors/csharp.js +14 -3
- package/src/extractors/dart.js +9 -7
- package/src/extractors/deps.js +81 -0
- package/src/extractors/go.js +9 -5
- package/src/extractors/java.js +14 -3
- package/src/extractors/javascript.js +34 -6
- package/src/extractors/kotlin.js +9 -5
- package/src/extractors/php.js +13 -4
- package/src/extractors/prdiff.js +45 -0
- package/src/extractors/python.js +136 -20
- package/src/extractors/ruby.js +13 -2
- package/src/extractors/rust.js +15 -5
- package/src/extractors/scala.js +13 -4
- package/src/extractors/svelte.js +11 -4
- package/src/extractors/swift.js +15 -5
- package/src/extractors/todos.js +26 -0
- package/src/extractors/typescript.js +31 -4
- package/src/extractors/vue.js +16 -2
- package/src/mcp/server.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sigmap",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-beta.1",
|
|
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
|
"bin": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"test": "node test/run.js",
|
|
13
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:integration:all": "node test/integration/all.js",
|
|
14
15
|
"test:all": "node test/run.js && node test/integration/strategy.test.js && node test/integration/secret-scan.test.js",
|
|
15
16
|
"generate": "node gen-context.js",
|
|
16
17
|
"watch": "node gen-context.js --watch",
|
package/src/config/defaults.js
CHANGED
|
@@ -12,7 +12,13 @@ const DEFAULTS = {
|
|
|
12
12
|
outputs: ['copilot'],
|
|
13
13
|
|
|
14
14
|
// Directories to scan (relative to project root)
|
|
15
|
-
srcDirs: [
|
|
15
|
+
srcDirs: [
|
|
16
|
+
'src', 'app', 'lib', 'packages', 'services', 'api',
|
|
17
|
+
// common monorepo / multi-project top-level names
|
|
18
|
+
'server', 'client', 'web', 'frontend', 'backend',
|
|
19
|
+
'desktop', 'mobile', 'shared', 'common', 'core',
|
|
20
|
+
'workers', 'functions', 'lambda', 'cmd',
|
|
21
|
+
],
|
|
16
22
|
|
|
17
23
|
// Directory/file names to exclude entirely
|
|
18
24
|
exclude: [
|
|
@@ -39,6 +45,15 @@ const DEFAULTS = {
|
|
|
39
45
|
// Sort recently git-committed files higher in output
|
|
40
46
|
diffPriority: true,
|
|
41
47
|
|
|
48
|
+
// Context strategy controls how the output is split and injected.
|
|
49
|
+
// 'full' -> single context file (default)
|
|
50
|
+
// 'per-module' -> one context-<module>.md per top-level srcDir + thin overview
|
|
51
|
+
// 'hot-cold' -> recent files in primary output, older files in context-cold.md
|
|
52
|
+
strategy: 'full',
|
|
53
|
+
|
|
54
|
+
// For hot-cold strategy: how many recent git commits count as "hot"
|
|
55
|
+
hotCommits: 10,
|
|
56
|
+
|
|
42
57
|
// Debounce delay (ms) between file-system events and regeneration in watch mode
|
|
43
58
|
watchDebounce: 300,
|
|
44
59
|
|
|
@@ -56,6 +71,33 @@ const DEFAULTS = {
|
|
|
56
71
|
mcp: {
|
|
57
72
|
autoRegister: true,
|
|
58
73
|
},
|
|
74
|
+
|
|
75
|
+
// Enrich signatures with return types, type hints, and schema field collapse
|
|
76
|
+
enrichSignatures: true,
|
|
77
|
+
|
|
78
|
+
// Include a compact import dependency map at top of output
|
|
79
|
+
depMap: true,
|
|
80
|
+
|
|
81
|
+
// Collapse Pydantic BaseModel / @dataclass fields to a single line
|
|
82
|
+
schemaFields: true,
|
|
83
|
+
|
|
84
|
+
// Include TODO/FIXME/HACK/XXX comments as compact section
|
|
85
|
+
todos: true,
|
|
86
|
+
|
|
87
|
+
// Include compact recent git changes section
|
|
88
|
+
changes: true,
|
|
89
|
+
|
|
90
|
+
// Number of commits used for changes section
|
|
91
|
+
changesCommits: 5,
|
|
92
|
+
|
|
93
|
+
// Add test coverage markers to extracted function signatures (opt-in)
|
|
94
|
+
testCoverage: false,
|
|
95
|
+
|
|
96
|
+
// Directories scanned for tests when testCoverage is enabled
|
|
97
|
+
testDirs: ['tests', 'test', '__tests__', 'spec'],
|
|
98
|
+
|
|
99
|
+
// Add reverse dependency usage hints on file headings (opt-in)
|
|
100
|
+
impactRadius: false,
|
|
59
101
|
};
|
|
60
102
|
|
|
61
103
|
module.exports = { DEFAULTS };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function walkFiles(dir) {
|
|
7
|
+
let out = [];
|
|
8
|
+
let entries;
|
|
9
|
+
try {
|
|
10
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
11
|
+
} catch (_) {
|
|
12
|
+
return out;
|
|
13
|
+
}
|
|
14
|
+
for (const entry of entries) {
|
|
15
|
+
const full = path.join(dir, entry.name);
|
|
16
|
+
if (entry.isDirectory()) out = out.concat(walkFiles(full));
|
|
17
|
+
else if (entry.isFile()) out.push(full);
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function buildTestIndex(cwd, testDirs) {
|
|
23
|
+
const dirs = Array.isArray(testDirs) && testDirs.length ? testDirs : ['tests', 'test', '__tests__', 'spec'];
|
|
24
|
+
const names = new Set();
|
|
25
|
+
|
|
26
|
+
for (const dir of dirs) {
|
|
27
|
+
const abs = path.join(cwd, dir);
|
|
28
|
+
if (!fs.existsSync(abs)) continue;
|
|
29
|
+
for (const file of walkFiles(abs)) {
|
|
30
|
+
let src = '';
|
|
31
|
+
try {
|
|
32
|
+
src = fs.readFileSync(file, 'utf8');
|
|
33
|
+
} catch (_) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (const m of src.matchAll(/\b(?:test_|it\(|test\(|describe\()\s*['"`]?([\w_]+)/g)) {
|
|
38
|
+
if (m[1]) names.add(m[1].toLowerCase());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const m of src.matchAll(/\b([a-zA-Z_][a-zA-Z0-9_]*)\b/g)) {
|
|
42
|
+
if (m[1]) names.add(m[1].toLowerCase());
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return names;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isTested(funcName, testIndex) {
|
|
51
|
+
if (!funcName || !testIndex || testIndex.size === 0) return false;
|
|
52
|
+
const lower = funcName.toLowerCase();
|
|
53
|
+
if (testIndex.has(lower) || testIndex.has(`test_${lower}`)) return true;
|
|
54
|
+
for (const token of testIndex) {
|
|
55
|
+
if (token.includes(lower)) return true;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { buildTestIndex, isTested };
|
package/src/extractors/cpp.js
CHANGED
|
@@ -23,9 +23,11 @@ function extract(src) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
// Top-level function declarations/definitions (not inside a class)
|
|
26
|
-
for (const m of stripped.matchAll(/^(?!class|struct|if|for|while|switch)[\w:*&<> ]
|
|
27
|
-
if (m[
|
|
28
|
-
|
|
26
|
+
for (const m of stripped.matchAll(/^(?!class|struct|if|for|while|switch)([\w:*&<> ]+?)\s+(\w+)\s*\(([^)]*)\)\s*(?:const\s*)?\{/gm)) {
|
|
27
|
+
if (m[2].startsWith('_')) continue;
|
|
28
|
+
const ret = normalizeType(m[1]);
|
|
29
|
+
const retStr = ret ? ` → ${ret}` : '';
|
|
30
|
+
sigs.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
return sigs.slice(0, 25);
|
|
@@ -44,10 +46,12 @@ function extractBlock(src, startIndex) {
|
|
|
44
46
|
|
|
45
47
|
function extractMembers(block) {
|
|
46
48
|
const members = [];
|
|
47
|
-
const methodRe = /^\s+(?:virtual\s+|static\s+|inline\s+)?(?!private:|protected:|public:)[\w:*&<> ]
|
|
49
|
+
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
50
|
for (const m of block.matchAll(methodRe)) {
|
|
49
|
-
if (m[
|
|
50
|
-
|
|
51
|
+
if (m[2].startsWith('_')) continue;
|
|
52
|
+
const ret = normalizeType(m[1]);
|
|
53
|
+
const retStr = ret ? ` → ${ret}` : '';
|
|
54
|
+
members.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
|
|
51
55
|
}
|
|
52
56
|
return members.slice(0, 8);
|
|
53
57
|
}
|
|
@@ -57,4 +61,9 @@ function normalizeParams(params) {
|
|
|
57
61
|
return params.trim().replace(/\s+/g, ' ');
|
|
58
62
|
}
|
|
59
63
|
|
|
64
|
+
function normalizeType(type) {
|
|
65
|
+
if (!type) return '';
|
|
66
|
+
return type.trim().replace(/\s+/g, ' ').slice(0, 30);
|
|
67
|
+
}
|
|
68
|
+
|
|
60
69
|
module.exports = { extract };
|
package/src/extractors/csharp.js
CHANGED
|
@@ -37,12 +37,23 @@ function extractBlock(src, startIndex) {
|
|
|
37
37
|
|
|
38
38
|
function extractMembers(block) {
|
|
39
39
|
const members = [];
|
|
40
|
-
const methodRe = /^\s+(?:public|internal|protected)\s+(?:static\s+|virtual\s+|override\s+|async\s+)*(?:[\w<>\[\]
|
|
40
|
+
const methodRe = /^\s+(?:public|internal|protected)\s+(?:static\s+|virtual\s+|override\s+|async\s+)*(?:where\s+\w+\s*:\s*[^\n]+\s+)?([\w<>\[\]?., ]+)\s+(\w+)\s*\(([^)]*)\)/gm;
|
|
41
41
|
for (const m of block.matchAll(methodRe)) {
|
|
42
|
-
const
|
|
43
|
-
|
|
42
|
+
const ret = normalizeType(m[1]);
|
|
43
|
+
const retStr = ret ? ` → ${ret}` : '';
|
|
44
|
+
members.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
|
|
44
45
|
}
|
|
45
46
|
return members.slice(0, 8);
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
function normalizeParams(params) {
|
|
50
|
+
if (!params) return '';
|
|
51
|
+
return params.trim().replace(/\s+/g, ' ');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function normalizeType(type) {
|
|
55
|
+
if (!type) return '';
|
|
56
|
+
return type.trim().replace(/\s+/g, ' ').slice(0, 30);
|
|
57
|
+
}
|
|
58
|
+
|
|
48
59
|
module.exports = { extract };
|
package/src/extractors/dart.js
CHANGED
|
@@ -21,10 +21,11 @@ function extract(src) {
|
|
|
21
21
|
for (const meth of extractMembers(block)) sigs.push(` ${meth}`);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
// Top-level functions
|
|
25
|
-
for (const m of stripped.matchAll(/^(?:Future
|
|
26
|
-
if (m[
|
|
27
|
-
|
|
24
|
+
// Top-level functions — capture return type (prefix before name) and show as suffix
|
|
25
|
+
for (const m of stripped.matchAll(/^((?:Future<[\w<>?,\s]*>|[\w<>?]+))\s+(\w+)\s*\(([^)]*)\)/gm)) {
|
|
26
|
+
if (m[2].startsWith('_')) continue;
|
|
27
|
+
const retStr = (m[1] && m[1] !== 'void') ? ` \u2192 ${m[1].replace(/\s+/g, '').slice(0, 25)}` : '';
|
|
28
|
+
sigs.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
return sigs.slice(0, 25);
|
|
@@ -43,9 +44,10 @@ function extractBlock(src, startIndex) {
|
|
|
43
44
|
|
|
44
45
|
function extractMembers(block) {
|
|
45
46
|
const members = [];
|
|
46
|
-
for (const m of block.matchAll(/^\s+(?:@override\s+)?(?:Future
|
|
47
|
-
if (m[
|
|
48
|
-
|
|
47
|
+
for (const m of block.matchAll(/^\s+(?:@override\s+)?(?:@\w+\s+)*((?:Future<[\w<>?,\s]*>|[\w<>?]+))\s+(\w+)\s*\(([^)]*)\)/gm)) {
|
|
48
|
+
if (m[2].startsWith('_')) continue;
|
|
49
|
+
const retStr = (m[1] && m[1] !== 'void') ? ` \u2192 ${m[1].replace(/\s+/g, '').slice(0, 25)}` : '';
|
|
50
|
+
members.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
|
|
49
51
|
}
|
|
50
52
|
return members.slice(0, 8);
|
|
51
53
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extract import dependencies from Python and TypeScript/JavaScript files.
|
|
5
|
+
* Returns compact dependency arrays for the dep-map section of the context output.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const PYTHON_STDLIB = new Set([
|
|
9
|
+
'os', 'sys', 're', 'json', 'time', 'threading', 'logging', 'typing',
|
|
10
|
+
'dataclasses', 'datetime', 'uuid', 'pathlib', 'collections', 'functools',
|
|
11
|
+
'itertools', 'math', 'random', 'string', 'struct', 'io', 'copy', 'pprint',
|
|
12
|
+
'traceback', 'inspect', 'abc', 'enum', 'contextlib', 'weakref', 'gc',
|
|
13
|
+
'socket', 'ssl', 'http', 'urllib', 'email', 'html', 'xml', 'csv', 'sqlite3',
|
|
14
|
+
'argparse', 'subprocess', 'shutil', 'tempfile', 'glob', 'fnmatch', 'stat',
|
|
15
|
+
'hashlib', 'hmac', 'base64', 'binascii', 'codecs', 'unicodedata', 'locale',
|
|
16
|
+
'decimal', 'fractions', 'numbers', 'cmath', 'heapq', 'bisect', 'array',
|
|
17
|
+
'queue', 'asyncio', 'concurrent', 'multiprocessing', 'signal', 'mmap',
|
|
18
|
+
'builtins', 'warnings', 'operator', 'textwrap', 'difflib', 'readline',
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Extract project-level import dependencies from Python source.
|
|
23
|
+
* @param {string} src
|
|
24
|
+
* @returns {string[]}
|
|
25
|
+
*/
|
|
26
|
+
function extractPythonDeps(src) {
|
|
27
|
+
const deps = new Set();
|
|
28
|
+
for (const m of src.matchAll(/^from\s+([\w.]+)\s+import/gm)) {
|
|
29
|
+
const mod = m[1];
|
|
30
|
+
const root = mod.replace(/^\.+/, '').split('.')[0];
|
|
31
|
+
// Include relative imports and non-stdlib modules
|
|
32
|
+
if (mod.startsWith('.') || (root && !PYTHON_STDLIB.has(root))) {
|
|
33
|
+
deps.add(root || mod);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (const m of src.matchAll(/^import\s+([\w.]+)/gm)) {
|
|
37
|
+
const root = m[1].split('.')[0];
|
|
38
|
+
if (root && !PYTHON_STDLIB.has(root)) deps.add(root);
|
|
39
|
+
}
|
|
40
|
+
return [...deps].filter(Boolean).slice(0, 5);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Extract relative import dependencies from TypeScript/JavaScript source.
|
|
45
|
+
* @param {string} src
|
|
46
|
+
* @returns {string[]}
|
|
47
|
+
*/
|
|
48
|
+
function extractTSDeps(src) {
|
|
49
|
+
// Strip single-line comments to avoid matching commented-out imports
|
|
50
|
+
const stripped = src.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
51
|
+
const deps = new Set();
|
|
52
|
+
for (const m of stripped.matchAll(/from\s+['"](\.[\/\w.-]+)['"]/g)) {
|
|
53
|
+
// Normalise: '../store/authStore' → store/authStore, './utils' → utils
|
|
54
|
+
const clean = m[1]
|
|
55
|
+
.replace(/^\.\.\//, '')
|
|
56
|
+
.replace(/^\.\//, '')
|
|
57
|
+
.replace(/\.\w+$/, '');
|
|
58
|
+
if (clean) deps.add(clean);
|
|
59
|
+
}
|
|
60
|
+
return [...deps].slice(0, 5);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Build reverse dependency map from forward map.
|
|
65
|
+
* @param {Map<string, string[]>} forwardMap
|
|
66
|
+
* @returns {Map<string, string[]>}
|
|
67
|
+
*/
|
|
68
|
+
function buildReverseDepMap(forwardMap) {
|
|
69
|
+
const reverse = new Map();
|
|
70
|
+
if (!forwardMap || typeof forwardMap.entries !== 'function') return reverse;
|
|
71
|
+
for (const [file, deps] of forwardMap.entries()) {
|
|
72
|
+
if (!Array.isArray(deps)) continue;
|
|
73
|
+
for (const dep of deps) {
|
|
74
|
+
if (!reverse.has(dep)) reverse.set(dep, []);
|
|
75
|
+
reverse.get(dep).push(file);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return reverse;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = { extractPythonDeps, extractTSDeps, buildReverseDepMap };
|
package/src/extractors/go.js
CHANGED
|
@@ -25,10 +25,12 @@ function extract(src) {
|
|
|
25
25
|
for (const method of extractInterfaceMethods(block)) sigs.push(` ${method}`);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
// Functions and methods
|
|
29
|
-
for (const m of stripped.matchAll(/^func\s+(?:\((\w+)\s+[\w*]+\)\s+)?(\w+)\s*\(([^)]*)\)(
|
|
28
|
+
// Functions and methods — capture return type between ) and {
|
|
29
|
+
for (const m of stripped.matchAll(/^func\s+(?:\((\w+)\s+[\w*]+\)\s+)?(\w+)\s*\(([^)]*)\)([^{]*)\{/gm)) {
|
|
30
30
|
const receiver = m[1] ? `(${m[1]}) ` : '';
|
|
31
|
-
|
|
31
|
+
const retType = m[4] ? m[4].trim().replace(/\s+/g, ' ') : '';
|
|
32
|
+
const retStr = retType ? ` \u2192 ${retType.slice(0, 30)}` : '';
|
|
33
|
+
sigs.push(`func ${receiver}${m[2]}(${normalizeParams(m[3])})${retStr}`);
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
return sigs.slice(0, 25);
|
|
@@ -47,8 +49,10 @@ function extractBlock(src, startIndex) {
|
|
|
47
49
|
|
|
48
50
|
function extractInterfaceMethods(block) {
|
|
49
51
|
const methods = [];
|
|
50
|
-
for (const m of block.matchAll(/^\s+(\w+)\s*\(([^)]*)\)/gm)) {
|
|
51
|
-
|
|
52
|
+
for (const m of block.matchAll(/^\s+(\w+)\s*\(([^)]*)\)([^\n]*)/gm)) {
|
|
53
|
+
const retType = m[3] ? m[3].trim().replace(/\s+/g, ' ') : '';
|
|
54
|
+
const retStr = retType ? ` \u2192 ${retType.slice(0, 30)}` : '';
|
|
55
|
+
methods.push(`${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
52
56
|
}
|
|
53
57
|
return methods.slice(0, 8);
|
|
54
58
|
}
|
package/src/extractors/java.js
CHANGED
|
@@ -38,12 +38,23 @@ function extractBlock(src, startIndex) {
|
|
|
38
38
|
|
|
39
39
|
function extractMembers(block) {
|
|
40
40
|
const members = [];
|
|
41
|
-
const methodRe = /^\s+(?:public|protected)\s+(?:static\s+)?(?:final\s+)?(?:[\w<>\[\]]
|
|
41
|
+
const methodRe = /^\s+(?:public|protected)\s+(?:static\s+)?(?:final\s+)?(?:synchronized\s+)?(?:<[^>]+>\s+)?([\w<>\[\], ?.]+)\s+(\w+)\s*\(([^)]*)\)/gm;
|
|
42
42
|
for (const m of block.matchAll(methodRe)) {
|
|
43
|
-
const
|
|
44
|
-
|
|
43
|
+
const ret = normalizeType(m[1]);
|
|
44
|
+
const retStr = ret ? ` → ${ret}` : '';
|
|
45
|
+
members.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
|
|
45
46
|
}
|
|
46
47
|
return members.slice(0, 8);
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
function normalizeParams(params) {
|
|
51
|
+
if (!params) return '';
|
|
52
|
+
return params.trim().replace(/\s+/g, ' ');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function normalizeType(type) {
|
|
56
|
+
if (!type) return '';
|
|
57
|
+
return type.trim().replace(/\s+/g, ' ').slice(0, 30);
|
|
58
|
+
}
|
|
59
|
+
|
|
49
60
|
module.exports = { extract };
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
function extract(src) {
|
|
9
9
|
if (!src || typeof src !== 'string') return [];
|
|
10
10
|
const sigs = [];
|
|
11
|
+
const returnHints = buildReturnHints(src);
|
|
11
12
|
|
|
12
13
|
const stripped = src
|
|
13
14
|
.replace(/\/\/.*$/gm, '')
|
|
@@ -19,19 +20,21 @@ function extract(src) {
|
|
|
19
20
|
const prefix = m[1] ? m[1].trim() + ' ' : '';
|
|
20
21
|
sigs.push(`${prefix}class ${m[2]}`);
|
|
21
22
|
const block = extractBlock(stripped, m.index + m[0].length);
|
|
22
|
-
for (const meth of extractClassMembers(block)) sigs.push(` ${meth}`);
|
|
23
|
+
for (const meth of extractClassMembers(block, returnHints)) sigs.push(` ${meth}`);
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
// Exported named functions
|
|
26
27
|
for (const m of stripped.matchAll(/^export\s+(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/gm)) {
|
|
27
28
|
const asyncKw = /export\s+async/.test(m[0]) ? 'async ' : '';
|
|
28
|
-
|
|
29
|
+
const retStr = formatReturnHint(returnHints.get(m[1]));
|
|
30
|
+
sigs.push(`export ${asyncKw}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
// Exported arrow functions
|
|
32
34
|
for (const m of stripped.matchAll(/^export\s+const\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s*=>/gm)) {
|
|
33
35
|
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
34
|
-
|
|
36
|
+
const retStr = formatReturnHint(returnHints.get(m[1]));
|
|
37
|
+
sigs.push(`export const ${m[1]} = ${asyncKw}(${normalizeParams(m[2])}) =>${retStr}`);
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
// module.exports = { ... }
|
|
@@ -44,7 +47,8 @@ function extract(src) {
|
|
|
44
47
|
// Top-level named functions (non-exported)
|
|
45
48
|
for (const m of stripped.matchAll(/^(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/gm)) {
|
|
46
49
|
const asyncKw = m[0].startsWith('async') ? 'async ' : '';
|
|
47
|
-
|
|
50
|
+
const retStr = formatReturnHint(returnHints.get(m[1]));
|
|
51
|
+
sigs.push(`${asyncKw}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
return sigs.slice(0, 25);
|
|
@@ -62,18 +66,42 @@ function extractBlock(src, startIndex) {
|
|
|
62
66
|
return src.slice(startIndex, i - 1);
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
function extractClassMembers(block) {
|
|
69
|
+
function extractClassMembers(block, returnHints) {
|
|
66
70
|
const members = [];
|
|
67
71
|
for (const m of block.matchAll(/^\s+(?:static\s+|async\s+|get\s+|set\s+)*(\w+)\s*\(([^)]*)\)\s*\{/gm)) {
|
|
68
72
|
if (/^_/.test(m[1])) continue;
|
|
69
73
|
if (m[1] === 'constructor') { members.push(`constructor(${normalizeParams(m[2])})`); continue; }
|
|
70
74
|
const isAsync = m[0].includes('async ') ? 'async ' : '';
|
|
71
75
|
const isStatic = m[0].includes('static ') ? 'static ' : '';
|
|
72
|
-
|
|
76
|
+
const retStr = formatReturnHint(returnHints.get(m[1]));
|
|
77
|
+
members.push(`${isStatic}${isAsync}${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
73
78
|
}
|
|
74
79
|
return members.slice(0, 8);
|
|
75
80
|
}
|
|
76
81
|
|
|
82
|
+
function buildReturnHints(src) {
|
|
83
|
+
const hints = new Map();
|
|
84
|
+
for (const m of src.matchAll(/\/\*\*[\s\S]*?@returns?\s+\{([^}]+)\}[\s\S]*?\*\/\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/g)) {
|
|
85
|
+
hints.set(m[2], normalizeType(m[1]));
|
|
86
|
+
}
|
|
87
|
+
for (const m of src.matchAll(/\/\*\*[\s\S]*?@returns?\s+\{([^}]+)\}[\s\S]*?\*\/\s*export\s+const\s+(\w+)\s*=\s*(?:async\s+)?\(/g)) {
|
|
88
|
+
hints.set(m[2], normalizeType(m[1]));
|
|
89
|
+
}
|
|
90
|
+
for (const m of src.matchAll(/\/\*\*[\s\S]*?@returns?\s+\{([^}]+)\}[\s\S]*?\*\/\s*(?:static\s+|async\s+|get\s+|set\s+)*(\w+)\s*\(/g)) {
|
|
91
|
+
hints.set(m[2], normalizeType(m[1]));
|
|
92
|
+
}
|
|
93
|
+
return hints;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function normalizeType(type) {
|
|
97
|
+
if (!type) return '';
|
|
98
|
+
return type.trim().replace(/\s+/g, ' ').slice(0, 25);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function formatReturnHint(type) {
|
|
102
|
+
return type ? ` → ${type}` : '';
|
|
103
|
+
}
|
|
104
|
+
|
|
77
105
|
function normalizeParams(params) {
|
|
78
106
|
if (!params) return '';
|
|
79
107
|
return params.trim().replace(/\s+/g, ' ');
|
package/src/extractors/kotlin.js
CHANGED
|
@@ -20,10 +20,12 @@ function extract(src) {
|
|
|
20
20
|
for (const meth of extractMembers(block)) sigs.push(` ${meth}`);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
// Top-level functions
|
|
24
|
-
for (const m of stripped.matchAll(/^(?:public\s+|internal\s+)?(?:suspend\s+)?fun\s+(\w+)\s*(?:<[^(]*>)?\s*\(([^)]*)\)
|
|
23
|
+
// Top-level functions — capture `: RetType` after params
|
|
24
|
+
for (const m of stripped.matchAll(/^(?:public\s+|internal\s+)?(?:suspend\s+)?fun\s+(\w+)\s*(?:<[^(]*>)?\s*\(([^)]*)\)(?:\s*:\s*([^\n{=]+))?/gm)) {
|
|
25
25
|
const suspend = m[0].includes('suspend') ? 'suspend ' : '';
|
|
26
|
-
|
|
26
|
+
const retType = m[3] ? m[3].trim().replace(/\s+/g, ' ') : '';
|
|
27
|
+
const retStr = retType ? ` \u2192 ${retType.slice(0, 25)}` : '';
|
|
28
|
+
sigs.push(`${suspend}fun ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
return sigs.slice(0, 25);
|
|
@@ -42,10 +44,12 @@ function extractBlock(src, startIndex) {
|
|
|
42
44
|
|
|
43
45
|
function extractMembers(block) {
|
|
44
46
|
const members = [];
|
|
45
|
-
for (const m of block.matchAll(/^\s+(?:public\s+|internal\s+|override\s+)?(?:suspend\s+)?fun\s+(\w+)\s*(?:<[^(]*>)?\s*\(([^)]*)\)
|
|
47
|
+
for (const m of block.matchAll(/^\s+(?:public\s+|internal\s+|override\s+)?(?:suspend\s+)?fun\s+(\w+)\s*(?:<[^(]*>)?\s*\(([^)]*)\)(?:\s*:\s*([^\n{=]+))?/gm)) {
|
|
46
48
|
if (m[1].startsWith('_')) continue;
|
|
47
49
|
const suspend = m[0].includes('suspend') ? 'suspend ' : '';
|
|
48
|
-
|
|
50
|
+
const retType = m[3] ? m[3].trim().replace(/\s+/g, ' ') : '';
|
|
51
|
+
const retStr = retType ? ` \u2192 ${retType.slice(0, 25)}` : '';
|
|
52
|
+
members.push(`${suspend}fun ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
49
53
|
}
|
|
50
54
|
return members.slice(0, 8);
|
|
51
55
|
}
|
package/src/extractors/php.js
CHANGED
|
@@ -25,8 +25,10 @@ function extract(src) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// Top-level functions
|
|
28
|
-
for (const m of stripped.matchAll(/^function\s+(\w+)\s*\(([^)]*)\)
|
|
29
|
-
|
|
28
|
+
for (const m of stripped.matchAll(/^function\s+(\w+)\s*\(([^)]*)\)\s*(?::\s*([^\n{]+))?/gm)) {
|
|
29
|
+
const ret = normalizeType(m[3]);
|
|
30
|
+
const retStr = ret ? ` → ${ret}` : '';
|
|
31
|
+
sigs.push(`function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
return sigs.slice(0, 25);
|
|
@@ -45,11 +47,13 @@ function extractBlock(src, startIndex) {
|
|
|
45
47
|
|
|
46
48
|
function extractMembers(block) {
|
|
47
49
|
const members = [];
|
|
48
|
-
const methodRe = /^\s+(?:public|protected)\s+(?:static\s+)?function\s+(\w+)\s*\(([^)]*)\)
|
|
50
|
+
const methodRe = /^\s+(?:public|protected)\s+(?:static\s+)?function\s+(\w+)\s*\(([^)]*)\)\s*(?::\s*([^\n{]+))?/gm;
|
|
49
51
|
for (const m of block.matchAll(methodRe)) {
|
|
50
52
|
if (m[1].startsWith('_')) continue;
|
|
51
53
|
const isStatic = m[0].includes('static ') ? 'static ' : '';
|
|
52
|
-
|
|
54
|
+
const ret = normalizeType(m[3]);
|
|
55
|
+
const retStr = ret ? ` → ${ret}` : '';
|
|
56
|
+
members.push(`${isStatic}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
53
57
|
}
|
|
54
58
|
return members.slice(0, 8);
|
|
55
59
|
}
|
|
@@ -59,4 +63,9 @@ function normalizeParams(params) {
|
|
|
59
63
|
return params.trim().replace(/\s+/g, ' ');
|
|
60
64
|
}
|
|
61
65
|
|
|
66
|
+
function normalizeType(type) {
|
|
67
|
+
if (!type) return '';
|
|
68
|
+
return type.trim().replace(/[;\s]+$/g, '').replace(/\s+/g, ' ').slice(0, 25);
|
|
69
|
+
}
|
|
70
|
+
|
|
62
71
|
module.exports = { extract };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Compare signature arrays and produce compact diff markers.
|
|
5
|
+
* @param {string[]} baseSigs
|
|
6
|
+
* @param {string[]} currentSigs
|
|
7
|
+
* @returns {{added:string[], removed:string[], modified:string[]}}
|
|
8
|
+
*/
|
|
9
|
+
function diffSignatures(baseSigs, currentSigs) {
|
|
10
|
+
const base = new Set(baseSigs || []);
|
|
11
|
+
const curr = new Set(currentSigs || []);
|
|
12
|
+
|
|
13
|
+
const added = [...curr].filter((s) => !base.has(s));
|
|
14
|
+
const removed = [...base].filter((s) => !curr.has(s));
|
|
15
|
+
|
|
16
|
+
const byName = (arr) => {
|
|
17
|
+
const m = new Map();
|
|
18
|
+
for (const s of arr) {
|
|
19
|
+
const n = extractName(s);
|
|
20
|
+
if (!n) continue;
|
|
21
|
+
if (!m.has(n)) m.set(n, []);
|
|
22
|
+
m.get(n).push(s);
|
|
23
|
+
}
|
|
24
|
+
return m;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const aBy = byName(added);
|
|
28
|
+
const rBy = byName(removed);
|
|
29
|
+
const modified = [];
|
|
30
|
+
|
|
31
|
+
for (const [name] of aBy) {
|
|
32
|
+
if (rBy.has(name)) modified.push(name);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { added, removed, modified };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function extractName(sig) {
|
|
39
|
+
if (!sig) return '';
|
|
40
|
+
const t = sig.trim();
|
|
41
|
+
const m = t.match(/(?:def|function|func|class|interface|trait|struct|enum|record)?\s*([A-Za-z_][A-Za-z0-9_]*)\s*(?:\(|$)/);
|
|
42
|
+
return m ? m[1] : '';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { diffSignatures, extractName };
|