dsh-plugin-codegraph-tree-sitter 0.5.1 → 0.5.3

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.d.ts CHANGED
@@ -73,6 +73,17 @@ export declare const DEFAULT_EXCLUDE: string[];
73
73
  export declare const DEFAULT_MAX_FILE_BYTES = 2000000;
74
74
  /** Default ceiling on how many files one run indexes. */
75
75
  export declare const DEFAULT_MAX_FILES = 50000;
76
+ /**
77
+ * How many candidate source files a workspace may contain before a **session-start** index declines
78
+ * to run (default 8,000).
79
+ *
80
+ * `maxFiles` above is the bound for a run someone asked for; this one guards the run nobody asked
81
+ * for. A session opened with `cwd` pointing at a home directory or a tool's own data directory can
82
+ * cover tens of thousands of files — a real case reached 21,752 under `~/.dsh`, under the 50,000
83
+ * ceiling, and the Tree-sitter WASM heap died mid-parse while the desktop froze. Declining early
84
+ * costs one `readdir` pass; declining late costs the whole machine.
85
+ */
86
+ export declare const DEFAULT_AUTO_INDEX_MAX_FILES = 8000;
76
87
  /** Default number of files parsed concurrently. */
77
88
  export declare const DEFAULT_CONCURRENCY = 4;
78
89
  /** Default quiet period after the last change before a watch-triggered refresh runs. */
@@ -175,6 +186,8 @@ export interface Config {
175
186
  * Requires the host's `jobs` service: the build is never run synchronously inside session creation,
176
187
  * so a composition without background jobs simply schedules nothing.
177
188
  */
189
+ /** Cap on candidate source files a session-start scan may find before it declines to index. */
190
+ autoIndexMaxFiles?: number;
178
191
  autoIndexOnSession?: boolean;
179
192
  /**
180
193
  * How stale an existing index may be before a new session refreshes it in the background (default
package/lib/index.js CHANGED
@@ -28,12 +28,13 @@
28
28
  * @module dsh-plugin-codegraph-tree-sitter
29
29
  */
30
30
  import { readFileSync } from 'node:fs';
31
+ import { readdir } from 'node:fs/promises';
31
32
  import { stat } from 'node:fs/promises';
32
- import { join } from 'node:path';
33
+ import { join, posix } from 'node:path';
33
34
  import z from '@deepseek-ai/schemastery';
34
35
  import { CodegraphIndexerId, INDEX_STATUS_RELATIVE_PATH } from 'dsh-plugin-codegraph-service';
35
- import { LANGUAGE_TABLE } from "./languages.js";
36
- import { walkAndExtract } from "./walk.js";
36
+ import { LANGUAGE_TABLE, languageFor } from "./languages.js";
37
+ import { isExcluded, walkAndExtract } from "./walk.js";
37
38
  import { writeParseCache } from "./cache.js";
38
39
  import { resolveWorkspace } from "./resolve.js";
39
40
  import { writeGraph } from "./schema.js";
@@ -77,6 +78,17 @@ export const DEFAULT_EXCLUDE = ['node_modules', 'dist', 'build', 'coverage', '.g
77
78
  export const DEFAULT_MAX_FILE_BYTES = 2_000_000;
78
79
  /** Default ceiling on how many files one run indexes. */
79
80
  export const DEFAULT_MAX_FILES = 50_000;
81
+ /**
82
+ * How many candidate source files a workspace may contain before a **session-start** index declines
83
+ * to run (default 8,000).
84
+ *
85
+ * `maxFiles` above is the bound for a run someone asked for; this one guards the run nobody asked
86
+ * for. A session opened with `cwd` pointing at a home directory or a tool's own data directory can
87
+ * cover tens of thousands of files — a real case reached 21,752 under `~/.dsh`, under the 50,000
88
+ * ceiling, and the Tree-sitter WASM heap died mid-parse while the desktop froze. Declining early
89
+ * costs one `readdir` pass; declining late costs the whole machine.
90
+ */
91
+ export const DEFAULT_AUTO_INDEX_MAX_FILES = 8_000;
80
92
  /** Default number of files parsed concurrently. */
81
93
  export const DEFAULT_CONCURRENCY = 4;
82
94
  /** Default quiet period after the last change before a watch-triggered refresh runs. */
@@ -100,6 +112,8 @@ export const Config = z.object({
100
112
  watch: z.boolean().default(true),
101
113
  watchDebounceMs: z.number().default(DEFAULT_WATCH_DEBOUNCE_MS),
102
114
  maxWatchedDirectories: z.number().default(DEFAULT_MAX_WATCHED_DIRECTORIES),
115
+ /** Cap on candidate source files a session-start scan may find before it declines to index. */
116
+ autoIndexMaxFiles: z.number().default(DEFAULT_AUTO_INDEX_MAX_FILES),
103
117
  autoIndexOnSession: z.boolean().default(true),
104
118
  autoRefreshAfterMs: z.number().default(DEFAULT_AUTO_REFRESH_AFTER_MS),
105
119
  });
@@ -248,7 +262,7 @@ function autoIndexOnSession(ctx, config, lastAutoRun, payload) {
248
262
  owner: agent.id,
249
263
  kind: AUTO_INDEX_JOB_KIND,
250
264
  label: `codegraph auto index ${root}`,
251
- run: handle => runAutoIndexJob(ctx, root, handle),
265
+ run: handle => runAutoIndexJob(ctx, root, config, handle),
252
266
  });
253
267
  // Marked only after the registry accepted the job, so a transient refusal leaves the workspace
254
268
  // eligible for the next session that opens in it.
@@ -260,6 +274,53 @@ function autoIndexOnSession(ctx, config, lastAutoRun, payload) {
260
274
  console.error(`codegraph-tree-sitter: automatic indexing for a new session failed: ${String(error)}`);
261
275
  }
262
276
  }
