sigmap 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,133 @@
1
+ # sigmap-core
2
+
3
+ Programmatic API for [SigMap](https://manojmallick.github.io/sigmap/) — zero-dependency code signature extraction, ranked retrieval, secret scanning, and project health scoring.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install sigmap # installs the full package (CLI + core)
9
+ ```
10
+
11
+ `require('sigmap')` resolves to this library via the root `exports` field.
12
+
13
+ ## Quick start
14
+
15
+ ```js
16
+ const { extract, rank, buildSigIndex, scan, score } = require('sigmap');
17
+
18
+ // 1. Extract signatures from any source file
19
+ const sigs = extract('function hello() { return "world"; }', 'javascript');
20
+ // → ['function hello()']
21
+
22
+ // 2. Scan for secrets before storing signatures
23
+ const { safe, redacted } = scan(sigs, 'src/utils.js');
24
+
25
+ // 3. Build an index from the generated context file
26
+ const index = buildSigIndex('/path/to/your/project');
27
+
28
+ // 4. Rank files against a query
29
+ const results = rank('add a new language extractor', index, { topK: 5 });
30
+ // → [{ file: 'src/extractors/python.js', score: 3.5, sigs: [...], tokens: 42 }, ...]
31
+
32
+ // 5. Check project health
33
+ const health = score('/path/to/your/project');
34
+ // → { score: 92, grade: 'A', strategy: 'full', ... }
35
+ ```
36
+
37
+ ## API reference
38
+
39
+ ### `extract(src, language)` → `string[]`
40
+
41
+ Extract code signatures from source text.
42
+
43
+ | Param | Type | Description |
44
+ |---|---|---|
45
+ | `src` | `string` | Raw file content |
46
+ | `language` | `string` | Language name (`'typescript'`, `'python'`, etc.) **or** a file path/name with a recognised extension |
47
+
48
+ Returns an array of signature strings. Never throws — returns `[]` on any error.
49
+
50
+ **Supported languages:** typescript, javascript, python, java, kotlin, go, rust, csharp, cpp, ruby, php, swift, dart, scala, vue, svelte, html, css, yaml, shell, dockerfile (21 total)
51
+
52
+ ```js
53
+ // By language name
54
+ extract(src, 'python');
55
+
56
+ // By file path (extension is used to detect language)
57
+ extract(src, 'src/server.ts');
58
+ extract(src, 'Dockerfile');
59
+ ```
60
+
61
+ ---
62
+
63
+ ### `rank(query, sigIndex, opts?)` → `Result[]`
64
+
65
+ Rank all files in a signature index against a natural-language query.
66
+
67
+ | Param | Type | Description |
68
+ |---|---|---|
69
+ | `query` | `string` | Natural language or keyword query |
70
+ | `sigIndex` | `Map<string, string[]>` | File → signatures map (from `buildSigIndex`) |
71
+ | `opts.topK` | `number` | Max files to return (default: `10`) |
72
+ | `opts.weights` | `object` | Override default scoring weights |
73
+ | `opts.recencySet` | `Set<string>` | Files to boost with `recencyBoost` multiplier |
74
+
75
+ Each result: `{ file: string, score: number, sigs: string[], tokens: number }`
76
+
77
+ ---
78
+
79
+ ### `buildSigIndex(cwd)` → `Map<string, string[]>`
80
+
81
+ Build a file→signatures map from the generated `.github/copilot-instructions.md`.
82
+ Requires `node gen-context.js` to have been run first.
83
+
84
+ ```js
85
+ const index = buildSigIndex('/path/to/project');
86
+ // → Map { 'src/extractors/python.js' => ['class Extractor', ' def extract(src)'], ... }
87
+ ```
88
+
89
+ ---
90
+
91
+ ### `scan(sigs, filePath)` → `{ safe: string[], redacted: boolean }`
92
+
93
+ Scan signature strings for secrets (AWS keys, GitHub tokens, DB connection strings, etc.) and redact any matches.
94
+
95
+ ```js
96
+ const { safe, redacted } = scan(
97
+ ['const SECRET = "ghp_abc123xyz..."'],
98
+ 'src/config.ts'
99
+ );
100
+ // safe → ['[REDACTED — GitHub Token detected in src/config.ts]']
101
+ // redacted → true
102
+ ```
103
+
104
+ **Detected patterns:** AWS Access Key, AWS Secret Key, GCP API Key, GitHub Token, JWT Token, DB Connection String, SSH Private Key, Stripe Key, Twilio Key, Generic Secret
105
+
106
+ ---
107
+
108
+ ### `score(cwd)` → `HealthResult`
109
+
110
+ Compute a composite health score for the SigMap installation in a project.
111
+
112
+ ```js
113
+ const health = score('/path/to/project');
114
+ // {
115
+ // score: 92,
116
+ // grade: 'A', // A ≥90 | B ≥75 | C ≥60 | D <60
117
+ // strategy: 'full',
118
+ // tokenReductionPct: 97.2,
119
+ // daysSinceRegen: 0.1,
120
+ // totalRuns: 48,
121
+ // overBudgetRuns: 0,
122
+ // }
123
+ ```
124
+
125
+ ## Migration from v2.3 and earlier
126
+
127
+ `require('sigmap')` was not available before v2.4. The programmatic API is new — no migration needed for CLI usage.
128
+
129
+ All existing CLI flags (`--generate`, `--watch`, `--mcp`, `--query`, `--analyze`, `--benchmark`, `--health`, …) are unchanged.
130
+
131
+ ## Zero dependencies
132
+
133
+ This package has zero runtime npm dependencies. It uses only Node.js built-ins: `fs`, `path`.
@@ -0,0 +1,215 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * sigmap-core — public programmatic API
5
+ *
6
+ * Usage:
7
+ * const { extract, rank, scan, score } = require('sigmap');
8
+ *
9
+ * All functions are zero-dependency and never throw.
10
+ */
11
+
12
+ const path = require('path');
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Language extractor registry
16
+ // ---------------------------------------------------------------------------
17
+ const EXT_MAP = {
18
+ '.ts': 'typescript', '.tsx': 'typescript',
19
+ '.js': 'javascript', '.jsx': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript',
20
+ '.py': 'python', '.pyw': 'python',
21
+ '.java': 'java',
22
+ '.kt': 'kotlin', '.kts': 'kotlin',
23
+ '.go': 'go',
24
+ '.rs': 'rust',
25
+ '.cs': 'csharp',
26
+ '.cpp': 'cpp', '.c': 'cpp', '.h': 'cpp', '.hpp': 'cpp', '.cc': 'cpp',
27
+ '.rb': 'ruby', '.rake': 'ruby',
28
+ '.php': 'php',
29
+ '.swift': 'swift',
30
+ '.dart': 'dart',
31
+ '.scala': 'scala', '.sc': 'scala',
32
+ '.vue': 'vue',
33
+ '.svelte': 'svelte',
34
+ '.html': 'html', '.htm': 'html',
35
+ '.css': 'css', '.scss': 'css', '.sass': 'css', '.less': 'css',
36
+ '.yml': 'yaml', '.yaml': 'yaml',
37
+ '.sh': 'shell', '.bash': 'shell', '.zsh': 'shell', '.fish': 'shell',
38
+ };
39
+
40
+ const SRC_ROOT = path.resolve(__dirname, '..', '..', 'src');
41
+
42
+ function _resolveExtractor(language) {
43
+ const extPath = path.join(SRC_ROOT, 'extractors', language + '.js');
44
+ try {
45
+ return require(extPath);
46
+ } catch (_) {
47
+ return null;
48
+ }
49
+ }
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // extract(src, language) → string[]
53
+ // ---------------------------------------------------------------------------
54
+ /**
55
+ * Extract code signatures from source text for the given language.
56
+ *
57
+ * @param {string} src - Raw source file content
58
+ * @param {string} language - Language name (e.g. 'typescript', 'python')
59
+ * OR a file path/name with a recognised extension
60
+ * @returns {string[]} Array of signature strings (never throws)
61
+ *
62
+ * @example
63
+ * const sigs = extract('function hello() {}', 'javascript');
64
+ * // → ['function hello()']
65
+ *
66
+ * const sigs2 = extract(src, 'src/server.ts');
67
+ * // → detected as typescript via extension
68
+ */
69
+ function extract(src, language) {
70
+ if (!src || typeof src !== 'string') return [];
71
+ if (!language || typeof language !== 'string') return [];
72
+
73
+ // If language looks like a file path, derive language from extension
74
+ let lang = language;
75
+ if (language.includes('.') || language.includes('/') || language.includes('\\')) {
76
+ const ext = path.extname(language).toLowerCase();
77
+ const base = path.basename(language);
78
+ if (base === 'Dockerfile' || base.startsWith('Dockerfile.')) {
79
+ lang = 'dockerfile';
80
+ } else {
81
+ lang = EXT_MAP[ext] || null;
82
+ }
83
+ if (!lang) return [];
84
+ } else {
85
+ // Normalise e.g. 'JavaScript' → 'javascript'
86
+ lang = language.toLowerCase();
87
+ }
88
+
89
+ const mod = _resolveExtractor(lang);
90
+ if (!mod || typeof mod.extract !== 'function') return [];
91
+
92
+ try {
93
+ const result = mod.extract(src);
94
+ return Array.isArray(result) ? result : [];
95
+ } catch (_) {
96
+ return [];
97
+ }
98
+ }
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // rank(query, sigIndex, opts?) → { file, score, sigs, tokens }[]
102
+ // ---------------------------------------------------------------------------
103
+ /**
104
+ * Rank files in a signature index against a natural-language query.
105
+ *
106
+ * @param {string} query - Natural language or keyword query
107
+ * @param {Map<string, string[]>} sigIndex - File → signatures map
108
+ * @param {object} [opts]
109
+ * @param {number} [opts.topK=10] - Maximum results to return
110
+ * @param {number} [opts.recencyBoost] - Score multiplier for recent files
111
+ * @param {Set<string>} [opts.recencySet] - Set of file paths considered recent
112
+ * @param {object} [opts.weights] - Override default scoring weights
113
+ * @returns {{ file: string, score: number, sigs: string[], tokens: number }[]}
114
+ *
115
+ * @example
116
+ * const { rank, buildSigIndex } = require('sigmap');
117
+ * const index = buildSigIndex('/path/to/project');
118
+ * const results = rank('add a new language extractor', index, { topK: 5 });
119
+ */
120
+ function rank(query, sigIndex, opts) {
121
+ try {
122
+ const { rank: _rank } = require(path.join(SRC_ROOT, 'retrieval', 'ranker.js'));
123
+ return _rank(query, sigIndex, opts);
124
+ } catch (_) {
125
+ return [];
126
+ }
127
+ }
128
+
129
+ // ---------------------------------------------------------------------------
130
+ // buildSigIndex(cwd) → Map<string, string[]>
131
+ // ---------------------------------------------------------------------------
132
+ /**
133
+ * Build a file→signatures index from the generated context file.
134
+ * Requires gen-context.js to have been run first.
135
+ *
136
+ * @param {string} cwd - Project root directory
137
+ * @returns {Map<string, string[]>}
138
+ */
139
+ function buildSigIndex(cwd) {
140
+ try {
141
+ const { buildSigIndex: _build } = require(path.join(SRC_ROOT, 'retrieval', 'ranker.js'));
142
+ return _build(cwd);
143
+ } catch (_) {
144
+ return new Map();
145
+ }
146
+ }
147
+
148
+ // ---------------------------------------------------------------------------
149
+ // scan(sigs, filePath) → { safe: string[], redacted: boolean }
150
+ // ---------------------------------------------------------------------------
151
+ /**
152
+ * Scan an array of signature strings for secrets and redact any matches.
153
+ *
154
+ * @param {string[]} sigs - Signature strings to scan
155
+ * @param {string} filePath - Source file path (used in redaction message)
156
+ * @returns {{ safe: string[], redacted: boolean }}
157
+ *
158
+ * @example
159
+ * const { safe, redacted } = scan(['const KEY = "AKIAEXAMPLE123..."'], 'config.js');
160
+ * // redacted === true — key was replaced with [REDACTED — AWS Access Key ...]
161
+ */
162
+ function scan(sigs, filePath) {
163
+ try {
164
+ const { scan: _scan } = require(path.join(SRC_ROOT, 'security', 'scanner.js'));
165
+ return _scan(sigs, filePath);
166
+ } catch (_) {
167
+ return { safe: Array.isArray(sigs) ? sigs : [], redacted: false };
168
+ }
169
+ }
170
+
171
+ // ---------------------------------------------------------------------------
172
+ // score(cwd) → { score, grade, ... }
173
+ // ---------------------------------------------------------------------------
174
+ /**
175
+ * Compute a composite health score for the project at cwd.
176
+ *
177
+ * @param {string} cwd - Project root directory
178
+ * @returns {{
179
+ * score: number,
180
+ * grade: 'A'|'B'|'C'|'D',
181
+ * strategy: string,
182
+ * tokenReductionPct: number|null,
183
+ * daysSinceRegen: number|null,
184
+ * totalRuns: number,
185
+ * overBudgetRuns: number,
186
+ * }}
187
+ *
188
+ * @example
189
+ * const health = score('/path/to/project');
190
+ * console.log(health.grade); // 'A'
191
+ */
192
+ function score(cwd) {
193
+ try {
194
+ const { score: _score } = require(path.join(SRC_ROOT, 'health', 'scorer.js'));
195
+ return _score(cwd);
196
+ } catch (_) {
197
+ return { score: 0, grade: 'D', strategy: 'full', tokenReductionPct: null, daysSinceRegen: null, totalRuns: 0, overBudgetRuns: 0 };
198
+ }
199
+ }
200
+
201
+ // ---------------------------------------------------------------------------
202
+ // Exports
203
+ // ---------------------------------------------------------------------------
204
+ module.exports = {
205
+ /** Extract signatures from source text */
206
+ extract,
207
+ /** Rank project files against a query */
208
+ rank,
209
+ /** Build a signature index from the generated context file */
210
+ buildSigIndex,
211
+ /** Scan signatures for secrets (redacts matches) */
212
+ scan,
213
+ /** Compute project health score */
214
+ score,
215
+ };
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "sigmap-core",
3
+ "version": "2.4.0",
4
+ "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
+ "main": "index.js",
6
+ "keywords": [
7
+ "sigmap",
8
+ "ai-context",
9
+ "code-signatures",
10
+ "extraction",
11
+ "retrieval",
12
+ "zero-dependency"
13
+ ],
14
+ "author": {
15
+ "name": "Manoj Mallick",
16
+ "url": "https://github.com/manojmallick"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/manojmallick/sigmap.git",
21
+ "directory": "packages/core"
22
+ },
23
+ "homepage": "https://manojmallick.github.io/sigmap/",
24
+ "license": "MIT",
25
+ "engines": {
26
+ "node": ">=18.0.0"
27
+ }
28
+ }
@@ -92,6 +92,14 @@ const DEFAULTS = {
92
92
 
93
93
  // Add reverse dependency usage hints on file headings (opt-in)
94
94
  impactRadius: false,
95
+
96
+ // Query-aware retrieval settings (v2.3)
97
+ retrieval: {
98
+ // Maximum number of files to return for --query
99
+ topK: 10,
100
+ // Multiplier applied to recently-changed files (>1 boosts them up)
101
+ recencyBoost: 1.5,
102
+ },
95
103
  };
96
104
 
97
105
  module.exports = { DEFAULTS };
@@ -430,4 +430,31 @@ function listModules(args, cwd) {
430
430
  ].join('\n');
431
431
  }
432
432
 
433
- module.exports = { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules };
433
+ /**
434
+ * query_context({ query, topK? }) → string
435
+ *
436
+ * Ranks context-file entries by relevance to the query and returns the
437
+ * top-K most relevant files with their signatures and scores.
438
+ */
439
+ function queryContext(args, cwd) {
440
+ if (!args || !args.query) return 'Missing required argument: query';
441
+
442
+ const contextPath = path.join(cwd, CONTEXT_FILE);
443
+ if (!fs.existsSync(contextPath)) {
444
+ return 'No context file found. Run: node gen-context.js';
445
+ }
446
+
447
+ try {
448
+ const { rank, buildSigIndex, formatRankTable } = require('../retrieval/ranker');
449
+ const index = buildSigIndex(cwd);
450
+ if (index.size === 0) return 'No signatures indexed. Run: node gen-context.js';
451
+
452
+ const topK = Math.min(Math.max(1, parseInt(args.topK, 10) || 10), 25);
453
+ const results = rank(args.query, index, { topK });
454
+ return formatRankTable(results, args.query);
455
+ } catch (err) {
456
+ return `_query_context failed: ${err.message}_`;
457
+ }
458
+ }
459
+
460
+ module.exports = { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext };
package/src/mcp/server.js CHANGED
@@ -14,11 +14,11 @@
14
14
 
15
15
  const readline = require('readline');
16
16
  const { TOOLS } = require('./tools');
17
- const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules } = require('./handlers');
17
+ const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext } = require('./handlers');
18
18
 
19
19
  const SERVER_INFO = {
20
20
  name: 'sigmap',
21
- version: '2.2.0',
21
+ version: '2.4.0',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24
 
@@ -73,6 +73,7 @@ function dispatch(msg, cwd) {
73
73
  else if (name === 'get_routing') text = getRouting(args, cwd);
74
74
  else if (name === 'explain_file') text = explainFile(args, cwd);
75
75
  else if (name === 'list_modules') text = listModules(args, cwd);
76
+ else if (name === 'query_context') text = queryContext(args, cwd);
76
77
  else {
77
78
  respondError(id, -32601, `Unknown tool: ${name}`);
78
79
  return;
package/src/mcp/tools.js CHANGED
@@ -120,6 +120,30 @@ const TOOLS = [
120
120
  required: [],
121
121
  },
122
122
  },
123
+ {
124
+ name: 'query_context',
125
+ description:
126
+ 'Rank and return the most relevant files for a specific task or question. ' +
127
+ 'Uses keyword + symbol + path scoring to surface only the top-K files relevant ' +
128
+ 'to the query — much cheaper than reading all context. ' +
129
+ 'Returns ranked file list with signatures and relevance scores.',
130
+ inputSchema: {
131
+ type: 'object',
132
+ properties: {
133
+ query: {
134
+ type: 'string',
135
+ description:
136
+ 'Natural language task description or keyword(s) to rank files against. ' +
137
+ 'E.g. "add a new language extractor", "fix secret scanning", "auth module".',
138
+ },
139
+ topK: {
140
+ type: 'number',
141
+ description: 'Maximum number of files to return (default: 10, max: 25).',
142
+ },
143
+ },
144
+ required: ['query'],
145
+ },
146
+ },
123
147
  ];
124
148
 
125
149
  module.exports = { TOOLS };