sigmap 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.contextignore.example +34 -0
  2. package/CHANGELOG.md +402 -0
  3. package/LICENSE +21 -0
  4. package/README.md +601 -0
  5. package/gen-context.config.json.example +40 -0
  6. package/gen-context.js +4316 -0
  7. package/gen-project-map.js +172 -0
  8. package/package.json +67 -0
  9. package/src/config/defaults.js +61 -0
  10. package/src/config/loader.js +60 -0
  11. package/src/extractors/cpp.js +60 -0
  12. package/src/extractors/csharp.js +48 -0
  13. package/src/extractors/css.js +51 -0
  14. package/src/extractors/dart.js +58 -0
  15. package/src/extractors/dockerfile.js +49 -0
  16. package/src/extractors/go.js +61 -0
  17. package/src/extractors/html.js +39 -0
  18. package/src/extractors/java.js +49 -0
  19. package/src/extractors/javascript.js +82 -0
  20. package/src/extractors/kotlin.js +62 -0
  21. package/src/extractors/php.js +62 -0
  22. package/src/extractors/python.js +69 -0
  23. package/src/extractors/ruby.js +43 -0
  24. package/src/extractors/rust.js +72 -0
  25. package/src/extractors/scala.js +67 -0
  26. package/src/extractors/shell.js +43 -0
  27. package/src/extractors/svelte.js +51 -0
  28. package/src/extractors/swift.js +63 -0
  29. package/src/extractors/typescript.js +109 -0
  30. package/src/extractors/vue.js +66 -0
  31. package/src/extractors/yaml.js +59 -0
  32. package/src/format/cache.js +53 -0
  33. package/src/health/scorer.js +123 -0
  34. package/src/map/class-hierarchy.js +117 -0
  35. package/src/map/import-graph.js +148 -0
  36. package/src/map/route-table.js +127 -0
  37. package/src/mcp/handlers.js +433 -0
  38. package/src/mcp/server.js +128 -0
  39. package/src/mcp/tools.js +125 -0
  40. package/src/routing/classifier.js +102 -0
  41. package/src/routing/hints.js +103 -0
  42. package/src/security/patterns.js +51 -0
  43. package/src/security/scanner.js +36 -0
  44. package/src/tracking/logger.js +115 -0
