sigmap 8.9.0 → 8.10.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 +33 -0
- package/README.md +8 -3
- package/gen-context.js +644 -211
- package/llms-full.txt +2 -2
- package/llms.txt +2 -2
- package/package.json +9 -3
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/conventions/extract.js +13 -3
- package/src/conventions/fix.js +4 -1
- package/src/extractors/javascript.js +4 -4
- package/src/extractors/typescript.js +5 -5
- package/src/graph/builder.js +124 -16
- package/src/graph/impact.js +2 -2
- package/src/health/scorer.js +172 -97
- package/src/judge/judge-engine.js +62 -2
- package/src/mcp/server.js +1 -1
- package/src/plan/planner.js +44 -15
- package/src/review/pr-evidence.js +2 -1
- package/src/review/review-pr.js +24 -2
- package/src/util/truncate.js +42 -0
package/llms-full.txt
CHANGED
|
@@ -11,13 +11,13 @@ ranking keeps the relevant context in scope (cutting tokens ~97% as a side
|
|
|
11
11
|
effect), with no LLM calls, embeddings, or vector database. Works with Claude,
|
|
12
12
|
Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
13
13
|
|
|
14
|
-
# Version: 8.
|
|
14
|
+
# Version: 8.10.0 | Benchmark: sigmap-v8.10-main (2026-07-09)
|
|
15
15
|
# Source: auto-generated from package.json, version.json, benchmarks/latest.json, src/mcp/tools.js, src/config/defaults.js
|
|
16
16
|
# Regenerate: npm run generate:llms | Validate: npm run validate:llms
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
20
|
-
## Core metrics (benchmark: sigmap-v8.
|
|
20
|
+
## Core metrics (benchmark: sigmap-v8.10-main, 2026-07-09)
|
|
21
21
|
|
|
22
22
|
| Metric | Without SigMap | With SigMap |
|
|
23
23
|
|--------|----------------|-------------|
|
package/llms.txt
CHANGED
|
@@ -11,7 +11,7 @@ ranking keeps the relevant context in scope (cutting tokens ~97% as a side
|
|
|
11
11
|
effect), with no LLM calls, embeddings, or vector database. Works with Claude,
|
|
12
12
|
Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
13
13
|
|
|
14
|
-
# Version: 8.
|
|
14
|
+
# Version: 8.10.0 | Benchmark: sigmap-v8.10-main (2026-07-09)
|
|
15
15
|
# Source: auto-generated from package.json, version.json, benchmarks/latest.json, src/mcp/tools.js, src/config/defaults.js
|
|
16
16
|
# Regenerate: npm run generate:llms | Validate: npm run validate:llms
|
|
17
17
|
|
|
@@ -23,7 +23,7 @@ Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
|
23
23
|
- No blast-radius awareness before editing a hub file — `--impact` shows every file a change touches.
|
|
24
24
|
- Pasted stack traces, CI logs, and JSON bloat the prompt — `squeeze` minimizes them and enriches the top frame from the symbol index.
|
|
25
25
|
|
|
26
|
-
## Core metrics (benchmark: sigmap-v8.
|
|
26
|
+
## Core metrics (benchmark: sigmap-v8.10-main, 2026-07-09)
|
|
27
27
|
|
|
28
28
|
- hit@5 retrieval: 87.8% vs 13.6% random baseline (6.5× lift)
|
|
29
29
|
- Token reduction: 97.0% average across benchmark repos
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sigmap",
|
|
3
|
-
"version": "8.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "8.10.0",
|
|
4
|
+
"description": "The deterministic, verifiable grounding layer for AI code work — a zero-dependency signature-and-evidence map that grounds Claude, Cursor, Copilot, Aider, Windsurf, local LLMs & MCP agents against your real code (repo + installed libraries) so they stop hallucinating files, imports & APIs. Runs offline via npx; byte-stable output; ~97% token reduction as proof.",
|
|
5
5
|
"main": "packages/core/index.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./packages/core/index.js",
|
|
@@ -80,7 +80,13 @@
|
|
|
80
80
|
"mcp",
|
|
81
81
|
"function-signatures",
|
|
82
82
|
"code-intelligence",
|
|
83
|
-
"
|
|
83
|
+
"ai-grounding",
|
|
84
|
+
"hallucination-detection",
|
|
85
|
+
"deterministic",
|
|
86
|
+
"verifiable-ai",
|
|
87
|
+
"code-signatures",
|
|
88
|
+
"ai-code-review",
|
|
89
|
+
"evidence-pack",
|
|
84
90
|
"local-llm",
|
|
85
91
|
"ollama",
|
|
86
92
|
"ai-coding"
|
|
@@ -26,8 +26,16 @@ const TIER_MOSTLY = 0.7;
|
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Classify a file's base name (without extension) into a naming style.
|
|
29
|
+
*
|
|
30
|
+
* A single lowercase word (`user`, `index`, `loader`) is classified
|
|
31
|
+
* `single-word`, NOT `camelCase`: it has no case boundary or separator, so it is
|
|
32
|
+
* compatible with camelCase, kebab-case AND snake_case at once and expresses no
|
|
33
|
+
* distinguishable convention. `scoreConvention` treats it as style-neutral
|
|
34
|
+
* (excluded), so a repo of single-word files reports "unknown" rather than a
|
|
35
|
+
* spurious "100% camelCase".
|
|
36
|
+
*
|
|
29
37
|
* @param {string} basename a file basename, e.g. "user-service.ts"
|
|
30
|
-
* @returns {'PascalCase'|'camelCase'|'kebab-case'|'snake_case'|'other'}
|
|
38
|
+
* @returns {'PascalCase'|'camelCase'|'kebab-case'|'snake_case'|'single-word'|'other'}
|
|
31
39
|
*/
|
|
32
40
|
function classifyNaming(basename) {
|
|
33
41
|
let stem = String(basename || '');
|
|
@@ -38,7 +46,7 @@ function classifyNaming(basename) {
|
|
|
38
46
|
if (/[_]/.test(stem) && /^[a-z0-9]+(?:_[a-z0-9]+)+$/.test(stem)) return 'snake_case';
|
|
39
47
|
if (/^[A-Z][A-Za-z0-9]*$/.test(stem) && /[a-z]/.test(stem)) return 'PascalCase';
|
|
40
48
|
if (/^[a-z][A-Za-z0-9]*$/.test(stem) && /[A-Z]/.test(stem)) return 'camelCase';
|
|
41
|
-
if (/^[a-z][a-z0-9]*$/.test(stem)) return '
|
|
49
|
+
if (/^[a-z][a-z0-9]*$/.test(stem)) return 'single-word'; // style-neutral (no case boundary)
|
|
42
50
|
return 'other';
|
|
43
51
|
}
|
|
44
52
|
|
|
@@ -61,7 +69,9 @@ function scoreConvention(labels, refs) {
|
|
|
61
69
|
let total = 0;
|
|
62
70
|
for (let i = 0; i < all.length; i++) {
|
|
63
71
|
const l = all[i];
|
|
64
|
-
|
|
72
|
+
// 'other' = unclassifiable; 'single-word' = style-neutral. Both express no
|
|
73
|
+
// distinguishable convention and are excluded from the score.
|
|
74
|
+
if (l == null || l === 'other' || l === 'single-word') continue;
|
|
65
75
|
total++;
|
|
66
76
|
counts.set(l, (counts.get(l) || 0) + 1);
|
|
67
77
|
if (refs && refs[i] != null) {
|
package/src/conventions/fix.js
CHANGED
|
@@ -47,7 +47,10 @@ function buildFixList(cwd, files, conventions) {
|
|
|
47
47
|
if (TEST_RE.test(f)) continue;
|
|
48
48
|
const base = path.basename(f);
|
|
49
49
|
const style = classifyNaming(base);
|
|
50
|
-
|
|
50
|
+
// Skip unclassifiable ('other') and style-neutral single-word names — a
|
|
51
|
+
// single lowercase word already satisfies any convention, so renaming it
|
|
52
|
+
// (e.g. user.js → user.js) is a no-op at best and noise at worst.
|
|
53
|
+
if (style === 'other' || style === 'single-word' || style === dominant) continue;
|
|
51
54
|
const rel = path.relative(cwd, f).replace(/\\/g, '/');
|
|
52
55
|
renames.push({ from: rel, to: _renamePath(rel, dominant), fromStyle: style });
|
|
53
56
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { lineAt, withAnchor } = require('./line-anchor');
|
|
4
|
+
const { capWithNotice, capMembersWithNotice } = require('../util/truncate');
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Extract signatures from JavaScript source code.
|
|
@@ -80,9 +81,8 @@ function extract(src) {
|
|
|
80
81
|
anchors.push([startLn, fnEndLine(m.index + m[0].length, startLn)]);
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
.slice(0, 25);
|
|
84
|
+
const withAnchors = sigs.map((s, i) => (anchors[i] ? withAnchor(s, anchors[i][0], anchors[i][1]) : s));
|
|
85
|
+
return capWithNotice(withAnchors, 25, 'signatures');
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
function extractBlock(src, startIndex) {
|
|
@@ -113,7 +113,7 @@ function extractClassMembers(block, returnHints) {
|
|
|
113
113
|
const retStr = formatReturnHint(returnHints.get(m[1]));
|
|
114
114
|
members.push({ text: `${isStatic}${isAsync}${m[1]}(${normalizeParams(m[2])})${retStr}`, start, end });
|
|
115
115
|
}
|
|
116
|
-
return members
|
|
116
|
+
return capMembersWithNotice(members, 8, 'methods');
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
function buildReturnHints(src) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { lineAt, withAnchor } = require('./line-anchor');
|
|
4
|
+
const { capWithNotice, capMembersWithNotice } = require('../util/truncate');
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Extract signatures from TypeScript source code.
|
|
@@ -152,9 +153,8 @@ function extract(src) {
|
|
|
152
153
|
}
|
|
153
154
|
}
|
|
154
155
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
.slice(0, 35);
|
|
156
|
+
const withAnchors = sigs.map((s, i) => (anchors[i] ? withAnchor(s, anchors[i][0], anchors[i][1]) : s));
|
|
157
|
+
return capWithNotice(withAnchors, 35, 'signatures');
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
function extractBlock(src, startIndex) {
|
|
@@ -184,7 +184,7 @@ function extractInterfaceMembers(block) {
|
|
|
184
184
|
const start = m.index + (m[0].length - m[0].replace(/^\s+/, '').length);
|
|
185
185
|
members.push({ text: `${m[1]}(${normalizeParams(m[2])})`, start, end: m.index + m[0].length });
|
|
186
186
|
}
|
|
187
|
-
return members
|
|
187
|
+
return capMembersWithNotice(members, 8, 'members');
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
const _CTRL_KEYWORDS = new Set(['if', 'for', 'while', 'switch', 'do', 'try', 'catch', 'finally', 'else', 'return']);
|
|
@@ -210,7 +210,7 @@ function extractClassMembers(block) {
|
|
|
210
210
|
const retStr = retType ? ` → ${retType}` : '';
|
|
211
211
|
members.push({ text: `${isStatic}${isAsync}${m[1]}(${normalizeParams(m[2])})${retStr}`, start, end });
|
|
212
212
|
}
|
|
213
|
-
return members
|
|
213
|
+
return capMembersWithNotice(members, 8, 'methods');
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
function normalizeParams(params) {
|
package/src/graph/builder.js
CHANGED
|
@@ -31,20 +31,19 @@ const RB_EXTS = new Set(['.rb', '.rake']);
|
|
|
31
31
|
const R_EXTS = new Set(['.r', '.R']);
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* @param {string}
|
|
34
|
+
* Probe an absolute base path for a JS/TS module file in fileSet, trying the
|
|
35
|
+
* usual extension and index-file candidates.
|
|
36
|
+
* @param {string} base - absolute path (no extension) to probe
|
|
37
37
|
* @param {Set<string>} fileSet
|
|
38
38
|
* @returns {string|null}
|
|
39
39
|
*/
|
|
40
|
-
function
|
|
41
|
-
const base = path.resolve(dir, importStr);
|
|
40
|
+
function probeJs(base, fileSet) {
|
|
42
41
|
const candidates = [
|
|
43
42
|
base,
|
|
44
43
|
base + '.ts', base + '.tsx',
|
|
45
44
|
base + '.js', base + '.jsx', base + '.mjs', base + '.cjs',
|
|
46
|
-
path.join(base, 'index.ts'),
|
|
47
|
-
path.join(base, 'index.js'),
|
|
45
|
+
path.join(base, 'index.ts'), path.join(base, 'index.tsx'),
|
|
46
|
+
path.join(base, 'index.js'), path.join(base, 'index.jsx'),
|
|
48
47
|
];
|
|
49
48
|
for (const c of candidates) {
|
|
50
49
|
const normC = normalizePath(c);
|
|
@@ -53,6 +52,103 @@ function resolveJsPath(dir, importStr, fileSet) {
|
|
|
53
52
|
return null;
|
|
54
53
|
}
|
|
55
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Resolve a JS/TS relative import string to an absolute path in fileSet.
|
|
57
|
+
* @param {string} dir - directory of the importing file
|
|
58
|
+
* @param {string} importStr - raw import string (e.g. './utils', '../store')
|
|
59
|
+
* @param {Set<string>} fileSet
|
|
60
|
+
* @returns {string|null}
|
|
61
|
+
*/
|
|
62
|
+
function resolveJsPath(dir, importStr, fileSet) {
|
|
63
|
+
return probeJs(path.resolve(dir, importStr), fileSet);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Strip comments and trailing commas so a tsconfig/jsconfig (JSONC) parses.
|
|
68
|
+
* Deliberately conservative — leaves string contents alone.
|
|
69
|
+
*/
|
|
70
|
+
function stripJsonc(src) {
|
|
71
|
+
let out = '';
|
|
72
|
+
let inStr = false, quote = '', inLine = false, inBlock = false;
|
|
73
|
+
for (let i = 0; i < src.length; i++) {
|
|
74
|
+
const c = src[i], n = src[i + 1];
|
|
75
|
+
if (inLine) { if (c === '\n') { inLine = false; out += c; } continue; }
|
|
76
|
+
if (inBlock) { if (c === '*' && n === '/') { inBlock = false; i++; } continue; }
|
|
77
|
+
if (inStr) { out += c; if (c === '\\') { out += (n || ''); i++; } else if (c === quote) inStr = false; continue; }
|
|
78
|
+
if (c === '"' || c === "'") { inStr = true; quote = c; out += c; continue; }
|
|
79
|
+
if (c === '/' && n === '/') { inLine = true; i++; continue; }
|
|
80
|
+
if (c === '/' && n === '*') { inBlock = true; i++; continue; }
|
|
81
|
+
out += c;
|
|
82
|
+
}
|
|
83
|
+
// remove trailing commas before } or ]
|
|
84
|
+
return out.replace(/,(\s*[}\]])/g, '$1');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Load the JS/TS path-alias map from tsconfig.json / jsconfig.json.
|
|
89
|
+
* Resolves `compilerOptions.paths` and `baseUrl` into absolute target bases so
|
|
90
|
+
* bare/aliased imports (e.g. `@/utils`, `components/Button`) can be resolved to
|
|
91
|
+
* on-disk files. Returns null when no config or no aliasing is configured.
|
|
92
|
+
*
|
|
93
|
+
* @param {string} cwd
|
|
94
|
+
* @returns {{ baseUrl: string|null, entries: Array<{prefix:string,wildcard:boolean,targets:string[]}> }|null}
|
|
95
|
+
*/
|
|
96
|
+
function loadAliasMap(cwd) {
|
|
97
|
+
if (!cwd) return null;
|
|
98
|
+
for (const name of ['tsconfig.json', 'jsconfig.json']) {
|
|
99
|
+
let json;
|
|
100
|
+
try { json = JSON.parse(stripJsonc(fs.readFileSync(path.join(cwd, name), 'utf8'))); }
|
|
101
|
+
catch (_) { continue; }
|
|
102
|
+
const co = (json && json.compilerOptions) || {};
|
|
103
|
+
const baseUrl = co.baseUrl ? path.resolve(cwd, co.baseUrl) : null;
|
|
104
|
+
const base = baseUrl || cwd;
|
|
105
|
+
const entries = [];
|
|
106
|
+
for (const [pattern, targets] of Object.entries(co.paths || {})) {
|
|
107
|
+
const wildcard = pattern.includes('*');
|
|
108
|
+
const prefix = pattern.replace(/\*.*$/, '');
|
|
109
|
+
const tgs = (Array.isArray(targets) ? targets : [])
|
|
110
|
+
.map((t) => path.resolve(base, String(t).replace(/\*.*$/, '')));
|
|
111
|
+
if (tgs.length) entries.push({ prefix, wildcard, targets: tgs });
|
|
112
|
+
}
|
|
113
|
+
if (baseUrl || entries.length) return { baseUrl, entries };
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Resolve a non-relative JS/TS import specifier through the alias map.
|
|
121
|
+
* @param {string} spec - e.g. '@/utils', '@app/Button', 'components/Nav'
|
|
122
|
+
* @param {object|null} aliasMap - from loadAliasMap
|
|
123
|
+
* @param {Set<string>} fileSet
|
|
124
|
+
* @returns {string|null}
|
|
125
|
+
*/
|
|
126
|
+
function resolveAlias(spec, aliasMap, fileSet) {
|
|
127
|
+
if (!aliasMap) return null;
|
|
128
|
+
for (const e of aliasMap.entries) {
|
|
129
|
+
if (e.wildcard) {
|
|
130
|
+
if (spec.startsWith(e.prefix)) {
|
|
131
|
+
const rest = spec.slice(e.prefix.length);
|
|
132
|
+
for (const t of e.targets) {
|
|
133
|
+
const r = probeJs(rest ? path.join(t, rest) : t, fileSet);
|
|
134
|
+
if (r) return r;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
} else if (spec === e.prefix) {
|
|
138
|
+
for (const t of e.targets) {
|
|
139
|
+
const r = probeJs(t, fileSet);
|
|
140
|
+
if (r) return r;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Bare import resolved from baseUrl (tsconfig baseUrl without an explicit alias).
|
|
145
|
+
if (aliasMap.baseUrl) {
|
|
146
|
+
const r = probeJs(path.join(aliasMap.baseUrl, spec), fileSet);
|
|
147
|
+
if (r) return r;
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
56
152
|
/**
|
|
57
153
|
* Resolve an R `source(...)` argument to an absolute path in fileSet.
|
|
58
154
|
* Tries the dir-relative path first, then a cwd-relative path so that
|
|
@@ -98,21 +194,29 @@ function extractFileDeps(filePath, content, fileSet, cwd, ctx) {
|
|
|
98
194
|
|
|
99
195
|
// ── JS / TS ───────────────────────────────────────────────────────────────
|
|
100
196
|
if (JS_EXTS.has(ext)) {
|
|
197
|
+
const aliasMap = ctx && ctx.aliasMap;
|
|
198
|
+
// Resolve any specifier: relative → dir-relative; otherwise via tsconfig/
|
|
199
|
+
// jsconfig path aliases + baseUrl. Bare npm packages (react, lodash) fall
|
|
200
|
+
// through to null because they are not in fileSet, so no false edges.
|
|
201
|
+
const resolveSpec = (spec) => spec.startsWith('.')
|
|
202
|
+
? resolveJsPath(dir, spec, fileSet)
|
|
203
|
+
: resolveAlias(spec, aliasMap, fileSet);
|
|
204
|
+
|
|
101
205
|
const stripped = content
|
|
102
206
|
.replace(/\/\/.*$/gm, '')
|
|
103
207
|
.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
104
208
|
|
|
105
|
-
// ES imports: import ... from './foo' or import './side-effect'
|
|
106
|
-
const reEs = /(?:^|[\r\n])\s*import\s+(?:[^'";\r\n]*?\s+from\s+)?['"](\.[^'"]+)['"]/g;
|
|
107
209
|
let m;
|
|
210
|
+
// ES imports: import ... from 'x' | import 'x' | export ... from 'x'
|
|
211
|
+
const reEs = /(?:^|[\r\n])\s*(?:import|export)\s+(?:[^'";\r\n]*?\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
108
212
|
while ((m = reEs.exec(stripped)) !== null) {
|
|
109
|
-
const r =
|
|
213
|
+
const r = resolveSpec(m[1]);
|
|
110
214
|
if (r) found.push(r);
|
|
111
215
|
}
|
|
112
|
-
// CommonJS
|
|
113
|
-
const
|
|
114
|
-
while ((m =
|
|
115
|
-
const r =
|
|
216
|
+
// CommonJS require('x') and dynamic import('x').
|
|
217
|
+
const reCall = /\b(?:require|import)\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
218
|
+
while ((m = reCall.exec(stripped)) !== null) {
|
|
219
|
+
const r = resolveSpec(m[1]);
|
|
116
220
|
if (r) found.push(r);
|
|
117
221
|
}
|
|
118
222
|
}
|
|
@@ -284,6 +388,10 @@ function build(files, cwd, ctx) {
|
|
|
284
388
|
const fileSet = new Set(files.map((f) => path.resolve(f)));
|
|
285
389
|
// Create a normalized version for cross-platform case-insensitive lookups
|
|
286
390
|
const fileSetNormalized = new Set([...fileSet].map(normalizePath));
|
|
391
|
+
// Resolve the JS/TS path-alias map once (tsconfig/jsconfig paths + baseUrl),
|
|
392
|
+
// unless a caller supplied one explicitly via ctx.
|
|
393
|
+
const aliasMap = (ctx && 'aliasMap' in ctx) ? ctx.aliasMap : loadAliasMap(cwd);
|
|
394
|
+
const effectiveCtx = Object.assign({}, ctx, { aliasMap });
|
|
287
395
|
const forward = new Map();
|
|
288
396
|
const reverse = new Map();
|
|
289
397
|
|
|
@@ -304,7 +412,7 @@ function build(files, cwd, ctx) {
|
|
|
304
412
|
}
|
|
305
413
|
|
|
306
414
|
const normFilePath = normalizePath(filePath);
|
|
307
|
-
const deps = extractFileDeps(filePath, content, fileSetNormalized, cwd,
|
|
415
|
+
const deps = extractFileDeps(filePath, content, fileSetNormalized, cwd, effectiveCtx);
|
|
308
416
|
if (deps.length > 0) {
|
|
309
417
|
forward.set(normFilePath, deps);
|
|
310
418
|
for (const dep of deps) {
|
|
@@ -383,4 +491,4 @@ function buildFromCwd(cwd, opts) {
|
|
|
383
491
|
return build(files, cwd, ctx);
|
|
384
492
|
}
|
|
385
493
|
|
|
386
|
-
module.exports = { build, buildFromCwd, extractFileDeps, normalizePath };
|
|
494
|
+
module.exports = { build, buildFromCwd, extractFileDeps, normalizePath, loadAliasMap, resolveAlias };
|
package/src/graph/impact.js
CHANGED
|
@@ -186,11 +186,11 @@ function formatImpact(result) {
|
|
|
186
186
|
lines.push('');
|
|
187
187
|
|
|
188
188
|
if (result.direct.length === 0 && result.transitive.length === 0) {
|
|
189
|
-
lines.push('_No
|
|
189
|
+
lines.push('_No importers found via relative + aliased imports (lower bound — dynamic/computed imports are not tracked)._');
|
|
190
190
|
return lines.join('\n');
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
lines.push(`**Total impacted files:** ${result.totalImpact}`);
|
|
193
|
+
lines.push(`**Total impacted files:** ${result.totalImpact} _(lower bound — resolves relative + tsconfig/jsconfig-aliased imports)_`);
|
|
194
194
|
lines.push('');
|
|
195
195
|
|
|
196
196
|
if (result.direct.length > 0) {
|