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.
- package/.contextignore.example +34 -0
- package/CHANGELOG.md +402 -0
- package/LICENSE +21 -0
- package/README.md +601 -0
- package/gen-context.config.json.example +40 -0
- package/gen-context.js +4316 -0
- package/gen-project-map.js +172 -0
- package/package.json +67 -0
- package/src/config/defaults.js +61 -0
- package/src/config/loader.js +60 -0
- package/src/extractors/cpp.js +60 -0
- package/src/extractors/csharp.js +48 -0
- package/src/extractors/css.js +51 -0
- package/src/extractors/dart.js +58 -0
- package/src/extractors/dockerfile.js +49 -0
- package/src/extractors/go.js +61 -0
- package/src/extractors/html.js +39 -0
- package/src/extractors/java.js +49 -0
- package/src/extractors/javascript.js +82 -0
- package/src/extractors/kotlin.js +62 -0
- package/src/extractors/php.js +62 -0
- package/src/extractors/python.js +69 -0
- package/src/extractors/ruby.js +43 -0
- package/src/extractors/rust.js +72 -0
- package/src/extractors/scala.js +67 -0
- package/src/extractors/shell.js +43 -0
- package/src/extractors/svelte.js +51 -0
- package/src/extractors/swift.js +63 -0
- package/src/extractors/typescript.js +109 -0
- package/src/extractors/vue.js +66 -0
- package/src/extractors/yaml.js +59 -0
- package/src/format/cache.js +53 -0
- package/src/health/scorer.js +123 -0
- package/src/map/class-hierarchy.js +117 -0
- package/src/map/import-graph.js +148 -0
- package/src/map/route-table.js +127 -0
- package/src/mcp/handlers.js +433 -0
- package/src/mcp/server.js +128 -0
- package/src/mcp/tools.js +125 -0
- package/src/routing/classifier.js +102 -0
- package/src/routing/hints.js +103 -0
- package/src/security/patterns.js +51 -0
- package/src/security/scanner.js +36 -0
- package/src/tracking/logger.js +115 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Classify files by complexity tier for model routing hints.
|
|
5
|
+
*
|
|
6
|
+
* Tiers:
|
|
7
|
+
* 'fast' — simple files: config, markup, templates, trivial utilities
|
|
8
|
+
* 'balanced' — standard application code: moderate functions and classes
|
|
9
|
+
* 'powerful' — complex files: large classes, many exports, security-critical paths
|
|
10
|
+
*
|
|
11
|
+
* @param {string} filePath - absolute path to the file
|
|
12
|
+
* @param {string[]} sigs - extracted signatures for the file
|
|
13
|
+
* @returns {'fast'|'balanced'|'powerful'}
|
|
14
|
+
*/
|
|
15
|
+
function classify(filePath, sigs) {
|
|
16
|
+
const lower = filePath.toLowerCase();
|
|
17
|
+
const sigCount = sigs.length;
|
|
18
|
+
|
|
19
|
+
// ── Fast tier heuristics ────────────────────────────────────────────────
|
|
20
|
+
// Configuration, markup, templates, and trivial utilities are small tasks
|
|
21
|
+
if (
|
|
22
|
+
lower.endsWith('.json') ||
|
|
23
|
+
lower.endsWith('.yml') ||
|
|
24
|
+
lower.endsWith('.yaml') ||
|
|
25
|
+
lower.endsWith('.toml') ||
|
|
26
|
+
lower.endsWith('.env') ||
|
|
27
|
+
lower.endsWith('.html') ||
|
|
28
|
+
lower.endsWith('.htm') ||
|
|
29
|
+
lower.endsWith('.css') ||
|
|
30
|
+
lower.endsWith('.scss') ||
|
|
31
|
+
lower.endsWith('.sass') ||
|
|
32
|
+
lower.endsWith('.less') ||
|
|
33
|
+
/dockerfile/i.test(lower) ||
|
|
34
|
+
lower.endsWith('.sh') ||
|
|
35
|
+
lower.endsWith('.bash') ||
|
|
36
|
+
lower.endsWith('.zsh') ||
|
|
37
|
+
lower.endsWith('.fish')
|
|
38
|
+
) {
|
|
39
|
+
return 'fast';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Config-like directories
|
|
43
|
+
if (
|
|
44
|
+
lower.includes('/config/') ||
|
|
45
|
+
lower.includes('/configs/') ||
|
|
46
|
+
lower.includes('/fixtures/') ||
|
|
47
|
+
lower.includes('/migrations/') ||
|
|
48
|
+
lower.includes('/seeds/')
|
|
49
|
+
) {
|
|
50
|
+
return sigCount > 4 ? 'balanced' : 'fast';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Test files — usually standard complexity
|
|
54
|
+
if (/\.(test|spec)\.[a-z]+$/.test(lower) || /_test\.[a-z]+$/.test(lower)) {
|
|
55
|
+
return 'balanced';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── Powerful tier heuristics — high-signal keywords in the path ─────────
|
|
59
|
+
if (
|
|
60
|
+
lower.includes('/security/') ||
|
|
61
|
+
lower.includes('/auth/') ||
|
|
62
|
+
lower.includes('/crypto/') ||
|
|
63
|
+
lower.includes('/core/') ||
|
|
64
|
+
lower.includes('/engine/') ||
|
|
65
|
+
lower.includes('/compiler/') ||
|
|
66
|
+
lower.includes('/parser/') ||
|
|
67
|
+
lower.includes('/scheduler/') ||
|
|
68
|
+
lower.includes('/orchestrat')
|
|
69
|
+
) {
|
|
70
|
+
return 'powerful';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Many exports → complex file
|
|
74
|
+
if (sigCount >= 12) return 'powerful';
|
|
75
|
+
|
|
76
|
+
// Large number of class-level methods (indented 2-space sigs)
|
|
77
|
+
const methodCount = sigs.filter((s) => s.startsWith(' ')).length;
|
|
78
|
+
if (methodCount >= 8) return 'powerful';
|
|
79
|
+
|
|
80
|
+
// ── Balanced covers everything else ────────────────────────────────────
|
|
81
|
+
if (sigCount <= 2) return 'fast';
|
|
82
|
+
return 'balanced';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Classify all file entries and group them by tier.
|
|
87
|
+
*
|
|
88
|
+
* @param {Array<{filePath: string, sigs: string[]}>} fileEntries
|
|
89
|
+
* @returns {{ fast: string[], balanced: string[], powerful: string[] }}
|
|
90
|
+
* Each array contains relative paths (relative to cwd).
|
|
91
|
+
*/
|
|
92
|
+
function classifyAll(fileEntries, cwd) {
|
|
93
|
+
const path = require('path');
|
|
94
|
+
const result = { fast: [], balanced: [], powerful: [] };
|
|
95
|
+
for (const { filePath, sigs } of fileEntries) {
|
|
96
|
+
const tier = classify(filePath, sigs);
|
|
97
|
+
result[tier].push(path.relative(cwd, filePath));
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = { classify, classifyAll };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Model routing hint definitions for SigMap.
|
|
5
|
+
*
|
|
6
|
+
* Maps complexity tiers to:
|
|
7
|
+
* - example model names (vendor-agnostic labels used as hints)
|
|
8
|
+
* - task types that belong to each tier
|
|
9
|
+
* - estimated per-1K-token API cost (USD, illustrative — update as needed)
|
|
10
|
+
*
|
|
11
|
+
* These are embedded in the generated context file when `routing: true`
|
|
12
|
+
* so that AI agents and developers know which model tier to invoke.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const TIERS = {
|
|
16
|
+
fast: {
|
|
17
|
+
label: 'Fast (low-cost)',
|
|
18
|
+
examples: 'claude-haiku-4-5, gpt-5-1-codex-mini, gemini-3-flash',
|
|
19
|
+
tasks: [
|
|
20
|
+
'Autocomplete and inline suggestions',
|
|
21
|
+
'Edit config or markup files',
|
|
22
|
+
'Fix typos and rename symbols',
|
|
23
|
+
'Format, lint, and trivial style changes',
|
|
24
|
+
'Explain a short utility function',
|
|
25
|
+
'Generate simple shell scripts or Dockerfiles',
|
|
26
|
+
],
|
|
27
|
+
costHint: '~$0.0008 / 1K tokens',
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
balanced: {
|
|
31
|
+
label: 'Balanced (mid-tier)',
|
|
32
|
+
examples: 'claude-sonnet-4-6, gpt-5-2, gemini-3-1-pro',
|
|
33
|
+
tasks: [
|
|
34
|
+
'Write unit or integration tests',
|
|
35
|
+
'Implement a well-scoped feature function',
|
|
36
|
+
'Debug a runtime error with stack trace',
|
|
37
|
+
'Refactor a module (< 200 lines)',
|
|
38
|
+
'Generate a PR description',
|
|
39
|
+
'Explain a multi-function module',
|
|
40
|
+
],
|
|
41
|
+
costHint: '~$0.003 / 1K tokens',
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
powerful: {
|
|
45
|
+
label: 'Powerful (high-cost)',
|
|
46
|
+
examples: 'claude-opus-4-6, gpt-5-4, gemini-2-5-pro',
|
|
47
|
+
tasks: [
|
|
48
|
+
'Cross-cutting architecture decisions',
|
|
49
|
+
'Multi-file refactor spanning 5+ files',
|
|
50
|
+
'Security audit (OWASP Top 10)',
|
|
51
|
+
'Complex debugging across async boundaries',
|
|
52
|
+
'Migration plan for a library/framework upgrade',
|
|
53
|
+
'Designing a new module from requirements',
|
|
54
|
+
],
|
|
55
|
+
costHint: '~$0.015 / 1K tokens',
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Format the routing section as markdown to append to the context file.
|
|
61
|
+
*
|
|
62
|
+
* @param {{ fast: string[], balanced: string[], powerful: string[] }} groups
|
|
63
|
+
* Relative file paths grouped by tier (from classifier.classifyAll).
|
|
64
|
+
* @returns {string} Markdown block to embed in the context output.
|
|
65
|
+
*/
|
|
66
|
+
function formatRoutingSection(groups) {
|
|
67
|
+
const lines = [
|
|
68
|
+
'',
|
|
69
|
+
'---',
|
|
70
|
+
'',
|
|
71
|
+
'## Model routing hints',
|
|
72
|
+
'<!-- Generated by SigMap routing module — update gen-context.config.json to disable -->',
|
|
73
|
+
'',
|
|
74
|
+
'Select the model tier based on the task complexity and the files involved.',
|
|
75
|
+
'',
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
for (const [tier, info] of Object.entries(TIERS)) {
|
|
79
|
+
const files = groups[tier] || [];
|
|
80
|
+
lines.push(`### ${info.label}`);
|
|
81
|
+
lines.push(`**Examples:** ${info.examples} `);
|
|
82
|
+
lines.push(`**Cost:** ${info.costHint}`);
|
|
83
|
+
lines.push('');
|
|
84
|
+
lines.push('**Use for tasks like:**');
|
|
85
|
+
for (const task of info.tasks) lines.push(`- ${task}`);
|
|
86
|
+
lines.push('');
|
|
87
|
+
if (files.length > 0) {
|
|
88
|
+
lines.push('**Files in this tier:**');
|
|
89
|
+
for (const f of files.slice(0, 15)) lines.push(`- \`${f}\``);
|
|
90
|
+
if (files.length > 15) lines.push(`- … and ${files.length - 15} more`);
|
|
91
|
+
} else {
|
|
92
|
+
lines.push('**Files in this tier:** _(none detected)_');
|
|
93
|
+
}
|
|
94
|
+
lines.push('');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
lines.push('> **Tip:** Run `node gen-context.js --routing` to regenerate routing hints.');
|
|
98
|
+
lines.push('> See `docs/MODEL_ROUTING.md` for full routing guide and cost optimisation tips.');
|
|
99
|
+
|
|
100
|
+
return lines.join('\n');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = { TIERS, formatRoutingSection };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Secret detection patterns for SigMap scanner.
|
|
5
|
+
* Each pattern has a name and a regex tested against signature strings.
|
|
6
|
+
*/
|
|
7
|
+
const PATTERNS = [
|
|
8
|
+
{
|
|
9
|
+
name: 'AWS Access Key',
|
|
10
|
+
regex: /AKIA[0-9A-Z]{16}/,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'AWS Secret Key',
|
|
14
|
+
// 40-char base64-like string following common AWS secret key assignment patterns
|
|
15
|
+
regex: /(?:aws_secret|secret_access_key|SecretAccessKey)\s*[:=]\s*['"]?[0-9a-zA-Z/+]{40}/i,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'GCP API Key',
|
|
19
|
+
regex: /AIza[0-9A-Za-z\-_]{35}/,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'GitHub Token',
|
|
23
|
+
regex: /gh[pousr]_[A-Za-z0-9_]{36,}/,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'JWT Token',
|
|
27
|
+
regex: /eyJ[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+/,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'DB Connection String',
|
|
31
|
+
regex: /(mongodb|postgres|postgresql|mysql|redis):\/\/[^:]+:[^@]+@/i,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'SSH Private Key',
|
|
35
|
+
regex: /-----BEGIN (RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----/,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'Stripe Key',
|
|
39
|
+
regex: /sk_(live|test)_[0-9a-zA-Z]{24,}/,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'Twilio Key',
|
|
43
|
+
regex: /SK[0-9a-fA-F]{32}/,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'Generic Secret',
|
|
47
|
+
regex: /(secret|password|passwd|api_key|apikey|auth_token|access_token)\s*[:=]\s*['"][^'"]{8,}['"]/i,
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
module.exports = { PATTERNS };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { PATTERNS } = require('./patterns');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Scan an array of signature strings for secrets.
|
|
7
|
+
*
|
|
8
|
+
* @param {string[]} signatures - Array of extracted signature strings
|
|
9
|
+
* @param {string} filePath - Source file path (used in redaction message)
|
|
10
|
+
* @returns {{ safe: string[], redacted: boolean }}
|
|
11
|
+
* safe — signatures with any secret-containing entries replaced
|
|
12
|
+
* redacted — true if at least one signature was redacted
|
|
13
|
+
*/
|
|
14
|
+
function scan(signatures, filePath) {
|
|
15
|
+
if (!Array.isArray(signatures)) return { safe: [], redacted: false };
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
let redacted = false;
|
|
19
|
+
const safe = signatures.map((sig) => {
|
|
20
|
+
if (typeof sig !== 'string') return sig;
|
|
21
|
+
for (const pattern of PATTERNS) {
|
|
22
|
+
if (pattern.regex.test(sig)) {
|
|
23
|
+
redacted = true;
|
|
24
|
+
return `[REDACTED — ${pattern.name} detected in ${filePath}]`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return sig;
|
|
28
|
+
});
|
|
29
|
+
return { safe, redacted };
|
|
30
|
+
} catch (_) {
|
|
31
|
+
// Never throw — return original signatures on any error
|
|
32
|
+
return { safe: signatures, redacted: false };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = { scan };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SigMap usage logger (v0.9)
|
|
5
|
+
*
|
|
6
|
+
* Writes an append-only newline-delimited JSON (NDJSON) log at
|
|
7
|
+
* .context/usage.ndjson
|
|
8
|
+
*
|
|
9
|
+
* Each line is one JSON object describing a gen-context run.
|
|
10
|
+
* Zero npm dependencies — pure Node.js fs.
|
|
11
|
+
*
|
|
12
|
+
* Enabled by:
|
|
13
|
+
* config.tracking: true (gen-context.config.json)
|
|
14
|
+
* --track CLI flag
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const LOG_FILE = path.join('.context', 'usage.ndjson');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Append one run entry to the usage log.
|
|
24
|
+
* @param {object} entry - Run metrics from runGenerate()
|
|
25
|
+
* @param {string} cwd - Project root (absolute path)
|
|
26
|
+
*/
|
|
27
|
+
function logRun(entry, cwd) {
|
|
28
|
+
try {
|
|
29
|
+
const logPath = path.join(cwd, LOG_FILE);
|
|
30
|
+
const dir = path.dirname(logPath);
|
|
31
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
32
|
+
|
|
33
|
+
const record = {
|
|
34
|
+
ts: new Date().toISOString(),
|
|
35
|
+
version: entry.version || '0.9.0',
|
|
36
|
+
fileCount: entry.fileCount || 0,
|
|
37
|
+
droppedCount: entry.droppedCount || 0,
|
|
38
|
+
rawTokens: entry.rawTokens || 0,
|
|
39
|
+
finalTokens: entry.finalTokens || 0,
|
|
40
|
+
reductionPct: entry.rawTokens > 0
|
|
41
|
+
? parseFloat((100 - (entry.finalTokens / entry.rawTokens) * 100).toFixed(1))
|
|
42
|
+
: 0,
|
|
43
|
+
overBudget: entry.overBudget || false,
|
|
44
|
+
budgetLimit: entry.budgetLimit || 6000,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
fs.appendFileSync(logPath, JSON.stringify(record) + '\n', 'utf8');
|
|
48
|
+
} catch (err) {
|
|
49
|
+
// Never crash the main process — tracking is optional
|
|
50
|
+
process.stderr.write(`[sigmap] tracking: could not write log: ${err.message}\n`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Read and parse all usage log entries.
|
|
56
|
+
* @param {string} cwd - Project root (absolute path)
|
|
57
|
+
* @returns {object[]} Array of parsed log records (oldest first)
|
|
58
|
+
*/
|
|
59
|
+
function readLog(cwd) {
|
|
60
|
+
try {
|
|
61
|
+
const logPath = path.join(cwd, LOG_FILE);
|
|
62
|
+
if (!fs.existsSync(logPath)) return [];
|
|
63
|
+
const raw = fs.readFileSync(logPath, 'utf8');
|
|
64
|
+
return raw
|
|
65
|
+
.split('\n')
|
|
66
|
+
.filter(Boolean)
|
|
67
|
+
.map((line) => {
|
|
68
|
+
try { return JSON.parse(line); } catch (_) { return null; }
|
|
69
|
+
})
|
|
70
|
+
.filter(Boolean);
|
|
71
|
+
} catch (_) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Compute summary statistics from an array of log records.
|
|
78
|
+
* @param {object[]} entries
|
|
79
|
+
* @returns {object} Summary stats
|
|
80
|
+
*/
|
|
81
|
+
function summarize(entries) {
|
|
82
|
+
if (!entries || entries.length === 0) {
|
|
83
|
+
return {
|
|
84
|
+
totalRuns: 0,
|
|
85
|
+
avgReductionPct: 0,
|
|
86
|
+
avgFinalTokens: 0,
|
|
87
|
+
avgRawTokens: 0,
|
|
88
|
+
minFinalTokens: 0,
|
|
89
|
+
maxFinalTokens: 0,
|
|
90
|
+
firstRun: null,
|
|
91
|
+
lastRun: null,
|
|
92
|
+
overBudgetRuns: 0,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const reductions = entries.map((e) => e.reductionPct || 0);
|
|
97
|
+
const finals = entries.map((e) => e.finalTokens || 0);
|
|
98
|
+
const raws = entries.map((e) => e.rawTokens || 0);
|
|
99
|
+
|
|
100
|
+
const avg = (arr) => arr.reduce((a, b) => a + b, 0) / arr.length;
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
totalRuns: entries.length,
|
|
104
|
+
avgReductionPct: parseFloat(avg(reductions).toFixed(1)),
|
|
105
|
+
avgFinalTokens: Math.round(avg(finals)),
|
|
106
|
+
avgRawTokens: Math.round(avg(raws)),
|
|
107
|
+
minFinalTokens: Math.min(...finals),
|
|
108
|
+
maxFinalTokens: Math.max(...finals),
|
|
109
|
+
firstRun: entries[0].ts || null,
|
|
110
|
+
lastRun: entries[entries.length - 1].ts || null,
|
|
111
|
+
overBudgetRuns: entries.filter((e) => e.overBudget).length,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = { logRun, readLog, summarize };
|