277
+ /**
278
+ * How many candidate source files `root` holds, when that is past the session-start limit.
279
+ *
280
+ * Deliberately a **bounded directory walk that never reads a file**: it counts what `discover` would
281
+ * hand the parser and stops one past the limit. The alternative — starting the real run and bailing
282
+ * when memory runs out — is what froze the machine this guard exists to prevent.
283
+ * @param root - absolute workspace path.
284
+ * @param config - the resolved plugin configuration (for the limit and the exclude list).
285
+ * @param signal - aborts the walk.
286
+ * @returns the count once it exceeds the limit, or `undefined` when the workspace is small enough.
287
+ */
288
+ async function exceedsAutoIndexLimit(root, config, signal) {
289
+ const excluded = new Set(config.exclude);
290
+ const limit = config.autoIndexMaxFiles;
291
+ let count = 0;
292
+ async function scan(relativeDir) {
293
+ signal.throwIfAborted();
294
+ let entries;
295
+ try {
296
+ entries = await readdir(posix.join(root, relativeDir), { withFileTypes: true });
297
+ }
298
+ catch {
299
+ // An unreadable directory contributes nothing to a count that exists to protect against size.
300
+ return false;
301
+ }
302
+ for (const entry of entries) {
303
+ const relativePath = relativeDir === '' ? entry.name : `${relativeDir}/${entry.name}`;
304
+ if (isExcluded(entry.name, relativePath, entry.isDirectory(), excluded, []))
305
+ continue;
306
+ if (entry.isDirectory()) {
307
+ if (await scan(relativePath))
308
+ return true;
309
+ continue;
310
+ }
311
+ if (!entry.isFile())
312
+ continue;
313
+ const spec = languageFor(posix.extname(entry.name));
314
+ if (spec === undefined)
315
+ continue;
316
+ count += 1;
317
+ if (count > limit)
318
+ return true;
319
+ }
320
+ return false;
321
+ }
322
+ return (await scan('')) ? count : undefined;
323
+ }
263
324
  /**
264
325
  * When the workspace's sidecar says its index was built.
265
326
  * @param root - the workspace root.
@@ -289,10 +350,19 @@ function readStatusIndexedAt(root) {
289
350
  * @param handle - the job's producer face, for the job's live log.
290
351
  * @returns the job's cancellation hook and settlement promise.
291
352
  */
292
- function runAutoIndexJob(ctx, root, handle) {
353
+ function runAutoIndexJob(ctx, root, config, handle) {
293
354
  const controller = new AbortController();
294
355
  const done = (async () => {
295
356
  try {
357
+ const oversized = await exceedsAutoIndexLimit(root, config, controller.signal);
358
+ if (oversized !== undefined) {
359
+ // Declining here is the whole point of the check: parsing this workspace would exhaust the
360
+ // WASM heap and take the desktop down with it, and an automatic index is a convenience.
361
+ handle.append(`Skipped automatic indexing of ${root}: it holds at least ${String(oversized)} candidate ` +
362
+ `source files, past the ${String(config.autoIndexMaxFiles)}-file session-start limit. ` +
363
+ `Run codegraph_index yourself if you want a graph for it.\n`);
364
+ return { status: 'failed', detail: 'workspace too large for automatic indexing' };
365
+ }
296
366
  const report = await ctx.codegraph.index(root, controller.signal);
297
367
  handle.append(`Indexed ${root}: ${report.filesIndexed} files, ${report.nodeCount} symbols, ` +
298
368
  `${report.edgeCount} relationships.\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-codegraph-tree-sitter",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
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,7 +51,7 @@
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.5.1"
54
+ "dsh-plugin-codegraph-service": "^0.5.3"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "@deepseek-ai/cordis": ">=4.0.1",