sigmap 6.10.1 → 6.10.2

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 CHANGED
@@ -10,6 +10,21 @@ Format: [Semantic Versioning](https://semver.org/)
10
10
 
11
11
  ---
12
12
 
13
+ ## [6.10.2] — 2026-05-11
14
+
15
+ ### Added
16
+
17
+ - **Open-source agents documentation** — Comprehensive integration guides for OpenCode, Aider, OpenHands, and Cline with setup examples and context injection patterns. Clear separation of coding agents from inference backends.
18
+ - **Local LLM workflows guide** — Complete setup guide for Ollama, llama.cpp, vLLM with model recommendations, performance tuning, and benchmarking. Emphasizes model-agnostic nature: no API costs, full privacy, offline capability.
19
+ - **Integrations sidebar** — New VitePress navigation section highlighting open-source agents, local LLMs, MCP server, and Repomix integration.
20
+
21
+ ### Changed
22
+
23
+ - **README model-agnostic messaging** — Updated to clarify support for cloud LLMs, open-source agents, and local models with full privacy. Removed proprietary-focused language.
24
+ - **Quick-start guide** — Added links to new agent and local-LLM guides in "Next steps" section.
25
+
26
+ ---
27
+
13
28
  ## [6.10.1] — 2026-05-10
14
29
 
15
30
  ### Added
package/README.md CHANGED
@@ -36,7 +36,12 @@ Zero config. Zero dependencies. Under 10 seconds.
36
36
 
37
37
  SigMap extracts function and class signatures from your codebase and feeds the right files — not the whole repo — to your AI.
38
38
 
39
- Works with Copilot, Claude, Cursor, Windsurf, and any LLM.
39
+ **Model-agnostic.** Works with:
40
+ - **Cloud LLMs:** Claude, GPT-4, Copilot, Gemini
41
+ - **Open-source agents:** OpenCode, Aider, OpenHands, Cline
42
+ - **Local LLMs:** Ollama, llama.cpp, vLLM (no API keys, full privacy)
43
+ - **Any editor:** VS Code, Cursor, Windsurf, Neovim, JetBrains
44
+ - **Any model:** Use what you want, no vendor lock-in
40
45
 
41
46
  ---
42
47
 
@@ -46,7 +51,9 @@ Works with Copilot, Claude, Cursor, Windsurf, and any LLM.
46
51
  - **40–98% token reduction** — 2K–4K tokens instead of 80K+
47
52
  - **52.2% task success rate** — up from 10% without context
48
53
  - **1.68 prompts per task** — down from 2.84
49
- - **Works with any LLM** — no API key, no cloud, no accounts
54
+ - **No vendor lock-in** — works with any AI assistant or local LLM
55
+ - **No API costs** — use local models (Ollama, llama.cpp, vLLM) with zero token fees
56
+ - **Full privacy** — keep your code and context on your machine
50
57
  - **Zero npm dependencies** — `npx sigmap` on any machine
51
58
 
52
59
  ---
@@ -147,20 +154,26 @@ volta install sigmap
147
154
 
148
155
  | Adapter | Output file | Used by |
149
156
  |---|---|---|
150
- | `copilot` | `.github/copilot-instructions.md` | GitHub Copilot |
157
+ | `copilot` | `.github/copilot-instructions.md` | GitHub Copilot, OpenCode |
151
158
  | `claude` | `CLAUDE.md` | Claude / Claude Code |
152
- | `cursor` | `.cursorrules` | Cursor |
159
+ | `cursor` | `.cursorrules` | Cursor, Cline |
153
160
  | `windsurf` | `.windsurfrules` | Windsurf |
154
- | `openai` | `.github/openai-context.md` | OpenAI models |
161
+ | `openai` | `.github/openai-context.md` | OpenAI API, Aider, local Ollama/llama.cpp |
155
162
  | `gemini` | `.github/gemini-context.md` | Google Gemini |
156
- | `codex` | `AGENTS.md` | OpenAI Codex · OpenCode |
163
+ | `codex` | `AGENTS.md` | OpenAI Codex (legacy) |
157
164
 
158
165
  ```bash
159
- sigmap --adapter copilot # default
160
- sigmap --adapter claude
161
- sigmap --adapter cursor
166
+ sigmap --adapter copilot # default — works with Copilot, OpenCode
167
+ sigmap --adapter openai # works with Ollama, llama.cpp, vLLM, Aider
168
+ sigmap --adapter claude # works with Claude Code
162
169
  ```
163
170
 
171
+ **Open-source agents & local LLMs:**
172
+
173
+ Use SigMap with open-source tools and fully self-hosted setups:
174
+ - **[Open-source agents guide →](https://manojmallick.github.io/sigmap/guide/agents)** — OpenCode, Aider, OpenHands, Cline
175
+ - **[Local LLMs guide →](https://manojmallick.github.io/sigmap/guide/local-llms)** — Ollama, llama.cpp, vLLM (no API keys, full privacy)
176
+
164
177
  **IDE extensions:**
165
178
 
166
179
  | IDE | Install | Source | Features |
package/gen-context.js CHANGED
@@ -2980,6 +2980,148 @@ __factories["./src/extractors/prdiff"] = function(module, exports) {
2980
2980
 
2981
2981
  };
2982
2982
 
2983
+ // ── ./src/extractors/r ──
2984
+ __factories["./src/extractors/r"] = function(module, exports) {
2985
+
2986
+ 'use strict';
2987
+
2988
+ /**
2989
+ * Extract signatures from R source code.
2990
+ * @param {string} src - Raw file content
2991
+ * @returns {string[]} Array of signature strings
2992
+ */
2993
+ function extract(src) {
2994
+ if (!src || typeof src !== 'string') return [];
2995
+ const sigs = [];
2996
+
2997
+ // Strip line comments. R uses # comments. Roxygen2 (#') comments are
2998
+ // stripped along with regular ones; Phase 2 may parse them.
2999
+ const stripped = src.replace(/#.*$/gm, '');
3000
+
3001
+ // Function definitions:
3002
+ // name <- function(args) { ... }
3003
+ // name = function(args) { ... }
3004
+ // name <<- function(args) { ... }
3005
+ // Args may span multiple lines and contain default values, so we need to
3006
+ // match a balanced parenthesis group rather than a single line.
3007
+ const funcRe = /^(?:[ \t]*)([\w.]+)\s*(?:<<-|<-|=)\s*function\s*\(/gm;
3008
+ let m;
3009
+ while ((m = funcRe.exec(stripped)) !== null) {
3010
+ const name = m[1];
3011
+ if (name.startsWith('.')) continue; // private convention
3012
+ const argsStart = funcRe.lastIndex;
3013
+ const args = readBalancedParens(stripped, argsStart - 1);
3014
+ if (args === null) continue;
3015
+ sigs.push(`${name} <- function(${normalizeParams(args)})`);
3016
+ }
3017
+
3018
+ // S4 setMethod / setGeneric:
3019
+ // setGeneric("name", function(args) standardGeneric("name"))
3020
+ // setMethod("name", "ClassName", function(args) { ... })
3021
+ for (const sm of stripped.matchAll(/^[ \t]*setGeneric\s*\(\s*["']([\w.]+)["']/gm)) {
3022
+ sigs.push(`setGeneric("${sm[1]}")`);
3023
+ }
3024
+ for (const sm of stripped.matchAll(/^[ \t]*setMethod\s*\(\s*["']([\w.]+)["']\s*,\s*["']([\w.]+)["']/gm)) {
3025
+ sigs.push(`setMethod("${sm[1]}", "${sm[2]}")`);
3026
+ }
3027
+
3028
+ // S4 class definitions:
3029
+ // setClass("Name", representation(...), ...)
3030
+ for (const sm of stripped.matchAll(/^[ \t]*setClass\s*\(\s*["']([\w.]+)["']/gm)) {
3031
+ sigs.push(`setClass("${sm[1]}")`);
3032
+ }
3033
+
3034
+ return sigs.slice(0, 30);
3035
+ }
3036
+
3037
+ /**
3038
+ * Read a parenthesis-balanced substring starting at the position of the
3039
+ * opening '(' character, returning the inner content (without the outer
3040
+ * parens). Returns null if no matching close paren is found within `cap`
3041
+ * characters, which guards against runaway scans on malformed input.
3042
+ */
3043
+ function readBalancedParens(src, openIdx, cap = 4096) {
3044
+ if (src[openIdx] !== '(') return null;
3045
+ let depth = 1;
3046
+ let i = openIdx + 1;
3047
+ const end = Math.min(src.length, openIdx + cap);
3048
+ let inString = null; // null | '"' | "'"
3049
+ while (i < end) {
3050
+ const ch = src[i];
3051
+ if (inString) {
3052
+ if (ch === '\\') { i += 2; continue; }
3053
+ if (ch === inString) inString = null;
3054
+ i++;
3055
+ continue;
3056
+ }
3057
+ if (ch === '"' || ch === "'") { inString = ch; i++; continue; }
3058
+ if (ch === '(') depth++;
3059
+ else if (ch === ')') {
3060
+ depth--;
3061
+ if (depth === 0) return src.slice(openIdx + 1, i);
3062
+ }
3063
+ i++;
3064
+ }
3065
+ return null;
3066
+ }
3067
+
3068
+ /**
3069
+ * Compress whitespace inside a parameter list, collapse multi-line default
3070
+ * expressions onto a single line, and trim. The goal is one-line readable
3071
+ * signatures, not a faithful AST.
3072
+ *
3073
+ * String literals are protected so that commas/equals inside default values
3074
+ * like sep = "," don't get respaced.
3075
+ */
3076
+ function normalizeParams(raw) {
3077
+ const tokens = [];
3078
+ let buf = '';
3079
+ let inString = null;
3080
+ for (let i = 0; i < raw.length; i++) {
3081
+ const ch = raw[i];
3082
+ if (inString) {
3083
+ buf += ch;
3084
+ if (ch === '\\' && i + 1 < raw.length) { buf += raw[i + 1]; i++; continue; }
3085
+ if (ch === inString) inString = null;
3086
+ continue;
3087
+ }
3088
+ if (ch === '"' || ch === "'") { inString = ch; buf += ch; continue; }
3089
+ buf += ch;
3090
+ }
3091
+ // Now buf === raw with strings preserved character-for-character.
3092
+ // Walk again: collapse non-string runs of whitespace, normalize ', ' and ' = '.
3093
+ let out = '';
3094
+ inString = null;
3095
+ for (let i = 0; i < buf.length; i++) {
3096
+ const ch = buf[i];
3097
+ if (inString) {
3098
+ out += ch;
3099
+ if (ch === '\\' && i + 1 < buf.length) { out += buf[i + 1]; i++; continue; }
3100
+ if (ch === inString) inString = null;
3101
+ continue;
3102
+ }
3103
+ if (ch === '"' || ch === "'") { inString = ch; out += ch; continue; }
3104
+ if (/\s/.test(ch)) {
3105
+ if (out.length && !/\s$/.test(out)) out += ' ';
3106
+ continue;
3107
+ }
3108
+ if (ch === ',') {
3109
+ out = out.replace(/\s+$/, '') + ', ';
3110
+ continue;
3111
+ }
3112
+ if (ch === '=') {
3113
+ out = out.replace(/\s+$/, '') + ' = ';
3114
+ continue;
3115
+ }
3116
+ out += ch;
3117
+ }
3118
+ return out.trim();
3119
+ }
3120
+
3121
+ module.exports = { extract };
3122
+
3123
+ };
3124
+
2983
3125
  // ── ./src/format/cache ──
2984
3126
  __factories["./src/format/cache"] = function(module, exports) {
2985
3127
 
@@ -5387,7 +5529,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
5387
5529
 
5388
5530
  const SERVER_INFO = {
5389
5531
  name: 'sigmap',
5390
- version: '6.10.1',
5532
+ version: '6.10.2',
5391
5533
  description: 'SigMap MCP server — code signatures on demand',
5392
5534
  };
5393
5535
 
@@ -8014,7 +8156,7 @@ const path = require('path');
8014
8156
  const os = require('os');
8015
8157
  const { execSync } = require('child_process');
8016
8158
 
8017
- const VERSION = '6.10.1';
8159
+ const VERSION = '6.10.2';
8018
8160
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
8019
8161
 
8020
8162
  function requireSourceOrBundled(key) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "6.10.1",
3
+ "version": "6.10.2",
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": {
@@ -11,14 +11,16 @@
11
11
 
12
12
  const path = require('path');
13
13
 
14
- const ADAPTER_NAMES = ['copilot', 'claude', 'cursor', 'windsurf', 'openai', 'gemini', 'codex'];
14
+ // Third-party adapters: 'willow' writes atoms to a Willow MCP knowledge store
15
+ // instead of a flat file (see willow.js for configuration).
16
+ const ADAPTER_NAMES = ['copilot', 'claude', 'cursor', 'windsurf', 'openai', 'gemini', 'codex', 'willow'];
15
17
 
16
18
  // Lazy-load adapters so unused ones don't pay any require() cost
17
19
  const _cache = {};
18
20
 
19
21
  /**
20
22
  * Load and return an adapter module by name.
21
- * @param {string} name - Adapter name (copilot|claude|cursor|windsurf|openai|gemini|codex)
23
+ * @param {string} name - Adapter name (copilot|claude|cursor|windsurf|openai|gemini|codex|willow)
22
24
  * @returns {{ name: string, format: Function, outputPath: Function }|null}
23
25
  */
24
26
  function getAdapter(name) {
@@ -0,0 +1,200 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Willow adapter — writes SigMap context to Willow MCP knowledge store.
5
+ *
6
+ * Instead of writing a flat .willow-context.md file, this adapter sends
7
+ * signature atoms to a Willow MCP server (https://github.com/rudi193-cmd/willow-1.9)
8
+ * via HTTP POST. Each indexed file becomes a searchable knowledge atom.
9
+ *
10
+ * Contract:
11
+ * format(context, opts?) → string (markdown for display/debug)
12
+ * outputPath(cwd) → string (placeholder — no file written)
13
+ * write(context, cwd, opts?) → Promise<void> (POSTs to Willow MCP, must await)
14
+ *
15
+ * Configuration (env vars or opts):
16
+ * WILLOW_MCP_URL — MCP server base URL (default: http://localhost:8000)
17
+ * WILLOW_AGENT — agent namespace (default: sigmap)
18
+ * WILLOW_TIMEOUT — fetch timeout in ms (default: 30000)
19
+ * WILLOW_MAX_ATOM_SIZE — max atom size in bytes (default: 100000)
20
+ * WILLOW_RETRIES — max retry attempts for transient failures (default: 3)
21
+ */
22
+
23
+ const crypto = require('crypto');
24
+ const name = 'willow';
25
+ const DEFAULT_MCP_URL = 'http://localhost:8000';
26
+ const DEFAULT_TIMEOUT_MS = 30000;
27
+ const DEFAULT_MAX_ATOM_SIZE = 100000;
28
+ const DEFAULT_RETRIES = 3;
29
+
30
+ /**
31
+ * Format SigMap context as markdown for display or debug.
32
+ * @param {string} context - Raw SigMap context string
33
+ * @param {object} [opts] - Unused; reserved for future options
34
+ * @returns {string}
35
+ */
36
+ function format(context, opts = {}) {
37
+ if (!context || typeof context !== 'string') return '';
38
+ const ts = new Date().toISOString();
39
+ return `<!-- SigMap Willow context — ${ts} -->\n\n${context}`;
40
+ }
41
+
42
+ /**
43
+ * Return the placeholder output path (no file is written by this adapter).
44
+ * @param {string} cwd - Working directory
45
+ * @returns {string}
46
+ */
47
+ function outputPath(cwd) {
48
+ return '.willow-context.md';
49
+ }
50
+
51
+ /**
52
+ * Generate a cryptographically strong ID for an atom.
53
+ * Uses SHA256 hash of the filepath to ensure uniqueness and prevent collisions.
54
+ * @param {string} filepath - File path to hash
55
+ * @returns {string} - sigmap-{32-char-hex}
56
+ */
57
+ function generateAtomId(filepath) {
58
+ const hash = crypto
59
+ .createHash('sha256')
60
+ .update(filepath)
61
+ .digest('hex');
62
+ return `sigmap-${hash}`;
63
+ }
64
+
65
+ /**
66
+ * Fetch with timeout support.
67
+ * @param {string} url - URL to fetch
68
+ * @param {object} opts - Fetch options
69
+ * @param {number} timeoutMs - Timeout in milliseconds
70
+ * @returns {Promise<Response>}
71
+ */
72
+ async function fetchWithTimeout(url, opts, timeoutMs) {
73
+ const controller = new AbortController();
74
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
75
+ try {
76
+ return await fetch(url, { ...opts, signal: controller.signal });
77
+ } finally {
78
+ clearTimeout(timeoutId);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * POST an atom to Willow with exponential backoff retry.
84
+ * @param {object} atom - Atom to ingest
85
+ * @param {string} mcpUrl - MCP server URL
86
+ * @param {number} timeoutMs - Fetch timeout
87
+ * @param {number} maxRetries - Max retry attempts
88
+ * @returns {Promise<boolean>} - True if succeeded, false if all retries exhausted
89
+ */
90
+ async function postAtomWithRetry(atom, mcpUrl, timeoutMs, maxRetries) {
91
+ let lastErr;
92
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
93
+ try {
94
+ const resp = await fetchWithTimeout(
95
+ `${mcpUrl}/tools/call`,
96
+ {
97
+ method: 'POST',
98
+ headers: { 'Content-Type': 'application/json' },
99
+ body: JSON.stringify({
100
+ name: 'willow_knowledge_ingest',
101
+ arguments: {
102
+ app_id: atom.agent,
103
+ title: atom.title,
104
+ summary: atom.summary,
105
+ domain: atom.domain,
106
+ source_type: atom.source_type,
107
+ category: 'code',
108
+ record_id: atom.id,
109
+ },
110
+ }),
111
+ },
112
+ timeoutMs,
113
+ );
114
+
115
+ if (resp.ok) {
116
+ return true;
117
+ }
118
+
119
+ if (resp.status >= 500) {
120
+ lastErr = new Error(`HTTP ${resp.status}`);
121
+ if (attempt < maxRetries - 1) {
122
+ await new Promise((resolve) => setTimeout(resolve, 100 * Math.pow(2, attempt)));
123
+ continue;
124
+ }
125
+ } else {
126
+ process.stderr.write(`[willow-adapter] ${atom.id}: HTTP ${resp.status} (not retryable)\n`);
127
+ return false;
128
+ }
129
+ } catch (err) {
130
+ lastErr = err;
131
+ if (attempt < maxRetries - 1) {
132
+ await new Promise((resolve) => setTimeout(resolve, 100 * Math.pow(2, attempt)));
133
+ continue;
134
+ }
135
+ }
136
+ }
137
+
138
+ process.stderr.write(
139
+ `[willow-adapter] ${atom.id}: failed after ${maxRetries} attempts: ${lastErr?.message || 'unknown'}\n`,
140
+ );
141
+ return false;
142
+ }
143
+
144
+ /**
145
+ * POST each file section from SigMap context to the Willow MCP knowledge store.
146
+ * Each `## filepath` section becomes one searchable knowledge atom.
147
+ * Failures are per-atom and logged to stderr; function never throws.
148
+ * IMPORTANT: This is async — caller MUST await write() before process exit.
149
+ *
150
+ * @param {string} context - Raw SigMap context string
151
+ * @param {string} cwd - Working directory (used as project label)
152
+ * @param {object} [opts] - Optional overrides: { mcpUrl, agent, timeoutMs, maxAtomSize, maxRetries }
153
+ * @returns {Promise<void>}
154
+ */
155
+ async function write(context, cwd, opts = {}) {
156
+ if (!context) return;
157
+
158
+ const mcpUrl = opts.mcpUrl || process.env.WILLOW_MCP_URL || DEFAULT_MCP_URL;
159
+ const agent = opts.agent || process.env.WILLOW_AGENT || 'sigmap';
160
+ const timeoutMs = opts.timeoutMs || parseInt(process.env.WILLOW_TIMEOUT, 10) || DEFAULT_TIMEOUT_MS;
161
+ const maxAtomSize = opts.maxAtomSize || parseInt(process.env.WILLOW_MAX_ATOM_SIZE, 10) || DEFAULT_MAX_ATOM_SIZE;
162
+ const maxRetries = opts.maxRetries || parseInt(process.env.WILLOW_RETRIES, 10) || DEFAULT_RETRIES;
163
+
164
+ const sections = context.split(/\n(?=##\s)/);
165
+ const atoms = sections
166
+ .map((section) => {
167
+ const titleMatch = section.match(/^##\s+(.+)/);
168
+ if (!titleMatch) return null;
169
+
170
+ const title = titleMatch[1].trim();
171
+ const contentSize = section.length;
172
+
173
+ if (contentSize > maxAtomSize) {
174
+ process.stderr.write(`[willow-adapter] ${title}: oversized (${contentSize} > ${maxAtomSize} bytes)\n`);
175
+ return null;
176
+ }
177
+
178
+ return {
179
+ id: generateAtomId(title),
180
+ title,
181
+ summary: `${title} (${contentSize} bytes)`,
182
+ content: section.trim(),
183
+ domain: 'code',
184
+ source_type: 'sigmap',
185
+ agent,
186
+ project: cwd ? require('path').basename(cwd) : 'unknown',
187
+ };
188
+ })
189
+ .filter(Boolean);
190
+
191
+ if (!atoms.length) return;
192
+
193
+ await Promise.all(
194
+ atoms.map((atom) => postAtomWithRetry(atom, mcpUrl, timeoutMs, maxRetries).catch((err) => {
195
+ process.stderr.write(`[willow-adapter] ${atom.id}: unexpected error: ${err.message}\n`);
196
+ })),
197
+ );
198
+ }
199
+
200
+ module.exports = { name, format, outputPath, write };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-cli",
3
- "version": "6.10.1",
3
+ "version": "6.10.2",
4
4
  "description": "SigMap CLI wrapper — thin adapter for programmatic CLI invocation",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -29,6 +29,7 @@ const EXT_MAP = {
29
29
  '.swift': 'swift',
30
30
  '.dart': 'dart',
31
31
  '.scala': 'scala', '.sc': 'scala',
32
+ '.gd': 'gdscript',
32
33
  '.r': 'r', '.R': 'r',
33
34
  '.vue': 'vue',
34
35
  '.svelte': 'svelte',
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-core",
3
- "version": "6.10.1",
3
+ "version": "6.10.2",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -19,6 +19,7 @@ const EXT_TO_LANG = {
19
19
  '.java': 'java', '.kt': 'kotlin', '.cs': 'csharp', '.cpp': 'cpp',
20
20
  '.c': 'cpp', '.h': 'cpp', '.hpp': 'cpp', '.swift': 'swift',
21
21
  '.dart': 'dart', '.scala': 'scala', '.php': 'php',
22
+ '.gd': 'gdscript',
22
23
  '.r': 'r', '.R': 'r',
23
24
  };
24
25
 
@@ -29,6 +29,7 @@ const EXT_MAP = {
29
29
  '.swift': 'swift',
30
30
  '.dart': 'dart',
31
31
  '.scala': 'scala', '.sc': 'scala',
32
+ '.gd': 'gdscript',
32
33
  '.r': 'r', '.R': 'r',
33
34
  '.vue': 'vue',
34
35
  '.svelte': 'svelte',
@@ -0,0 +1,131 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Extract signatures from Godot GDScript source code.
5
+ * @param {string} src - Raw file content
6
+ * @returns {string[]} Array of signature strings
7
+ */
8
+
9
+ function extract(src) {
10
+ if (!src || typeof src !== 'string') return [];
11
+ const sigs = [];
12
+
13
+ const stripped = src.replace(/#.*$/gm, '');
14
+
15
+ let className = null;
16
+ let baseName = null;
17
+ const addedClasses = new Set();
18
+
19
+ const cm = stripped.match(/^class_name\s+(\w+)(?:\s+extends\s+([\w.]+))?/m);
20
+ if (cm) {
21
+ className = cm[1];
22
+ if (cm[2]) baseName = cm[2];
23
+ }
24
+ if (!baseName) {
25
+ const em = stripped.match(/^extends\s+([\w."/]+)/m);
26
+ if (em) baseName = em[1];
27
+ }
28
+
29
+ if (className) {
30
+ sigs.push(baseName ? `class ${className}(${baseName})` : `class ${className}`);
31
+ addedClasses.add(className);
32
+ } else if (baseName) {
33
+ sigs.push(`extends ${baseName}`);
34
+ }
35
+
36
+ const indent = (className || baseName) ? ' ' : '';
37
+
38
+ for (const m of stripped.matchAll(/^signal\s+(\w+)(?:\s*\(([^)]*)\))?/gm)) {
39
+ sigs.push(`${indent}signal ${m[1]}(${normalizeParams(m[2] || '')})`);
40
+ }
41
+
42
+ for (const m of stripped.matchAll(/^enum\s+(\w+)\s*\{([^}]*)\}/gm)) {
43
+ const members = m[2]
44
+ .split(',')
45
+ .map((s) => s.trim().split(/\s*=/)[0].trim())
46
+ .filter(Boolean);
47
+ sigs.push(`${indent}enum ${m[1]} { ${members.slice(0, 6).join(', ')} }`);
48
+ }
49
+
50
+ let constCount = 0;
51
+ for (const m of stripped.matchAll(/^const\s+(\w+)(?:\s*:\s*[^=\n]+)?\s*:?=\s*([^\n]+)$/gm)) {
52
+ let val = m[2].trim();
53
+ const preloadMatch = val.match(/^preload\s*\(([^)]+)\)/);
54
+ if (preloadMatch) {
55
+ val = `preload(${preloadMatch[1]})`;
56
+ } else if (val.length > 40) {
57
+ val = val.slice(0, 37) + '...';
58
+ }
59
+ sigs.push(`${indent}const ${m[1]} = ${val}`);
60
+ if (++constCount >= 5) break;
61
+ }
62
+
63
+ for (const m of stripped.matchAll(/^((?:@\w+(?:\([^)]*\))?\s+)*)var\s+(\w+)(?:\s*:\s*([^=\n]+?))?(?:\s*:?=\s*[^\n]+)?$/gm)) {
64
+ const decorators = m[1] || '';
65
+ const name = m[2];
66
+ const hasDecorator = /@\w+/.test(decorators);
67
+ if (!hasDecorator && name.startsWith('_')) continue;
68
+
69
+ let prefix = decorators.replace(/\([^)]*\)/g, '').trim().split(/\s+/).join(' ');
70
+ if (prefix) prefix += ' ';
71
+
72
+ const type = (m[3] || '').trim();
73
+ const typeStr = type ? `: ${type}` : '';
74
+ sigs.push(`${indent}${prefix}var ${name}${typeStr}`);
75
+ }
76
+
77
+ for (const m of stripped.matchAll(/^(static\s+)?func\s+(\w+)\s*\(([^)]*)\)(?:\s*->\s*([^:\n]+))?\s*:/gm)) {
78
+ const params = normalizeParams(m[3]);
79
+ const ret = (m[4] || '').trim();
80
+ const retStr = ret ? ` → ${ret.slice(0, 30)}` : '';
81
+ const staticKw = m[1] ? 'static ' : '';
82
+ sigs.push(`${indent}${staticKw}func ${m[2]}(${params})${retStr}`);
83
+ }
84
+
85
+ for (const m of stripped.matchAll(/^class\s+(\w+)(?:\s+extends\s+(\w+))?\s*:/gm)) {
86
+ if (addedClasses.has(m[1])) continue;
87
+ addedClasses.add(m[1]);
88
+ sigs.push(m[2] ? `class ${m[1]}(${m[2]})` : `class ${m[1]}`);
89
+ const startIdx = m.index + m[0].length;
90
+ for (const meth of extractInnerMembers(stripped, startIdx)) {
91
+ sigs.push(` ${meth}`);
92
+ }
93
+ }
94
+
95
+ return sigs.slice(0, 25);
96
+ }
97
+
98
+ function extractInnerMembers(stripped, startIndex) {
99
+ const members = [];
100
+ const lines = stripped.slice(startIndex).split('\n');
101
+ for (const line of lines) {
102
+ if (line.trim() === '') continue;
103
+ if (!/^\s+/.test(line)) break;
104
+ const fm = line.match(/^\s+(static\s+)?func\s+(\w+)\s*\(([^)]*)\)(?:\s*->\s*([^:\n]+))?\s*:/);
105
+ if (fm) {
106
+ const params = normalizeParams(fm[3]);
107
+ const ret = (fm[4] || '').trim();
108
+ const retStr = ret ? ` → ${ret.slice(0, 30)}` : '';
109
+ const staticKw = fm[1] ? 'static ' : '';
110
+ members.push(`${staticKw}func ${fm[2]}(${params})${retStr}`);
111
+ }
112
+ }
113
+ return members.slice(0, 6);
114
+ }
115
+
116
+ function normalizeParams(params) {
117
+ if (!params) return '';
118
+ return params.trim()
119
+ .split(',')
120
+ .map((p) => {
121
+ const part = p.trim();
122
+ if (!part) return '';
123
+ const eqIdx = part.indexOf('=');
124
+ const noDefault = eqIdx !== -1 ? part.slice(0, eqIdx).trim() : part;
125
+ return noDefault.split(':')[0].trim();
126
+ })
127
+ .filter(Boolean)
128
+ .join(', ');
129
+ }
130
+
131
+ module.exports = { extract };
@@ -145,4 +145,4 @@ function analyze(files, cwd) {
145
145
  return lines.join('\n');
146
146
  }
147
147
 
148
- module.exports = { analyze };
148
+ module.exports = { analyze, extractImports };
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: '6.10.1',
21
+ version: '6.10.2',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24