dsh-plugin-codegraph-tree-sitter 0.5.2 → 0.6.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/lib/index.js +34 -5
- package/lib/rust-engine.d.ts +49 -11
- package/lib/rust-engine.js +77 -11
- package/lib/walk.js +1 -1
- package/package.json +9 -2
package/lib/index.js
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* this package ships with.
|
|
28
28
|
* @module dsh-plugin-codegraph-tree-sitter
|
|
29
29
|
*/
|
|
30
|
-
import { readFileSync } from 'node:fs';
|
|
30
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
31
31
|
import { readdir } from 'node:fs/promises';
|
|
32
32
|
import { stat } from 'node:fs/promises';
|
|
33
33
|
import { join, posix } from 'node:path';
|
|
@@ -210,13 +210,26 @@ export function apply(ctx, config) {
|
|
|
210
210
|
if (resolved.autoIndexOnSession) {
|
|
211
211
|
// Registered inside this effect so the listener follows the plugin's lifetime: unloading the
|
|
212
212
|
// plugin must stop new sessions from scheduling work this instance can no longer run.
|
|
213
|
-
yield agentEvents.on('agent/created', payload => autoIndexOnSession(ctx, resolved, lastAutoRun, payload));
|
|
213
|
+
yield agentEvents.on('agent/created', payload => autoIndexOnSession(ctx, resolved, lastAutoRun, payload, ensureWatching));
|
|
214
214
|
}
|
|
215
215
|
yield ctx.codegraph.registerIndexer(indexer);
|
|
216
216
|
}, 'codegraph-tree-sitter');
|
|
217
217
|
}
|
|
218
218
|
/** Stable job kind of an automatic session-start index, used for retention and diagnostics. */
|
|
219
219
|
const AUTO_INDEX_JOB_KIND = 'codegraph-index';
|
|
220
|
+
/**
|
|
221
|
+
* Whether a directory is the harness's own home rather than a project checkout.
|
|
222
|
+
*
|
|
223
|
+
* `sessions/` and `profiles/` together are what makes it one: either alone is a plausible folder
|
|
224
|
+
* name inside an ordinary repository, and requiring both keeps a real workspace eligible. The check
|
|
225
|
+
* exists because a DSH home is by construction tens of thousands of files — indexing it can only
|
|
226
|
+
* ever hit the session-start size guard, so queueing the job produces a failure and nothing else.
|
|
227
|
+
* @param root - the absolute workspace path.
|
|
228
|
+
* @returns true when the directory holds the harness's own state.
|
|
229
|
+
*/
|
|
230
|
+
function isHarnessHome(root) {
|
|
231
|
+
return existsSync(join(root, 'sessions')) && existsSync(join(root, 'profiles'));
|
|
232
|
+
}
|
|
220
233
|
/**
|
|
221
234
|
* Index a new session's workspace in the background — when nothing has indexed it yet, or when what
|
|
222
235
|
* is there has gone stale.
|
|
@@ -234,13 +247,20 @@ const AUTO_INDEX_JOB_KIND = 'codegraph-index';
|
|
|
234
247
|
* @param lastAutoRun - when this instance last started an automatic index for each root.
|
|
235
248
|
* @param payload - the `agent/created` dispatch payload.
|
|
236
249
|
*/
|
|
237
|
-
function autoIndexOnSession(ctx, config, lastAutoRun, payload) {
|
|
250
|
+
function autoIndexOnSession(ctx, config, lastAutoRun, payload, ensureWatching) {
|
|
238
251
|
try {
|
|
239
252
|
const agent = payload.agent;
|
|
240
253
|
const root = agent?.session?.header?.cwd;
|
|
241
254
|
// No workspace means no root to claim: an agent created without a `cwd` has nothing to index.
|
|
242
255
|
if (agent === undefined || !root)
|
|
243
256
|
return;
|
|
257
|
+
// DSH's own home directory is not a code repository — `sessions/`, `attachments/`, the index
|
|
258
|
+
// databases and the installed plugins below it are a program's state. It is also tens of
|
|
259
|
+
// thousands of files, so every session opened there queued a job whose only possible outcome was
|
|
260
|
+
// the size guard failing. Skipping here keeps the job list honest: what is not a candidate never
|
|
261
|
+
// becomes a failure the user cannot act on.
|
|
262
|
+
if (isHarnessHome(root))
|
|
263
|
+
return;
|
|
244
264
|
const now = Date.now();
|
|
245
265
|
// A burst of sessions opened on one workspace must not queue the same rebuild repeatedly.
|
|
246
266
|
const previous = lastAutoRun.get(root);
|
|
@@ -250,8 +270,14 @@ function autoIndexOnSession(ctx, config, lastAutoRun, payload) {
|
|
|
250
270
|
// or old one means the graph is missing or behind. Checked before the job lookup so a composition
|
|
251
271
|
// without background jobs never pays for the probe.
|
|
252
272
|
const indexedAt = readStatusIndexedAt(root);
|
|
253
|
-
if (indexedAt !== undefined && now - indexedAt < config.autoRefreshAfterMs)
|
|
273
|
+
if (indexedAt !== undefined && now - indexedAt < config.autoRefreshAfterMs) {
|
|
274
|
+
// The graph is current, so no build is warranted — but watching is process-local state and
|
|
275
|
+
// this is the first time this process has seen this workspace. Without this call, a restart
|
|
276
|
+
// followed by a session in an up-to-date workspace would leave it unwatched until the sidecar
|
|
277
|
+
// aged out, and every edit until then would silently not reach the graph.
|
|
278
|
+
ensureWatching(root);
|
|
254
279
|
return;
|
|
280
|
+
}
|
|
255
281
|
// `ctx.get` rather than an `inject` entry: `jobs` is a host composition choice, and requiring it in
|
|
256
282
|
// `inject` would hide this whole indexer from a profile that runs no background jobs.
|
|
257
283
|
const jobs = ctx.get('jobs');
|
|
@@ -361,7 +387,10 @@ function runAutoIndexJob(ctx, root, config, handle) {
|
|
|
361
387
|
handle.append(`Skipped automatic indexing of ${root}: it holds at least ${String(oversized)} candidate ` +
|
|
362
388
|
`source files, past the ${String(config.autoIndexMaxFiles)}-file session-start limit. ` +
|
|
363
389
|
`Run codegraph_index yourself if you want a graph for it.\n`);
|
|
364
|
-
|
|
390
|
+
// `completed`, not `failed`: nothing went wrong. The guard decided this workspace is not a
|
|
391
|
+
// candidate for an automatic run, and a user reading the job list must be able to tell that
|
|
392
|
+
// apart from a build that actually broke.
|
|
393
|
+
return { status: 'completed', detail: 'skipped: workspace too large for automatic indexing' };
|
|
365
394
|
}
|
|
366
395
|
const report = await ctx.codegraph.index(root, controller.signal);
|
|
367
396
|
handle.append(`Indexed ${root}: ${report.filesIndexed} files, ${report.nodeCount} symbols, ` +
|
package/lib/rust-engine.d.ts
CHANGED
|
@@ -18,9 +18,9 @@ import type { FileExtraction } from './extract.ts';
|
|
|
18
18
|
export declare const RUST_LANGUAGES: readonly string[];
|
|
19
19
|
/**
|
|
20
20
|
* Files handed to one addon call. The addon parses a batch across every core it can, so a large batch
|
|
21
|
-
* is what makes it fast
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* is what makes it fast. The call runs on libuv's thread pool (see {@link RustEngine}), so a batch no
|
|
22
|
+
* longer stalls the event loop; the bound now keeps one promise's lifetime — and the file text it
|
|
23
|
+
* holds in flight — reasonable, instead of capping a blocking call.
|
|
24
24
|
*/
|
|
25
25
|
export declare const RUST_BATCH_SIZE = 200;
|
|
26
26
|
/** One file as the addon takes it. */
|
|
@@ -39,11 +39,16 @@ export interface RustExtractInput {
|
|
|
39
39
|
export interface RustEngine {
|
|
40
40
|
/**
|
|
41
41
|
* Extract definitions, calls, imports, and heritage for a batch of files.
|
|
42
|
+
*
|
|
43
|
+
* Answers a promise because the addon's body runs on libuv's thread pool: a synchronous native call
|
|
44
|
+
* blocked the host's event loop for the whole batch, which is what froze the desktop when automatic
|
|
45
|
+
* indexing ran inside the renderer. Callers `await` it; an older addon that still returns a string
|
|
46
|
+
* keeps working, because `await` on a non-promise is the value itself.
|
|
42
47
|
* @param inputs - the files to extract; inputs in an unsupported language are silently skipped.
|
|
43
48
|
* @param rulesJson - the `LanguageSpec` table, as JSON.
|
|
44
|
-
* @returns
|
|
49
|
+
* @returns a promise of JSON `{ results: [{ path, extraction }], supported: string[] }`.
|
|
45
50
|
*/
|
|
46
|
-
extractWorkspace(inputs: RustExtractInput[], rulesJson: string): string
|
|
51
|
+
extractWorkspace(inputs: RustExtractInput[], rulesJson: string): Promise<string>;
|
|
47
52
|
}
|
|
48
53
|
/** Options for {@link loadRustEngine}. */
|
|
49
54
|
export interface LoadRustEngineOptions {
|
|
@@ -53,14 +58,47 @@ export interface LoadRustEngineOptions {
|
|
|
53
58
|
*/
|
|
54
59
|
readonly modulePath?: string;
|
|
55
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Where the native package for this platform lives, when npm installed one.
|
|
63
|
+
*
|
|
64
|
+
* The addon ships as one optional dependency per platform (`optionalDependencies` in this package's
|
|
65
|
+
* manifest), so a `pnpm add` pulls exactly one binary — the one the running machine can `dlopen` —
|
|
66
|
+
* instead of every platform's. npm's `os`/`cpu` fields do the selection; a platform this project does
|
|
67
|
+
* not build for simply never installs one and the walk falls back to JS.
|
|
68
|
+
* @param platform - `process.platform`.
|
|
69
|
+
* @param arch - `process.arch`.
|
|
70
|
+
* @returns the package name, or `undefined` for a platform with no published build.
|
|
71
|
+
*/
|
|
72
|
+
export declare function nativePackageName(platform: string, arch: string): string | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Resolve this platform's optional native package to its `.node` entry, if it is installed.
|
|
75
|
+
*
|
|
76
|
+
* `require.resolve` is the only thing that knows where a dependency actually landed — pnpm's store,
|
|
77
|
+
* npm's flat tree, a hoisted profile — so the path is asked for rather than constructed. A missing
|
|
78
|
+
* package, or one whose `main` does not resolve, is `undefined`: that is the documented "no addon"
|
|
79
|
+
* answer, not an error.
|
|
80
|
+
* @param platform - `process.platform`.
|
|
81
|
+
* @param arch - `process.arch`.
|
|
82
|
+
* @param resolve - module resolution, injectable so a test does not depend on what this machine has
|
|
83
|
+
* installed.
|
|
84
|
+
* @returns the absolute path to the addon, or `undefined`.
|
|
85
|
+
*/
|
|
86
|
+
export declare function nativePackageModulePath(platform: string, arch: string, resolve?: (name: string) => string): string | undefined;
|
|
56
87
|
/**
|
|
57
88
|
* Every place the addon may live, most specific first.
|
|
58
89
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* and
|
|
63
|
-
* installed or checked out.
|
|
90
|
+
* Three layouts matter: a package that ships the binary puts it in `artifacts/` beside `lib/` (the
|
|
91
|
+
* shape an older hand-copied install has), a repository checkout builds it under
|
|
92
|
+
* `crates/indexer-rust/artifacts/` (three directories up from both `src/` and `lib/`, which sit at the
|
|
93
|
+
* same depth), and an npm install carries it in the platform's optional dependency. Trying all three
|
|
94
|
+
* means one module works whether it was installed, copied in, or checked out.
|
|
95
|
+
* @param base - the importing module's URL; parameterized so a test can point at a scratch tree.
|
|
96
|
+
* @param fromPackage - this platform's optional-dependency path, or `undefined` when there is none.
|
|
97
|
+
* @returns absolute paths to try, in order.
|
|
98
|
+
*/
|
|
99
|
+
export declare function modulePathsFor(base: string, fromPackage: string | undefined): string[];
|
|
100
|
+
/**
|
|
101
|
+
* The paths production should try, with this platform's optional package worked out for it.
|
|
64
102
|
* @param base - the importing module's URL; parameterized so a test can point at a scratch tree.
|
|
65
103
|
* @returns absolute paths to try, in order.
|
|
66
104
|
*/
|
|
@@ -89,7 +127,7 @@ export declare function loadRustEngine(options?: LoadRustEngineOptions): RustEng
|
|
|
89
127
|
* @returns one extraction per path the addon answered for, or `undefined` when no answer can be
|
|
90
128
|
* trusted at all — no addon, a thrown call, or JSON that is not the documented shape.
|
|
91
129
|
*/
|
|
92
|
-
export declare function extractWithRust(files: readonly RustExtractInput[], engine: RustEngine | undefined): Map<string, FileExtraction> | undefined
|
|
130
|
+
export declare function extractWithRust(files: readonly RustExtractInput[], engine: RustEngine | undefined): Promise<Map<string, FileExtraction> | undefined>;
|
|
93
131
|
/**
|
|
94
132
|
* Install a stand-in for the addon the walk resolves, or restore the real one.
|
|
95
133
|
*
|
package/lib/rust-engine.js
CHANGED
|
@@ -20,30 +20,93 @@ import { LANGUAGE_TABLE } from "./languages.js";
|
|
|
20
20
|
export const RUST_LANGUAGES = ['typescript', 'tsx', 'javascript', 'jsx'];
|
|
21
21
|
/**
|
|
22
22
|
* Files handed to one addon call. The addon parses a batch across every core it can, so a large batch
|
|
23
|
-
* is what makes it fast
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* is what makes it fast. The call runs on libuv's thread pool (see {@link RustEngine}), so a batch no
|
|
24
|
+
* longer stalls the event loop; the bound now keeps one promise's lifetime — and the file text it
|
|
25
|
+
* holds in flight — reasonable, instead of capping a blocking call.
|
|
26
26
|
*/
|
|
27
27
|
export const RUST_BATCH_SIZE = 200;
|
|
28
28
|
const require = createRequire(import.meta.url);
|
|
29
|
+
/**
|
|
30
|
+
* Where the native package for this platform lives, when npm installed one.
|
|
31
|
+
*
|
|
32
|
+
* The addon ships as one optional dependency per platform (`optionalDependencies` in this package's
|
|
33
|
+
* manifest), so a `pnpm add` pulls exactly one binary — the one the running machine can `dlopen` —
|
|
34
|
+
* instead of every platform's. npm's `os`/`cpu` fields do the selection; a platform this project does
|
|
35
|
+
* not build for simply never installs one and the walk falls back to JS.
|
|
36
|
+
* @param platform - `process.platform`.
|
|
37
|
+
* @param arch - `process.arch`.
|
|
38
|
+
* @returns the package name, or `undefined` for a platform with no published build.
|
|
39
|
+
*/
|
|
40
|
+
export function nativePackageName(platform, arch) {
|
|
41
|
+
const suffix = NATIVE_PACKAGES[`${platform}-${arch}`];
|
|
42
|
+
return suffix === undefined ? undefined : `dsh-plugin-codegraph-indexer-${suffix}`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The suffix each published platform package carries, keyed by `platform-arch`.
|
|
46
|
+
*
|
|
47
|
+
* A suffix is not mechanical: the Linux builds are `-gnu` because a musl one would have to be a
|
|
48
|
+
* separate artifact, while Windows spells its ABI out. Keeping the table explicit is what stops this
|
|
49
|
+
* from silently computing a package name nobody publishes.
|
|
50
|
+
*/
|
|
51
|
+
const NATIVE_PACKAGES = {
|
|
52
|
+
'darwin-arm64': 'darwin-arm64',
|
|
53
|
+
'darwin-x64': 'darwin-x64',
|
|
54
|
+
'linux-x64': 'linux-x64-gnu',
|
|
55
|
+
'linux-arm64': 'linux-arm64-gnu',
|
|
56
|
+
'win32-x64': 'win32-x64-msvc',
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Resolve this platform's optional native package to its `.node` entry, if it is installed.
|
|
60
|
+
*
|
|
61
|
+
* `require.resolve` is the only thing that knows where a dependency actually landed — pnpm's store,
|
|
62
|
+
* npm's flat tree, a hoisted profile — so the path is asked for rather than constructed. A missing
|
|
63
|
+
* package, or one whose `main` does not resolve, is `undefined`: that is the documented "no addon"
|
|
64
|
+
* answer, not an error.
|
|
65
|
+
* @param platform - `process.platform`.
|
|
66
|
+
* @param arch - `process.arch`.
|
|
67
|
+
* @param resolve - module resolution, injectable so a test does not depend on what this machine has
|
|
68
|
+
* installed.
|
|
69
|
+
* @returns the absolute path to the addon, or `undefined`.
|
|
70
|
+
*/
|
|
71
|
+
export function nativePackageModulePath(platform, arch, resolve = require.resolve) {
|
|
72
|
+
const name = nativePackageName(platform, arch);
|
|
73
|
+
if (name === undefined)
|
|
74
|
+
return undefined;
|
|
75
|
+
try {
|
|
76
|
+
return resolve(name);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
29
82
|
/**
|
|
30
83
|
* Every place the addon may live, most specific first.
|
|
31
84
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* and
|
|
36
|
-
* installed or checked out.
|
|
85
|
+
* Three layouts matter: a package that ships the binary puts it in `artifacts/` beside `lib/` (the
|
|
86
|
+
* shape an older hand-copied install has), a repository checkout builds it under
|
|
87
|
+
* `crates/indexer-rust/artifacts/` (three directories up from both `src/` and `lib/`, which sit at the
|
|
88
|
+
* same depth), and an npm install carries it in the platform's optional dependency. Trying all three
|
|
89
|
+
* means one module works whether it was installed, copied in, or checked out.
|
|
37
90
|
* @param base - the importing module's URL; parameterized so a test can point at a scratch tree.
|
|
91
|
+
* @param fromPackage - this platform's optional-dependency path, or `undefined` when there is none.
|
|
38
92
|
* @returns absolute paths to try, in order.
|
|
39
93
|
*/
|
|
40
|
-
export function
|
|
94
|
+
export function modulePathsFor(base, fromPackage) {
|
|
41
95
|
const artifact = `codegraph-indexer.${process.platform}-${process.arch}.node`;
|
|
42
96
|
return [
|
|
43
97
|
fileURLToPath(new URL(`../artifacts/${artifact}`, base)),
|
|
44
98
|
fileURLToPath(new URL(`../../../crates/indexer-rust/artifacts/${artifact}`, base)),
|
|
99
|
+
...fromPackage === undefined ? [] : [fromPackage],
|
|
45
100
|
];
|
|
46
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* The paths production should try, with this platform's optional package worked out for it.
|
|
104
|
+
* @param base - the importing module's URL; parameterized so a test can point at a scratch tree.
|
|
105
|
+
* @returns absolute paths to try, in order.
|
|
106
|
+
*/
|
|
107
|
+
export function candidateModulePaths(base = import.meta.url) {
|
|
108
|
+
return modulePathsFor(base, nativePackageModulePath(process.platform, process.arch));
|
|
109
|
+
}
|
|
47
110
|
/** Addons already loaded, keyed by path, so a test's stand-in never shadows the real artifact. */
|
|
48
111
|
const loaded = new Map();
|
|
49
112
|
/**
|
|
@@ -149,7 +212,7 @@ function rustRulesJson() {
|
|
|
149
212
|
* @returns one extraction per path the addon answered for, or `undefined` when no answer can be
|
|
150
213
|
* trusted at all — no addon, a thrown call, or JSON that is not the documented shape.
|
|
151
214
|
*/
|
|
152
|
-
export function extractWithRust(files, engine) {
|
|
215
|
+
export async function extractWithRust(files, engine) {
|
|
153
216
|
const supported = files.filter(file => RUST_LANGUAGES.includes(file.language));
|
|
154
217
|
if (supported.length === 0)
|
|
155
218
|
return new Map();
|
|
@@ -159,7 +222,10 @@ export function extractWithRust(files, engine) {
|
|
|
159
222
|
try {
|
|
160
223
|
for (let start = 0; start < supported.length; start += RUST_BATCH_SIZE) {
|
|
161
224
|
const batch = supported.slice(start, start + RUST_BATCH_SIZE);
|
|
162
|
-
|
|
225
|
+
// Awaited one batch at a time: the addon already parses a batch across every core, so
|
|
226
|
+
// overlapping batches would only compete for the same threads while multiplying the text held
|
|
227
|
+
// in flight.
|
|
228
|
+
const answer = JSON.parse(await engine.extractWorkspace(batch, rustRulesJson()));
|
|
163
229
|
if (!isRecord(answer) || !Array.isArray(answer.results))
|
|
164
230
|
return undefined;
|
|
165
231
|
for (const entry of answer.results) {
|
package/lib/walk.js
CHANGED
|
@@ -223,7 +223,7 @@ async function extractPending(pending, config, signal) {
|
|
|
223
223
|
// those itself (`RUST_LANGUAGES`), so the walk never keeps a second copy of that list in sync.
|
|
224
224
|
const rusted = engine === undefined
|
|
225
225
|
? new Map()
|
|
226
|
-
: extractWithRust(pending.map(toRustInput), engine) ?? new Map();
|
|
226
|
+
: await extractWithRust(pending.map(toRustInput), engine) ?? new Map();
|
|
227
227
|
const extracted = new Array(pending.length);
|
|
228
228
|
const fallback = [];
|
|
229
229
|
for (const [index, file] of pending.entries()) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-codegraph-tree-sitter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Self-built code-graph indexer for DeepSeek Harness — parses a workspace with web-tree-sitter and writes schema version 4 to .codegraph/codegraph.db, the same on-disk format the codegraph CLI writes, so no external indexer is required",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -51,13 +51,20 @@
|
|
|
51
51
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
52
52
|
"tree-sitter-wasms": "^0.1.13",
|
|
53
53
|
"web-tree-sitter": "^0.25.10",
|
|
54
|
-
"dsh-plugin-codegraph-service": "^0.
|
|
54
|
+
"dsh-plugin-codegraph-service": "^0.6.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"@deepseek-ai/cordis": ">=4.0.1",
|
|
58
58
|
"@deepseek-ai/dsh-fs": ">=0.1.0-rc.6",
|
|
59
59
|
"@deepseek-ai/dsh-invariants": ">=0.1.0-rc.6"
|
|
60
60
|
},
|
|
61
|
+
"optionalDependencies": {
|
|
62
|
+
"dsh-plugin-codegraph-indexer-darwin-arm64": "0.6.0",
|
|
63
|
+
"dsh-plugin-codegraph-indexer-darwin-x64": "0.6.0",
|
|
64
|
+
"dsh-plugin-codegraph-indexer-linux-x64-gnu": "0.6.0",
|
|
65
|
+
"dsh-plugin-codegraph-indexer-linux-arm64-gnu": "0.6.0",
|
|
66
|
+
"dsh-plugin-codegraph-indexer-win32-x64-msvc": "0.6.0"
|
|
67
|
+
},
|
|
61
68
|
"devDependencies": {
|
|
62
69
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
63
70
|
"@deepseek-ai/dsh-invariants": "0.1.0-rc.6"
|