sigmap 7.10.0 → 7.12.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/CHANGELOG.md +18 -0
- package/gen-context.js +618 -273
- package/llms-full.txt +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/mcp/server.js +1 -1
- package/src/plan/verify-plan.js +108 -0
- package/src/review/review-pr.js +106 -0
package/llms-full.txt
CHANGED
|
@@ -9,7 +9,7 @@ the files relevant to the task — cutting tokens ~97% while keeping answers
|
|
|
9
9
|
grounded. Deterministic, offline, no embeddings or vector database. Works with
|
|
10
10
|
Claude, Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
11
11
|
|
|
12
|
-
# Version: 7.
|
|
12
|
+
# Version: 7.12.0 | Benchmark: sigmap-v7.0-main (2026-06-14)
|
|
13
13
|
# Source: auto-generated from package.json, version.json, src/mcp/tools.js, src/config/defaults.js
|
|
14
14
|
# Regenerate: npm run generate:llms | Validate: npm run validate:llms
|
|
15
15
|
|
package/llms.txt
CHANGED
|
@@ -9,7 +9,7 @@ the files relevant to the task — cutting tokens ~97% while keeping answers
|
|
|
9
9
|
grounded. Deterministic, offline, no embeddings or vector database. Works with
|
|
10
10
|
Claude, Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
11
11
|
|
|
12
|
-
# Version: 7.
|
|
12
|
+
# Version: 7.12.0 | Benchmark: sigmap-v7.0-main (2026-06-14)
|
|
13
13
|
# Source: auto-generated from package.json, version.json, src/mcp/tools.js, src/config/defaults.js
|
|
14
14
|
# Regenerate: npm run generate:llms | Validate: npm run validate:llms
|
|
15
15
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sigmap",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.12.0",
|
|
4
4
|
"description": "97% token reduction for AI coding. Extracts function & class signatures with TF-IDF ranking to feed only the right files to Claude, Cursor, Copilot, Aider, Windsurf, local LLMs & MCP. Zero dependencies, runs offline via npx.",
|
|
5
5
|
"main": "packages/core/index.js",
|
|
6
6
|
"exports": {
|
package/src/mcp/server.js
CHANGED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* verify-plan (IMPL.md §6.1 — Gap 2, step 2 of the `create` pipeline).
|
|
5
|
+
*
|
|
6
|
+
* Checks a plan (markdown) against the LIVE index before execution: do the
|
|
7
|
+
* referenced files and symbols exist, is the blast radius acceptable, is the
|
|
8
|
+
* scope in bounds? Catches Cause 1+2 at plan time — cheaper than after the
|
|
9
|
+
* code is written. Reuses the verify primitives + the impact graph.
|
|
10
|
+
* Zero-dependency, bundle-safe.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { extractFilePaths, extractSymbols } = require('../verify/parsers');
|
|
16
|
+
const { buildSymbolSet } = require('../verify/hallucination-guard');
|
|
17
|
+
const { closestMatch } = require('../verify/closest-match');
|
|
18
|
+
const { analyzeImpact } = require('../graph/impact');
|
|
19
|
+
|
|
20
|
+
const DEFAULT_BLAST_THRESHOLD = 20; // transitive+direct dependents → "high blast radius"
|
|
21
|
+
const DEFAULT_SCOPE_THRESHOLD = 10; // distinct referenced files → "broad scope"
|
|
22
|
+
|
|
23
|
+
/** Resolve a referenced path against cwd (handles a leading "./"). */
|
|
24
|
+
function _fileExists(cwd, ref) {
|
|
25
|
+
const clean = ref.replace(/^\.\//, '');
|
|
26
|
+
for (const c of [path.resolve(cwd, clean), path.resolve(cwd, ref)]) {
|
|
27
|
+
try { if (fs.existsSync(c)) return true; } catch (_) {}
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Verify a plan against the live index.
|
|
34
|
+
* @param {string} planText the plan as markdown
|
|
35
|
+
* @param {string} cwd repo root
|
|
36
|
+
* @param {object} [opts]
|
|
37
|
+
* @param {number} [opts.blastThreshold=20]
|
|
38
|
+
* @param {number} [opts.scopeThreshold=10]
|
|
39
|
+
* @param {(ref:string)=>boolean} [opts.fileExists] override for testing
|
|
40
|
+
* @returns {{ issues: object[], blast: object[], scope: object, summary: object }}
|
|
41
|
+
*/
|
|
42
|
+
function verifyPlan(planText, cwd, opts = {}) {
|
|
43
|
+
const blastThreshold = opts.blastThreshold != null ? opts.blastThreshold : DEFAULT_BLAST_THRESHOLD;
|
|
44
|
+
const scopeThreshold = opts.scopeThreshold != null ? opts.scopeThreshold : DEFAULT_SCOPE_THRESHOLD;
|
|
45
|
+
const fileExists = opts.fileExists || ((ref) => _fileExists(cwd, ref));
|
|
46
|
+
|
|
47
|
+
const text = String(planText || '');
|
|
48
|
+
const filesRef = extractFilePaths(text); // [{ path, line }]
|
|
49
|
+
const symbolsRef = extractSymbols(text); // [{ name, line }]
|
|
50
|
+
const { set: symbolSet, symbolCandidates } = buildSymbolSet(cwd);
|
|
51
|
+
|
|
52
|
+
const issues = [];
|
|
53
|
+
|
|
54
|
+
// 1. Referenced files must exist.
|
|
55
|
+
const existingFiles = [];
|
|
56
|
+
for (const f of filesRef) {
|
|
57
|
+
if (fileExists(f.path)) existingFiles.push(f.path);
|
|
58
|
+
else issues.push({ type: 'missing-file', ref: f.path, line: f.line, severity: 'error' });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 2. Referenced symbols must exist in the live index (suggest a near match).
|
|
62
|
+
for (const s of symbolsRef) {
|
|
63
|
+
if (symbolSet.has(s.name)) continue;
|
|
64
|
+
const match = closestMatch(s.name, symbolCandidates);
|
|
65
|
+
issues.push({
|
|
66
|
+
type: 'unknown-symbol', ref: s.name, line: s.line, severity: 'error',
|
|
67
|
+
suggestion: match ? match.name : null,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 3. Blast radius for each existing referenced file (one graph build).
|
|
72
|
+
const blast = [];
|
|
73
|
+
if (existingFiles.length) {
|
|
74
|
+
let impacts = [];
|
|
75
|
+
try { impacts = analyzeImpact(existingFiles, cwd, {}); } catch (_) { impacts = []; }
|
|
76
|
+
for (const { file, impact } of impacts) {
|
|
77
|
+
const entry = { file, totalImpact: impact.totalImpact, tests: impact.tests.length };
|
|
78
|
+
blast.push(entry);
|
|
79
|
+
if (impact.totalImpact > blastThreshold) {
|
|
80
|
+
issues.push({ type: 'high-blast-radius', ref: file, count: impact.totalImpact, severity: 'warn' });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
blast.sort((a, b) => b.totalImpact - a.totalImpact);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 4. Scope.
|
|
87
|
+
const scope = { files: filesRef.length, symbols: symbolsRef.length, threshold: scopeThreshold };
|
|
88
|
+
if (filesRef.length > scopeThreshold) {
|
|
89
|
+
issues.push({ type: 'broad-scope', count: filesRef.length, threshold: scopeThreshold, severity: 'warn' });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const errors = issues.filter((i) => i.severity === 'error').length;
|
|
93
|
+
const warnings = issues.filter((i) => i.severity === 'warn').length;
|
|
94
|
+
return {
|
|
95
|
+
issues,
|
|
96
|
+
blast,
|
|
97
|
+
scope,
|
|
98
|
+
summary: {
|
|
99
|
+
filesReferenced: filesRef.length,
|
|
100
|
+
symbolsReferenced: symbolsRef.length,
|
|
101
|
+
errors,
|
|
102
|
+
warnings,
|
|
103
|
+
ok: errors === 0,
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = { verifyPlan, DEFAULT_BLAST_THRESHOLD, DEFAULT_SCOPE_THRESHOLD };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* review-pr (IMPL.md §6 step 4 — last guard stage of the `create` pipeline).
|
|
5
|
+
*
|
|
6
|
+
* Audits a diff for drift and side effects after a PR is opened: scope drift,
|
|
7
|
+
* edits to high-fan-in "god-node" files, source changes without matching tests,
|
|
8
|
+
* and changes to security-sensitive files. Pure (takes a changed-file list),
|
|
9
|
+
* zero-dependency, bundle-safe; reuses the impact graph for blast radius.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { analyzeImpact } = require('../graph/impact');
|
|
14
|
+
|
|
15
|
+
const SECURITY_PATTERNS = [
|
|
16
|
+
/(^|\/)\.env(\.|$)/i,
|
|
17
|
+
/(^|\/)(secrets?|credentials?)(\/|\.|$)/i,
|
|
18
|
+
/(^|\/)auth(\/|\.|-)/i,
|
|
19
|
+
/(^|\/)package(-lock)?\.json$/,
|
|
20
|
+
/(^|\/)(yarn\.lock|pnpm-lock\.yaml)$/,
|
|
21
|
+
/(^|\/)\.github\/workflows\//,
|
|
22
|
+
/(^|\/)Dockerfile/i,
|
|
23
|
+
/\.(pem|key|crt|p12)$/i,
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const SRC_EXTS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.py', '.go', '.rs', '.java', '.rb', '.php']);
|
|
27
|
+
const GOD_NODE_THRESHOLD = 15; // transitive dependents → high-fan-in "god node"
|
|
28
|
+
const SCOPE_DIR_THRESHOLD = 5; // distinct top-level dirs → scope drift
|
|
29
|
+
|
|
30
|
+
function isTestFile(p) {
|
|
31
|
+
return /\.(test|spec)\.[jt]sx?$|(^|\/)test_|_test\.(py|go)$|(^|\/)(tests?|__tests__|spec)\//.test(p);
|
|
32
|
+
}
|
|
33
|
+
function isSource(p) {
|
|
34
|
+
return SRC_EXTS.has(path.extname(p).toLowerCase()) && !isTestFile(p);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Audit a changed-file list.
|
|
39
|
+
* @param {Array<{path:string,status:string}>|string[]} changedFiles
|
|
40
|
+
* @param {string} cwd
|
|
41
|
+
* @param {object} [opts]
|
|
42
|
+
* @param {number} [opts.godNodeThreshold=15]
|
|
43
|
+
* @param {number} [opts.scopeThreshold=5]
|
|
44
|
+
* @returns {{ findings: object[], blast: object[], summary: object }}
|
|
45
|
+
*/
|
|
46
|
+
function reviewPr(changedFiles, cwd, opts = {}) {
|
|
47
|
+
const godThreshold = opts.godNodeThreshold != null ? opts.godNodeThreshold : GOD_NODE_THRESHOLD;
|
|
48
|
+
const scopeThreshold = opts.scopeThreshold != null ? opts.scopeThreshold : SCOPE_DIR_THRESHOLD;
|
|
49
|
+
const files = (changedFiles || []).map((f) => (typeof f === 'string' ? { path: f, status: 'M' } : f));
|
|
50
|
+
|
|
51
|
+
const findings = [];
|
|
52
|
+
const paths = files.map((f) => f.path);
|
|
53
|
+
const live = files.filter((f) => f.status !== 'D');
|
|
54
|
+
const srcChanged = live.filter((f) => isSource(f.path)).map((f) => f.path);
|
|
55
|
+
const testChanged = paths.filter(isTestFile);
|
|
56
|
+
|
|
57
|
+
// 1. Missing tests: a changed source file with no matching changed test file.
|
|
58
|
+
for (const s of srcChanged) {
|
|
59
|
+
const stem = path.basename(s).replace(/\.[^.]+$/, '');
|
|
60
|
+
const covered = testChanged.some((t) => path.basename(t).includes(stem));
|
|
61
|
+
if (!covered) findings.push({ type: 'missing-tests', file: s, severity: 'warn' });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 2. Security-sensitive files.
|
|
65
|
+
for (const f of live) {
|
|
66
|
+
if (SECURITY_PATTERNS.some((re) => re.test(f.path))) {
|
|
67
|
+
findings.push({ type: 'security-file', file: f.path, severity: 'warn' });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 3. God-node edits (high blast radius).
|
|
72
|
+
const blast = [];
|
|
73
|
+
if (srcChanged.length) {
|
|
74
|
+
let impacts = [];
|
|
75
|
+
try { impacts = analyzeImpact(srcChanged, cwd, {}); } catch (_) { impacts = []; }
|
|
76
|
+
for (const { file, impact } of impacts) {
|
|
77
|
+
blast.push({ file, totalImpact: impact.totalImpact });
|
|
78
|
+
if (impact.totalImpact > godThreshold) {
|
|
79
|
+
findings.push({ type: 'god-node', file, count: impact.totalImpact, severity: 'warn' });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
blast.sort((a, b) => b.totalImpact - a.totalImpact);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 4. Scope drift: distinct top-level directories touched.
|
|
86
|
+
const dirs = [...new Set(paths.map((p) => (p.includes('/') ? p.split('/')[0] : '.')))];
|
|
87
|
+
if (dirs.length > scopeThreshold) {
|
|
88
|
+
findings.push({ type: 'scope-drift', dirs, count: dirs.length, threshold: scopeThreshold, severity: 'warn' });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const byType = findings.reduce((a, f) => { a[f.type] = (a[f.type] || 0) + 1; return a; }, {});
|
|
92
|
+
return {
|
|
93
|
+
findings,
|
|
94
|
+
blast,
|
|
95
|
+
summary: {
|
|
96
|
+
filesChanged: files.length,
|
|
97
|
+
sourceChanged: srcChanged.length,
|
|
98
|
+
testsChanged: testChanged.length,
|
|
99
|
+
findings: findings.length,
|
|
100
|
+
byType,
|
|
101
|
+
ok: findings.length === 0,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = { reviewPr, SECURITY_PATTERNS, GOD_NODE_THRESHOLD, SCOPE_DIR_THRESHOLD };
|