sigmap 3.3.4 → 3.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.
@@ -0,0 +1,135 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Phase D: Cross-module pattern inference and architectural detection.
5
+ * Identifies DI patterns, service/repo layers, circular deps, type linkage, and unsafe patterns.
6
+ *
7
+ * @param {string} src - Raw source code
8
+ * @returns {string[]} Array of pattern signatures
9
+ */
10
+ function extract(src) {
11
+ if (!src || typeof src !== 'string') return [];
12
+ const sigs = [];
13
+
14
+ // ────────────────────────────────────────────────────────────────
15
+ // Dependency Injection Pattern Detection
16
+ // ────────────────────────────────────────────────────────────────
17
+
18
+ // Service container / factory patterns
19
+ const containerRe = /(?:class|function|const)\s+([A-Z]\w*(?:Container|Factory|Registry|Provider|Injector))\b/g;
20
+ for (const m of src.matchAll(containerRe)) {
21
+ sigs.push(`di-container ${m[1]}`);
22
+ }
23
+
24
+ // Service decorators: @Injectable, @Service, @Singleton, @Provide
25
+ const decoratorRe = /@(?:Injectable|Service|Singleton|Provide|Module|Component)\s*(?:\([^)]*\))?\s*(?:class|export\s+class|const\s+)\s+([A-Z]\w*)/g;
26
+ for (const m of src.matchAll(decoratorRe)) {
27
+ sigs.push(`service-decorated ${m[1]}`);
28
+ }
29
+
30
+ // Dependency injection via constructor: constructor(private readonly ...: Service)
31
+ const ctorDiRe = /constructor\s*\([^)]*(?:private|protected)?\s+(?:readonly\s+)?([a-z_]\w*)\s*:\s*([A-Z]\w*)/g;
32
+ const diServices = new Set();
33
+ for (const m of src.matchAll(ctorDiRe)) {
34
+ diServices.add(m[1]);
35
+ }
36
+ if (diServices.size > 0) {
37
+ sigs.push(`di-injection ${diServices.size} params`);
38
+ }
39
+
40
+ // ────────────────────────────────────────────────────────────────
41
+ // Service/Repository/Middleware Layer Detection
42
+ // ────────────────────────────────────────────────────────────────
43
+
44
+ // Repository pattern: extends Repository, implements IRepository
45
+ const repoRe = /class\s+([A-Z]\w*(?:Repository|Repo|DataAccess))\b/g;
46
+ for (const m of src.matchAll(repoRe)) {
47
+ sigs.push(`repo ${m[1]}`);
48
+ }
49
+
50
+ // Service layer: @Service or ServiceImpl pattern
51
+ const serviceRe = /(?:export\s+)?class\s+([A-Z]\w*Service\b)/g;
52
+ for (const m of src.matchAll(serviceRe)) {
53
+ sigs.push(`service ${m[1]}`);
54
+ }
55
+
56
+ // Middleware detection: app.use(), router.use(), middleware function
57
+ if (/app\.use\s*\(|router\.use\s*\(|\.use\s*\(\s*function|middleware|app\.get\s*\(\s*['"`]\/[^'"`]*['"`]/.test(src)) {
58
+ sigs.push('middleware-present');
59
+ }
60
+
61
+ // ────────────────────────────────────────────────────────────────
62
+ // Type Linkage: Exported types → Implementations
63
+ // ────────────────────────────────────────────────────────────────
64
+
65
+ // Export type/interface followed by class implementing it
66
+ const exportTypeRe = /export\s+(?:type|interface)\s+([A-Z]\w*)/g;
67
+ const exportedTypes = new Set();
68
+ for (const m of src.matchAll(exportTypeRe)) {
69
+ exportedTypes.add(m[1]);
70
+ }
71
+
72
+ // Check if exported types have implementations
73
+ for (const type of exportedTypes) {
74
+ const implRe = new RegExp(`class\\s+([A-Z]\\w*)\\s+(?:extends|implements)\\s+(?:.*\\s+)?${type}\\b`, 'i');
75
+ if (implRe.test(src)) {
76
+ sigs.push(`type-impl ${type}`);
77
+ }
78
+ }
79
+
80
+ // ────────────────────────────────────────────────────────────────
81
+ // Unsafe Pattern Detection
82
+ // ────────────────────────────────────────────────────────────────
83
+
84
+ // Unchecked nulls / optional without validation
85
+ if (/\?\s*{|Optional\s*<|\?\s*\.get\(\)|\?\s*\[|\?\s*\.length|\.split\(\)\.filter\(Boolean\)|if\s*\(\s*!.*\).*throw/.test(src)) {
86
+ sigs.push('unsafe-null-check');
87
+ }
88
+
89
+ // Missing validation (direct use of user input)
90
+ const userInputRe = /(?:request|input|params|body|query|args)\s*\[\s*['"][^'"]*['"]\s*\]|req(?:uest)?\..*\s*==|params\.split|String\(.*\)\.toLowerCase/;
91
+ if (userInputRe.test(src)) {
92
+ sigs.push('unsafe-input-validation');
93
+ }
94
+
95
+ // Weak error handling (catch {} or empty catch)
96
+ if (/catch\s*\(\s*\)\s*\{|\}\s*catch\s*\{(\s*\/\/|\s*\})/.test(src)) {
97
+ sigs.push('weak-error-handling');
98
+ }
99
+
100
+ // Direct error exposure
101
+ if (/throw\s+new\s+Error\(|console\s*\.\s*error|res\.status\(500\)\.send\(err\)/.test(src)) {
102
+ sigs.push('unsafe-error-exposure');
103
+ }
104
+
105
+ // ────────────────────────────────────────────────────────────────
106
+ // Circular Dependency Hints
107
+ // ────────────────────────────────────────────────────────────────
108
+
109
+ // Mutual imports detected (A imports B, B imports A) — can't directly detect in single file,
110
+ // but we can flag suspicious patterns
111
+ const importRe = /(?:import|require)\s+(?:{[^}]*}|[a-zA-Z_]\w*)\s+from\s+['"]\.?\.?\/[^'"]+['"]/g;
112
+ const importCount = (src.match(importRe) || []).length;
113
+ if (importCount > 5) {
114
+ sigs.push(`heavy-imports ${importCount}`);
115
+ }
116
+
117
+ // ────────────────────────────────────────────────────────────────
118
+ // Layer/Module Organization Hints
119
+ // ────────────────────────────────────────────────────────────────
120
+
121
+ // Controller/Handler layer
122
+ const controllerRe = /(?:class|export)\s+([A-Z]\w*(?:Controller|Handler|Route))\b/g;
123
+ for (const m of src.matchAll(controllerRe)) {
124
+ sigs.push(`controller ${m[1]}`);
125
+ }
126
+
127
+ // Use case / Domain logic
128
+ if (/UseCase|Command|Query|UseCase\b|Interactor/.test(src)) {
129
+ sigs.push('domain-usecase');
130
+ }
131
+
132
+ return Array.from(new Set(sigs)).slice(0, 60);
133
+ }
134
+
135
+ module.exports = { extract };
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Extract signatures from .properties configuration files.
5
+ * Captures key names, grouped by prefixes where possible.
6
+ *
7
+ * @param {string} src - Raw properties 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
+ const lines = src.split(/\r?\n/);
15
+ for (const line of lines) {
16
+ const trimmed = line.trim();
17
+ if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('!')) continue;
18
+
19
+ const idxEq = trimmed.indexOf('=');
20
+ const idxColon = trimmed.indexOf(':');
21
+ const idx = idxEq >= 0 && idxColon >= 0 ? Math.min(idxEq, idxColon) : Math.max(idxEq, idxColon);
22
+ if (idx <= 0) continue;
23
+
24
+ const key = trimmed.slice(0, idx).trim();
25
+ if (!key) continue;
26
+
27
+ const parts = key.split('.').filter(Boolean);
28
+ if (parts.length >= 2) {
29
+ sigs.push(`group ${parts[0]}.${parts[1]}`);
30
+ }
31
+ sigs.push(`key ${key}`);
32
+ }
33
+
34
+ return Array.from(new Set(sigs)).slice(0, 50);
35
+ }
36
+
37
+ module.exports = { extract };
@@ -0,0 +1,77 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Extract Python dataclass, Pydantic model, and SQLAlchemy ORM metadata.
5
+ * Focuses on model fields, validation, and relationships.
6
+ *
7
+ * @param {string} src - Raw Python 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
+ // Dataclass definitions
15
+ const dataclassRe = /@dataclass(?:\([^)]*\))?[\s\n]+class\s+([A-Z]\w*)/g;
16
+ for (const m of src.matchAll(dataclassRe)) {
17
+ sigs.push(`dataclass ${m[1]}`);
18
+ }
19
+
20
+ // Pydantic BaseModel classes
21
+ const pydanticRe = /class\s+([A-Z]\w*)\s*\([^)]*BaseModel[^)]*\)/g;
22
+ for (const m of src.matchAll(pydanticRe)) {
23
+ sigs.push(`model ${m[1]}`);
24
+ }
25
+
26
+ // Pydantic v2 model_validate / field definitions
27
+ if (/model_validate|field_validator|computed_field/.test(src)) {
28
+ sigs.push('pydantic v2+');
29
+ }
30
+
31
+ // SQLAlchemy model classes
32
+ const sqlalchemyRe = /class\s+([A-Z]\w*)\s*\([^)]*(?:Base|declarative_base)[^)]*\)/g;
33
+ for (const m of src.matchAll(sqlalchemyRe)) {
34
+ sigs.push(`orm ${m[1]}`);
35
+ }
36
+
37
+ // Model fields with type hints (for dataclass/Pydantic)
38
+ const fieldRe = /^\s+([a-z_]\w*)\s*:\s*([A-Z]\w*|List|Dict|Optional|Union)[^=]*/gm;
39
+ const fields = new Set();
40
+ for (const m of src.matchAll(fieldRe)) {
41
+ if (fields.size < 15) fields.add(m[1]);
42
+ }
43
+ for (const f of fields) {
44
+ sigs.push(`field ${f}`);
45
+ }
46
+
47
+ // SQLAlchemy Column definitions
48
+ const columnRe = /([a-z_]\w*)\s*=\s*Column\s*\([^)]*\)/g;
49
+ for (const m of src.matchAll(columnRe)) {
50
+ sigs.push(`column ${m[1]}`);
51
+ }
52
+
53
+ // Relationships (SQLAlchemy ForeignKey, relationship)
54
+ const relRe = /(?:ForeignKey|relationship)\s*\(\s*['"]([a-zA-Z_]\w*)['"]/g;
55
+ for (const m of src.matchAll(relRe)) {
56
+ sigs.push(`relation ${m[1]}`);
57
+ }
58
+
59
+ // Validators (@validator, @field_validator)
60
+ const validatorRe = /@(?:validator|field_validator)\s*\(\s*['"]?([a-z_]\w*)(?:['"]|,|\s|\))/g;
61
+ const validators = new Set();
62
+ for (const m of src.matchAll(validatorRe)) {
63
+ validators.add(m[1]);
64
+ }
65
+ for (const v of validators) {
66
+ sigs.push(`validator ${v}`);
67
+ }
68
+
69
+ // Config class (Pydantic v1 / SQLAlchemy)
70
+ if (/class\s+Config\s*:/.test(src)) {
71
+ sigs.push('config-class');
72
+ }
73
+
74
+ return Array.from(new Set(sigs)).slice(0, 50);
75
+ }
76
+
77
+ module.exports = { extract };
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Extract signatures from TOML configuration files.
5
+ * Focuses on section/table names and high-value keys.
6
+ *
7
+ * @param {string} src - Raw TOML 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
+ // Remove # comments while preserving values before comment markers.
15
+ const stripped = src.replace(/\s+#.*$/gm, '');
16
+
17
+ // [section] and [[array.section]]
18
+ for (const m of stripped.matchAll(/^\s*(\[\[?[^\]]+\]\]?)\s*$/gm)) {
19
+ sigs.push(`table ${m[1].trim()}`);
20
+ }
21
+
22
+ // Key-value lines (top-level and nested) — keep key names only.
23
+ for (const m of stripped.matchAll(/^\s*([A-Za-z0-9_.-]+)\s*=\s*(.+)$/gm)) {
24
+ const key = m[1].trim();
25
+ const value = m[2].trim();
26
+
27
+ // Prefer common metadata/config keys for compact, useful output.
28
+ if (/^(name|version|description|authors|license|requires-python|dependencies|optional-dependencies|scripts|tool\.|build-system\.|project\.)/.test(key)) {
29
+ sigs.push(`key ${key}`);
30
+ continue;
31
+ }
32
+
33
+ // Include booleans and simple scalar keys as generic config signal.
34
+ if (/^(true|false|"[^"]*"|'[^']*'|[0-9._-]+)$/.test(value)) {
35
+ sigs.push(`key ${key}`);
36
+ }
37
+ }
38
+
39
+ return Array.from(new Set(sigs)).slice(0, 40);
40
+ }
41
+
42
+ module.exports = { extract };
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Extract React component signatures from .tsx files.
5
+ * Captures component props interfaces, hooks usage, and exports.
6
+ *
7
+ * @param {string} src - Raw TypeScript/TSX 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
+ // Remove comments to simplify matching
15
+ const stripped = src
16
+ .replace(/\/\*[\s\S]*?\*\//g, '')
17
+ .replace(/\/\/.*$/gm, '');
18
+
19
+ // Component function declarations
20
+ const compRe = /(?:export\s+)?(?:const|function)\s+([A-Z]\w*)\s*(?:<[^>]*>)?\s*\(\s*(?:props|{\s*[^}]*})?/g;
21
+ for (const m of stripped.matchAll(compRe)) {
22
+ sigs.push(`component ${m[1]}`);
23
+ }
24
+
25
+ // Props interfaces: interface SomeProps { ... }
26
+ const propsRe = /interface\s+(\w*Props)\s*(?:<[^>]*>)?\s*{/g;
27
+ for (const m of stripped.matchAll(propsRe)) {
28
+ sigs.push(`props ${m[1]}`);
29
+ }
30
+
31
+ // React hooks (useState, useEffect, useContext, useCallback, useMemo, useReducer)
32
+ const hookRe = /use([A-Z]\w*)\s*(?:<[^>]*>)?\s*\(/g;
33
+ const hooks = new Set();
34
+ for (const m of stripped.matchAll(hookRe)) {
35
+ hooks.add(m[1]);
36
+ }
37
+ for (const h of hooks) {
38
+ sigs.push(`hook use${h}`);
39
+ }
40
+
41
+ // Import/export statements for components
42
+ const exportRe = /export\s+(?:const|function|default|interface|type)\s+([A-Z]\w*)/g;
43
+ for (const m of stripped.matchAll(exportRe)) {
44
+ sigs.push(`export ${m[1]}`);
45
+ }
46
+
47
+ // Event handler patterns: onClick, onChange, onSubmit, etc
48
+ const handlerRe = /on([A-Z]\w+)\s*=\s*{?\s*\(?[a-zA-Z_$]/g;
49
+ const handlers = new Set();
50
+ for (const m of stripped.matchAll(handlerRe)) {
51
+ handlers.add(m[1]);
52
+ }
53
+ for (const h of handlers) {
54
+ sigs.push(`handler on${h}`);
55
+ }
56
+
57
+ return Array.from(new Set(sigs)).slice(0, 50);
58
+ }
59
+
60
+ module.exports = { extract };
@@ -0,0 +1,99 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Extract Vue Single-File Component (SFC) signatures from .vue files.
5
+ * Captures component metadata: name, props, emits, slots, composables, lifecycle.
6
+ *
7
+ * @param {string} src - Raw Vue SFC 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
+ // Extract script block (both <script> and <script setup>)
15
+ const scriptMatch = src.match(/<script[^>]*>([\s\S]*?)<\/script>/);
16
+ if (!scriptMatch) return sigs;
17
+ const script = scriptMatch[1];
18
+
19
+ // Component name (from export default { name: '...' })
20
+ const nameRe = /(?:name\s*:\s*['"`]([a-zA-Z0-9]+)['"`]|export\s+default\s+defineComponent\s*\(\s*{\s*name\s*:\s*['"`]([a-zA-Z0-9]+)['"`])/;
21
+ const nameMatch = script.match(nameRe);
22
+ if (nameMatch) {
23
+ sigs.push(`component ${nameMatch[1] || nameMatch[2]}`);
24
+ }
25
+
26
+ // Props definition
27
+ const propsRe = /props\s*:\s*{([^}]*)}/;
28
+ const propsMatch = script.match(propsRe);
29
+ if (propsMatch) {
30
+ const propLines = propsMatch[1].split(',');
31
+ for (const line of propLines) {
32
+ const propName = line.trim().match(/([a-zA-Z_$]\w*)/);
33
+ if (propName) sigs.push(`prop ${propName[1]}`);
34
+ }
35
+ }
36
+
37
+ // Props in composition API (defineProps)
38
+ const definePropsRe = /defineProps\s*(?:<([^>]+)>)?\s*\(/;
39
+ if (definePropsRe.test(script)) {
40
+ sigs.push('props composition-api');
41
+ }
42
+
43
+ // Emits definition
44
+ const emitsRe = /emits\s*:\s*\[([^\]]+)\]/;
45
+ const emitsMatch = script.match(emitsRe);
46
+ if (emitsMatch) {
47
+ const emitNames = emitsMatch[1].split(',').map(e => e.trim().replace(/['"`]/g, ''));
48
+ for (const e of emitNames) {
49
+ if (e) sigs.push(`emit ${e}`);
50
+ }
51
+ }
52
+
53
+ // Emits in composition API (defineEmits)
54
+ const defineEmitsRe = /defineEmits\s*(?:<([^>]+)>)?\s*\(/;
55
+ if (defineEmitsRe.test(script)) {
56
+ sigs.push('emits composition-api');
57
+ }
58
+
59
+ // Lifecycle hooks
60
+ const lifecycleHooks = ['setup', 'created', 'mounted', 'updated', 'unmounted'];
61
+ for (const hook of lifecycleHooks) {
62
+ if (new RegExp(`\\b${hook}\\s*\\(`).test(script)) {
63
+ sigs.push(`lifecycle ${hook}`);
64
+ }
65
+ }
66
+
67
+ // Composable/hooks usage (useXxx)
68
+ const composableRe = /(?:import|const)\s+(?:{[^}]*}|[a-zA-Z_$]\w*)\s+from\s+['"]/g;
69
+ if (composableRe.test(script)) {
70
+ const useRe = /use([A-Z]\w*)/g;
71
+ for (const m of script.matchAll(useRe)) {
72
+ sigs.push(`composable use${m[1]}`);
73
+ }
74
+ }
75
+
76
+ // Slots definition — capture from <slot name="..."> and template slots
77
+ const namedSlotRe = /<slot\s+name=['"]([a-zA-Z_$]\w*)['"][^>]*>/g;
78
+ const templateSlotRe = /<template\s+#([a-zA-Z_$]\w*)|v-slot:([a-zA-Z_$]\w*)/g;
79
+ const slots = new Set();
80
+
81
+ // Named slots in template: <slot name="header">
82
+ for (const m of src.matchAll(namedSlotRe)) {
83
+ if (m[1]) slots.add(m[1]);
84
+ }
85
+
86
+ // Template slots with # or v-slot
87
+ for (const m of src.matchAll(templateSlotRe)) {
88
+ const slotName = m[1] || m[2];
89
+ if (slotName) slots.add(slotName);
90
+ }
91
+
92
+ for (const s of slots) {
93
+ sigs.push(`slot ${s}`);
94
+ }
95
+
96
+ return Array.from(new Set(sigs)).slice(0, 50);
97
+ }
98
+
99
+ module.exports = { extract };
@@ -0,0 +1,46 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Lightweight XML config extractor.
5
+ * Captures root tags, key config tags, and id/name/class attributes.
6
+ *
7
+ * @param {string} src - Raw XML 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
+ // Remove comments and XML declaration for simpler scanning.
15
+ const stripped = src
16
+ .replace(/<!--[\s\S]*?-->/g, '')
17
+ .replace(/<\?xml[\s\S]*?\?>/gi, '');
18
+
19
+ // Root element (first opening tag).
20
+ const root = stripped.match(/<\s*([A-Za-z_][\w:.-]*)\b[^>]*>/);
21
+ if (root) sigs.push(`root ${root[1]}`);
22
+
23
+ // High-value config-like tags.
24
+ const tagRe = /<\s*([A-Za-z_][\w:.-]*)\b([^>]*)>/g;
25
+ for (const m of stripped.matchAll(tagRe)) {
26
+ const tag = m[1];
27
+ const attrs = m[2] || '';
28
+
29
+ if (/^(bean|beans|route|routes|property|properties|dependency|dependencies|plugin|plugins|configuration|settings|profile|profiles|module|modules)$/i.test(tag)) {
30
+ sigs.push(`tag ${tag}`);
31
+ }
32
+
33
+ const id = attrs.match(/\bid\s*=\s*"([^"]+)"/i);
34
+ if (id) sigs.push(`${tag}#${id[1]}`);
35
+
36
+ const name = attrs.match(/\bname\s*=\s*"([^"]+)"/i);
37
+ if (name) sigs.push(`${tag}[name=${name[1]}]`);
38
+
39
+ const cls = attrs.match(/\bclass\s*=\s*"([^"]+)"/i);
40
+ if (cls) sigs.push(`${tag} -> ${cls[1]}`);
41
+ }
42
+
43
+ return Array.from(new Set(sigs)).slice(0, 50);
44
+ }
45
+
46
+ module.exports = { extract };
package/src/mcp/server.js CHANGED
@@ -18,7 +18,7 @@ const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, exp
18
18
 
19
19
  const SERVER_INFO = {
20
20
  name: 'sigmap',
21
- version: '3.3.2',
21
+ version: '3.5.0',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24