sigmap 3.4.0 → 3.5.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/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.4.0',
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': 'typescript',
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': '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',
@@ -5602,6 +5881,153 @@ __factories["./packages/adapters/index"] = function(module, exports) {
5602
5881
  module.exports = { getAdapter, listAdapters, adapt, outputsToAdapters };
5603
5882
  };
5604
5883
 
5884
+ // ── ./src/extractors/generic ──
5885
+ __factories["./src/extractors/generic"] = function(module, exports) {
5886
+ 'use strict';
5887
+ const PATTERNS = [
5888
+ /^(?:pub\s+)?(?:async\s+)?function\s+\w+\s*\(/,
5889
+ /^(?:pub\s+)?(?:async\s+)?fn\s+\w+[\s(<]/,
5890
+ /^def\s+\w+[\s(|:]/,
5891
+ /^(?:pub\s+)?func\s+\w+\s*\(/,
5892
+ /^(?:let|let\s+rec)\s+\w+\s*[=(]/,
5893
+ /^class\s+\w+/,
5894
+ /^(?:proc|sub|method)\s+\w+\s*\(/,
5895
+ ];
5896
+ function extract(src) {
5897
+ if (!src || typeof src !== 'string') return [];
5898
+ const results = [];
5899
+ for (const raw of src.split('\n')) {
5900
+ const line = raw.trim();
5901
+ if (!line || /^[#\-]/.test(line) || /^\/\//.test(line) || line.includes('\0')) continue;
5902
+ for (const pat of PATTERNS) {
5903
+ if (pat.test(line)) { results.push(line.slice(0, 120)); break; }
5904
+ }
5905
+ if (results.length >= 15) break;
5906
+ }
5907
+ return results;
5908
+ }
5909
+ module.exports = { extract };
5910
+ };
5911
+
5912
+ // ── ./src/format/llm-txt ──
5913
+ __factories["./src/format/llm-txt"] = function(module, exports) {
5914
+ 'use strict';
5915
+ const path = require('path');
5916
+ function outputPath(cwd) { return path.join(cwd, 'llm.txt'); }
5917
+ function format(context, cwd, version) {
5918
+ const name = context.projectName || path.basename(cwd);
5919
+ const langs = [...new Set((context.fileEntries || []).map(f => f.language).filter(Boolean))];
5920
+ const mods = context.srcDirs || [];
5921
+ return [
5922
+ `# Project: ${name}`,
5923
+ `Languages: ${langs.join(', ') || 'unknown'}`,
5924
+ `Root: ${mods[0] || 'src/'}`,
5925
+ '',
5926
+ '## Modules',
5927
+ ...mods.map(m => `- ${m}/`),
5928
+ '',
5929
+ '## Key flows',
5930
+ '- <!-- describe your main user flows here -->',
5931
+ '',
5932
+ '## Rules',
5933
+ '- <!-- describe your team conventions here -->',
5934
+ '',
5935
+ `Generated: ${new Date().toISOString()} | SigMap v${version}`,
5936
+ ].join('\n');
5937
+ }
5938
+ module.exports = { format, outputPath };
5939
+ };
5940
+
5941
+ // ── ./src/format/llms-txt ──
5942
+ __factories["./src/format/llms-txt"] = function(module, exports) {
5943
+ 'use strict';
5944
+ const path = require('path');
5945
+ const fs = require('fs');
5946
+ const { execSync } = require('child_process');
5947
+ function outputPath(cwd) { return path.join(cwd, 'llms.txt'); }
5948
+ function getShortCommit(cwd) {
5949
+ try { return execSync('git rev-parse --short HEAD', { cwd, timeout: 2000 }).toString().trim(); }
5950
+ catch (_) { return ''; }
5951
+ }
5952
+ function detectVersion(cwd) {
5953
+ try {
5954
+ const pkg = path.join(cwd, 'package.json');
5955
+ if (fs.existsSync(pkg)) return JSON.parse(fs.readFileSync(pkg, 'utf8')).version || '';
5956
+ } catch (_) {}
5957
+ return '';
5958
+ }
5959
+ function format(context, cwd, writtenFiles, sigmapVersion) {
5960
+ writtenFiles = writtenFiles || [];
5961
+ sigmapVersion = sigmapVersion || '';
5962
+ const name = context.projectName || path.basename(cwd);
5963
+ const ver = detectVersion(cwd);
5964
+ const commit = getShortCommit(cwd);
5965
+ const langs = [...new Set((context.fileEntries || []).map(f => f.language).filter(Boolean))];
5966
+ const lines = [
5967
+ '# SigMap Context Index',
5968
+ `> Generated by SigMap v${sigmapVersion} — zero-dependency AI context engine`,
5969
+ '',
5970
+ '## Project',
5971
+ `- Name: ${name}`,
5972
+ ];
5973
+ if (ver) lines.push(`- Version: ${ver}`);
5974
+ if (langs.length) lines.push(`- Language: ${langs.join(', ')}`);
5975
+ if (commit) lines.push(`- Commit: ${commit}`);
5976
+ if (writtenFiles.length) {
5977
+ lines.push('', '## Context Files');
5978
+ for (const f of writtenFiles) {
5979
+ const rel = path.relative(cwd, f.path);
5980
+ const extra = f.tokens ? `: ${f.tokens} tokens` : '';
5981
+ lines.push(`- [${f.label || rel}](${rel})${extra}`);
5982
+ }
5983
+ }
5984
+ const mods = context.srcDirs || [];
5985
+ if (mods.length) {
5986
+ lines.push('', '## Source Modules');
5987
+ for (const m of mods) lines.push(`- [${m}/](${m}/)`);
5988
+ }
5989
+ const top5 = (context.fileEntries || [])
5990
+ .sort((a, b) => (b.sigs || []).length - (a.sigs || []).length)
5991
+ .slice(0, 5);
5992
+ if (top5.length) {
5993
+ lines.push('', '## Key Files');
5994
+ for (const f of top5) {
5995
+ const rel = path.relative(cwd, f.filePath);
5996
+ const preview = (f.sigs || []).slice(0, 3).join(', ');
5997
+ lines.push(`- ${rel}: ${preview}`);
5998
+ }
5999
+ }
6000
+ return lines.join('\n');
6001
+ }
6002
+ module.exports = { format, outputPath };
6003
+ };
6004
+
6005
+ // ── ./packages/adapters/llm-full ──
6006
+ __factories["./packages/adapters/llm-full"] = function(module, exports) {
6007
+ 'use strict';
6008
+ const path = require('path');
6009
+ const fs = require('fs');
6010
+ function outputPath(cwd) { return path.join(cwd, 'llm-full.txt'); }
6011
+ function format(context, opts) {
6012
+ opts = opts || {};
6013
+ const lines = [
6014
+ `# ${context.projectName || 'Project'} — SigMap Context`,
6015
+ `Generated: ${new Date().toISOString()} | SigMap v${opts.version || ''}`,
6016
+ '',
6017
+ ];
6018
+ for (const entry of (context.fileEntries || [])) {
6019
+ const rel = path.relative(opts.cwd || '', entry.filePath);
6020
+ lines.push(`## ${rel}`, '```', ...(entry.sigs || []), '```', '');
6021
+ }
6022
+ return lines.join('\n');
6023
+ }
6024
+ function write(context, cwd, opts) {
6025
+ opts = opts || {};
6026
+ fs.writeFileSync(outputPath(cwd), format(context, { ...opts, cwd }));
6027
+ }
6028
+ module.exports = { name: 'llm-full', format, outputPath, write };
6029
+ };
6030
+
5605
6031
  /**
5606
6032
  * SigMap — gen-context.js v1.2.0
5607
6033
  * Zero-dependency AI context engine.
@@ -5614,7 +6040,7 @@ const path = require('path');
5614
6040
  const os = require('os');
5615
6041
  const { execSync } = require('child_process');
5616
6042
 
5617
- const VERSION = '3.4.0';
6043
+ const VERSION = '3.5.1';
5618
6044
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
5619
6045
 
5620
6046
  function requireSourceOrBundled(key) {
@@ -5636,7 +6062,7 @@ const { DEFAULTS } = requireSourceOrBundled('./src/config/defaults');
5636
6062
  // Language → extractor mapping (by file extension)
5637
6063
  // ---------------------------------------------------------------------------
5638
6064
  const EXT_MAP = {
5639
- '.ts': 'typescript', '.tsx': 'typescript',
6065
+ '.ts': 'typescript', '.tsx': 'typescript_react',
5640
6066
  '.js': 'javascript', '.jsx': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript',
5641
6067
  '.py': 'python', '.pyw': 'python',
5642
6068
  '.java': 'java',
@@ -5650,7 +6076,7 @@ const EXT_MAP = {
5650
6076
  '.swift': 'swift',
5651
6077
  '.dart': 'dart',
5652
6078
  '.scala': 'scala', '.sc': 'scala',
5653
- '.vue': 'vue',
6079
+ '.vue': 'vue_sfc',
5654
6080
  '.svelte': 'svelte',
5655
6081
  '.html': 'html', '.htm': 'html',
5656
6082
  '.css': 'css', '.scss': 'css', '.sass': 'css', '.less': 'css',
@@ -5763,7 +6189,8 @@ function detectAndExtract(filePath, content, maxSigsPerFile) {
5763
6189
  const ext = path.extname(base).toLowerCase();
5764
6190
  let extractorName = EXT_MAP[ext] || null;
5765
6191
  if (!extractorName && isDockerfile(base)) extractorName = 'dockerfile';
5766
- if (!extractorName) return [];
6192
+ // Feature 7: generic fallback — catches Nix, Elixir, Gleam, Zig, Go templates, etc.
6193
+ if (!extractorName) extractorName = 'generic';
5767
6194
 
5768
6195
  const extractor = getExtractor(extractorName);
5769
6196
  if (!extractor) return [];
@@ -6701,7 +7128,37 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
6701
7128
  const finalTokens = estimateTokens(content);
6702
7129
  const formatIdx = process.argv.indexOf('--format');
6703
7130
  const formatValue = formatIdx >= 0 ? process.argv[formatIdx + 1] : (config.format || 'default');
6704
- writeOutputs(content, config.outputs, cwd, config);
7131
+ // Feature 2: --mode write matrix
7132
+ const activeMode = process.argv.indexOf('--mode') >= 0
7133
+ ? process.argv[process.argv.indexOf('--mode') + 1]
7134
+ : (config.mode || 'default');
7135
+ if (activeMode === 'fast') {
7136
+ const llmTxtMod = requireSourceOrBundled('./src/format/llm-txt');
7137
+ const syncContext = { projectName: path.basename(cwd), fileEntries, srcDirs: config.srcDirs || [] };
7138
+ fs.writeFileSync(llmTxtMod.outputPath(cwd), llmTxtMod.format(syncContext, cwd, VERSION));
7139
+ console.warn(`[sigmap] --mode fast → llm.txt`);
7140
+ } else if (activeMode === 'full') {
7141
+ const llmFullMod = requireSourceOrBundled('./packages/adapters/llm-full');
7142
+ const syncContext = { projectName: path.basename(cwd), fileEntries, srcDirs: config.srcDirs || [] };
7143
+ llmFullMod.write(syncContext, cwd, { version: VERSION });
7144
+ console.warn(`[sigmap] --mode full → llm-full.txt`);
7145
+ } else if (activeMode === 'both') {
7146
+ const llmTxtMod = requireSourceOrBundled('./src/format/llm-txt');
7147
+ const llmFullMod = requireSourceOrBundled('./packages/adapters/llm-full');
7148
+ const llmsMod = requireSourceOrBundled('./src/format/llms-txt');
7149
+ const syncContext = { projectName: path.basename(cwd), fileEntries, srcDirs: config.srcDirs || [] };
7150
+ fs.writeFileSync(llmTxtMod.outputPath(cwd), llmTxtMod.format(syncContext, cwd, VERSION));
7151
+ llmFullMod.write(syncContext, cwd, { version: VERSION });
7152
+ writeOutputs(content, config.outputs, cwd, config);
7153
+ const llmsContent = llmsMod.format(syncContext, cwd, [
7154
+ { path: llmTxtMod.outputPath(cwd), label: 'llm.txt' },
7155
+ { path: llmFullMod.outputPath(cwd), label: 'llm-full.txt' },
7156
+ ], VERSION);
7157
+ fs.writeFileSync(llmsMod.outputPath(cwd), llmsContent);
7158
+ console.warn(`[sigmap] --mode both → copilot-instructions.md + llm.txt + llm-full.txt + llms.txt`);
7159
+ } else {
7160
+ writeOutputs(content, config.outputs, cwd, config);
7161
+ }
6705
7162
  if (formatValue === 'cache') writeCacheOutput(content, cwd);
6706
7163
  result = { inputTokenTotal, finalTokens, fileCount: beforeCount, droppedCount };
6707
7164
  }
@@ -6737,6 +7194,29 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
6737
7194
  }
6738
7195
  }
6739
7196
 
7197
+ // Feature 8: post-run summary — 6-line stdout after every normal run
7198
+ if (!reportMode
7199
+ && !process.argv.includes('--json')
7200
+ && !process.argv.includes('--report')
7201
+ && !process.argv.includes('--quiet')) {
7202
+ const bar = '\u2500'.repeat(43);
7203
+ const syms = fileEntries.reduce((n, f) => n + (f.sigs ? f.sigs.length : 0), 0);
7204
+ const pct = result.inputTokenTotal > 0
7205
+ ? Math.round((1 - result.finalTokens / result.inputTokenTotal) * 100)
7206
+ : 0;
7207
+ process.stderr.write([
7208
+ bar,
7209
+ ` SigMap v${VERSION}`,
7210
+ ` Files scanned : ${result.fileCount}`,
7211
+ ` Symbols found : ${syms.toLocaleString()}`,
7212
+ ` Token reduction: ${pct}% (${result.inputTokenTotal.toLocaleString()} \u2192 ${result.finalTokens.toLocaleString()})`,
7213
+ ` Output : .github/copilot-instructions.md`,
7214
+ bar,
7215
+ ` Try: "explain the architecture" \u00b7 "find the auth module"`,
7216
+ bar, '',
7217
+ ].join('\n'));
7218
+ }
7219
+
6740
7220
  return result;
6741
7221
  }
6742
7222
 
@@ -7048,6 +7528,12 @@ function registerMcp(cwd, scriptPath) {
7048
7528
 
7049
7529
  function main() {
7050
7530
  const args = process.argv.slice(2);
7531
+
7532
+ // Feature 1: `sigmap run` alias — strip positional 'run' so existing logic handles it
7533
+ if (args[0] === 'run' && !args[0].startsWith('--')) {
7534
+ args.shift();
7535
+ }
7536
+
7051
7537
  const invokedFrom = process.cwd();
7052
7538
  const cwd = resolveProjectRoot(invokedFrom);
7053
7539
  const scriptPath = process.argv[1] || path.join(invokedFrom, 'gen-context.js');
@@ -7075,6 +7561,73 @@ function main() {
7075
7561
 
7076
7562
  const config = loadConfig(cwd);
7077
7563
 
7564
+ // Feature 2: `--mode fast|full|both`
7565
+ const modeIdx = args.indexOf('--mode');
7566
+ const mode = modeIdx !== -1
7567
+ ? args[modeIdx + 1]
7568
+ : (config.mode || 'default');
7569
+ const VALID_MODES = ['default', 'fast', 'full', 'both'];
7570
+ if (mode && !VALID_MODES.includes(mode)) {
7571
+ console.error(`[sigmap] unknown --mode "${mode}". Valid: ${VALID_MODES.join(', ')}`);
7572
+ process.exit(1);
7573
+ }
7574
+
7575
+ // Feature 6: `sigmap sync` — write all outputs + llms.txt + print compact diff
7576
+ if (args[0] === 'sync') {
7577
+ try {
7578
+ // Generate copilot-instructions.md first (existing output)
7579
+ runGenerate(cwd, config, false);
7580
+ const llmTxt = requireSourceOrBundled('./src/format/llm-txt');
7581
+ const llmsGen = requireSourceOrBundled('./src/format/llms-txt');
7582
+ const llmFullMod = requireSourceOrBundled('./packages/adapters/llm-full');
7583
+
7584
+ // Gather file entries for llm.txt / llm-full.txt
7585
+ const ignorePatterns = loadIgnorePatterns(cwd);
7586
+ let syncFiles = buildFileList(cwd, config).filter((f) => {
7587
+ const rel = path.relative(cwd, f).replace(/\\/g, '/');
7588
+ return !matchesIgnore(rel, ignorePatterns);
7589
+ });
7590
+ const syncEntries = [];
7591
+ for (const fp of syncFiles) {
7592
+ let content = '';
7593
+ try { content = fs.readFileSync(fp, 'utf8'); } catch (_) { continue; }
7594
+ const sigs = detectAndExtract(fp, content, config.maxSigsPerFile || 25);
7595
+ if (sigs.length === 0) continue;
7596
+ syncEntries.push({ filePath: fp, sigs, language: null });
7597
+ }
7598
+ const syncContext = {
7599
+ projectName: path.basename(cwd),
7600
+ fileEntries: syncEntries,
7601
+ srcDirs: config.srcDirs || [],
7602
+ };
7603
+
7604
+ const llmTxtPath = llmTxt.outputPath(cwd);
7605
+ const llmFullPath = llmFullMod.outputPath(cwd);
7606
+ const llmsTxtPath = llmsGen.outputPath(cwd);
7607
+
7608
+ fs.writeFileSync(llmTxtPath, llmTxt.format(syncContext, cwd, VERSION));
7609
+ llmFullMod.write(syncContext, cwd, { version: VERSION });
7610
+
7611
+ const prev = fs.existsSync(llmsTxtPath) ? fs.readFileSync(llmsTxtPath, 'utf8') : '';
7612
+ const next = llmsGen.format(syncContext, cwd, [
7613
+ { path: llmTxtPath, label: 'llm.txt' },
7614
+ { path: llmFullPath, label: 'llm-full.txt' },
7615
+ ], VERSION);
7616
+ const changed = prev !== next ? 'updated' : 'no change';
7617
+ fs.writeFileSync(llmsTxtPath, next);
7618
+
7619
+ console.log('[sigmap] sync complete');
7620
+ console.log(` .github/copilot-instructions.md updated`);
7621
+ console.log(` llm.txt updated`);
7622
+ console.log(` llm-full.txt updated`);
7623
+ console.log(` llms.txt ${changed}`);
7624
+ } catch (err) {
7625
+ console.error(`[sigmap] sync error: ${err.message}`);
7626
+ process.exit(1);
7627
+ }
7628
+ process.exit(0);
7629
+ }
7630
+
7078
7631
  if (args.includes('--init')) {
7079
7632
  writeInitConfig(cwd);
7080
7633
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "3.4.0",
3
+ "version": "3.5.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
  "exports": {
@@ -24,6 +24,7 @@
24
24
  "init": "node gen-context.js --init",
25
25
  "report": "node gen-context.js --report",
26
26
  "audit:strategies": "node scripts/run-strategy-audit.mjs",
27
+ "benchmark:matrix": "node scripts/run-benchmark-matrix.mjs --save --skip-clone",
27
28
  "health": "node gen-context.js --health",
28
29
  "map": "node gen-project-map.js",
29
30
  "mcp": "node gen-context.js --mcp",