sigmap 3.4.0 → 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.
- package/AGENTS.md +600 -17
- package/CHANGELOG.md +35 -0
- package/README.md +2 -0
- package/gen-context.js +285 -6
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/index.js +10 -0
- package/packages/core/package.json +1 -1
- package/src/eval/analyzer.js +3 -0
- package/src/extractors/patterns.js +135 -0
- package/src/extractors/python_dataclass.js +77 -0
- package/src/extractors/typescript_react.js +60 -0
- package/src/extractors/vue_sfc.js +99 -0
- package/src/mcp/server.js +1 -1
package/gen-context.js
CHANGED
|
@@ -2179,6 +2179,285 @@ __factories["./src/extractors/markdown"] = function(module, exports) {
|
|
|
2179
2179
|
|
|
2180
2180
|
};
|
|
2181
2181
|
|
|
2182
|
+
// ── ./src/extractors/typescript_react ──
|
|
2183
|
+
__factories["./src/extractors/typescript_react"] = function(module, exports) {
|
|
2184
|
+
|
|
2185
|
+
'use strict';
|
|
2186
|
+
|
|
2187
|
+
function extract(src) {
|
|
2188
|
+
if (!src || typeof src !== 'string') return [];
|
|
2189
|
+
const sigs = [];
|
|
2190
|
+
const stripped = src
|
|
2191
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
2192
|
+
.replace(/\/\/.*$/gm, '');
|
|
2193
|
+
const compRe = /(?:export\s+)?(?:const|function)\s+([A-Z]\w*)\s*(?:<[^>]*>)?\s*\(\s*(?:props|{\s*[^}]*})?/g;
|
|
2194
|
+
for (const m of stripped.matchAll(compRe)) {
|
|
2195
|
+
sigs.push(`component ${m[1]}`);
|
|
2196
|
+
}
|
|
2197
|
+
const propsRe = /interface\s+(\w*Props)\s*(?:<[^>]*>)?\s*{/g;
|
|
2198
|
+
for (const m of stripped.matchAll(propsRe)) {
|
|
2199
|
+
sigs.push(`props ${m[1]}`);
|
|
2200
|
+
}
|
|
2201
|
+
const hookRe = /use([A-Z]\w*)\s*(?:<[^>]*>)?\s*\(/g;
|
|
2202
|
+
const hooks = new Set();
|
|
2203
|
+
for (const m of stripped.matchAll(hookRe)) {
|
|
2204
|
+
hooks.add(m[1]);
|
|
2205
|
+
}
|
|
2206
|
+
for (const h of hooks) {
|
|
2207
|
+
sigs.push(`hook use${h}`);
|
|
2208
|
+
}
|
|
2209
|
+
const exportRe = /export\s+(?:const|function|default|interface|type)\s+([A-Z]\w*)/g;
|
|
2210
|
+
for (const m of stripped.matchAll(exportRe)) {
|
|
2211
|
+
sigs.push(`export ${m[1]}`);
|
|
2212
|
+
}
|
|
2213
|
+
const handlerRe = /on([A-Z]\w+)\s*=\s*{?\s*\(?[a-zA-Z_$]/g;
|
|
2214
|
+
const handlers = new Set();
|
|
2215
|
+
for (const m of stripped.matchAll(handlerRe)) {
|
|
2216
|
+
handlers.add(m[1]);
|
|
2217
|
+
}
|
|
2218
|
+
for (const h of handlers) {
|
|
2219
|
+
sigs.push(`handler on${h}`);
|
|
2220
|
+
}
|
|
2221
|
+
return Array.from(new Set(sigs)).slice(0, 50);
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
module.exports = { extract };
|
|
2225
|
+
|
|
2226
|
+
};
|
|
2227
|
+
|
|
2228
|
+
// ── ./src/extractors/vue_sfc ──
|
|
2229
|
+
__factories["./src/extractors/vue_sfc"] = function(module, exports) {
|
|
2230
|
+
|
|
2231
|
+
'use strict';
|
|
2232
|
+
|
|
2233
|
+
function extract(src) {
|
|
2234
|
+
if (!src || typeof src !== 'string') return [];
|
|
2235
|
+
const sigs = [];
|
|
2236
|
+
const scriptMatch = src.match(/<script[^>]*>([\s\S]*?)<\/script>/);
|
|
2237
|
+
if (!scriptMatch) return sigs;
|
|
2238
|
+
const script = scriptMatch[1];
|
|
2239
|
+
const nameRe = /(?:name\s*:\s*['"`]([a-zA-Z0-9]+)['"`]|export\s+default\s+defineComponent\s*\(\s*{\s*name\s*:\s*['"`]([a-zA-Z0-9]+)['"`])/;
|
|
2240
|
+
const nameMatch = script.match(nameRe);
|
|
2241
|
+
if (nameMatch) {
|
|
2242
|
+
sigs.push(`component ${nameMatch[1] || nameMatch[2]}`);
|
|
2243
|
+
}
|
|
2244
|
+
const propsRe = /props\s*:\s*{([^}]*)}/;
|
|
2245
|
+
const propsMatch = script.match(propsRe);
|
|
2246
|
+
if (propsMatch) {
|
|
2247
|
+
const propLines = propsMatch[1].split(',');
|
|
2248
|
+
for (const line of propLines) {
|
|
2249
|
+
const propName = line.trim().match(/([a-zA-Z_$]\w*)/);
|
|
2250
|
+
if (propName) sigs.push(`prop ${propName[1]}`);
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
const definePropsRe = /defineProps\s*(?:<([^>]+)>)?\s*\(/;
|
|
2254
|
+
if (definePropsRe.test(script)) {
|
|
2255
|
+
sigs.push('props composition-api');
|
|
2256
|
+
}
|
|
2257
|
+
const emitsRe = /emits\s*:\s*\[([^\]]+)\]/;
|
|
2258
|
+
const emitsMatch = script.match(emitsRe);
|
|
2259
|
+
if (emitsMatch) {
|
|
2260
|
+
const emitNames = emitsMatch[1].split(',').map(e => e.trim().replace(/['"`]/g, ''));
|
|
2261
|
+
for (const e of emitNames) {
|
|
2262
|
+
if (e) sigs.push(`emit ${e}`);
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
const defineEmitsRe = /defineEmits\s*(?:<([^>]+)>)?\s*\(/;
|
|
2266
|
+
if (defineEmitsRe.test(script)) {
|
|
2267
|
+
sigs.push('emits composition-api');
|
|
2268
|
+
}
|
|
2269
|
+
const lifecycleHooks = ['setup', 'created', 'mounted', 'updated', 'unmounted'];
|
|
2270
|
+
for (const hook of lifecycleHooks) {
|
|
2271
|
+
if (new RegExp(`\\b${hook}\\s*\\(`).test(script)) {
|
|
2272
|
+
sigs.push(`lifecycle ${hook}`);
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
const namedSlotRe = /<slot\s+name=['"]([a-zA-Z_$]\w*)['"][^>]*>/g;
|
|
2276
|
+
const templateSlotRe = /<template\s+#([a-zA-Z_$]\w*)|v-slot:([a-zA-Z_$]\w*)/g;
|
|
2277
|
+
const slots = new Set();
|
|
2278
|
+
for (const m of src.matchAll(namedSlotRe)) {
|
|
2279
|
+
if (m[1]) slots.add(m[1]);
|
|
2280
|
+
}
|
|
2281
|
+
for (const m of src.matchAll(templateSlotRe)) {
|
|
2282
|
+
const slotName = m[1] || m[2];
|
|
2283
|
+
if (slotName) slots.add(slotName);
|
|
2284
|
+
}
|
|
2285
|
+
for (const s of slots) {
|
|
2286
|
+
sigs.push(`slot ${s}`);
|
|
2287
|
+
}
|
|
2288
|
+
return Array.from(new Set(sigs)).slice(0, 50);
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
module.exports = { extract };
|
|
2292
|
+
|
|
2293
|
+
};
|
|
2294
|
+
|
|
2295
|
+
// ── ./src/extractors/python_dataclass ──
|
|
2296
|
+
__factories["./src/extractors/python_dataclass"] = function(module, exports) {
|
|
2297
|
+
|
|
2298
|
+
'use strict';
|
|
2299
|
+
|
|
2300
|
+
function extract(src) {
|
|
2301
|
+
if (!src || typeof src !== 'string') return [];
|
|
2302
|
+
const sigs = [];
|
|
2303
|
+
const dataclassRe = /@dataclass(?:\([^)]*\))?[\s\n]+class\s+([A-Z]\w*)/g;
|
|
2304
|
+
for (const m of src.matchAll(dataclassRe)) {
|
|
2305
|
+
sigs.push(`dataclass ${m[1]}`);
|
|
2306
|
+
}
|
|
2307
|
+
const pydanticRe = /class\s+([A-Z]\w*)\s*\([^)]*BaseModel[^)]*\)/g;
|
|
2308
|
+
for (const m of src.matchAll(pydanticRe)) {
|
|
2309
|
+
sigs.push(`model ${m[1]}`);
|
|
2310
|
+
}
|
|
2311
|
+
if (/model_validate|field_validator|computed_field/.test(src)) {
|
|
2312
|
+
sigs.push('pydantic v2+');
|
|
2313
|
+
}
|
|
2314
|
+
const sqlalchemyRe = /class\s+([A-Z]\w*)\s*\([^)]*(?:Base|declarative_base)[^)]*\)/g;
|
|
2315
|
+
for (const m of src.matchAll(sqlalchemyRe)) {
|
|
2316
|
+
sigs.push(`orm ${m[1]}`);
|
|
2317
|
+
}
|
|
2318
|
+
const fieldRe = /^\s+([a-z_]\w*)\s*:\s*([A-Z]\w*|List|Dict|Optional|Union)[^=]*/gm;
|
|
2319
|
+
const fields = new Set();
|
|
2320
|
+
for (const m of src.matchAll(fieldRe)) {
|
|
2321
|
+
if (fields.size < 15) fields.add(m[1]);
|
|
2322
|
+
}
|
|
2323
|
+
for (const f of fields) {
|
|
2324
|
+
sigs.push(`field ${f}`);
|
|
2325
|
+
}
|
|
2326
|
+
const columnRe = /([a-z_]\w*)\s*=\s*Column\s*\([^)]*\)/g;
|
|
2327
|
+
for (const m of src.matchAll(columnRe)) {
|
|
2328
|
+
sigs.push(`column ${m[1]}`);
|
|
2329
|
+
}
|
|
2330
|
+
const relRe = /(?:ForeignKey|relationship)\s*\(\s*['"]([a-zA-Z_]\w*)['"]/g;
|
|
2331
|
+
for (const m of src.matchAll(relRe)) {
|
|
2332
|
+
sigs.push(`relation ${m[1]}`);
|
|
2333
|
+
}
|
|
2334
|
+
const validatorRe = /@(?:validator|field_validator)\s*\(\s*['"]?([a-z_]\w*)(?:['"]|,|\s|\))/g;
|
|
2335
|
+
const validators = new Set();
|
|
2336
|
+
for (const m of src.matchAll(validatorRe)) {
|
|
2337
|
+
validators.add(m[1]);
|
|
2338
|
+
}
|
|
2339
|
+
for (const v of validators) {
|
|
2340
|
+
sigs.push(`validator ${v}`);
|
|
2341
|
+
}
|
|
2342
|
+
if (/class\s+Config\s*:/.test(src)) {
|
|
2343
|
+
sigs.push('config-class');
|
|
2344
|
+
}
|
|
2345
|
+
return Array.from(new Set(sigs)).slice(0, 50);
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
module.exports = { extract };
|
|
2349
|
+
|
|
2350
|
+
};
|
|
2351
|
+
|
|
2352
|
+
// ── ./src/extractors/patterns ──
|
|
2353
|
+
__factories["./src/extractors/patterns"] = function(module, exports) {
|
|
2354
|
+
|
|
2355
|
+
'use strict';
|
|
2356
|
+
|
|
2357
|
+
function extract(src) {
|
|
2358
|
+
if (!src || typeof src !== 'string') return [];
|
|
2359
|
+
const sigs = [];
|
|
2360
|
+
|
|
2361
|
+
// Service container / factory patterns
|
|
2362
|
+
const containerRe = /(?:class|function|const)\s+([A-Z]\w*(?:Container|Factory|Registry|Provider|Injector))\b/g;
|
|
2363
|
+
for (const m of src.matchAll(containerRe)) {
|
|
2364
|
+
sigs.push(`di-container ${m[1]}`);
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
// Service decorators: @Injectable, @Service, @Singleton, @Provide
|
|
2368
|
+
const decoratorRe = /@(?:Injectable|Service|Singleton|Provide|Module|Component)\s*(?:\([^)]*\))?\s*(?:class|export\s+class|const\s+)\s+([A-Z]\w*)/g;
|
|
2369
|
+
for (const m of src.matchAll(decoratorRe)) {
|
|
2370
|
+
sigs.push(`service-decorated ${m[1]}`);
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
// Dependency injection via constructor: constructor(private readonly ...: Service)
|
|
2374
|
+
const ctorDiRe = /constructor\s*\([^)]*(?:private|protected)?\s+(?:readonly\s+)?([a-z_]\w*)\s*:\s*([A-Z]\w*)/g;
|
|
2375
|
+
const diServices = new Set();
|
|
2376
|
+
for (const m of src.matchAll(ctorDiRe)) {
|
|
2377
|
+
diServices.add(m[1]);
|
|
2378
|
+
}
|
|
2379
|
+
if (diServices.size > 0) {
|
|
2380
|
+
sigs.push(`di-injection ${diServices.size} params`);
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
// Repository pattern: extends Repository, implements IRepository
|
|
2384
|
+
const repoRe = /class\s+([A-Z]\w*(?:Repository|Repo|DataAccess))\b/g;
|
|
2385
|
+
for (const m of src.matchAll(repoRe)) {
|
|
2386
|
+
sigs.push(`repo ${m[1]}`);
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
// Service layer: @Service or ServiceImpl pattern
|
|
2390
|
+
const serviceRe = /(?:export\s+)?class\s+([A-Z]\w*Service\b)/g;
|
|
2391
|
+
for (const m of src.matchAll(serviceRe)) {
|
|
2392
|
+
sigs.push(`service ${m[1]}`);
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
// Middleware detection: app.use(), router.use(), middleware function
|
|
2396
|
+
if (/app\.use\s*\(|router\.use\s*\(|\.use\s*\(\s*function|middleware|app\.get\s*\(\s*['"`]\/[^'"`]*['"`]/.test(src)) {
|
|
2397
|
+
sigs.push('middleware-present');
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
// Export type/interface followed by class implementing it
|
|
2401
|
+
const exportTypeRe = /export\s+(?:type|interface)\s+([A-Z]\w*)/g;
|
|
2402
|
+
const exportedTypes = new Set();
|
|
2403
|
+
for (const m of src.matchAll(exportTypeRe)) {
|
|
2404
|
+
exportedTypes.add(m[1]);
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
// Check if exported types have implementations
|
|
2408
|
+
for (const type of exportedTypes) {
|
|
2409
|
+
const implRe = new RegExp(`class\\s+([A-Z]\\w*)\\s+(?:extends|implements)\\s+(?:.*\\s+)?${type}\\b`, 'i');
|
|
2410
|
+
if (implRe.test(src)) {
|
|
2411
|
+
sigs.push(`type-impl ${type}`);
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
// Unchecked nulls / optional without validation
|
|
2416
|
+
if (/\?\s*{|Optional\s*<|\?\s*\.get\(\)|\?\s*\[|\?\s*\.length|\.split\(\)\.filter\(Boolean\)|if\s*\(\s*!.*\).*throw/.test(src)) {
|
|
2417
|
+
sigs.push('unsafe-null-check');
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
// Missing validation (direct use of user input)
|
|
2421
|
+
const userInputRe = /(?:request|input|params|body|query|args)\s*\[\s*['"][^'"]*['"]\s*\]|req(?:uest)?\..*\s*==|params\.split|String\(.*\)\.toLowerCase/;
|
|
2422
|
+
if (userInputRe.test(src)) {
|
|
2423
|
+
sigs.push('unsafe-input-validation');
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
// Weak error handling (catch {} or empty catch)
|
|
2427
|
+
if (/catch\s*\(\s*\)\s*\{|\}\s*catch\s*\{(\s*\/\/|\s*\})/.test(src)) {
|
|
2428
|
+
sigs.push('weak-error-handling');
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
// Direct error exposure
|
|
2432
|
+
if (/throw\s+new\s+Error\(|console\s*\.\s*error|res\.status\(500\)\.send\(err\)/.test(src)) {
|
|
2433
|
+
sigs.push('unsafe-error-exposure');
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
// Heavy imports
|
|
2437
|
+
const importRe = /(?:import|require)\s+(?:{[^}]*}|[a-zA-Z_]\w*)\s+from\s+['"]\.?\.?\/[^'"]+['"]/g;
|
|
2438
|
+
const importCount = (src.match(importRe) || []).length;
|
|
2439
|
+
if (importCount > 5) {
|
|
2440
|
+
sigs.push(`heavy-imports ${importCount}`);
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
// Controller/Handler layer
|
|
2444
|
+
const controllerRe = /(?:class|export)\s+([A-Z]\w*(?:Controller|Handler|Route))\b/g;
|
|
2445
|
+
for (const m of src.matchAll(controllerRe)) {
|
|
2446
|
+
sigs.push(`controller ${m[1]}`);
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
// Use case / Domain logic
|
|
2450
|
+
if (/UseCase|Command|Query|UseCase\b|Interactor/.test(src)) {
|
|
2451
|
+
sigs.push('domain-usecase');
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
return Array.from(new Set(sigs)).slice(0, 60);
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
module.exports = { extract };
|
|
2458
|
+
|
|
2459
|
+
};
|
|
2460
|
+
|
|
2182
2461
|
// ── ./src/extractors/deps ──
|
|
2183
2462
|
__factories["./src/extractors/deps"] = function(module, exports) {
|
|
2184
2463
|
|
|
@@ -4199,7 +4478,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
|
|
|
4199
4478
|
|
|
4200
4479
|
const SERVER_INFO = {
|
|
4201
4480
|
name: 'sigmap',
|
|
4202
|
-
version: '3.
|
|
4481
|
+
version: '3.5.0',
|
|
4203
4482
|
description: 'SigMap MCP server — code signatures on demand',
|
|
4204
4483
|
};
|
|
4205
4484
|
|
|
@@ -5098,7 +5377,7 @@ __factories["./src/eval/analyzer"] = function(module, exports) {
|
|
|
5098
5377
|
const path = require('path');
|
|
5099
5378
|
|
|
5100
5379
|
const EXT_MAP = {
|
|
5101
|
-
'.ts': 'typescript', '.tsx': '
|
|
5380
|
+
'.ts': 'typescript', '.tsx': 'typescript_react',
|
|
5102
5381
|
'.js': 'javascript', '.jsx': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript',
|
|
5103
5382
|
'.py': 'python', '.pyw': 'python',
|
|
5104
5383
|
'.java': 'java',
|
|
@@ -5112,7 +5391,7 @@ __factories["./src/eval/analyzer"] = function(module, exports) {
|
|
|
5112
5391
|
'.swift': 'swift',
|
|
5113
5392
|
'.dart': 'dart',
|
|
5114
5393
|
'.scala': 'scala', '.sc': 'scala',
|
|
5115
|
-
'.vue': '
|
|
5394
|
+
'.vue': 'vue_sfc',
|
|
5116
5395
|
'.svelte': 'svelte',
|
|
5117
5396
|
'.html': 'html', '.htm': 'html',
|
|
5118
5397
|
'.css': 'css', '.scss': 'css', '.sass': 'css', '.less': 'css',
|
|
@@ -5614,7 +5893,7 @@ const path = require('path');
|
|
|
5614
5893
|
const os = require('os');
|
|
5615
5894
|
const { execSync } = require('child_process');
|
|
5616
5895
|
|
|
5617
|
-
const VERSION = '3.
|
|
5896
|
+
const VERSION = '3.5.0';
|
|
5618
5897
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
5619
5898
|
|
|
5620
5899
|
function requireSourceOrBundled(key) {
|
|
@@ -5636,7 +5915,7 @@ const { DEFAULTS } = requireSourceOrBundled('./src/config/defaults');
|
|
|
5636
5915
|
// Language → extractor mapping (by file extension)
|
|
5637
5916
|
// ---------------------------------------------------------------------------
|
|
5638
5917
|
const EXT_MAP = {
|
|
5639
|
-
'.ts': 'typescript', '.tsx': '
|
|
5918
|
+
'.ts': 'typescript', '.tsx': 'typescript_react',
|
|
5640
5919
|
'.js': 'javascript', '.jsx': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript',
|
|
5641
5920
|
'.py': 'python', '.pyw': 'python',
|
|
5642
5921
|
'.java': 'java',
|
|
@@ -5650,7 +5929,7 @@ const EXT_MAP = {
|
|
|
5650
5929
|
'.swift': 'swift',
|
|
5651
5930
|
'.dart': 'dart',
|
|
5652
5931
|
'.scala': 'scala', '.sc': 'scala',
|
|
5653
|
-
'.vue': '
|
|
5932
|
+
'.vue': 'vue_sfc',
|
|
5654
5933
|
'.svelte': 'svelte',
|
|
5655
5934
|
'.html': 'html', '.htm': 'html',
|
|
5656
5935
|
'.css': 'css', '.scss': 'css', '.sass': 'css', '.less': 'css',
|
package/package.json
CHANGED
package/packages/core/index.js
CHANGED
|
@@ -45,6 +45,16 @@ const EXT_MAP = {
|
|
|
45
45
|
'.properties': 'properties',
|
|
46
46
|
'.xml': 'xml',
|
|
47
47
|
'.md': 'markdown',
|
|
48
|
+
// Phase C specialized extractors
|
|
49
|
+
'.tsx': 'typescript_react',
|
|
50
|
+
'.vue': 'vue_sfc',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Phase C fallback: also try specialized extractors for base extensions
|
|
54
|
+
const PHASE_C_EXTRACTORS = {
|
|
55
|
+
'typescript_react': '.tsx',
|
|
56
|
+
'vue_sfc': '.vue',
|
|
57
|
+
'python_dataclass': '.py',
|
|
48
58
|
};
|
|
49
59
|
|
|
50
60
|
const SRC_ROOT = path.resolve(__dirname, '..', '..', 'src');
|
package/src/eval/analyzer.js
CHANGED
|
@@ -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,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,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 };
|