dsh-plugin-codegraph-tree-sitter 0.2.0 → 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/lib/index.d.ts CHANGED
@@ -15,6 +15,13 @@
15
15
  * backend serves from another execution world is watched where it actually lives, and a backend that
16
16
  * cannot watch at all degrades to explicit indexing instead of failing the plugin.
17
17
  *
18
+ * A workspace with no index yet is also indexed automatically the first time a session is created in
19
+ * it (`agent/created`), through the host's background-job registry when the composition has one. The
20
+ * graph is most useful on the first `codegraph` query, and a session that starts in an unindexed
21
+ * workspace is exactly the moment a caller would otherwise have to ask for a build by hand. This
22
+ * never runs synchronously inside the event dispatch: creating an agent must not wait on a
23
+ * multi-minute parse, and an index is an optimization, never a precondition for starting work.
24
+ *
18
25
  * Namespace plugin (named exports, no default export). Grammars load lazily, one per language, on
19
26
  * first sight of a matching file, and stay cached for the process — never eagerly for every grammar
20
27
  * this package ships with.
@@ -143,6 +150,16 @@ export interface Config {
143
150
  * covering only part of the tree.
144
151
  */
145
152
  maxWatchedDirectories?: number;
153
+ /**
154
+ * Build a first index automatically, in the background, when a session is created in a workspace
155
+ * that has none (default true). A workspace is only ever auto-indexed once per process and never
156
+ * when its status sidecar already exists, so this cannot rebuild a graph that is already there.
157
+ * Set false to keep every build explicitly requested.
158
+ *
159
+ * Requires the host's `jobs` service: the build is never run synchronously inside session creation,
160
+ * so a composition without background jobs simply schedules nothing.
161
+ */
162
+ autoIndexOnSession?: boolean;
146
163
  }
147
164
  export declare const Config: z<Config>;
148
165
  /**
package/lib/index.js CHANGED
@@ -15,15 +15,23 @@
15
15
  * backend serves from another execution world is watched where it actually lives, and a backend that
16
16
  * cannot watch at all degrades to explicit indexing instead of failing the plugin.
17
17
  *
18
+ * A workspace with no index yet is also indexed automatically the first time a session is created in
19
+ * it (`agent/created`), through the host's background-job registry when the composition has one. The
20
+ * graph is most useful on the first `codegraph` query, and a session that starts in an unindexed
21
+ * workspace is exactly the moment a caller would otherwise have to ask for a build by hand. This
22
+ * never runs synchronously inside the event dispatch: creating an agent must not wait on a
23
+ * multi-minute parse, and an index is an optimization, never a precondition for starting work.
24
+ *
18
25
  * Namespace plugin (named exports, no default export). Grammars load lazily, one per language, on
19
26
  * first sight of a matching file, and stay cached for the process — never eagerly for every grammar
20
27
  * this package ships with.
21
28
  * @module dsh-plugin-codegraph-tree-sitter
22
29
  */
30
+ import { existsSync } from 'node:fs';
23
31
  import { stat } from 'node:fs/promises';
24
32
  import { join } from 'node:path';
25
33
  import z from '@deepseek-ai/schemastery';
26
- import { CodegraphIndexerId } from 'dsh-plugin-codegraph-service';
34
+ import { CodegraphIndexerId, INDEX_STATUS_RELATIVE_PATH } from 'dsh-plugin-codegraph-service';
27
35
  import { LANGUAGE_TABLE } from "./languages.js";
28
36
  import { walkAndExtract } from "./walk.js";
29
37
  import { writeParseCache } from "./cache.js";
@@ -81,6 +89,7 @@ export const Config = z.object({
81
89
  watch: z.boolean().default(true),
82
90
  watchDebounceMs: z.number().default(DEFAULT_WATCH_DEBOUNCE_MS),
83
91
  maxWatchedDirectories: z.number().default(DEFAULT_MAX_WATCHED_DIRECTORIES),
92
+ autoIndexOnSession: z.boolean().default(true),
84
93
  });
85
94
  /**
86
95
  * Register the tree-sitter indexer.
@@ -105,6 +114,14 @@ export function apply(ctx, config) {
105
114
  // cross-plugin relation here for an invariant to describe — just process-local bookkeeping this
106
115
  // effect's cleanup below tears down on unload.
107
116
  const watchers = new Map();
117
+ // Roots this plugin instance has already started an automatic index for. The set is only a guard
118
+ // against a burst of agents opened on one workspace each scheduling the same rebuild; what a
119
+ // previous *process* indexed is recorded by the status sidecar `autoIndexOnSession` reads instead.
120
+ const autoIndexed = new Set();
121
+ // `agent/created` is typed by `@deepseek-ai/dsh-agent`'s module augmentation. Reading the event
122
+ // through a local structural face keeps this package's host dependencies unchanged: it needs one
123
+ // event name and one payload shape, not the whole agent package.
124
+ const agentEvents = ctx;
108
125
  /** Start (or leave running) file watching for `projectRoot`, once it has a baseline to refresh. */
