weavatrix 0.2.13 → 0.2.14
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/README.md +83 -39
- package/SECURITY.md +2 -2
- package/docs/releases/v0.2.14.md +93 -0
- package/package.json +2 -2
- package/skill/SKILL.md +57 -38
- package/src/analysis/architecture/contract-graph.js +119 -0
- package/src/analysis/architecture/contract-schema.js +110 -0
- package/src/analysis/architecture/contract-storage.js +35 -0
- package/src/analysis/architecture/contract-verification.js +168 -0
- package/src/analysis/architecture-contract.js +16 -343
- package/src/analysis/change-classification/diff-parser.js +103 -0
- package/src/analysis/change-classification/options.js +40 -0
- package/src/analysis/change-classification/symbol-classifier.js +177 -0
- package/src/analysis/change-classification.js +120 -519
- package/src/analysis/dead-code-review/policy.js +23 -0
- package/src/analysis/dead-code-review.js +9 -19
- package/src/analysis/dep-check.js +10 -57
- package/src/analysis/dep-rules.js +10 -303
- package/src/analysis/dependency/scoped-dependencies.js +40 -0
- package/src/analysis/endpoints/common.js +62 -0
- package/src/analysis/endpoints/extract.js +107 -0
- package/src/analysis/endpoints/inventory.js +84 -0
- package/src/analysis/endpoints/mounts.js +129 -0
- package/src/analysis/endpoints-java.js +80 -3
- package/src/analysis/endpoints.js +3 -448
- package/src/analysis/git-history/analytics.js +177 -0
- package/src/analysis/git-history/collector.js +109 -0
- package/src/analysis/git-history/options.js +68 -0
- package/src/analysis/git-history.js +128 -577
- package/src/analysis/health-capabilities.js +82 -0
- package/src/analysis/http-contracts/analysis.js +169 -0
- package/src/analysis/http-contracts/client-call-detection.js +81 -0
- package/src/analysis/http-contracts/client-call-parser.js +255 -0
- package/src/analysis/http-contracts/graph-context.js +143 -0
- package/src/analysis/http-contracts/matching.js +61 -0
- package/src/analysis/http-contracts/shared.js +86 -0
- package/src/analysis/http-contracts.js +6 -822
- package/src/analysis/internal-audit/dependency-health.js +111 -0
- package/src/analysis/internal-audit/python-manifests.js +65 -0
- package/src/analysis/internal-audit/repo-files.js +55 -0
- package/src/analysis/internal-audit/supply-chain.js +132 -0
- package/src/analysis/internal-audit.collect.js +5 -106
- package/src/analysis/internal-audit.run.js +113 -200
- package/src/analysis/jvm-dependency-evidence.js +69 -0
- package/src/analysis/source-correctness/retry-patterns.js +93 -0
- package/src/analysis/source-correctness.js +225 -0
- package/src/analysis/structure/dependency-graph.js +156 -0
- package/src/analysis/structure/findings.js +142 -0
- package/src/graph/builder/go-receiver-resolution.js +112 -0
- package/src/graph/builder/js/queries.js +48 -0
- package/src/graph/builder/lang-go.js +89 -4
- package/src/graph/builder/lang-js.js +4 -44
- package/src/graph/builder/pass2-resolution.js +68 -0
- package/src/graph/freshness-probe.js +2 -1
- package/src/graph/incremental-refresh.js +3 -2
- package/src/graph/internal-builder.build.js +22 -183
- package/src/graph/internal-builder.pass2.js +161 -0
- package/src/graph/internal-builder.resolvers.js +2 -105
- package/src/graph/resolvers/rust.js +117 -0
- package/src/mcp/actions/advisories.mjs +18 -0
- package/src/mcp/actions/graph-lifecycle.mjs +148 -0
- package/src/mcp/actions/graph-sync.mjs +195 -0
- package/src/mcp/actions/hosted-architecture.mjs +57 -0
- package/src/mcp/architecture-bootstrap.mjs +168 -0
- package/src/mcp/architecture-starter.mjs +234 -0
- package/src/mcp/catalog.mjs +20 -6
- package/src/mcp/evidence/bun-lock-graph.mjs +209 -0
- package/src/mcp/evidence/package-graph-common.mjs +115 -0
- package/src/mcp/evidence/package-lock-graph.mjs +92 -0
- package/src/mcp/evidence-snapshot.package-graph.mjs +5 -474
- package/src/mcp/graph/context-core.mjs +111 -0
- package/src/mcp/graph/context-seeds.mjs +208 -0
- package/src/mcp/graph/context-state.mjs +86 -0
- package/src/mcp/graph/tools-core.mjs +143 -0
- package/src/mcp/graph/tools-query.mjs +223 -0
- package/src/mcp/graph-context.mjs +4 -496
- package/src/mcp/health/audit-format.mjs +172 -0
- package/src/mcp/health/audit.mjs +171 -0
- package/src/mcp/health/dead-code.mjs +110 -0
- package/src/mcp/health/duplicates.mjs +83 -0
- package/src/mcp/health/endpoints.mjs +42 -0
- package/src/mcp/health/structure.mjs +219 -0
- package/src/mcp/runtime-version.mjs +32 -0
- package/src/mcp/server/auto-refresh.mjs +66 -0
- package/src/mcp/server/runtime-config.mjs +43 -0
- package/src/mcp/server/shutdown.mjs +40 -0
- package/src/mcp/sync/evidence-architecture.mjs +54 -0
- package/src/mcp/sync/evidence-common.mjs +88 -0
- package/src/mcp/sync/evidence-duplicates.mjs +100 -0
- package/src/mcp/sync/evidence-health.mjs +56 -0
- package/src/mcp/sync/evidence-packages.mjs +95 -0
- package/src/mcp/sync/payload-common.mjs +97 -0
- package/src/mcp/sync/payload-v2.mjs +53 -0
- package/src/mcp/sync/payload-v3.mjs +79 -0
- package/src/mcp/sync-evidence.mjs +7 -402
- package/src/mcp/sync-payload.mjs +10 -244
- package/src/mcp/tools-actions.mjs +18 -414
- package/src/mcp/tools-architecture.mjs +18 -70
- package/src/mcp/tools-context.mjs +56 -15
- package/src/mcp/tools-endpoints.mjs +4 -1
- package/src/mcp/tools-graph.mjs +17 -331
- package/src/mcp/tools-health.mjs +24 -705
- package/src/mcp-server.mjs +35 -146
- package/src/precision/lsp-client/constants.js +12 -0
- package/src/precision/lsp-client/environment.js +20 -0
- package/src/precision/lsp-client/errors.js +15 -0
- package/src/precision/lsp-client/lifecycle.js +127 -0
- package/src/precision/lsp-client/message-parser.js +81 -0
- package/src/precision/lsp-client/protocol.js +212 -0
- package/src/precision/lsp-client/registry.js +58 -0
- package/src/precision/lsp-client/repo-uri.js +60 -0
- package/src/precision/lsp-client/stdio-client.js +151 -0
- package/src/precision/lsp-client.js +10 -682
- package/src/precision/lsp-overlay/build.js +238 -0
- package/src/precision/lsp-overlay/contract.js +61 -0
- package/src/precision/lsp-overlay/reference-results.js +108 -0
- package/src/precision/lsp-overlay/semantic-inputs.js +62 -0
- package/src/precision/lsp-overlay/source-session.js +134 -0
- package/src/precision/lsp-overlay/store.js +150 -0
- package/src/precision/lsp-overlay/target-index.js +147 -0
- package/src/precision/lsp-overlay/target-query.js +55 -0
- package/src/precision/lsp-overlay.js +11 -868
- package/src/precision/typescript-lsp-provider.js +12 -682
- package/src/precision/typescript-provider/client.js +69 -0
- package/src/precision/typescript-provider/discovery.js +124 -0
- package/src/precision/typescript-provider/project-host.js +249 -0
- package/src/precision/typescript-provider/project-safety.js +161 -0
- package/src/precision/typescript-provider/reference-usage.js +53 -0
- package/src/version.js +7 -0
- package/docs/releases/v0.2.13.md +0 -48
|
@@ -1,682 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const MAX_CONFIG_FILES = 128
|
|
14
|
-
const MAX_CONFIG_BYTES = 8 * 1024 * 1024
|
|
15
|
-
const MAX_EXTRA_INPUT_BYTES = 64 * 1024 * 1024
|
|
16
|
-
const MAX_PROJECT_INPUT_BYTES = 128 * 1024 * 1024
|
|
17
|
-
const MAX_SINGLE_INPUT_BYTES = 4 * 1024 * 1024
|
|
18
|
-
const MAX_DIRECTORY_ENTRIES = 32_768
|
|
19
|
-
const MAX_DIRECTORIES = 4_096
|
|
20
|
-
const DEFAULT_SAFETY_TIMEOUT_MS = 5_000
|
|
21
|
-
const norm = (value) => String(value || '').replace(/\\/g, '/').replace(/^\.\//, '')
|
|
22
|
-
|
|
23
|
-
let discoveredProvider = null
|
|
24
|
-
|
|
25
|
-
function resolveOwn(specifier) {
|
|
26
|
-
const resolved = requireFromWeavatrix.resolve(specifier)
|
|
27
|
-
if (!isAbsolute(resolved)) throw new Error(`Resolved dependency path is not absolute: ${specifier}`)
|
|
28
|
-
return realpathSync.native(resolved)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function packageInfoFrom(startPath, expectedName) {
|
|
32
|
-
let directory = dirname(startPath)
|
|
33
|
-
for (let depth = 0; depth < 10; depth++) {
|
|
34
|
-
const packagePath = join(directory, 'package.json')
|
|
35
|
-
if (existsSync(packagePath)) {
|
|
36
|
-
try {
|
|
37
|
-
const manifest = JSON.parse(readFileSync(packagePath, 'utf8'))
|
|
38
|
-
if (manifest.name === expectedName) return {directory, manifest, packagePath: realpathSync.native(packagePath)}
|
|
39
|
-
} catch {
|
|
40
|
-
// Continue upward; this may be an unrelated or malformed nested package.
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
const parent = dirname(directory)
|
|
44
|
-
if (parent === directory) break
|
|
45
|
-
directory = parent
|
|
46
|
-
}
|
|
47
|
-
throw new Error(`Could not locate ${expectedName} package metadata`)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function resolveServerCli() {
|
|
51
|
-
const candidates = [
|
|
52
|
-
'typescript-language-server',
|
|
53
|
-
'typescript-language-server/lib/cli.mjs',
|
|
54
|
-
'typescript-language-server/lib/cli.js',
|
|
55
|
-
]
|
|
56
|
-
let lastError
|
|
57
|
-
for (const candidate of candidates) {
|
|
58
|
-
try {
|
|
59
|
-
const path = resolveOwn(candidate)
|
|
60
|
-
if (existsSync(path)) return path
|
|
61
|
-
} catch (error) {
|
|
62
|
-
lastError = error
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
throw lastError || new Error('typescript-language-server CLI was not found')
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function resolveTypeScriptServer() {
|
|
69
|
-
const candidates = ['typescript/lib/tsserver.js', 'typescript/lib/_tsserver.js']
|
|
70
|
-
let lastError
|
|
71
|
-
for (const candidate of candidates) {
|
|
72
|
-
try {
|
|
73
|
-
const path = resolveOwn(candidate)
|
|
74
|
-
if (existsSync(path)) return path
|
|
75
|
-
} catch (error) {
|
|
76
|
-
lastError = error
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
try {
|
|
80
|
-
const typescriptEntry = resolveOwn('typescript')
|
|
81
|
-
for (const name of ['tsserver.js', '_tsserver.js']) {
|
|
82
|
-
const candidate = join(dirname(typescriptEntry), name)
|
|
83
|
-
if (existsSync(candidate)) return realpathSync.native(candidate)
|
|
84
|
-
}
|
|
85
|
-
} catch (error) {
|
|
86
|
-
lastError = error
|
|
87
|
-
}
|
|
88
|
-
throw lastError || new Error('Bundled TypeScript tsserver was not found')
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function discover() {
|
|
92
|
-
if (discoveredProvider) return discoveredProvider
|
|
93
|
-
const cliPath = resolveServerCli()
|
|
94
|
-
const tsserverPath = resolveTypeScriptServer()
|
|
95
|
-
const serverPackage = packageInfoFrom(cliPath, PROVIDER)
|
|
96
|
-
const typescriptPackage = packageInfoFrom(tsserverPath, 'typescript')
|
|
97
|
-
discoveredProvider = Object.freeze({
|
|
98
|
-
available: true,
|
|
99
|
-
provider: PROVIDER,
|
|
100
|
-
version: String(serverPackage.manifest.version || 'unknown'),
|
|
101
|
-
typescriptVersion: String(typescriptPackage.manifest.version || 'unknown'),
|
|
102
|
-
cliPath,
|
|
103
|
-
tsserverPath,
|
|
104
|
-
})
|
|
105
|
-
return discoveredProvider
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function typeScriptLspContract() {
|
|
109
|
-
const availability = typeScriptLspAvailability()
|
|
110
|
-
return [
|
|
111
|
-
TYPESCRIPT_LSP_CAPABILITY_CONTRACT,
|
|
112
|
-
`${PROVIDER}@${availability.version || 'unavailable'}`,
|
|
113
|
-
`typescript@${availability.typescriptVersion || 'unavailable'}`,
|
|
114
|
-
`runtime@${process.platform}-${process.arch}-node${String(process.versions.node || '0').split('.')[0]}`,
|
|
115
|
-
].join('|')
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export function typeScriptLspAvailability() {
|
|
119
|
-
try {
|
|
120
|
-
const result = discover()
|
|
121
|
-
return {
|
|
122
|
-
available: true,
|
|
123
|
-
provider: result.provider,
|
|
124
|
-
version: result.version,
|
|
125
|
-
typescriptVersion: result.typescriptVersion,
|
|
126
|
-
}
|
|
127
|
-
} catch (error) {
|
|
128
|
-
return {
|
|
129
|
-
available: false,
|
|
130
|
-
provider: PROVIDER,
|
|
131
|
-
version: null,
|
|
132
|
-
typescriptVersion: null,
|
|
133
|
-
reason: error?.code === 'MODULE_NOT_FOUND' ? 'DEPENDENCY_NOT_INSTALLED' : 'DISCOVERY_FAILED',
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export function typeScriptLanguageId(filePath) {
|
|
139
|
-
const extension = extname(String(filePath)).toLowerCase()
|
|
140
|
-
if (extension === '.ts' || extension === '.mts' || extension === '.cts') return 'typescript'
|
|
141
|
-
if (extension === '.tsx') return 'typescriptreact'
|
|
142
|
-
if (extension === '.js' || extension === '.mjs' || extension === '.cjs') return 'javascript'
|
|
143
|
-
if (extension === '.jsx') return 'javascriptreact'
|
|
144
|
-
return null
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function typeScriptScriptKind(ts, filePath) {
|
|
148
|
-
const extension = extname(String(filePath)).toLowerCase()
|
|
149
|
-
if (extension === '.tsx') return ts.ScriptKind.TSX
|
|
150
|
-
if (extension === '.jsx') return ts.ScriptKind.JSX
|
|
151
|
-
if (extension === '.js' || extension === '.mjs' || extension === '.cjs') return ts.ScriptKind.JS
|
|
152
|
-
return ts.ScriptKind.TS
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Classify an exact LSP reference without consulting repository dependencies or executing project
|
|
156
|
-
// configuration. Unknown is deliberately fail-closed: callers must not promote it to runtime.
|
|
157
|
-
export function classifyTypeScriptReferenceUsage(filePath, text, position) {
|
|
158
|
-
if (!Number.isInteger(position?.line) || !Number.isInteger(position?.character)) return 'unknown'
|
|
159
|
-
let ts
|
|
160
|
-
try { ts = requireFromWeavatrix('typescript') } catch { return 'unknown' }
|
|
161
|
-
let sourceFile
|
|
162
|
-
let offset
|
|
163
|
-
try {
|
|
164
|
-
sourceFile = ts.createSourceFile(
|
|
165
|
-
String(filePath || 'source.ts'),
|
|
166
|
-
String(text || ''),
|
|
167
|
-
ts.ScriptTarget.Latest,
|
|
168
|
-
true,
|
|
169
|
-
typeScriptScriptKind(ts, filePath),
|
|
170
|
-
)
|
|
171
|
-
if (sourceFile.parseDiagnostics?.some((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error)) {
|
|
172
|
-
return 'unknown'
|
|
173
|
-
}
|
|
174
|
-
offset = sourceFile.getPositionOfLineAndCharacter(position.line, position.character)
|
|
175
|
-
} catch {
|
|
176
|
-
return 'unknown'
|
|
177
|
-
}
|
|
178
|
-
let token
|
|
179
|
-
try { token = ts.getTokenAtPosition(sourceFile, offset) } catch { return 'unknown' }
|
|
180
|
-
if (!token || (!ts.isIdentifier(token) && !ts.isPrivateIdentifier(token))) return 'unknown'
|
|
181
|
-
if (offset < token.getStart(sourceFile) || offset >= token.getEnd()) return 'unknown'
|
|
182
|
-
|
|
183
|
-
for (let current = token; current && current !== sourceFile; current = current.parent) {
|
|
184
|
-
if ((ts.isImportSpecifier(current) || ts.isImportClause(current)
|
|
185
|
-
|| ts.isExportSpecifier(current) || ts.isExportDeclaration(current))
|
|
186
|
-
&& current.isTypeOnly === true) return 'type'
|
|
187
|
-
|
|
188
|
-
// TypeScript models both class `extends` and `implements` as a type node. The base
|
|
189
|
-
// expression of a class extends clause is evaluated at runtime; implements, interface
|
|
190
|
-
// heritage, and generic arguments remain type-only.
|
|
191
|
-
if (ts.isExpressionWithTypeArguments(current) && ts.isHeritageClause(current.parent)) {
|
|
192
|
-
const heritage = current.parent
|
|
193
|
-
if (heritage.token === ts.SyntaxKind.ExtendsKeyword && ts.isClassLike(heritage.parent)) {
|
|
194
|
-
const expression = current.expression
|
|
195
|
-
if (offset >= expression.getStart(sourceFile) && offset < expression.getEnd()) return 'value'
|
|
196
|
-
}
|
|
197
|
-
return 'type'
|
|
198
|
-
}
|
|
199
|
-
if (ts.isTypeNode(current)) return 'type'
|
|
200
|
-
}
|
|
201
|
-
return 'value'
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function canonicalKey(path, caseSensitive) {
|
|
205
|
-
let canonical
|
|
206
|
-
try { canonical = realpathSync.native(path) } catch { return null }
|
|
207
|
-
return caseSensitive ? canonical : canonical.toLowerCase()
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function typeScriptRepoContext(repoRoot) {
|
|
211
|
-
let root
|
|
212
|
-
let ts
|
|
213
|
-
try {
|
|
214
|
-
root = realpathSync.native(repoRoot)
|
|
215
|
-
ts = requireFromWeavatrix('typescript')
|
|
216
|
-
} catch {
|
|
217
|
-
return null
|
|
218
|
-
}
|
|
219
|
-
return {root, ts, caseSensitive: Boolean(ts.sys.useCaseSensitiveFileNames)}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function guardedExistingPath(root, candidate, state = null) {
|
|
223
|
-
const input = String(candidate || '')
|
|
224
|
-
const lexical = isAbsolute(input) ? resolve(input) : resolve(root, input)
|
|
225
|
-
if (!isPathInside(root, lexical)) {
|
|
226
|
-
if (state) state.outsideAccess = true
|
|
227
|
-
return null
|
|
228
|
-
}
|
|
229
|
-
try {
|
|
230
|
-
const canonical = realpathSync.native(lexical)
|
|
231
|
-
if (!isPathInside(root, canonical)) {
|
|
232
|
-
if (state) state.outsideAccess = true
|
|
233
|
-
return null
|
|
234
|
-
}
|
|
235
|
-
return canonical
|
|
236
|
-
} catch {
|
|
237
|
-
return null
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function nearestTypeScriptConfig(context, absoluteFile) {
|
|
242
|
-
const {root} = context
|
|
243
|
-
let directory = dirname(absoluteFile)
|
|
244
|
-
while (isPathInside(root, directory)) {
|
|
245
|
-
for (const name of ['tsconfig.json', 'jsconfig.json']) {
|
|
246
|
-
const candidate = join(directory, name)
|
|
247
|
-
if (!existsSync(candidate)) continue
|
|
248
|
-
const configPath = guardedExistingPath(root, candidate)
|
|
249
|
-
return configPath
|
|
250
|
-
? {ok: true, configPath}
|
|
251
|
-
: {ok: false, reason: 'CONFIG_OUTSIDE_REPOSITORY'}
|
|
252
|
-
}
|
|
253
|
-
if (directory === root) break
|
|
254
|
-
const parent = dirname(directory)
|
|
255
|
-
if (parent === directory) break
|
|
256
|
-
directory = parent
|
|
257
|
-
}
|
|
258
|
-
return {ok: true, configPath: null}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function safetyBudget(options = {}) {
|
|
262
|
-
const requestedDeadline = Number(options.deadline)
|
|
263
|
-
const requestedTimeout = Number(options.timeoutMs)
|
|
264
|
-
const timeout = Number.isFinite(requestedTimeout)
|
|
265
|
-
? Math.max(100, Math.min(60_000, Math.floor(requestedTimeout)))
|
|
266
|
-
: DEFAULT_SAFETY_TIMEOUT_MS
|
|
267
|
-
return {
|
|
268
|
-
deadline: Number.isFinite(requestedDeadline) ? requestedDeadline : Date.now() + timeout,
|
|
269
|
-
maxEntries: Math.max(1, Math.min(MAX_DIRECTORY_ENTRIES,
|
|
270
|
-
Number.isFinite(Number(options.maxDirectoryEntries)) ? Math.floor(Number(options.maxDirectoryEntries)) : MAX_DIRECTORY_ENTRIES)),
|
|
271
|
-
maxDirectories: Math.max(1, Math.min(MAX_DIRECTORIES,
|
|
272
|
-
Number.isFinite(Number(options.maxDirectories)) ? Math.floor(Number(options.maxDirectories)) : MAX_DIRECTORIES)),
|
|
273
|
-
entries: 0,
|
|
274
|
-
directories: 0,
|
|
275
|
-
configBytes: 0,
|
|
276
|
-
configFiles: new Set(),
|
|
277
|
-
reason: null,
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function safetyLimitReached(budget, reason = 'PROJECT_INPUT_LIMIT') {
|
|
282
|
-
if (!budget.reason && Date.now() >= budget.deadline) budget.reason = 'SAFETY_DEADLINE'
|
|
283
|
-
if (!budget.reason && reason) budget.reason = reason
|
|
284
|
-
return Boolean(budget.reason)
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function boundedReadDirectory(context, state, budget, candidate, extensions, excludes, includes, depth) {
|
|
288
|
-
const {root, ts, caseSensitive} = context
|
|
289
|
-
const path = guardedExistingPath(root, candidate, state)
|
|
290
|
-
if (!path || safetyLimitReached(budget, null)) return []
|
|
291
|
-
const getFileSystemEntries = (directoryPath) => {
|
|
292
|
-
if (safetyLimitReached(budget, null)) return {files: [], directories: []}
|
|
293
|
-
const directory = guardedExistingPath(root, directoryPath, state)
|
|
294
|
-
if (!directory) return {files: [], directories: []}
|
|
295
|
-
budget.directories++
|
|
296
|
-
if (budget.directories > budget.maxDirectories) {
|
|
297
|
-
safetyLimitReached(budget)
|
|
298
|
-
return {files: [], directories: []}
|
|
299
|
-
}
|
|
300
|
-
const files = []
|
|
301
|
-
const directories = []
|
|
302
|
-
let handle
|
|
303
|
-
try {
|
|
304
|
-
handle = opendirSync(directory)
|
|
305
|
-
while (!safetyLimitReached(budget, null)) {
|
|
306
|
-
const entry = handle.readSync()
|
|
307
|
-
if (!entry) break
|
|
308
|
-
budget.entries++
|
|
309
|
-
if (budget.entries > budget.maxEntries) {
|
|
310
|
-
safetyLimitReached(budget)
|
|
311
|
-
break
|
|
312
|
-
}
|
|
313
|
-
if (entry.isFile()) files.push(entry.name)
|
|
314
|
-
else if (entry.isDirectory()) directories.push(entry.name)
|
|
315
|
-
else if (entry.isSymbolicLink()) {
|
|
316
|
-
// Follow only links whose canonical target remains inside the repository. The
|
|
317
|
-
// matcher also receives a guarded realpath callback to prevent cycles.
|
|
318
|
-
const linked = guardedExistingPath(root, join(directory, entry.name), state)
|
|
319
|
-
if (!linked) continue
|
|
320
|
-
let stats
|
|
321
|
-
try { stats = statSync(linked) } catch { continue }
|
|
322
|
-
if (stats.isFile()) files.push(entry.name)
|
|
323
|
-
else if (stats.isDirectory()) directories.push(entry.name)
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
} catch {
|
|
327
|
-
return {files: [], directories: []}
|
|
328
|
-
} finally {
|
|
329
|
-
try { handle?.closeSync() } catch {}
|
|
330
|
-
}
|
|
331
|
-
return {files, directories}
|
|
332
|
-
}
|
|
333
|
-
const guardedRealpath = (entry) => guardedExistingPath(root, entry, state) || resolve(root, '.weavatrix-invalid-path')
|
|
334
|
-
try {
|
|
335
|
-
return ts.matchFiles(
|
|
336
|
-
path,
|
|
337
|
-
extensions,
|
|
338
|
-
excludes,
|
|
339
|
-
includes,
|
|
340
|
-
caseSensitive,
|
|
341
|
-
root,
|
|
342
|
-
depth,
|
|
343
|
-
getFileSystemEntries,
|
|
344
|
-
guardedRealpath,
|
|
345
|
-
)
|
|
346
|
-
} catch {
|
|
347
|
-
state.limitExceeded = true
|
|
348
|
-
return []
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function parseRepoConfig(context, configPath, budget) {
|
|
353
|
-
const {root, ts, caseSensitive} = context
|
|
354
|
-
const state = {
|
|
355
|
-
outsideAccess: false,
|
|
356
|
-
limitExceeded: false,
|
|
357
|
-
configuredPlugins: false,
|
|
358
|
-
bytes: 0,
|
|
359
|
-
records: new Map(),
|
|
360
|
-
}
|
|
361
|
-
const diagnostics = []
|
|
362
|
-
const host = {
|
|
363
|
-
useCaseSensitiveFileNames: caseSensitive,
|
|
364
|
-
getCurrentDirectory: () => root,
|
|
365
|
-
fileExists(candidate) {
|
|
366
|
-
const path = guardedExistingPath(root, candidate, state)
|
|
367
|
-
return Boolean(path && ts.sys.fileExists(path))
|
|
368
|
-
},
|
|
369
|
-
readFile(candidate) {
|
|
370
|
-
if (safetyLimitReached(budget, null)) return undefined
|
|
371
|
-
const path = guardedExistingPath(root, candidate, state)
|
|
372
|
-
if (!path) return undefined
|
|
373
|
-
let body
|
|
374
|
-
try {
|
|
375
|
-
const size = statSync(path).size
|
|
376
|
-
if (size > MAX_CONFIG_BYTES || state.bytes + size > MAX_CONFIG_BYTES) {
|
|
377
|
-
state.limitExceeded = true
|
|
378
|
-
return undefined
|
|
379
|
-
}
|
|
380
|
-
body = readFileSync(path)
|
|
381
|
-
} catch {
|
|
382
|
-
return undefined
|
|
383
|
-
}
|
|
384
|
-
const rel = norm(relative(root, path))
|
|
385
|
-
if (!state.records.has(rel)) {
|
|
386
|
-
if (!budget.configFiles.has(rel)
|
|
387
|
-
&& (budget.configFiles.size >= MAX_CONFIG_FILES
|
|
388
|
-
|| budget.configBytes + body.byteLength > MAX_CONFIG_BYTES)) {
|
|
389
|
-
state.limitExceeded = true
|
|
390
|
-
safetyLimitReached(budget, 'CONFIG_INPUT_LIMIT')
|
|
391
|
-
return undefined
|
|
392
|
-
}
|
|
393
|
-
if (!budget.configFiles.has(rel)) {
|
|
394
|
-
budget.configFiles.add(rel)
|
|
395
|
-
budget.configBytes += body.byteLength
|
|
396
|
-
}
|
|
397
|
-
state.records.set(rel, createHash('sha256').update(body).digest('hex'))
|
|
398
|
-
state.bytes += body.byteLength
|
|
399
|
-
}
|
|
400
|
-
// TypeScript currently merges compilerOptions.plugins, but audit every config body it
|
|
401
|
-
// reads as well. This keeps the pre-spawn guard fail-closed across TypeScript versions
|
|
402
|
-
// and catches plugins hidden in an extends chain before tsserver can see the project.
|
|
403
|
-
try {
|
|
404
|
-
const raw = ts.parseConfigFileTextToJson(path, body.toString('utf8')).config
|
|
405
|
-
if (Array.isArray(raw?.compilerOptions?.plugins) && raw.compilerOptions.plugins.length) {
|
|
406
|
-
state.configuredPlugins = true
|
|
407
|
-
}
|
|
408
|
-
} catch {
|
|
409
|
-
// The parser's diagnostics below decide whether malformed configuration is safe.
|
|
410
|
-
}
|
|
411
|
-
return body.toString('utf8')
|
|
412
|
-
},
|
|
413
|
-
readDirectory(candidate, extensions, excludes, includes, depth) {
|
|
414
|
-
return boundedReadDirectory(context, state, budget, candidate, extensions, excludes, includes, depth)
|
|
415
|
-
},
|
|
416
|
-
onUnRecoverableConfigFileDiagnostic(diagnostic) { diagnostics.push(diagnostic) },
|
|
417
|
-
trace() {},
|
|
418
|
-
}
|
|
419
|
-
let parsed
|
|
420
|
-
try { parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, host) } catch {
|
|
421
|
-
return {complete: false, reason: 'CONFIG_PARSE_FAILED'}
|
|
422
|
-
}
|
|
423
|
-
const errors = [...diagnostics, ...(parsed?.errors || [])]
|
|
424
|
-
if (state.configuredPlugins || (Array.isArray(parsed?.options?.plugins) && parsed.options.plugins.length)) {
|
|
425
|
-
return {complete: false, reason: 'CONFIGURED_TSSERVER_PLUGINS'}
|
|
426
|
-
}
|
|
427
|
-
if (budget.reason) return {complete: false, reason: budget.reason}
|
|
428
|
-
if (!parsed || state.outsideAccess || state.limitExceeded
|
|
429
|
-
|| errors.some((diagnostic) => diagnostic?.category === ts.DiagnosticCategory.Error)) {
|
|
430
|
-
return {
|
|
431
|
-
complete: false,
|
|
432
|
-
reason: state.outsideAccess
|
|
433
|
-
? 'CONFIG_OUTSIDE_REPOSITORY'
|
|
434
|
-
: state.limitExceeded ? 'CONFIG_INPUT_LIMIT' : 'CONFIG_PARSE_FAILED',
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
const projectFiles = []
|
|
438
|
-
const projectKeys = new Set()
|
|
439
|
-
for (const file of parsed.fileNames) {
|
|
440
|
-
const canonical = guardedExistingPath(root, file, state)
|
|
441
|
-
if (!canonical) return {complete: false, reason: 'PROJECT_INPUT_OUTSIDE_REPOSITORY'}
|
|
442
|
-
const key = caseSensitive ? canonical : canonical.toLowerCase()
|
|
443
|
-
if (projectKeys.has(key)) continue
|
|
444
|
-
projectKeys.add(key)
|
|
445
|
-
projectFiles.push(norm(relative(root, canonical)))
|
|
446
|
-
if (projectFiles.length > MAX_PROJECT_FILES) return {complete: false, reason: 'PROJECT_INPUT_LIMIT'}
|
|
447
|
-
}
|
|
448
|
-
projectFiles.sort()
|
|
449
|
-
return {
|
|
450
|
-
complete: true,
|
|
451
|
-
parsed,
|
|
452
|
-
projectFiles,
|
|
453
|
-
projectKeys,
|
|
454
|
-
configRecords: state.records,
|
|
455
|
-
plugins: [],
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
function referenceConfigPath(context, reference) {
|
|
460
|
-
const {root, ts} = context
|
|
461
|
-
let candidate
|
|
462
|
-
try {
|
|
463
|
-
candidate = typeof ts.resolveProjectReferencePath === 'function'
|
|
464
|
-
? ts.resolveProjectReferencePath(reference)
|
|
465
|
-
: reference?.path
|
|
466
|
-
} catch {
|
|
467
|
-
return null
|
|
468
|
-
}
|
|
469
|
-
if (!candidate) return null
|
|
470
|
-
return guardedExistingPath(root, candidate)
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// This audit runs before spawning tsserver. It uses only bundled TypeScript and a repo-contained
|
|
474
|
-
// config host, rejects every configured plugin, recursively inspects project references, and binds
|
|
475
|
-
// ignored config/project inputs into a bounded digest for cache and post-LSP freshness checks.
|
|
476
|
-
export function typeScriptProjectSafety(repoRoot, relFiles = [], options = {}) {
|
|
477
|
-
const context = typeScriptRepoContext(repoRoot)
|
|
478
|
-
if (!context) return {safe: false, reason: 'TYPESCRIPT_UNAVAILABLE', fingerprint: null}
|
|
479
|
-
const {root} = context
|
|
480
|
-
const budget = safetyBudget(options)
|
|
481
|
-
const files = [...new Set((relFiles || []).map(norm).filter(Boolean))].sort()
|
|
482
|
-
if (files.length > MAX_PROJECT_FILES) return {safe: false, reason: 'PROJECT_INPUT_LIMIT', fingerprint: null}
|
|
483
|
-
const graphFiles = new Set(files)
|
|
484
|
-
const mappings = []
|
|
485
|
-
const fileConfigs = {}
|
|
486
|
-
const queue = []
|
|
487
|
-
const queued = new Set()
|
|
488
|
-
for (const file of files) {
|
|
489
|
-
const absolute = guardedExistingPath(root, file)
|
|
490
|
-
if (!absolute) return {safe: false, reason: 'UNREADABLE_PROJECT_INPUT', fingerprint: null}
|
|
491
|
-
const nearest = nearestTypeScriptConfig(context, absolute)
|
|
492
|
-
if (!nearest.ok) return {safe: false, reason: nearest.reason, fingerprint: null}
|
|
493
|
-
const configRel = nearest.configPath ? norm(relative(root, nearest.configPath)) : '<inferred>'
|
|
494
|
-
mappings.push(`${file}=>${configRel}`)
|
|
495
|
-
fileConfigs[file] = nearest.configPath ? configRel : null
|
|
496
|
-
if (nearest.configPath && !queued.has(nearest.configPath)) {
|
|
497
|
-
queued.add(nearest.configPath)
|
|
498
|
-
queue.push(nearest.configPath)
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
const configRecords = new Map()
|
|
503
|
-
const projectFiles = new Set()
|
|
504
|
-
const projects = {}
|
|
505
|
-
for (let cursor = 0; cursor < queue.length; cursor++) {
|
|
506
|
-
if (safetyLimitReached(budget, null)) return {safe: false, reason: budget.reason, fingerprint: null}
|
|
507
|
-
if (queue.length > MAX_CONFIG_FILES) return {safe: false, reason: 'CONFIG_INPUT_LIMIT', fingerprint: null}
|
|
508
|
-
const configPath = queue[cursor]
|
|
509
|
-
const parsed = parseRepoConfig(context, configPath, budget)
|
|
510
|
-
if (!parsed.complete) return {safe: false, reason: parsed.reason, fingerprint: null}
|
|
511
|
-
const configRel = norm(relative(root, configPath))
|
|
512
|
-
projects[configRel] = {
|
|
513
|
-
projectFiles: parsed.projectFiles,
|
|
514
|
-
configFiles: [...parsed.configRecords.keys()].sort(),
|
|
515
|
-
}
|
|
516
|
-
for (const [file, digest] of parsed.configRecords) {
|
|
517
|
-
configRecords.set(file, digest)
|
|
518
|
-
if (configRecords.size > MAX_CONFIG_FILES) return {safe: false, reason: 'CONFIG_INPUT_LIMIT', fingerprint: null}
|
|
519
|
-
}
|
|
520
|
-
for (const file of parsed.projectFiles) {
|
|
521
|
-
projectFiles.add(file)
|
|
522
|
-
if (projectFiles.size > MAX_PROJECT_FILES) return {safe: false, reason: 'PROJECT_INPUT_LIMIT', fingerprint: null}
|
|
523
|
-
}
|
|
524
|
-
for (const reference of parsed.parsed.projectReferences || []) {
|
|
525
|
-
const referenced = referenceConfigPath(context, reference)
|
|
526
|
-
if (!referenced) return {safe: false, reason: 'PROJECT_REFERENCE_UNRESOLVED', fingerprint: null}
|
|
527
|
-
if (!queued.has(referenced)) {
|
|
528
|
-
queued.add(referenced)
|
|
529
|
-
queue.push(referenced)
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
const extraRecords = []
|
|
535
|
-
let projectBytes = 0
|
|
536
|
-
let extraBytes = 0
|
|
537
|
-
for (const file of [...projectFiles].sort()) {
|
|
538
|
-
if (safetyLimitReached(budget, null)) return {safe: false, reason: budget.reason, fingerprint: null}
|
|
539
|
-
const absolute = guardedExistingPath(root, file)
|
|
540
|
-
if (!absolute) return {safe: false, reason: 'UNREADABLE_PROJECT_INPUT', fingerprint: null}
|
|
541
|
-
let size
|
|
542
|
-
try {
|
|
543
|
-
const stats = statSync(absolute)
|
|
544
|
-
if (!stats.isFile()) return {safe: false, reason: 'UNREADABLE_PROJECT_INPUT', fingerprint: null}
|
|
545
|
-
size = stats.size
|
|
546
|
-
if (size > MAX_SINGLE_INPUT_BYTES || projectBytes + size > MAX_PROJECT_INPUT_BYTES) {
|
|
547
|
-
return {safe: false, reason: 'PROJECT_INPUT_LIMIT', fingerprint: null}
|
|
548
|
-
}
|
|
549
|
-
} catch {
|
|
550
|
-
return {safe: false, reason: 'UNREADABLE_PROJECT_INPUT', fingerprint: null}
|
|
551
|
-
}
|
|
552
|
-
projectBytes += size
|
|
553
|
-
if (graphFiles.has(file)) continue
|
|
554
|
-
if (extraBytes + size > MAX_EXTRA_INPUT_BYTES) {
|
|
555
|
-
return {safe: false, reason: 'PROJECT_INPUT_LIMIT', fingerprint: null}
|
|
556
|
-
}
|
|
557
|
-
let body
|
|
558
|
-
try { body = readFileSync(absolute) } catch {
|
|
559
|
-
return {safe: false, reason: 'UNREADABLE_PROJECT_INPUT', fingerprint: null}
|
|
560
|
-
}
|
|
561
|
-
extraBytes += body.byteLength
|
|
562
|
-
extraRecords.push(`${file}:${createHash('sha256').update(body).digest('hex')}`)
|
|
563
|
-
}
|
|
564
|
-
const fingerprint = createHash('sha256').update([
|
|
565
|
-
...mappings.map((item) => `map:${item}`),
|
|
566
|
-
...[...configRecords.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([file, digest]) => `config:${file}:${digest}`),
|
|
567
|
-
...[...projectFiles].sort().map((file) => `project:${file}`),
|
|
568
|
-
...extraRecords.map((item) => `extra:${item}`),
|
|
569
|
-
].join('\n')).digest('hex')
|
|
570
|
-
return {
|
|
571
|
-
safe: true,
|
|
572
|
-
reason: null,
|
|
573
|
-
fingerprint,
|
|
574
|
-
configFiles: [...configRecords.keys()].sort(),
|
|
575
|
-
projectFiles: [...projectFiles].sort(),
|
|
576
|
-
fileConfigs,
|
|
577
|
-
projects,
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// An empty references response is useful dead-code evidence only when TypeScript itself confirms
|
|
582
|
-
// that the declaration belongs to a complete configured project. Merely finding an ancestor
|
|
583
|
-
// tsconfig is not enough: include/exclude/files can leave the target in an inferred project.
|
|
584
|
-
export function typeScriptConfiguredProjectMembership(repoRoot, relFile) {
|
|
585
|
-
const context = typeScriptRepoContext(repoRoot)
|
|
586
|
-
if (!context) return {complete: false, member: false, reason: 'TYPESCRIPT_UNAVAILABLE'}
|
|
587
|
-
const {root, caseSensitive} = context
|
|
588
|
-
const target = guardedExistingPath(root, relFile)
|
|
589
|
-
if (!target) {
|
|
590
|
-
return {complete: false, member: false, reason: 'UNREADABLE_PATH'}
|
|
591
|
-
}
|
|
592
|
-
const nearest = nearestTypeScriptConfig(context, target)
|
|
593
|
-
if (!nearest.ok) return {complete: false, member: false, reason: nearest.reason}
|
|
594
|
-
const configPath = nearest.configPath
|
|
595
|
-
if (!configPath) return {complete: false, member: false, reason: 'NO_CONFIGURED_PROJECT'}
|
|
596
|
-
const parsed = parseRepoConfig(context, configPath, safetyBudget())
|
|
597
|
-
if (!parsed.complete) return {complete: false, member: false, reason: parsed.reason}
|
|
598
|
-
const targetKey = canonicalKey(target, caseSensitive)
|
|
599
|
-
const member = parsed.projectKeys.has(targetKey)
|
|
600
|
-
return {
|
|
601
|
-
complete: true,
|
|
602
|
-
member,
|
|
603
|
-
projectFiles: parsed.projectFiles,
|
|
604
|
-
configFiles: [...parsed.configRecords.keys()].sort(),
|
|
605
|
-
configFile: norm(relative(root, configPath)),
|
|
606
|
-
reason: member ? null : 'NOT_IN_CONFIGURED_PROJECT',
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
/**
|
|
611
|
-
* Starts Weavatrix's own bundled TypeScript language server. Resolution is anchored to this
|
|
612
|
-
* module, never to the repository being analyzed; no repository command, package script, or npx
|
|
613
|
-
* executable is invoked.
|
|
614
|
-
*/
|
|
615
|
-
export async function createTypeScriptLspClient({repoRoot, timeoutMs = 10_000} = {}) {
|
|
616
|
-
const discovered = discover()
|
|
617
|
-
const absoluteRepoRoot = resolve(repoRoot)
|
|
618
|
-
let client
|
|
619
|
-
let reportedTypeScript = null
|
|
620
|
-
try {
|
|
621
|
-
client = await startStdioLspClient({
|
|
622
|
-
repoRoot: absoluteRepoRoot,
|
|
623
|
-
executablePath: process.execPath,
|
|
624
|
-
args: [discovered.cliPath, '--stdio'],
|
|
625
|
-
requestTimeoutMs: timeoutMs,
|
|
626
|
-
onNotification(method, params) {
|
|
627
|
-
if (method === '$/typescriptVersion' && params && typeof params === 'object') {
|
|
628
|
-
reportedTypeScript = {
|
|
629
|
-
version: typeof params.version === 'string' ? params.version : null,
|
|
630
|
-
source: typeof params.source === 'string' ? params.source : null,
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
},
|
|
634
|
-
})
|
|
635
|
-
await client.initialize({
|
|
636
|
-
clientInfo: {name: 'weavatrix', version: WEAVATRIX_VERSION},
|
|
637
|
-
capabilities: {
|
|
638
|
-
workspace: {configuration: true, workspaceFolders: true},
|
|
639
|
-
textDocument: {
|
|
640
|
-
definition: {linkSupport: true},
|
|
641
|
-
references: {},
|
|
642
|
-
publishDiagnostics: {relatedInformation: false},
|
|
643
|
-
},
|
|
644
|
-
},
|
|
645
|
-
initializationOptions: {
|
|
646
|
-
hostInfo: 'weavatrix',
|
|
647
|
-
disableAutomaticTypingAcquisition: true,
|
|
648
|
-
tsserver: {path: discovered.tsserverPath},
|
|
649
|
-
},
|
|
650
|
-
})
|
|
651
|
-
} catch (error) {
|
|
652
|
-
client?.kill(error)
|
|
653
|
-
throw error
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
return Object.freeze({
|
|
657
|
-
provider: discovered.provider,
|
|
658
|
-
version: discovered.version,
|
|
659
|
-
providerContract: typeScriptLspContract(),
|
|
660
|
-
get typescriptVersion() { return reportedTypeScript?.version || discovered.typescriptVersion },
|
|
661
|
-
get typescriptSource() { return reportedTypeScript?.source || 'configured-bundled-path' },
|
|
662
|
-
async openDocument(relPath, text, languageId = typeScriptLanguageId(relPath)) {
|
|
663
|
-
if (!languageId) throw new TypeError(`Unsupported TypeScript LSP document extension: ${relPath}`)
|
|
664
|
-
return client.openDocument({filePath: relPath, text, languageId})
|
|
665
|
-
},
|
|
666
|
-
references(relPath, position, includeDeclaration = true, referenceTimeoutMs = timeoutMs) {
|
|
667
|
-
return client.references({filePath: relPath, position, includeDeclaration, timeoutMs: referenceTimeoutMs})
|
|
668
|
-
},
|
|
669
|
-
definition(relPath, position) {
|
|
670
|
-
return client.definition({filePath: relPath, position})
|
|
671
|
-
},
|
|
672
|
-
closeDocument(relPath) {
|
|
673
|
-
return client.closeDocument(relPath)
|
|
674
|
-
},
|
|
675
|
-
close(shutdownTimeoutMs = timeoutMs) {
|
|
676
|
-
return client.shutdown({timeoutMs: shutdownTimeoutMs})
|
|
677
|
-
},
|
|
678
|
-
kill() {
|
|
679
|
-
client.kill()
|
|
680
|
-
},
|
|
681
|
-
})
|
|
682
|
-
}
|
|
1
|
+
export {
|
|
2
|
+
TYPESCRIPT_LSP_CAPABILITY_CONTRACT,
|
|
3
|
+
typeScriptLanguageId,
|
|
4
|
+
typeScriptLspAvailability,
|
|
5
|
+
typeScriptLspContract,
|
|
6
|
+
} from './typescript-provider/discovery.js'
|
|
7
|
+
export {classifyTypeScriptReferenceUsage} from './typescript-provider/reference-usage.js'
|
|
8
|
+
export {
|
|
9
|
+
typeScriptConfiguredProjectMembership,
|
|
10
|
+
typeScriptProjectSafety,
|
|
11
|
+
} from './typescript-provider/project-safety.js'
|
|
12
|
+
export {createTypeScriptLspClient} from './typescript-provider/client.js'
|