weavatrix 0.2.3 → 0.2.5
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 +91 -20
- package/SECURITY.md +18 -0
- package/package.json +3 -1
- package/skill/SKILL.md +49 -13
- package/src/analysis/dead-check.js +48 -2
- package/src/analysis/dead-code-review.js +22 -8
- package/src/analysis/duplicates.compute.js +11 -6
- package/src/analysis/git-ref-graph.js +10 -2
- package/src/analysis/hot-path-review.js +228 -0
- package/src/analysis/internal-audit.run.js +36 -0
- package/src/analysis/source-complexity.report.js +5 -0
- package/src/analysis/source-complexity.walk.js +64 -10
- package/src/build-graph.js +49 -10
- package/src/graph/build-worker.js +21 -1
- package/src/graph/builder/lang-java.js +1 -0
- package/src/graph/builder/lang-js.js +24 -0
- package/src/graph/builder/lang-python.js +161 -4
- package/src/graph/freshness-probe.js +3 -3
- package/src/graph/incremental-refresh.js +1 -1
- package/src/graph/internal-builder.barrels.js +33 -4
- package/src/graph/internal-builder.build.js +52 -8
- package/src/mcp/catalog.mjs +14 -11
- package/src/mcp/graph-context.mjs +143 -14
- package/src/mcp/sync-payload.mjs +2 -1
- package/src/mcp/tools-actions.mjs +59 -20
- package/src/mcp/tools-company.mjs +11 -3
- package/src/mcp/tools-graph.mjs +12 -6
- package/src/mcp/tools-health.mjs +112 -11
- package/src/mcp/tools-impact.mjs +24 -6
- package/src/mcp/tools-source.mjs +237 -0
- package/src/mcp-server.mjs +99 -11
- package/src/path-classification.js +2 -2
- package/src/precision/lsp-client.js +682 -0
- package/src/precision/lsp-overlay.js +872 -0
- package/src/precision/symbol-query.js +137 -0
- package/src/precision/typescript-lsp-provider.js +682 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {existsSync, readFileSync, statSync} from 'node:fs'
|
|
2
|
+
import {dirname, resolve} from 'node:path'
|
|
3
|
+
import {atomicWriteFileSync, withFileLock} from '../graph/file-lock.js'
|
|
4
|
+
import {
|
|
5
|
+
buildLspPrecisionOverlay,
|
|
6
|
+
precisionOverlayMatches,
|
|
7
|
+
precisionSemanticInputsMatch,
|
|
8
|
+
} from './lsp-overlay.js'
|
|
9
|
+
|
|
10
|
+
export const SYMBOL_PRECISION_CACHE_V = 1
|
|
11
|
+
export const SYMBOL_PRECISION_CACHE_FILE = 'precision-symbols.json'
|
|
12
|
+
|
|
13
|
+
const MAX_CACHE_ENTRIES = 32
|
|
14
|
+
const MAX_CACHE_BYTES = 8 * 1024 * 1024
|
|
15
|
+
const MAX_CACHE_READ_BYTES = 16 * 1024 * 1024
|
|
16
|
+
const inFlight = new Map()
|
|
17
|
+
|
|
18
|
+
const boundedInteger = (value, fallback, minimum, maximum) => {
|
|
19
|
+
const number = Number(value)
|
|
20
|
+
return Math.max(minimum, Math.min(maximum, Number.isFinite(number) ? Math.floor(number) : fallback))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function symbolPrecisionCachePath(graphPath) {
|
|
24
|
+
return resolve(dirname(graphPath), SYMBOL_PRECISION_CACHE_FILE)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function loadRawGraph(graphPath) {
|
|
28
|
+
const graph = JSON.parse(readFileSync(graphPath, 'utf8'))
|
|
29
|
+
if (!graph || !Array.isArray(graph.nodes) || !Array.isArray(graph.links)) {
|
|
30
|
+
throw new Error('the active graph is not a valid Weavatrix graph')
|
|
31
|
+
}
|
|
32
|
+
return graph
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function emptyCache() {
|
|
36
|
+
return {symbolPrecisionCacheV: SYMBOL_PRECISION_CACHE_V, entries: []}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function readCache(path) {
|
|
40
|
+
if (!existsSync(path)) return emptyCache()
|
|
41
|
+
try {
|
|
42
|
+
if (statSync(path).size > MAX_CACHE_READ_BYTES) return emptyCache()
|
|
43
|
+
const value = JSON.parse(readFileSync(path, 'utf8'))
|
|
44
|
+
if (Number(value?.symbolPrecisionCacheV) !== SYMBOL_PRECISION_CACHE_V || !Array.isArray(value.entries)) return emptyCache()
|
|
45
|
+
return {symbolPrecisionCacheV: SYMBOL_PRECISION_CACHE_V, entries: value.entries.slice(0, MAX_CACHE_ENTRIES)}
|
|
46
|
+
} catch {
|
|
47
|
+
return emptyCache()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function writeBoundedCache(path, entries) {
|
|
52
|
+
const kept = entries
|
|
53
|
+
.filter((entry) => entry && typeof entry === 'object')
|
|
54
|
+
.sort((left, right) => Number(right.usedAt) - Number(left.usedAt))
|
|
55
|
+
.slice(0, MAX_CACHE_ENTRIES)
|
|
56
|
+
let body
|
|
57
|
+
do {
|
|
58
|
+
body = JSON.stringify({symbolPrecisionCacheV: SYMBOL_PRECISION_CACHE_V, entries: kept})
|
|
59
|
+
if (Buffer.byteLength(body) <= MAX_CACHE_BYTES || !kept.length) break
|
|
60
|
+
kept.pop()
|
|
61
|
+
} while (true)
|
|
62
|
+
atomicWriteFileSync(path, body, 'utf8')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function cacheMatch(entry, {targetId, graph, repoRoot, request}) {
|
|
66
|
+
if (String(entry?.targetId || '') !== targetId || !entry.overlay) return false
|
|
67
|
+
if (!precisionOverlayMatches(entry.overlay, graph, {request})) return false
|
|
68
|
+
return precisionSemanticInputsMatch(entry.overlay, repoRoot, graph)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function storeEntry(path, entry) {
|
|
72
|
+
await withFileLock(`${path}.lock`, async () => {
|
|
73
|
+
const current = readCache(path)
|
|
74
|
+
const entries = current.entries.filter((item) => String(item?.targetId || '') !== entry.targetId)
|
|
75
|
+
entries.unshift(entry)
|
|
76
|
+
writeBoundedCache(path, entries)
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function cacheable(overlay) {
|
|
81
|
+
if (overlay?.state === 'COMPLETE') return true
|
|
82
|
+
return overlay?.state === 'PARTIAL'
|
|
83
|
+
&& overlay?.reason === 'semantic precision stopped at a configured safety limit'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function querySymbolPrecision({
|
|
87
|
+
repoRoot,
|
|
88
|
+
graphPath,
|
|
89
|
+
targetId,
|
|
90
|
+
maxReferences = 1_000,
|
|
91
|
+
timeoutMs = 30_000,
|
|
92
|
+
clientFactory,
|
|
93
|
+
} = {}) {
|
|
94
|
+
if (!repoRoot || !graphPath || !targetId) throw new Error('symbol precision requires repoRoot, graphPath, and targetId')
|
|
95
|
+
const boundedReferences = boundedInteger(maxReferences, 1_000, 1, 5_000)
|
|
96
|
+
const boundedTimeout = boundedInteger(timeoutMs, 30_000, 1_000, 60_000)
|
|
97
|
+
const request = {maxSymbols: 1, maxReferences: boundedReferences, maxLinks: boundedReferences}
|
|
98
|
+
const rawGraph = loadRawGraph(graphPath)
|
|
99
|
+
const graph = rawGraph.graphPrecisionMode === 'off' ? {...rawGraph, graphPrecisionMode: 'lsp'} : rawGraph
|
|
100
|
+
const target = graph.nodes.find((node) => String(node?.id || '') === String(targetId))
|
|
101
|
+
if (!target) throw new Error('the selected symbol is not present in the active raw graph')
|
|
102
|
+
if (!target.selection_start || !/\.(?:[cm]?[jt]sx?)$/i.test(String(target.source_file || ''))) {
|
|
103
|
+
throw new Error('exact symbol precision currently supports JavaScript and TypeScript symbols with source selections')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const id = String(targetId)
|
|
107
|
+
const cachePath = symbolPrecisionCachePath(graphPath)
|
|
108
|
+
const startedAt = Date.now()
|
|
109
|
+
const cached = readCache(cachePath).entries.find((entry) => cacheMatch(entry, {targetId: id, graph, repoRoot, request}))
|
|
110
|
+
if (cached) return {overlay: cached.overlay, cached: true, elapsedMs: Date.now() - startedAt, cachePath}
|
|
111
|
+
|
|
112
|
+
const flightKey = `${graphPath}\0${graph.graphRevision || ''}\0${id}\0${boundedReferences}`
|
|
113
|
+
if (inFlight.has(flightKey)) return inFlight.get(flightKey)
|
|
114
|
+
const operation = (async () => {
|
|
115
|
+
const overlay = await buildLspPrecisionOverlay({
|
|
116
|
+
repoRoot,
|
|
117
|
+
graph,
|
|
118
|
+
mode: 'lsp',
|
|
119
|
+
maxSymbols: 1,
|
|
120
|
+
maxReferences: boundedReferences,
|
|
121
|
+
maxLinks: boundedReferences,
|
|
122
|
+
timeoutMs: boundedTimeout,
|
|
123
|
+
targetIds: [id],
|
|
124
|
+
clientFactory,
|
|
125
|
+
})
|
|
126
|
+
if (cacheable(overlay)) {
|
|
127
|
+
await storeEntry(cachePath, {targetId: id, usedAt: Date.now(), overlay})
|
|
128
|
+
}
|
|
129
|
+
return {overlay, cached: false, elapsedMs: Date.now() - startedAt, cachePath}
|
|
130
|
+
})()
|
|
131
|
+
inFlight.set(flightKey, operation)
|
|
132
|
+
try {
|
|
133
|
+
return await operation
|
|
134
|
+
} finally {
|
|
135
|
+
inFlight.delete(flightKey)
|
|
136
|
+
}
|
|
137
|
+
}
|