109
126
  function ensureWatching(projectRoot) {
110
127
  if (watchers.has(projectRoot))
@@ -162,9 +179,92 @@ export function apply(ctx, config) {
162
179
  watcher.stop();
163
180
  watchers.clear();
164
181
  };
182
+ if (resolved.autoIndexOnSession) {
183
+ // Registered inside this effect so the listener follows the plugin's lifetime: unloading the
184
+ // plugin must stop new sessions from scheduling work this instance can no longer run.
185
+ yield agentEvents.on('agent/created', payload => autoIndexOnSession(ctx, autoIndexed, payload));
186
+ }
165
187
  yield ctx.codegraph.registerIndexer(indexer);
166
188
  }, 'codegraph-tree-sitter');
167
189
  }
190
+ /** Stable job kind of an automatic session-start index, used for retention and diagnostics. */
191
+ const AUTO_INDEX_JOB_KIND = 'codegraph-index';
192
+ /**
193
+ * Index a new session's workspace in the background, when nothing has indexed it yet.
194
+ *
195
+ * Runs synchronously inside `agent/created` dispatch, so it must never block or throw: it only decides
196
+ * whether a build is warranted and hands one to the host's job registry. A synchronous build here
197
+ * would stall agent creation for the length of a parse, which is why a composition without background
198
+ * jobs does nothing instead.
199
+ * @param ctx - the plugin context, consulted for the optional job registry.
200
+ * @param autoIndexed - roots this process has already started an automatic index for.
201
+ * @param payload - the `agent/created` dispatch payload.
202
+ */
203
+ function autoIndexOnSession(ctx, autoIndexed, payload) {
204
+ try {
205
+ const agent = payload.agent;
206
+ const root = agent?.session?.header?.cwd;
207
+ // No workspace means no root to claim: an agent created without a `cwd` has nothing to index.
208
+ if (agent === undefined || !root)
209
+ return;
210
+ // Once per root per process. A second agent opened on the same workspace must not queue a second
211
+ // full rebuild of a graph the first one is already building.
212
+ if (autoIndexed.has(root))
213
+ return;
214
+ // An existing status sidecar means a previous run already indexed this workspace. Checked before
215
+ // the job lookup so a composition without background jobs never pays for a filesystem probe.
216
+ if (existsSync(join(root, INDEX_STATUS_RELATIVE_PATH)))
217
+ return;
218
+ // `ctx.get` rather than an `inject` entry: `jobs` is a host composition choice, and requiring it in
219
+ // `inject` would hide this whole indexer from a profile that runs no background jobs.
220
+ const jobs = ctx.get('jobs');
221
+ if (jobs === undefined)
222
+ return;
223
+ jobs.start({
224
+ // The registry keys a job by its owning session id, not the agent object.
225
+ owner: agent.id,
226
+ kind: AUTO_INDEX_JOB_KIND,
227
+ label: `codegraph auto index ${root}`,
228
+ run: handle => runAutoIndexJob(ctx, root, handle),
229
+ });
230
+ // Marked only after the registry accepted the job, so a transient refusal leaves the workspace
231
+ // eligible for the next session that opens in it.
232
+ autoIndexed.add(root);
233
+ }
234
+ catch (error) {
235
+ // The host treats a throwing listener as vetoing publication. Losing an index is a degradation,
236
+ // never a reason to stop a session from starting.
237
+ console.error(`codegraph-tree-sitter: automatic indexing for a new session failed: ${String(error)}`);
238
+ }
239
+ }
240
+ /**
241
+ * The producer body of one automatic indexing job.
242
+ *
243
+ * Both halves of the host's job contract are mandatory: `cancel` aborts the run, and `done` settles
244
+ * even on failure — a rejected `done` breaks the registry's bookkeeping and leaves the job looking
245
+ * stuck forever.
246
+ * @param ctx - the plugin context.
247
+ * @param root - the workspace to index.
248
+ * @param handle - the job's producer face, for the job's live log.
249
+ * @returns the job's cancellation hook and settlement promise.
250
+ */
251
+ function runAutoIndexJob(ctx, root, handle) {
252
+ const controller = new AbortController();
253
+ const done = (async () => {
254
+ try {
255
+ const report = await ctx.codegraph.index(root, controller.signal);
256
+ handle.append(`Indexed ${root}: ${report.filesIndexed} files, ${report.nodeCount} symbols, ` +
257
+ `${report.edgeCount} relationships.\n`);
258
+ return { status: 'completed' };
259
+ }
260
+ catch (error) {
261
+ const message = String(error);
262
+ handle.append(`codegraph auto index failed for ${root}: ${message}\n`);
263
+ return { status: 'failed', detail: message };
264
+ }
265
+ })();
266
+ return { cancel: () => controller.abort(), done };
267
+ }
168
268
  /**
169
269
  * Run one watch-triggered reindex. Always a full rebuild in this version — `runIndex` has no notion of
170
270
  * a file subset — so the watcher's own collected paths are not consulted here.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-codegraph-tree-sitter",
3
- "version": "0.2.0",
3
+ "version": "0.3.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,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.2.0"
54
+ "dsh-plugin-codegraph-service": "^0.3.0"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "@deepseek-ai/cordis": ">=4.0.1",