@@ -0,0 +1,148 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Import graph analyzer.
5
+ * Extracts relative import relationships from JS/TS/Python files
6
+ * and detects circular dependencies.
7
+ *
8
+ * @param {string[]} files — absolute file paths to analyze
9
+ * @param {string} cwd — project root for relative path display
10
+ * @returns {string} formatted section content (empty string if nothing found)
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ const JS_EXTS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']);
17
+ const PY_EXTS = new Set(['.py', '.pyw']);
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Import extraction per language
21
+ // ---------------------------------------------------------------------------
22
+ function extractImports(filePath, content, fileSet) {
23
+ const ext = path.extname(filePath).toLowerCase();
24
+ const dir = path.dirname(filePath);
25
+ const found = [];
26
+
27
+ if (JS_EXTS.has(ext)) {
28
+ // ES: import ... from './foo' or import './side-effect'
29
+ const re1 = /(?:^|[\r\n])\s*import\s+(?:[^'";\r\n]*?\s+from\s+)?['"](\.[^'"]+)['"]/g;
30
+ let m;
31
+ while ((m = re1.exec(content)) !== null) {
32
+ const resolved = resolveJsPath(dir, m[1], fileSet);
33
+ if (resolved) found.push(resolved);
34
+ }
35
+ // CommonJS: require('./foo')
36
+ const re2 = /\brequire\s*\(\s*['"](\.[^'"]+)['"]\s*\)/g;
37
+ while ((m = re2.exec(content)) !== null) {
38
+ const resolved = resolveJsPath(dir, m[1], fileSet);
39
+ if (resolved) found.push(resolved);
40
+ }
41
+ }
42
+
43
+ if (PY_EXTS.has(ext)) {
44
+ // from .module import ... / from ..pkg import ...
45
+ const re = /^[ \t]*from\s+(\.+[\w.]*)\s+import/gm;
46
+ let m;
47
+ while ((m = re.exec(content)) !== null) {
48
+ const dotCount = (m[1].match(/^\.+/) || [''])[0].length;
49
+ const modPart = m[1].slice(dotCount).replace(/\./g, '/');
50
+ let base = dir;
51
+ for (let i = 1; i < dotCount; i++) base = path.dirname(base);
52
+ const candidate = modPart ? path.join(base, modPart + '.py') : null;
53
+ if (candidate && fileSet.has(candidate)) found.push(candidate);
54
+ }
55
+ }
56
+
57
+ return [...new Set(found)];
58
+ }
59
+
60
+ function resolveJsPath(dir, importStr, fileSet) {
61
+ const base = path.resolve(dir, importStr);
62
+ const candidates = [
63
+ base,
64
+ base + '.ts', base + '.tsx',
65
+ base + '.js', base + '.jsx',
66
+ base + '/index.ts', base + '/index.js',
67
+ ];
68
+ for (const c of candidates) {
69
+ if (fileSet.has(c)) return c;
70
+ }
71
+ return null;
72
+ }
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // Cycle detection (DFS with path tracking)
76
+ // ---------------------------------------------------------------------------
77
+ function detectCycles(graph) {
78
+ const cycles = [];
79
+ const visited = new Set();
80
+ const onStack = new Set();
81
+ const stackArr = [];
82
+
83
+ function dfs(node) {
84
+ if (onStack.has(node)) {
85
+ const start = stackArr.indexOf(node);
86
+ if (start !== -1) cycles.push([...stackArr.slice(start), node]);
87
+ return;
88
+ }
89
+ if (visited.has(node)) return;
90
+
91
+ onStack.add(node);
92
+ stackArr.push(node);
93
+ for (const dep of (graph.get(node) || [])) dfs(dep);
94
+ stackArr.pop();
95
+ onStack.delete(node);
96
+ visited.add(node);
97
+ }
98
+
99
+ for (const node of graph.keys()) {
100
+ if (!visited.has(node)) dfs(node);
101
+ }
102
+ return cycles;
103
+ }
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // Public API
107
+ // ---------------------------------------------------------------------------
108
+ function analyze(files, cwd) {
109
+ const fileSet = new Set(files.map((f) => path.resolve(f)));
110
+ const graph = new Map();
111
+
112
+ for (const filePath of files) {
113
+ let content;
114
+ try { content = fs.readFileSync(filePath, 'utf8'); } catch (_) { continue; }
115
+ const deps = extractImports(path.resolve(filePath), content, fileSet);
116
+ if (deps.length > 0) graph.set(path.resolve(filePath), deps);
117
+ }
118
+
119
+ if (graph.size === 0) return '';
120
+
121
+ const cycles = detectCycles(graph);
122
+ const cycleNodeSet = new Set(cycles.flatMap((c) => c));
123
+
124
+ const lines = [];
125
+ const sorted = [...graph.entries()].sort((a, b) => a[0].localeCompare(b[0]));
126
+
127
+ for (const [fp, deps] of sorted) {
128
+ const rel = path.relative(cwd, fp).replace(/\\/g, '/');
129
+ const depList = deps.map((d) => {
130
+ const drel = path.relative(cwd, d).replace(/\\/g, '/');
131
+ return cycleNodeSet.has(d) ? `${drel} ⚠` : drel;
132
+ });
133
+ lines.push(`${rel} → ${depList.join(', ')}`);
134
+ }
135
+
136
+ if (cycles.length > 0) {
137
+ lines.push('');
138
+ lines.push('Circular dependencies detected:');
139
+ for (const cycle of cycles) {
140
+ const relPath = cycle.map((n) => path.relative(cwd, n).replace(/\\/g, '/')).join(' → ');
141
+ lines.push(` ⚠ ${relPath}`);
142
+ }
143
+ }
144
+
145
+ return lines.join('\n');
146
+ }
147
+
148
+ module.exports = { analyze };
@@ -0,0 +1,127 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * HTTP route table extractor.
5
+ * Detects routes in Express, Fastify, NestJS, Flask, FastAPI, Gin, Spring.
6
+ *
7
+ * @param {string[]} files — absolute file paths to analyze
8
+ * @param {string} cwd — project root for relative path display
9
+ * @returns {string} formatted markdown table (empty string if no routes found)
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ const JS_EXTS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']);
16
+ const PY_EXTS = new Set(['.py', '.pyw']);
17
+
18
+ function shouldSkipFile(rel) {
19
+ const normalized = rel.replace(/\\/g, '/').toLowerCase();
20
+ return /(^|\/)(gen-context|gen-project-map)\.js$/.test(normalized);
21
+ }
22
+
23
+ function analyze(files, cwd) {
24
+ const routes = [];
25
+
26
+ for (const filePath of files) {
27
+ const ext = path.extname(filePath).toLowerCase();
28
+ const rel = path.relative(cwd, filePath).replace(/\\/g, '/');
29
+ if (shouldSkipFile(rel)) continue;
30
+ let content;
31
+ try { content = fs.readFileSync(filePath, 'utf8'); } catch (_) { continue; }
32
+
33
+ // -----------------------------------------------------------------------
34
+ // Express / Fastify / Koa (JS/TS)
35
+ // -----------------------------------------------------------------------
36
+ if (JS_EXTS.has(ext)) {
37
+ // app.get('/path', ...) / router.post('/path') / fastify.put('/path')
38
+ const re1 = /\b(?:app|router|fastify|server|koa|instance)\.(get|post|put|patch|delete|head|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
39
+ let m;
40
+ while ((m = re1.exec(content)) !== null) {
41
+ routes.push({ method: m[1].toUpperCase(), path: m[2], file: rel });
42
+ }
43
+
44
+ // NestJS decorators: @Get('/path') @Post('/path')
45
+ const re2 = /@(Get|Post|Put|Patch|Delete|Head|Options|All)\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
46
+ while ((m = re2.exec(content)) !== null) {
47
+ routes.push({ method: m[1].toUpperCase(), path: m[2], file: rel });
48
+ }
49
+
50
+ // NestJS: @Get() with no path
51
+ const re3 = /@(Get|Post|Put|Patch|Delete)\s*\(\s*\)/g;
52
+ while ((m = re3.exec(content)) !== null) {
53
+ routes.push({ method: m[1].toUpperCase(), path: '/', file: rel });
54
+ }
55
+ }
56
+
57
+ // -----------------------------------------------------------------------
58
+ // Flask / FastAPI (Python)
59
+ // -----------------------------------------------------------------------
60
+ if (PY_EXTS.has(ext)) {
61
+ // @app.route('/path', methods=['GET', 'POST'])
62
+ const re1 = /@[\w.]+\.route\s*\(\s*['"]([^'"]+)['"]([\s\S]{0,150}?)\)/g;
63
+ let m;
64
+ while ((m = re1.exec(content)) !== null) {
65
+ const routePath = m[1];
66
+ const methodsMatch = m[2].match(/methods\s*=\s*\[([^\]]+)\]/);
67
+ if (methodsMatch) {
68
+ const methods = methodsMatch[1].match(/['"]([A-Z]+)['"]/g) || [];
69
+ for (const meth of methods) {
70
+ routes.push({ method: meth.replace(/['"]/g, ''), path: routePath, file: rel });
71
+ }
72
+ } else {
73
+ routes.push({ method: 'GET', path: routePath, file: rel });
74
+ }
75
+ }
76
+
77
+ // @app.get('/path') @router.post('/path') FastAPI style
78
+ const re2 = /@[\w.]+\.(get|post|put|patch|delete|head|options)\s*\(\s*['"]([^'"]+)['"]/g;
79
+ while ((m = re2.exec(content)) !== null) {
80
+ routes.push({ method: m[1].toUpperCase(), path: m[2], file: rel });
81
+ }
82
+ }
83
+
84
+ // -----------------------------------------------------------------------
85
+ // Go — Gin / Echo / chi / net/http
86
+ // -----------------------------------------------------------------------
87
+ if (ext === '.go') {
88
+ // r.GET("/path", handler)
89
+ const re1 = /\b\w+\.(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s*\(\s*["']([^"']+)["']/g;
90
+ let m;
91
+ while ((m = re1.exec(content)) !== null) {
92
+ routes.push({ method: m[1], path: m[2], file: rel });
93
+ }
94
+ // http.HandleFunc("/path", handler)
95
+ const re2 = /http\.HandleFunc\s*\(\s*["']([^"']+)["']/g;
96
+ while ((m = re2.exec(content)) !== null) {
97
+ routes.push({ method: 'ANY', path: m[1], file: rel });
98
+ }
99
+ }
100
+
101
+ // -----------------------------------------------------------------------
102
+ // Spring (Java)
103
+ // -----------------------------------------------------------------------
104
+ if (ext === '.java') {
105
+ // @GetMapping("/path") @PostMapping @RequestMapping
106
+ const re1 = /@(Get|Post|Put|Patch|Delete|Request)Mapping\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']/g;
107
+ let m;
108
+ while ((m = re1.exec(content)) !== null) {
109
+ const method = m[1] === 'Request' ? 'ANY' : m[1].toUpperCase();
110
+ routes.push({ method, path: m[2], file: rel });
111
+ }
112
+ }
113
+ }
114
+
115
+ if (routes.length === 0) return '';
116
+
117
+ const lines = [
118
+ '| Method | Path | File |',
119
+ '|--------|------|------|',
120
+ ];
121
+ for (const r of routes) {
122
+ lines.push(`| ${r.method} | ${r.path} | ${r.file} |`);
123
+ }
124
+ return lines.join('\n');
125
+ }
126
+
127
+ module.exports = { analyze };