lsp-intelligence 0.2.1 → 0.3.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/dist/adapters/express/index.d.ts +9 -0
- package/dist/adapters/express/index.js +46 -0
- package/dist/adapters/express/index.js.map +1 -0
- package/dist/adapters/next/index.d.ts +10 -0
- package/dist/adapters/next/index.js +67 -0
- package/dist/adapters/next/index.js.map +1 -0
- package/dist/adapters/react/index.d.ts +11 -0
- package/dist/adapters/react/index.js +133 -0
- package/dist/adapters/react/index.js.map +1 -0
- package/dist/adapters/registry.d.ts +22 -0
- package/dist/adapters/registry.js +101 -0
- package/dist/adapters/registry.js.map +1 -0
- package/dist/adapters/types.d.ts +97 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/analysis/ts/applyVirtualEdit.d.ts +27 -0
- package/dist/analysis/ts/applyVirtualEdit.js +235 -0
- package/dist/analysis/ts/applyVirtualEdit.js.map +1 -0
- package/dist/analysis/ts/changeRecipes.d.ts +52 -0
- package/dist/analysis/ts/changeRecipes.js +14 -0
- package/dist/analysis/ts/changeRecipes.js.map +1 -0
- package/dist/analysis/ts/compatibility.d.ts +54 -0
- package/dist/analysis/ts/compatibility.js +113 -0
- package/dist/analysis/ts/compatibility.js.map +1 -0
- package/dist/analysis/ts/diffDeclarationShape.d.ts +26 -0
- package/dist/analysis/ts/diffDeclarationShape.js +114 -0
- package/dist/analysis/ts/diffDeclarationShape.js.map +1 -0
- package/dist/analysis/ts/exhaustiveness.d.ts +49 -0
- package/dist/analysis/ts/exhaustiveness.js +158 -0
- package/dist/analysis/ts/exhaustiveness.js.map +1 -0
- package/dist/analysis/ts/extractDeclarationShape.d.ts +30 -0
- package/dist/analysis/ts/extractDeclarationShape.js +127 -0
- package/dist/analysis/ts/extractDeclarationShape.js.map +1 -0
- package/dist/analysis/ts/extractExports.d.ts +19 -0
- package/dist/analysis/ts/extractExports.js +178 -0
- package/dist/analysis/ts/extractExports.js.map +1 -0
- package/dist/analysis/ts/extractRoutes.d.ts +12 -0
- package/dist/analysis/ts/extractRoutes.js +165 -0
- package/dist/analysis/ts/extractRoutes.js.map +1 -0
- package/dist/analysis/ts/parseSourceFile.d.ts +5 -1
- package/dist/analysis/ts/parseSourceFile.js +6 -2
- package/dist/analysis/ts/parseSourceFile.js.map +1 -1
- package/dist/analysis/ts/program/CheckerQueries.d.ts +56 -0
- package/dist/analysis/ts/program/CheckerQueries.js +187 -0
- package/dist/analysis/ts/program/CheckerQueries.js.map +1 -0
- package/dist/analysis/ts/program/ProgramManager.d.ts +27 -0
- package/dist/analysis/ts/program/ProgramManager.js +147 -0
- package/dist/analysis/ts/program/ProgramManager.js.map +1 -0
- package/dist/analysis/ts/program/TypeFacts.d.ts +58 -0
- package/dist/analysis/ts/program/TypeFacts.js +68 -0
- package/dist/analysis/ts/program/TypeFacts.js.map +1 -0
- package/dist/analysis/ts/typeState.d.ts +46 -0
- package/dist/analysis/ts/typeState.js +108 -0
- package/dist/analysis/ts/typeState.js.map +1 -0
- package/dist/ast/diffDeclarationShapes.js +25 -11
- package/dist/ast/diffDeclarationShapes.js.map +1 -1
- package/dist/ast/extractExportDeclarations.js +8 -3
- package/dist/ast/extractExportDeclarations.js.map +1 -1
- package/dist/cache/CacheSchema.d.ts +30 -0
- package/dist/cache/CacheSchema.js +9 -0
- package/dist/cache/CacheSchema.js.map +1 -0
- package/dist/cache/CacheStore.d.ts +22 -0
- package/dist/cache/CacheStore.js +111 -0
- package/dist/cache/CacheStore.js.map +1 -0
- package/dist/cache/SemanticCache.d.ts +38 -0
- package/dist/cache/SemanticCache.js +87 -0
- package/dist/cache/SemanticCache.js.map +1 -0
- package/dist/cache/SnapshotFingerprint.d.ts +17 -0
- package/dist/cache/SnapshotFingerprint.js +14 -0
- package/dist/cache/SnapshotFingerprint.js.map +1 -0
- package/dist/engine/DocumentManager.d.ts +16 -0
- package/dist/engine/DocumentManager.js +32 -0
- package/dist/engine/DocumentManager.js.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/search/adapters/http.d.ts +9 -3
- package/dist/search/adapters/http.js +7 -52
- package/dist/search/adapters/http.js.map +1 -1
- package/dist/search/adapters/react.d.ts +8 -8
- package/dist/search/adapters/react.js +3 -88
- package/dist/search/adapters/react.js.map +1 -1
- package/dist/search/adapters/registry.d.ts +7 -4
- package/dist/search/adapters/registry.js +7 -17
- package/dist/search/adapters/registry.js.map +1 -1
- package/dist/search/expand/graphExpansion.js +47 -14
- package/dist/search/expand/graphExpansion.js.map +1 -1
- package/dist/search/index/declarationIndex.d.ts +3 -1
- package/dist/search/index/declarationIndex.js +4 -2
- package/dist/search/index/declarationIndex.js.map +1 -1
- package/dist/search/index/routeIndex.d.ts +6 -0
- package/dist/search/index/routeIndex.js +19 -0
- package/dist/search/index/routeIndex.js.map +1 -0
- package/dist/search/index/usageIndex.d.ts +3 -1
- package/dist/search/index/usageIndex.js +4 -2
- package/dist/search/index/usageIndex.js.map +1 -1
- package/dist/search/index/workspaceIndex.d.ts +7 -1
- package/dist/search/index/workspaceIndex.js +54 -14
- package/dist/search/index/workspaceIndex.js.map +1 -1
- package/dist/search/query/compileEffectiveSearchSpec.js +9 -0
- package/dist/search/query/compileEffectiveSearchSpec.js.map +1 -1
- package/dist/search/query/parseQuery.js +5 -1
- package/dist/search/query/parseQuery.js.map +1 -1
- package/dist/search/query/planQuery.js +14 -4
- package/dist/search/query/planQuery.js.map +1 -1
- package/dist/search/ranking/mergeCandidates.d.ts +1 -0
- package/dist/search/ranking/mergeCandidates.js +16 -0
- package/dist/search/ranking/mergeCandidates.js.map +1 -1
- package/dist/search/retrievers/routeRetriever.d.ts +7 -0
- package/dist/search/retrievers/routeRetriever.js +64 -0
- package/dist/search/retrievers/routeRetriever.js.map +1 -0
- package/dist/search/types.d.ts +16 -4
- package/dist/session/OverlayStore.d.ts +15 -0
- package/dist/session/OverlayStore.js +46 -0
- package/dist/session/OverlayStore.js.map +1 -0
- package/dist/session/SnapshotResolver.d.ts +31 -0
- package/dist/session/SnapshotResolver.js +50 -0
- package/dist/session/SnapshotResolver.js.map +1 -0
- package/dist/session/WorkspaceSnapshot.d.ts +21 -0
- package/dist/session/WorkspaceSnapshot.js +2 -0
- package/dist/session/WorkspaceSnapshot.js.map +1 -0
- package/dist/tools/composites/apiGuard.d.ts +4 -0
- package/dist/tools/composites/apiGuard.js +158 -24
- package/dist/tools/composites/apiGuard.js.map +1 -1
- package/dist/tools/composites/findCode.js +12 -2
- package/dist/tools/composites/findCode.js.map +1 -1
- package/dist/tools/composites/rootCauseTrace.js +89 -13
- package/dist/tools/composites/rootCauseTrace.js.map +1 -1
- package/dist/workflows/simulateChange.d.ts +44 -0
- package/dist/workflows/simulateChange.js +194 -0
- package/dist/workflows/simulateChange.js.map +1 -0
- package/dist/workflows/verifyChangeSet.d.ts +58 -0
- package/dist/workflows/verifyChangeSet.js +300 -0
- package/dist/workflows/verifyChangeSet.js.map +1 -0
- package/package.json +6 -4
|
@@ -80,6 +80,22 @@ export function mergeCandidates(inputs) {
|
|
|
80
80
|
merged.set(key, { ...c });
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
+
// Add route candidates — boost on overlap with behavior/identifier
|
|
84
|
+
for (const c of inputs.route) {
|
|
85
|
+
const key = candidateKey(c);
|
|
86
|
+
const existing = merged.get(key);
|
|
87
|
+
if (existing) {
|
|
88
|
+
existing.score += c.score + 3;
|
|
89
|
+
existing.evidence.push(...c.evidence, 'route-overlap');
|
|
90
|
+
for (const s of c.sources) {
|
|
91
|
+
if (!existing.sources.includes(s))
|
|
92
|
+
existing.sources.push(s);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
merged.set(key, { ...c });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
83
99
|
return [...merged.values()];
|
|
84
100
|
}
|
|
85
101
|
//# sourceMappingURL=mergeCandidates.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mergeCandidates.js","sourceRoot":"","sources":["../../../src/search/ranking/mergeCandidates.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,
|
|
1
|
+
{"version":3,"file":"mergeCandidates.js","sourceRoot":"","sources":["../../../src/search/ranking/mergeCandidates.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SearchScope, WorkspaceIndex, CodeCandidate } from '../types.js';
|
|
2
|
+
import type { EffectiveSearchSpec } from '../query/compileEffectiveSearchSpec.js';
|
|
3
|
+
/**
|
|
4
|
+
* Retrieve route definition candidates from the route index.
|
|
5
|
+
* Does NOT use outbound fetch/axios as route-definition evidence.
|
|
6
|
+
*/
|
|
7
|
+
export declare function retrieveRouteCandidates(spec: EffectiveSearchSpec, scope: SearchScope, index: WorkspaceIndex): CodeCandidate[];
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { buildSnippetFromFile } from '../../analysis/ts/snippets.js';
|
|
2
|
+
/**
|
|
3
|
+
* Retrieve route definition candidates from the route index.
|
|
4
|
+
* Does NOT use outbound fetch/axios as route-definition evidence.
|
|
5
|
+
*/
|
|
6
|
+
export function retrieveRouteCandidates(spec, scope, index) {
|
|
7
|
+
if (index.routes.length === 0)
|
|
8
|
+
return [];
|
|
9
|
+
const queryTokens = [...spec.routeTerms, ...spec.behaviorTerms];
|
|
10
|
+
if (queryTokens.length === 0)
|
|
11
|
+
return [];
|
|
12
|
+
const candidates = [];
|
|
13
|
+
for (const route of index.routes) {
|
|
14
|
+
let score = 0;
|
|
15
|
+
const evidence = [];
|
|
16
|
+
// Path match — strongest signal
|
|
17
|
+
if (route.path) {
|
|
18
|
+
const pathTokens = route.path.toLowerCase().split(/[/\-_]+/).filter((t) => t.length > 1);
|
|
19
|
+
for (const qt of queryTokens) {
|
|
20
|
+
if (pathTokens.some((pt) => pt === qt || pt.includes(qt))) {
|
|
21
|
+
score += 6;
|
|
22
|
+
evidence.push(`route-path-match: ${qt} in ${route.path}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// Method match
|
|
27
|
+
if (route.method) {
|
|
28
|
+
const methodLower = route.method.toLowerCase();
|
|
29
|
+
if (queryTokens.some((qt) => qt === methodLower || qt === route.method)) {
|
|
30
|
+
score += 4;
|
|
31
|
+
evidence.push(`method-match: ${route.method}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Framework/token overlap
|
|
35
|
+
for (const qt of queryTokens) {
|
|
36
|
+
if (route.tokens.some((rt) => rt === qt)) {
|
|
37
|
+
score += 2;
|
|
38
|
+
evidence.push(`token-match: ${qt}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Route-term boost — query explicitly asks about routes/endpoints
|
|
42
|
+
if (spec.routeTerms.length > 0) {
|
|
43
|
+
score += 3;
|
|
44
|
+
evidence.push('route-intent-boost');
|
|
45
|
+
}
|
|
46
|
+
if (score === 0)
|
|
47
|
+
continue;
|
|
48
|
+
const { snippet, context } = buildSnippetFromFile(route.filePath, route.line, 1);
|
|
49
|
+
candidates.push({
|
|
50
|
+
candidateType: 'route',
|
|
51
|
+
filePath: route.filePath,
|
|
52
|
+
line: route.line,
|
|
53
|
+
symbol: route.enclosingSymbol,
|
|
54
|
+
kind: 'function',
|
|
55
|
+
snippet,
|
|
56
|
+
context,
|
|
57
|
+
score,
|
|
58
|
+
evidence: [`route-${route.framework}: "${route.text}"`, ...evidence],
|
|
59
|
+
sources: ['route'],
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return candidates.sort((a, b) => b.score - a.score);
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=routeRetriever.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routeRetriever.js","sourceRoot":"","sources":["../../../src/search/retrievers/routeRetriever.ts"],"names":[],"mappings":"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"}
|
package/dist/search/types.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface RegExpSpec {
|
|
|
16
16
|
export interface SearchRecipe {
|
|
17
17
|
id: string;
|
|
18
18
|
adapter: string;
|
|
19
|
-
retrievers: Array<'behavior' | 'identifier' | 'structural' | 'regex' | 'doc' | 'config'>;
|
|
19
|
+
retrievers: Array<'behavior' | 'identifier' | 'structural' | 'regex' | 'doc' | 'config' | 'route'>;
|
|
20
20
|
exactIdentifiers?: string[];
|
|
21
21
|
structuralPredicates?: StructuralPredicate[];
|
|
22
22
|
regexes?: RegExpSpec[];
|
|
@@ -50,7 +50,7 @@ export interface QueryIR {
|
|
|
50
50
|
}
|
|
51
51
|
export interface SearchPlan {
|
|
52
52
|
mode: 'behavior' | 'identifier' | 'structural' | 'mixed';
|
|
53
|
-
retrievers: Array<'behavior' | 'identifier' | 'structural' | 'regex' | 'doc' | 'config'>;
|
|
53
|
+
retrievers: Array<'behavior' | 'identifier' | 'structural' | 'regex' | 'doc' | 'config' | 'route'>;
|
|
54
54
|
reasons: string[];
|
|
55
55
|
expandGraph: boolean;
|
|
56
56
|
}
|
|
@@ -60,7 +60,7 @@ export interface SearchScope {
|
|
|
60
60
|
includeTests: boolean;
|
|
61
61
|
}
|
|
62
62
|
export interface CodeCandidate {
|
|
63
|
-
candidateType: 'declaration' | 'usage' | 'pattern' | 'doc' | 'config';
|
|
63
|
+
candidateType: 'declaration' | 'usage' | 'pattern' | 'doc' | 'config' | 'route';
|
|
64
64
|
filePath: string;
|
|
65
65
|
line: number;
|
|
66
66
|
column?: number;
|
|
@@ -75,7 +75,7 @@ export interface CodeCandidate {
|
|
|
75
75
|
score: number;
|
|
76
76
|
confidence?: 'high' | 'medium' | 'low';
|
|
77
77
|
evidence: string[];
|
|
78
|
-
sources: Array<'behavior' | 'identifier' | 'structural' | 'doc' | 'config' | 'lsp' | 'graph'>;
|
|
78
|
+
sources: Array<'behavior' | 'identifier' | 'structural' | 'doc' | 'config' | 'route' | 'lsp' | 'graph'>;
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
81
81
|
* Deduplication key for candidates.
|
|
@@ -122,6 +122,16 @@ export interface ConfigIndexEntry {
|
|
|
122
122
|
text: string;
|
|
123
123
|
tokens: string[];
|
|
124
124
|
}
|
|
125
|
+
export interface RouteIndexEntry {
|
|
126
|
+
filePath: string;
|
|
127
|
+
line: number;
|
|
128
|
+
method?: string;
|
|
129
|
+
path?: string;
|
|
130
|
+
framework: 'express' | 'fastify' | 'next-app-router' | 'next-pages-api' | 'route-map' | 'unknown';
|
|
131
|
+
enclosingSymbol?: string;
|
|
132
|
+
tokens: string[];
|
|
133
|
+
text: string;
|
|
134
|
+
}
|
|
125
135
|
export interface IndexedFile {
|
|
126
136
|
filePath: string;
|
|
127
137
|
mtimeMs: number;
|
|
@@ -137,6 +147,7 @@ export interface WorkspaceIndex {
|
|
|
137
147
|
usages: UsageIndexEntry[];
|
|
138
148
|
docs: DocIndexEntry[];
|
|
139
149
|
configs: ConfigIndexEntry[];
|
|
150
|
+
routes: RouteIndexEntry[];
|
|
140
151
|
scopeCapped: boolean;
|
|
141
152
|
capReason?: 'max-files' | 'max-depth';
|
|
142
153
|
}
|
|
@@ -166,6 +177,7 @@ export interface FindCodeResult {
|
|
|
166
177
|
regexHits: number;
|
|
167
178
|
docHits: number;
|
|
168
179
|
configHits: number;
|
|
180
|
+
routeHits: number;
|
|
169
181
|
graphExpanded: number;
|
|
170
182
|
lspEnriched: number;
|
|
171
183
|
elapsedMs: number;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DocumentManager } from '../engine/DocumentManager.js';
|
|
2
|
+
import type { WorkspaceSnapshot } from './WorkspaceSnapshot.js';
|
|
3
|
+
/**
|
|
4
|
+
* Builds a WorkspaceSnapshot from the current DocumentManager state.
|
|
5
|
+
* A snapshot is an immutable view of all open documents at a point in time.
|
|
6
|
+
*
|
|
7
|
+
* Documents whose in-memory content differs from disk are marked dirty=true.
|
|
8
|
+
* The snapshot is used by the SnapshotResolver to provide overlay text to
|
|
9
|
+
* the indexer and analysis tools, so they operate on live (unsaved) state.
|
|
10
|
+
*/
|
|
11
|
+
export declare class OverlayStore {
|
|
12
|
+
private readonly docManager;
|
|
13
|
+
constructor(docManager: DocumentManager);
|
|
14
|
+
createSnapshot(workspaceRoot: string): WorkspaceSnapshot;
|
|
15
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import { uriToPath } from '../engine/positions.js';
|
|
3
|
+
/**
|
|
4
|
+
* Builds a WorkspaceSnapshot from the current DocumentManager state.
|
|
5
|
+
* A snapshot is an immutable view of all open documents at a point in time.
|
|
6
|
+
*
|
|
7
|
+
* Documents whose in-memory content differs from disk are marked dirty=true.
|
|
8
|
+
* The snapshot is used by the SnapshotResolver to provide overlay text to
|
|
9
|
+
* the indexer and analysis tools, so they operate on live (unsaved) state.
|
|
10
|
+
*/
|
|
11
|
+
export class OverlayStore {
|
|
12
|
+
docManager;
|
|
13
|
+
constructor(docManager) {
|
|
14
|
+
this.docManager = docManager;
|
|
15
|
+
}
|
|
16
|
+
createSnapshot(workspaceRoot) {
|
|
17
|
+
const overlays = new Map();
|
|
18
|
+
for (const [uri, doc] of this.docManager.getOpenDocuments()) {
|
|
19
|
+
const filePath = uriToPath(uri);
|
|
20
|
+
// Mark dirty if in-memory content differs from disk
|
|
21
|
+
let dirty = false;
|
|
22
|
+
try {
|
|
23
|
+
const diskContent = fs.readFileSync(filePath, 'utf-8');
|
|
24
|
+
dirty = diskContent !== doc.content;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// File may not exist on disk (new/unsaved file) — always dirty
|
|
28
|
+
dirty = true;
|
|
29
|
+
}
|
|
30
|
+
overlays.set(filePath, {
|
|
31
|
+
filePath,
|
|
32
|
+
uri,
|
|
33
|
+
version: doc.version,
|
|
34
|
+
text: doc.content,
|
|
35
|
+
languageId: doc.languageId,
|
|
36
|
+
dirty,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
root: workspaceRoot,
|
|
41
|
+
createdAt: Date.now(),
|
|
42
|
+
overlays,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=OverlayStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OverlayStore.js","sourceRoot":"","sources":["../../src/session/OverlayStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAInD;;;;;;;GAOG;AACH,MAAM,OAAO,YAAY;IACM;IAA7B,YAA6B,UAA2B;QAA3B,eAAU,GAAV,UAAU,CAAiB;IAAG,CAAC;IAE5D,cAAc,CAAC,aAAqB;QAClC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA2B,CAAC;QAEpD,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,gBAAgB,EAAE,EAAE,CAAC;YAC5D,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;YAEhC,oDAAoD;YACpD,IAAI,KAAK,GAAG,KAAK,CAAC;YAClB,IAAI,CAAC;gBACH,MAAM,WAAW,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBACvD,KAAK,GAAG,WAAW,KAAK,GAAG,CAAC,OAAO,CAAC;YACtC,CAAC;YAAC,MAAM,CAAC;gBACP,+DAA+D;gBAC/D,KAAK,GAAG,IAAI,CAAC;YACf,CAAC;YAED,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE;gBACrB,QAAQ;gBACR,GAAG;gBACH,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,IAAI,EAAE,GAAG,CAAC,OAAO;gBACjB,UAAU,EAAE,GAAG,CAAC,UAAU;gBAC1B,KAAK;aACN,CAAC,CAAC;QACL,CAAC;QAED,OAAO;YACL,IAAI,EAAE,aAAa;YACnB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,QAAQ;SACT,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { DocumentManager } from '../engine/DocumentManager.js';
|
|
2
|
+
import type { WorkspaceSnapshot } from './WorkspaceSnapshot.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves file text, preferring in-memory overlay over disk.
|
|
5
|
+
*
|
|
6
|
+
* Consumers (indexer, analysis tools) call getText(filePath) to get
|
|
7
|
+
* the most up-to-date content. If the file has an unsaved edit in the
|
|
8
|
+
* editor, the overlay text is returned instead of the disk version.
|
|
9
|
+
*/
|
|
10
|
+
export interface SnapshotResolver {
|
|
11
|
+
/** Return overlay text if the file has an unsaved edit, otherwise undefined. */
|
|
12
|
+
getText(filePath: string): string | undefined;
|
|
13
|
+
/** True if the file has an in-memory overlay (whether dirty or not). */
|
|
14
|
+
hasOverlay(filePath: string): boolean;
|
|
15
|
+
/** All files that have dirty (unsaved) overlays. */
|
|
16
|
+
getDirtyFiles(): string[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Build a SnapshotResolver from a DocumentManager.
|
|
20
|
+
* Called in tool handlers to get live overlay state before indexing.
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildSnapshotResolver(docManager: DocumentManager, workspaceRoot: string): SnapshotResolver;
|
|
23
|
+
/**
|
|
24
|
+
* Build a SnapshotResolver from a pre-built WorkspaceSnapshot.
|
|
25
|
+
*/
|
|
26
|
+
export declare function fromSnapshot(snapshot: WorkspaceSnapshot): SnapshotResolver;
|
|
27
|
+
/**
|
|
28
|
+
* Build a SnapshotResolver directly from a static map of overlays.
|
|
29
|
+
* Used in tests and benchmarks without a live DocumentManager.
|
|
30
|
+
*/
|
|
31
|
+
export declare function buildStaticSnapshotResolver(overlays: Record<string, string>): SnapshotResolver;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { OverlayStore } from './OverlayStore.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build a SnapshotResolver from a DocumentManager.
|
|
4
|
+
* Called in tool handlers to get live overlay state before indexing.
|
|
5
|
+
*/
|
|
6
|
+
export function buildSnapshotResolver(docManager, workspaceRoot) {
|
|
7
|
+
const store = new OverlayStore(docManager);
|
|
8
|
+
const snapshot = store.createSnapshot(workspaceRoot);
|
|
9
|
+
return fromSnapshot(snapshot);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Build a SnapshotResolver from a pre-built WorkspaceSnapshot.
|
|
13
|
+
*/
|
|
14
|
+
export function fromSnapshot(snapshot) {
|
|
15
|
+
return {
|
|
16
|
+
getText(filePath) {
|
|
17
|
+
return snapshot.overlays.get(filePath)?.text;
|
|
18
|
+
},
|
|
19
|
+
hasOverlay(filePath) {
|
|
20
|
+
return snapshot.overlays.has(filePath);
|
|
21
|
+
},
|
|
22
|
+
getDirtyFiles() {
|
|
23
|
+
const dirty = [];
|
|
24
|
+
for (const [filePath, doc] of snapshot.overlays) {
|
|
25
|
+
if (doc.dirty)
|
|
26
|
+
dirty.push(filePath);
|
|
27
|
+
}
|
|
28
|
+
return dirty;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Build a SnapshotResolver directly from a static map of overlays.
|
|
34
|
+
* Used in tests and benchmarks without a live DocumentManager.
|
|
35
|
+
*/
|
|
36
|
+
export function buildStaticSnapshotResolver(overlays) {
|
|
37
|
+
const map = new Map(Object.entries(overlays));
|
|
38
|
+
return {
|
|
39
|
+
getText(filePath) {
|
|
40
|
+
return map.get(filePath);
|
|
41
|
+
},
|
|
42
|
+
hasOverlay(filePath) {
|
|
43
|
+
return map.has(filePath);
|
|
44
|
+
},
|
|
45
|
+
getDirtyFiles() {
|
|
46
|
+
return [...map.keys()];
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=SnapshotResolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SnapshotResolver.js","sourceRoot":"","sources":["../../src/session/SnapshotResolver.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAkBjD;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,UAA2B,EAAE,aAAqB;IACtF,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAG,KAAK,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;IACrD,OAAO,YAAY,CAAC,QAAQ,CAAC,CAAC;AAChC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,QAA2B;IACtD,OAAO;QACL,OAAO,CAAC,QAAgB;YACtB,OAAO,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC;QAC/C,CAAC;QACD,UAAU,CAAC,QAAgB;YACzB,OAAO,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACzC,CAAC;QACD,aAAa;YACX,MAAM,KAAK,GAAa,EAAE,CAAC;YAC3B,KAAK,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;gBAChD,IAAI,GAAG,CAAC,KAAK;oBAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACtC,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,2BAA2B,CAAC,QAAgC;IAC1E,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC9C,OAAO;QACL,OAAO,CAAC,QAAgB;YACtB,OAAO,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC3B,CAAC;QACD,UAAU,CAAC,QAAgB;YACzB,OAAO,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC3B,CAAC;QACD,aAAa;YACX,OAAO,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QACzB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single open document captured in a workspace snapshot.
|
|
3
|
+
*/
|
|
4
|
+
export interface OverlayDocument {
|
|
5
|
+
filePath: string;
|
|
6
|
+
uri: string;
|
|
7
|
+
version: number;
|
|
8
|
+
text: string;
|
|
9
|
+
languageId: string;
|
|
10
|
+
/** True if the in-memory text differs from the on-disk version */
|
|
11
|
+
dirty: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Immutable view of all open documents at a point in time.
|
|
15
|
+
* Used by SnapshotResolver to provide overlay text to tools.
|
|
16
|
+
*/
|
|
17
|
+
export interface WorkspaceSnapshot {
|
|
18
|
+
root: string;
|
|
19
|
+
createdAt: number;
|
|
20
|
+
overlays: Map<string, OverlayDocument>;
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkspaceSnapshot.js","sourceRoot":"","sources":["../../src/session/WorkspaceSnapshot.ts"],"names":[],"mappings":""}
|
|
@@ -1 +1,5 @@
|
|
|
1
|
+
import type { DeclRisk } from '../../analysis/ts/diffDeclarationShape.js';
|
|
2
|
+
import type { DeclarationDiff } from '../../analysis/ts/diffDeclarationShape.js';
|
|
3
|
+
export type ApiRiskLevel = DeclRisk;
|
|
4
|
+
export type ApiChangeKind = DeclarationDiff['kind'];
|
|
1
5
|
export declare const apiGuard: import("../registry.js").ToolDef;
|
|
@@ -2,14 +2,82 @@ import { z } from 'zod';
|
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import { defineTool } from '../registry.js';
|
|
4
4
|
import { relativePath, uriToPath, getPackageName } from '../../engine/positions.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { parseSourceContent } from '../../analysis/ts/parseSourceFile.js';
|
|
6
|
+
import { extractExports } from '../../analysis/ts/extractExports.js';
|
|
7
|
+
import { extractDeclarationShape } from '../../analysis/ts/extractDeclarationShape.js';
|
|
8
|
+
import { diffExportSets } from '../../analysis/ts/diffDeclarationShape.js';
|
|
8
9
|
import { getMergeBase } from '../../git/getMergeBase.js';
|
|
9
10
|
import { getChangedFiles } from '../../git/getChangedFiles.js';
|
|
10
11
|
import { getBaseFileContent } from '../../git/getBaseFileContent.js';
|
|
11
12
|
import { LspError, LspErrorCode, DEFAULT_TIMEOUTS } from '../../engine/types.js';
|
|
12
13
|
import { collectScopeFiles } from '../../resolve/searchScope.js';
|
|
14
|
+
import { programManager } from '../../analysis/ts/program/ProgramManager.js';
|
|
15
|
+
import { CheckerQueries } from '../../analysis/ts/program/CheckerQueries.js';
|
|
16
|
+
import { findNonExhaustiveSwitches, predictAddedMemberImpact } from '../../analysis/ts/exhaustiveness.js';
|
|
17
|
+
import { analyzeCallSiteCompatibility } from '../../analysis/ts/compatibility.js';
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Phase 3C: Migration step generation
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
function generateMigrationSteps(exportName, kind, diffs) {
|
|
22
|
+
const steps = [];
|
|
23
|
+
const d = diffs[0];
|
|
24
|
+
switch (kind) {
|
|
25
|
+
case 'removed':
|
|
26
|
+
steps.push(`Remove all imports of \`${exportName}\` — it no longer exists`);
|
|
27
|
+
steps.push(`Find replacement or implement the functionality at each call site`);
|
|
28
|
+
break;
|
|
29
|
+
case 'renamed':
|
|
30
|
+
steps.push(`Update all imports and usages to the new name`);
|
|
31
|
+
break;
|
|
32
|
+
case 'param_required': {
|
|
33
|
+
const paramMatch = d?.reason.match(/"([^"]+)"/);
|
|
34
|
+
const paramName = paramMatch?.[1] ?? 'newParam';
|
|
35
|
+
steps.push(`Add the required \`${paramName}\` argument to all callers of \`${exportName}\``);
|
|
36
|
+
steps.push(`Search for \`${exportName}(\` to find call sites needing updates`);
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
case 'param_added':
|
|
40
|
+
steps.push(`No immediate action required — new parameter is optional`);
|
|
41
|
+
steps.push(`Callers can opt in to the new \`${diffs[0]?.details?.[0] ?? 'param'}\` parameter as needed`);
|
|
42
|
+
break;
|
|
43
|
+
case 'param_removed': {
|
|
44
|
+
const paramMatch = d?.reason.match(/"([^"]+)"/);
|
|
45
|
+
steps.push(`Remove the \`${paramMatch?.[1] ?? 'param'}\` argument from all callers of \`${exportName}\``);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case 'return_type_changed':
|
|
49
|
+
steps.push(`Check all code that assigns or destructures the return value of \`${exportName}\``);
|
|
50
|
+
steps.push(`Update variable types or add explicit casts where needed`);
|
|
51
|
+
break;
|
|
52
|
+
case 'interface_shape_changed':
|
|
53
|
+
if (d?.reason.includes('required') || d?.reason.includes('Required')) {
|
|
54
|
+
const propMatch = d?.reason.match(/"([^"]+)"/);
|
|
55
|
+
steps.push(`Add the required \`${propMatch?.[1] ?? 'property'}\` field to all objects implementing \`${exportName}\``);
|
|
56
|
+
}
|
|
57
|
+
else if (d?.reason.includes('removed') || d?.reason.includes('Removed')) {
|
|
58
|
+
const propMatch = d?.reason.match(/"([^"]+)"/);
|
|
59
|
+
steps.push(`Remove references to the deleted \`${propMatch?.[1] ?? 'property'}\` property from all usages`);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
steps.push(`Update all objects implementing the \`${exportName}\` interface`);
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
case 'enum_member_removed': {
|
|
66
|
+
const memberMatch = d?.reason.match(/"([^"]+)"/);
|
|
67
|
+
steps.push(`Replace all usages of the removed \`${exportName}.${memberMatch?.[1] ?? 'member'}\` value`);
|
|
68
|
+
steps.push(`Check switch statements over \`${exportName}\` — some may now have dead code`);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case 'enum_member_added': {
|
|
72
|
+
const memberMatch = d?.reason.match(/"([^"]+)"/);
|
|
73
|
+
steps.push(`Add handling for the new \`${exportName}.${memberMatch?.[1] ?? 'member'}\` value in exhaustive switch statements`);
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
default:
|
|
77
|
+
steps.push(`Review all usages of \`${exportName}\` and update as needed`);
|
|
78
|
+
}
|
|
79
|
+
return steps;
|
|
80
|
+
}
|
|
13
81
|
// File collection uses shared searchScope (supports JS/TS/JSX/MJS/CJS)
|
|
14
82
|
export const apiGuard = defineTool({
|
|
15
83
|
name: 'api_guard',
|
|
@@ -50,45 +118,110 @@ export const apiGuard = defineTool({
|
|
|
50
118
|
for (const filePath of scopeFiles) {
|
|
51
119
|
try {
|
|
52
120
|
const currentContent = fs.readFileSync(filePath, 'utf-8');
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
totalExportsChecked +=
|
|
58
|
-
// Get base
|
|
59
|
-
let
|
|
121
|
+
const currentSf = parseSourceContent(currentContent, filePath);
|
|
122
|
+
astUsed = true;
|
|
123
|
+
const currentExports = extractExports(currentSf);
|
|
124
|
+
const currentShapes = currentExports.map((e) => extractDeclarationShape(currentSf, e));
|
|
125
|
+
totalExportsChecked += currentShapes.length;
|
|
126
|
+
// Get base shapes
|
|
127
|
+
let baseShapes = [];
|
|
60
128
|
if (base) {
|
|
61
129
|
const baseContent = getBaseFileContent(filePath, base, engine.workspaceRoot);
|
|
62
130
|
if (baseContent) {
|
|
63
|
-
const
|
|
64
|
-
baseExports =
|
|
65
|
-
|
|
66
|
-
: extractExportDeclarations(null, baseContent);
|
|
131
|
+
const baseSf = parseSourceContent(baseContent, filePath);
|
|
132
|
+
const baseExports = extractExports(baseSf);
|
|
133
|
+
baseShapes = baseExports.map((e) => extractDeclarationShape(baseSf, e));
|
|
67
134
|
}
|
|
68
135
|
}
|
|
69
|
-
const diffs =
|
|
136
|
+
const diffs = diffExportSets(baseShapes, currentShapes);
|
|
70
137
|
for (const d of diffs) {
|
|
71
138
|
if (params.symbol && d.name !== params.symbol)
|
|
72
139
|
continue;
|
|
140
|
+
// Aggregate reason and structural details across all per-declaration diffs
|
|
141
|
+
const reason = d.diffs.map((dd) => dd.reason).join('; ');
|
|
142
|
+
const structuralDiff = d.diffs.flatMap((dd) => dd.details);
|
|
73
143
|
entries.push({
|
|
74
144
|
exportName: d.name,
|
|
75
145
|
filePath,
|
|
76
|
-
line: d.
|
|
77
|
-
declarationKind: d.
|
|
78
|
-
kind: d.kind,
|
|
146
|
+
line: d.currentShape?.line ?? d.baseShape?.line,
|
|
147
|
+
declarationKind: d.baseShape?.kind ?? d.currentShape?.kind,
|
|
148
|
+
kind: d.diffs[0]?.kind ?? (d.status === 'added' ? 'added' : d.status === 'removed' ? 'removed' : 'signature_changed'),
|
|
79
149
|
risk: d.risk,
|
|
80
|
-
reason
|
|
81
|
-
currentSignature: d.
|
|
82
|
-
baseSignature: d.
|
|
83
|
-
structuralDiff
|
|
150
|
+
reason,
|
|
151
|
+
currentSignature: d.currentShape?.signatureText,
|
|
152
|
+
baseSignature: d.baseShape?.signatureText,
|
|
153
|
+
structuralDiff,
|
|
84
154
|
consumers: { samePackage: 0, crossPackage: 0, sampleFiles: [] },
|
|
85
155
|
evidence: [],
|
|
156
|
+
migrationSteps: generateMigrationSteps(d.name, d.diffs[0]?.kind ?? d.status, d.diffs),
|
|
157
|
+
filesToInspect: [],
|
|
86
158
|
});
|
|
87
159
|
}
|
|
88
160
|
filesParsed++;
|
|
89
161
|
}
|
|
90
162
|
catch { }
|
|
91
163
|
}
|
|
164
|
+
// Step 3b: Semantic enrichment via Phase 2C TypeScript checker
|
|
165
|
+
// Adds precise type information for changed params/returns — goes beyond AST text.
|
|
166
|
+
if (entries.length > 0) {
|
|
167
|
+
try {
|
|
168
|
+
const tsProgram = programManager.getOrBuild(engine.workspaceRoot);
|
|
169
|
+
const queries = new CheckerQueries(tsProgram);
|
|
170
|
+
for (const entry of entries) {
|
|
171
|
+
// Phase 2C: enrich with checker-verified type information
|
|
172
|
+
if (entry.kind === 'param_required' || entry.kind === 'param_added' || entry.kind === 'return_type_changed') {
|
|
173
|
+
const params = queries.getFunctionParams(entry.filePath, entry.exportName);
|
|
174
|
+
if (params) {
|
|
175
|
+
const paramDesc = params.map((p) => `${p.name}${p.optional ? '?' : ''}: ${p.typeText}`).join(', ');
|
|
176
|
+
entry.evidence.push(`checker: signature (${paramDesc})`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Phase 2D: exhaustiveness intelligence for enum changes
|
|
180
|
+
if (entry.kind === 'enum_member_removed') {
|
|
181
|
+
const members = queries.getEnumMembers(entry.filePath, entry.exportName);
|
|
182
|
+
if (members)
|
|
183
|
+
entry.evidence.push(`checker: remaining members [${members.join(', ')}]`);
|
|
184
|
+
const nonExhaustive = findNonExhaustiveSwitches(tsProgram, entry.filePath, entry.exportName);
|
|
185
|
+
if (nonExhaustive.length > 0) {
|
|
186
|
+
const files = [...new Set(nonExhaustive.map((s) => relativePath(s.filePath, engine.workspaceRoot)))].slice(0, 3).join(', ');
|
|
187
|
+
entry.evidence.push(`exhaustiveness: ${nonExhaustive.length} non-exhaustive switch(es) → ${files}`);
|
|
188
|
+
// Escalate risk: non-exhaustive switches mean more callers are broken
|
|
189
|
+
if (entry.risk !== 'breaking')
|
|
190
|
+
entry.risk = 'breaking';
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (entry.kind === 'enum_member_added') {
|
|
194
|
+
const members = queries.getEnumMembers(entry.filePath, entry.exportName);
|
|
195
|
+
if (members)
|
|
196
|
+
entry.evidence.push(`checker: all members [${members.join(', ')}]`);
|
|
197
|
+
const impact = predictAddedMemberImpact(tsProgram, entry.filePath, entry.exportName, 'new');
|
|
198
|
+
if (impact.affectedSwitches.length > 0) {
|
|
199
|
+
const files = [...new Set(impact.affectedSwitches.map((s) => relativePath(s.filePath, engine.workspaceRoot)))].slice(0, 3).join(', ');
|
|
200
|
+
entry.evidence.push(`exhaustiveness: ${impact.affectedSwitches.length} switch(es) without default will miss new member → ${files}`);
|
|
201
|
+
// Enum member additions are risky (not safe) when switches are affected
|
|
202
|
+
if (entry.risk === 'safe')
|
|
203
|
+
entry.risk = 'risky';
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Phase 2D: call-site compatibility for required param changes
|
|
207
|
+
if (entry.kind === 'param_required' && entry.risk === 'breaking') {
|
|
208
|
+
const paramFacts = queries.getFunctionParams(entry.filePath, entry.exportName);
|
|
209
|
+
if (paramFacts) {
|
|
210
|
+
const required = paramFacts.filter((p) => !p.optional && !p.rest).length;
|
|
211
|
+
const max = paramFacts.length;
|
|
212
|
+
const report = analyzeCallSiteCompatibility(tsProgram, entry.filePath, entry.exportName, required, max);
|
|
213
|
+
if (report.breakingCallers.length > 0) {
|
|
214
|
+
const files = [...new Set(report.breakingCallers.map((c) => relativePath(c.filePath, engine.workspaceRoot)))].slice(0, 3).join(', ');
|
|
215
|
+
entry.evidence.push(`compatibility: ${report.breakingCallers.length} breaking call site(s) → ${files}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// Checker enrichment is best-effort — never block on failures
|
|
223
|
+
}
|
|
224
|
+
}
|
|
92
225
|
// Step 4: Find consumers using declaration line data (no raw text rescanning)
|
|
93
226
|
let consumerChecks = 0;
|
|
94
227
|
for (const entry of entries.filter((e) => e.risk !== 'safe')) {
|
|
@@ -110,6 +243,7 @@ export const apiGuard = defineTool({
|
|
|
110
243
|
const crossPackage = refs.length - samePackage;
|
|
111
244
|
const sampleFiles = [...new Set(refs.map((r) => relativePath(uriToPath(r.uri), engine.workspaceRoot)))].slice(0, 5);
|
|
112
245
|
entry.consumers = { samePackage, crossPackage, sampleFiles };
|
|
246
|
+
entry.filesToInspect = sampleFiles.filter((f) => !f.includes('node_modules'));
|
|
113
247
|
entry.evidence.push(`${crossPackage} cross-package, ${samePackage} same-package consumers`);
|
|
114
248
|
// Populate diagnosticsInConsumers (sample up to 3 consumer files)
|
|
115
249
|
let diagCount = 0;
|
|
@@ -142,8 +276,8 @@ export const apiGuard = defineTool({
|
|
|
142
276
|
const breaking = entries.filter((e) => e.risk === 'breaking').length;
|
|
143
277
|
const risky = entries.filter((e) => e.risk === 'risky').length;
|
|
144
278
|
const safe = entries.filter((e) => e.risk === 'safe').length;
|
|
145
|
-
// Semver: breaking → major, additive-only → minor, else patch
|
|
146
|
-
const hasAdditive = entries.some((e) => e.kind === 'added' && e.risk === 'safe');
|
|
279
|
+
// Semver: breaking → major, additive-only (new export or new optional param) → minor, else patch
|
|
280
|
+
const hasAdditive = entries.some((e) => (e.kind === 'added' || e.kind === 'param_added') && e.risk === 'safe');
|
|
147
281
|
const semver = breaking > 0 ? 'major' : hasAdditive ? 'minor' : 'patch';
|
|
148
282
|
const result = {
|
|
149
283
|
summary: {
|