dsh-gitnexus-plus 0.1.1 → 0.1.2

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.
Files changed (54) hide show
  1. package/README.md +197 -49
  2. package/lib/bin/search-helper.d.ts +22 -0
  3. package/lib/bin/search-helper.d.ts.map +1 -0
  4. package/lib/bin/search-helper.js +57 -0
  5. package/lib/bin/search-helper.js.map +1 -0
  6. package/lib/client.js +576 -87
  7. package/lib/gitnexus/analyze.d.ts +27 -2
  8. package/lib/gitnexus/analyze.d.ts.map +1 -1
  9. package/lib/gitnexus/analyze.js +31 -6
  10. package/lib/gitnexus/analyze.js.map +1 -1
  11. package/lib/gitnexus/commands.d.ts +41 -8
  12. package/lib/gitnexus/commands.d.ts.map +1 -1
  13. package/lib/gitnexus/commands.js +62 -9
  14. package/lib/gitnexus/commands.js.map +1 -1
  15. package/lib/gitnexus/coverage.d.ts.map +1 -1
  16. package/lib/gitnexus/coverage.js +3 -1
  17. package/lib/gitnexus/coverage.js.map +1 -1
  18. package/lib/gitnexus/index-meta.d.ts +15 -5
  19. package/lib/gitnexus/index-meta.d.ts.map +1 -1
  20. package/lib/gitnexus/index-meta.js +25 -5
  21. package/lib/gitnexus/index-meta.js.map +1 -1
  22. package/lib/gitnexus/languages.d.ts +7 -0
  23. package/lib/gitnexus/languages.d.ts.map +1 -1
  24. package/lib/gitnexus/languages.js +82 -2
  25. package/lib/gitnexus/languages.js.map +1 -1
  26. package/lib/gitnexus/root.d.ts +6 -0
  27. package/lib/gitnexus/root.d.ts.map +1 -1
  28. package/lib/gitnexus/root.js +6 -0
  29. package/lib/gitnexus/root.js.map +1 -1
  30. package/lib/gitnexus/scan.d.ts +1 -0
  31. package/lib/gitnexus/scan.d.ts.map +1 -1
  32. package/lib/gitnexus/scan.js +3 -1
  33. package/lib/gitnexus/scan.js.map +1 -1
  34. package/lib/gitnexus/search.d.ts +167 -0
  35. package/lib/gitnexus/search.d.ts.map +1 -0
  36. package/lib/gitnexus/search.js +517 -0
  37. package/lib/gitnexus/search.js.map +1 -0
  38. package/lib/index.d.ts +32 -13
  39. package/lib/index.d.ts.map +1 -1
  40. package/lib/index.js +62 -21
  41. package/lib/index.js.map +1 -1
  42. package/lib/routes/analyze.d.ts +8 -2
  43. package/lib/routes/analyze.d.ts.map +1 -1
  44. package/lib/routes/analyze.js +5 -1
  45. package/lib/routes/analyze.js.map +1 -1
  46. package/lib/routes/coverage.d.ts +13 -5
  47. package/lib/routes/coverage.d.ts.map +1 -1
  48. package/lib/routes/coverage.js +24 -11
  49. package/lib/routes/coverage.js.map +1 -1
  50. package/lib/routes/search.d.ts +27 -0
  51. package/lib/routes/search.d.ts.map +1 -0
  52. package/lib/routes/search.js +77 -0
  53. package/lib/routes/search.js.map +1 -0
  54. package/package.json +2 -2
package/README.md CHANGED
@@ -50,7 +50,9 @@ It does **not**:
50
50
  and the two stay separate;
51
51
  - claim authorship of, or any contribution to, GitNexus. All rights in GitNexus belong
52
52
  to its author;
53
- - replace GitNexus. With no GitNexus index there is nothing here to display.
53
+ - replace GitNexus. With no GitNexus index there is no graph to display — only a
54
+ working-tree scan (file counts, bytes, languages), which is useful but is not a
55
+ substitute for the graph.
54
56
 
55
57
  ## Licence — read this before a commercial use
56
58
 
@@ -99,9 +101,22 @@ GitNexus, and installing this plugin installs GitNexus.**
99
101
  one is the point: of the files people are actively editing, which does the graph
100
102
  not see?
101
103
  - **Language distribution by bytes**, split three ways: languages GitNexus can
102
- *parse* into the graph, formats it merely *recognizes*, and everything else.
104
+ *parse* into the graph, formats it merely *recognizes*, and everything else. When
105
+ GitNexus is absent, a built-in extension map keeps the breakdown useful; those
106
+ rows are marked `detector: "builtin"` and are never presented as *parsed*.
103
107
  - **Resolver confidence** — untyped receiver sites, which make `impact` and
104
108
  `context` a lower bound rather than an exact answer.
109
+ - **A scan-only report when there is no index** — file count, selected count, bytes
110
+ and language split are readable from the working tree alone. The panel shows them
111
+ under "尚未索引" instead of an empty state, and the initialize command sits beside
112
+ them.
113
+ - **Keyword search with click-to-open** — the panel's search box asks the graph for
114
+ nodes whose `name` or `filePath` contains the query, and returns the graph node
115
+ kind, name, path and line. **Clicking a hit opens that file in the platform's file
116
+ viewer at that line** through `ctx.sidebarRight.openResource` and a
117
+ `dsh-resource://file/session/...` address; a separate 复制 button copies
118
+ `path:line`. When the graph helper cannot start, the bounded source-text fallback
119
+ is used instead and the card is labelled 源码关键词(回退).
105
120
 
106
121
  ## Why the git three-way split matters
107
122
 
@@ -169,6 +184,20 @@ Everything runs through `ctx.shell`, so a sandboxed deployment confines it exact
169
184
  confines your own commands. **A refusal is reported as a refusal and never retried
170
185
  wider** — the plugin never asks for more scope than the session already granted.
171
186
 
187
+ **The installed copy is the one every later step uses.** The install prefix is resolved
188
+ once and passed to all three consumers: the coverage engine's own scan primitives, the
189
+ language detector, and the `analyze` runner. This wiring was missing in 0.1.1 — the
190
+ installer wrote a plugin-owned tree, while the reader and runner probed only PATH and
191
+ the declared dependency, so the install succeeded and *nothing used it*. A machine with
192
+ no global GitNexus still reported "absent" after a successful install.
193
+
194
+ **Install → initialize is one authorised step.** If the current project has no index,
195
+ a successful install continues with a single `gitnexus analyze --index-only` for that
196
+ project, so the operator gets from "no GitNexus" to a visible index without a second
197
+ click. The install confirmation says this before anything starts. The chain is gated on
198
+ the install having been started from this panel and on the project still having no
199
+ index; it never silently refreshes an index that already exists.
200
+
172
201
  If your GitNexus lives somewhere unusual, name it directly and none of this applies:
173
202
 
174
203
  ```sh
@@ -200,23 +229,67 @@ In DSH the plugin exposes:
200
229
  - **tool** `gitnexus_coverage` — `{ repo, includeLanguages? }`
201
230
  - **route** `GET /api/gitnexus/coverage?repo=<project>` — one report, plus the
202
231
  maintenance commands for that project
232
+ - **route** `GET /api/gitnexus/search?repo=<project>&q=<keyword>&limit=20` —
233
+ read-only keyword search over the graph's symbol names and file paths
203
234
  - **panel** — a right-sidebar tab type `gitnexus`, opened from the sidebar's **+** →
204
235
  guide page ("GitNexus 索引")
205
236
 
206
- The coverage route is read-only and accepts only directories that are already GitNexus
207
- indexes, so it cannot be turned into a filesystem probe; it is additionally
208
- restricted to loopback peers because a third-party DSH route is unauthenticated by
209
- default on a bare `dsh web` profile.
237
+ The coverage route is read-only. Its bound is a **project root** — a directory with a
238
+ `.gitnexus` index or a `.git` marker — so it cannot be turned into a filesystem probe.
239
+ An index is no longer required: a git project without one gets the engine's scan-only
240
+ report, which is what makes "no index yet" informative. It is additionally restricted
241
+ to loopback peers because a third-party DSH route is unauthenticated by default on a
242
+ bare `dsh web` profile.
210
243
 
211
244
  There is now a **second** route, `/api/gitnexus/analyze`, and it is the only one that
212
- can change anything. It answers `POST` to start an incremental refresh, `GET` to report
213
- where that got to, and `DELETE` to stop it. It is a separate module from the coverage
245
+ can change anything. It answers `POST` to start an incremental refresh (`?force=1`
246
+ selects a full rebuild), `GET` to report where that got to, and `DELETE` to stop it. It is a separate module from the coverage
214
247
  route so the read-only half keeps a method policy that cannot mutate — the shared guard
215
248
  takes the accepted methods from the route rather than hard-coding one set for both. The
216
249
  mutating verbs additionally pass the write guard, which refuses cross-origin requests;
217
250
  loopback alone is not enough, because any page the operator visits can reach
218
251
  `127.0.0.1`. See **Maintenance** below for what it will and will not do.
219
252
 
253
+ A **third** route, `/api/gitnexus/search`, is read-only like coverage but queries the
254
+ LadybugDB graph instead of the manifest. It opens the database read-only and searches
255
+ node names and file paths; it does **not** use FTS, which is why it works on an index
256
+ built with `--skip-fts` and on a machine where the FTS extension fails to load. The
257
+ response carries the node kind, name, path and line, plus an honest warning when
258
+ content-level FTS is unavailable.
259
+
260
+ **Two tiers, because the host shell can be unavailable.** The normal path is the
261
+ graph query. If the helper cannot start — a Windows sandbox backend that cannot create
262
+ its own temp directory, no shell executor, a busy database — the route falls back to a
263
+ bounded source-text search over the files listed in the index manifest. That fallback
264
+ needs neither a shell nor the native addon; it returns `mode: "text"`, searches code
265
+ files before documentation, caps files/bytes/matches, and says so in `warning` and in
266
+ the panel's card title. A silent swap between "graph" and "source text" would be a
267
+ dishonest answer, so the mode is part of the response.
268
+
269
+ **The graph query runs in a Node helper subprocess, not in the DSH host.** The host runs on
270
+ Electron's Node, and GitNexus's LadybugDB addon is built for the system Node ABI.
271
+ Loading it in-process fails with `A dynamic link library (DLL) initialization routine
272
+ failed ... lbugjs.node`; the same addon loads under the system Node 24 that the CLI
273
+ uses. The host therefore starts:
274
+
275
+ ```
276
+ node lib/bin/search-helper.js <repo> <keyword> <limit> <managed-prefix>
277
+ ```
278
+
279
+ and parses one JSON object. That is the same execution boundary the analyze runner
280
+ already uses, and it keeps the panel process free of native state it cannot load.
281
+ Consequently the graph path needs a shell executor and `node` on the shell's PATH;
282
+ without them it falls back to source text, not an empty result list.
283
+
284
+ **Clicking a hit opens the file, not a copy.** The row calls
285
+ `ctx.sidebarRight.openResourceIn(sessionId, address, { params: { line } })` (or the
286
+ public `openResource` when the targeted variant is unavailable) with
287
+ `dsh-resource://file/session/<session>/<path>`. That is the same address and line
288
+ parameter the built-in file strip and the chat's file links use, so the platform's
289
+ own file viewer/editor handles the navigation. A separate 复制 button remains for the
290
+ `path:line` text. A profile without a right-sidebar controller falls back to copying
291
+ and says so.
292
+
220
293
  ## TypeScript, and why the build looks the way it does
221
294
 
222
295
  The source is TypeScript under `strict`, including `noImplicitAny`. That is not
@@ -290,6 +363,9 @@ That is not stylistic: a component that fetches cannot be rendered against a
290
363
  fixture, and the states worth asserting on — a report with skipped files, one
291
364
  without, an error, missing language data — are exactly the ones a fixture supplies.
292
365
  `test/client.mjs` renders each of them through a minimal single-pass hooks shim.
366
+ Keyword search follows the same split: `CoveragePanel` owns the request and
367
+ `SearchSection` is a pure function of its props, so the empty, error and result
368
+ states are all fixture-testable without a graph database.
293
369
 
294
370
  Styling is theme-agnostic by construction: host elements only, `color` inherited,
295
371
  every border derived from `currentColor`. `@deepseek-ai/dsh-client-ui-primitives` is
@@ -310,6 +386,28 @@ GET /api/gitnexus/coverage?repo=D:\Project\material-ui HTTP 200
310
386
  GET /api/gitnexus/coverage?repo=C:\Windows HTTP 404 (refused)
311
387
  ```
312
388
 
389
+ The search engine and its route were exercised over a loopback harness against this
390
+ repository's real index (the same database the panel reads):
391
+
392
+ ```
393
+ GET /api/gitnexus/search?repo=D:\Project\gitnexus-plus&q=analyzeCoverage HTTP 200
394
+ Function analyzeCoverage src/gitnexus/coverage.ts:507
395
+ GET /api/gitnexus/search?repo=D:\Project\gitnexus-plus&q=client/index&limit=3 HTTP 200
396
+ File index.ts src/client/index.ts
397
+ GET /api/gitnexus/search?repo=C:\Windows\System32&q=x HTTP 404 (refused)
398
+ ```
399
+
400
+ The helper boundary itself, run under the system Node 24, returns the JSON the host
401
+ parses (the host's Electron Node cannot load the native addon):
402
+
403
+ ```
404
+ node lib/bin/search-helper.js D:\Project\gitnexus-plus analyzeCoverage 5
405
+ {"ok":true,"report":{"query":"analyzecoverage","mode":"graph",
406
+ "results":[{"kind":"Function","name":"analyzeCoverage",
407
+ "filePath":"src/gitnexus/coverage.ts","startLine":507,"endLine":864}],
408
+ "ftsAvailable":false,"total":1,...}}
409
+ ```
410
+
313
411
  ## Scope: the project you are in, not a global list
314
412
 
315
413
  The panel reports **the project the session's workspace points at**. It does not
@@ -330,13 +428,15 @@ Two situations the read-only half cannot resolve on its own:
330
428
  - **初始化索引 / 增量刷新** — the index is missing or behind HEAD. This one the plugin
331
429
  runs for you.
332
430
  - **强制全量重构** — the incremental refresh has hung. GitNexus's update path is not
333
- reliably terminating, and when it wedges a full rebuild is the only way out. Handed
334
- over as text.
431
+ reliably terminating, and when it wedges a full rebuild is the only way out. This
432
+ one also has a button, behind its own destructive confirmation: the route implements
433
+ `--force`, and the dialog says what it rebuilds, how long it can take, and that the
434
+ index may be briefly unavailable.
335
435
 
336
- **增量刷新 runs through the host's shell executor.** An earlier revision handed over
337
- every command as text, justified by "a plugin that spawns processes bypasses the
338
- permission system". That reasoning was wrong, and the correction is worth stating
339
- precisely:
436
+ **Both maintenance actions run through the host's shell executor.** An earlier revision
437
+ handed over every command as text, justified by "a plugin that spawns processes
438
+ bypasses the permission system". That reasoning was wrong, and the correction is worth
439
+ stating precisely:
340
440
 
341
441
  DSH's approval service governs **sandbox escalation** — widening the scope a session was
342
442
  granted. Its audit pair must be turn-enclosed, which is why `ApprovalService.request()`
@@ -360,35 +460,50 @@ What the plugin commits to, and what is tested:
360
460
  the agent skill files in your tree — a far larger change than "refresh the index".
361
461
  - **A confirmation before it starts**, stating the exact command, what it touches and
362
462
  how long it takes.
463
+ - **The full rebuild is a separate confirmation.** It runs only when the request carries
464
+ `force=1`, its confirmation says it rebuilds the graph and FTS and may take minutes,
465
+ and its button is labelled 全量重建 rather than borrowing the incremental one.
363
466
  - **Cancellable**, with a ten-minute ceiling by default (`analyzeTimeoutMs`).
364
467
 
365
- The full rebuild still has no button, because the host route does not implement
366
- `--force`. A button that quietly ran something other than its label would be worse than
367
- no button.
468
+ The full rebuild used to have no button because the host route did not implement
469
+ `--force`. The route does now, and the button runs exactly the command its dialog
470
+ shows — a button that quietly ran something other than its label would still be worse
471
+ than no button.
368
472
 
369
- The commands handed over are not a bare `gitnexus analyze`, because that does not work
370
- in every deployment:
473
+ The commands shown and executed are not a bare `gitnexus analyze`, because that does not
474
+ work in every deployment:
371
475
 
372
476
  ```
373
477
  $env:GITNEXUS_MEMORY='off'; $env:NODE_OPTIONS='--max-old-space-size=8192'; \
478
+ $env:GITNEXUS_HOME='<project>\.gitnexus\plugin-home'; \
374
479
  gitnexus analyze --index-only '<project>'
375
480
 
376
481
  $env:GITNEXUS_MEMORY='off'; $env:NODE_OPTIONS='--max-old-space-size=8192'; \
482
+ $env:GITNEXUS_HOME='<project>\.gitnexus\plugin-home'; \
377
483
  gitnexus analyze --index-only --force '<project>'
378
484
  ```
379
485
 
380
486
  - `GITNEXUS_MEMORY=off` — the CLI otherwise re-spawns itself with a larger heap over
381
487
  piped stdio, which a confined sandbox denies (`spawn EPERM`).
382
488
  - `NODE_OPTIONS=--max-old-space-size=8192` — supplies the heap that respawn wanted.
489
+ - `GITNEXUS_HOME=<project>\.gitnexus\plugin-home` — the global registry write would
490
+ otherwise go to `~/.gitnexus`, outside a confined host's writable scope. A failed
491
+ registry write leaves the index marked `incremental-in-progress` and incomplete.
492
+ The plugin defaults this redirect to the project's own `.gitnexus/plugin-home`;
493
+ `gitnexusHome` overrides it.
383
494
  - `--index-only` — the default `analyze` also rewrites `AGENTS.md`, `CLAUDE.md` and
384
495
  agent skill files inside your repository. A maintenance action must not edit the
385
496
  working tree.
386
497
 
387
- `--skip-fts` is **no longer** part of either command. Measured on a 39-file repository,
388
- disabling a search index the store already has forces a full graph-store rebuild —
389
- 179.22 s against 8.57 s, a 21× penalty on the transition an operator is most likely to
390
- hit, and the opposite of incremental. Letting FTS fail on its own costs nothing: it
391
- prints a warning and indexes anyway.
498
+ `--skip-fts` is **adaptive**, not a static default. An index whose manifest says
499
+ `capabilities.fts.status: available` gets no flag; an index without FTS gets
500
+ `--skip-fts`, because the extension's install is an out-of-process step a confined
501
+ host denies (`spawn EPERM`), and that failure aborts the refresh. This matters in both
502
+ directions: measured on a 39-file repository, putting `--skip-fts` on an index that
503
+ already has FTS forces a full graph-store rebuild — 179.22 s against 8.57 s, a 21×
504
+ penalty — while omitting it on an index without FTS makes the refresh fail outright.
505
+ Setting `skipFts: true` forces the flag; there is no config that forces FTS on, because
506
+ enabling it is a repair (`gitnexus analyze --repair-fts`), not an incremental refresh.
392
507
 
393
508
  The panel prints these rationales next to the commands, so they do not look
394
509
  arbitrary. `src/gitnexus/commands.js` is pure and asserted verbatim by tests.
@@ -408,24 +523,27 @@ because this package declares `dsh.bundle`.
408
523
 
409
524
  ## Known limits
410
525
 
411
- - **A machine without GitNexus is not detected yet.** The plugin declares no runtime
412
- dependencies and never imports `gitnexus`; it reads an index another tool produced
413
- and, when it can, deep-imports the upstream scan primitives at runtime. That design
414
- keeps it a pure observer, but it makes "the operator has no GitNexus" a reachable
415
- state. `src/gitnexus/toolchain.ts` implements a three-state probe (`ready` /
416
- `library-only` / `absent`, separating "the library tree exists" from "a CLI is on
417
- PATH", because they gate different promises), and the refresh runner now consults it
418
- **before spawning anything**: with no CLI it reports `unavailable` and tells the
419
- operator to install one, rather than starting a process that cannot work. The engine
420
- also degrades correctly on its own — `scan.ts` falls back to its own walker and
421
- reports `primitives: 'fallback'`, and the built-in size caps apply.
526
+ - **Detection is a filesystem probe, not an execution check.** The plugin declares no
527
+ runtime dependencies and never imports `gitnexus`; it reads an index another tool
528
+ produced and, when it can, deep-imports the upstream scan primitives at runtime. That
529
+ design keeps it a pure observer, but it makes "the operator has no GitNexus" a
530
+ reachable state — so `src/gitnexus/toolchain.ts` probes it in three states (`ready` /
531
+ `library-only` / `absent`), separating "the library tree exists" from "a CLI is on
532
+ PATH" because they gate different promises. The refresh runner consults it **before
533
+ spawning anything**: with no CLI it reports `unavailable` and tells the operator to
534
+ install one, rather than starting a process that cannot work. Spawning the CLI to ask
535
+ its version would be the direct test, but a confined process denies piped stdio, so
536
+ the probe only stats the filesystem. The engine degrades on its own too — `scan.ts`
537
+ falls back to its own walker and reports `primitives: 'fallback'`, and the built-in
538
+ size caps apply.
422
539
  - **A report that reused GitNexus's scan rules and one that fell back to an
423
- approximation are still not distinguished inside the report itself.** The toolchain
424
- verdict now travels with it, so the panel knows whether GitNexus is present and says so
425
- — but the per-file numbers do not carry a provenance marker, so a fallback walk is not
426
- labelled as one where the numbers are shown. The built-in walker deliberately does
427
- **not** implement upstream's ignore rules faithfully, and its uncovered-file count can
428
- therefore carry false positives.
540
+ approximation are still not distinguished per file.** The toolchain verdict travels
541
+ with the report, the language breakdown carries `detector: "gitnexus" | "builtin"`,
542
+ and the fallback rows are never labelled *parsed* — but the remaining per-file numbers
543
+ do not carry a provenance marker, so a fallback walk is not labelled as one where
544
+ those numbers are shown. The built-in walker deliberately does **not** implement
545
+ upstream's ignore rules faithfully, and its uncovered-file count can therefore carry
546
+ false positives.
429
547
  - **Installing GitNexus from the panel has never been run against a real network.** The
430
548
  command shape is verified (`npm install --prefix … --no-audit --no-fund`, no hardcoded
431
549
  registry), the runner's state machine is covered by fourteen assertions, and the
@@ -433,7 +551,9 @@ because this package declares `dsh.bundle`.
433
551
  What is **not** verified is the download and compile itself: it is several hundred
434
552
  megabytes and was never executed from inside a confined DSH process. Treat the first
435
553
  authorised install on any machine as untested, and fall back to the manual command if
436
- it fails — which is why that command is offered first.
554
+ it fails — which is why that command is offered first. What *is* verified by tests is
555
+ that the installed prefix is the one the reader, the language detector and the analyze
556
+ runner all resolve; the download and compile remain unverified.
437
557
  - **`--omit=optional` saves about 486 MB of that install, and is not used by default.**
438
558
  Measured: a full GitNexus tree is 998.9 MB, and omitting the optional dependencies
439
559
  gives 513.2 MB by dropping `onnxruntime-node` (287 MB), `onnxruntime-web` (139 MB) and
@@ -475,10 +595,38 @@ because this package declares `dsh.bundle`.
475
595
  git-tracked files; real data killed it — 8 of the "missing" tracked files in a
476
596
  37-file repo were dotfiles excluded by the walker's `dot: false`, and 2 indexed
477
597
  files were untracked. The walker's own rules are the only honest denominator.
478
- - **Panel effects and re-render are untested.** `react-dom` is not installed on
479
- this machine, so rendering uses a single-pass shim: `useState` yields its initial
480
- value and `useEffect` never runs. The pure view is covered; the fetch lifecycle
481
- is not.
482
- - **No incremental update yet.** This ships the read-only monitoring half of the
483
- plan. Driving `analyze` is a separate concern with its own safety story — if
484
- nothing else, a status check must never start a 30-second rebuild.
598
+ - **Keyword search is graph name/path first, with a source-text fallback.** The graph
599
+ mode finds symbols and files whose *names* or *paths* contain the query; it does not
600
+ search function bodies, comments or descriptions. The fallback (`mode: "text"`) does
601
+ search line text, but only in the files the index manifest lists, and only when the
602
+ graph helper cannot start. Content-level search as a first-class mode is GitNexus's
603
+ FTS surface, and on this machine the FTS extension fails to load
604
+ (`libfts.lbug_extension` missing) and the current index was built with `--skip-fts`.
605
+ The panel labels the two modes separately (符号名 / 文件路径 vs 源码关键词(回退)) and
606
+ never presents a text hit as a graph hit.
607
+ - **Search needs a child Node process for the graph path.** The host's Electron Node
608
+ cannot load the LadybugDB addon (ABI mismatch), so the query is delegated to
609
+ `lib/bin/search-helper.js` under the system Node 24. This is deliberate, not a
610
+ workaround to remove: an in-process search fails on the host with the DLL
611
+ initialization error above. If the shell executor is unavailable (measured on this
612
+ machine: the Windows `windows-acl-run` sandbox cannot create its temp directory),
613
+ search does **not** fail — it falls back to the bounded source-text scan and labels
614
+ the result `text`. The graph query remains the preferred path when the helper can
615
+ start.
616
+ - **The source fallback is not the graph.** It searches line text in the files the
617
+ index manifest lists, not symbol relationships; code paths are searched before
618
+ documentation, and the file/byte/match caps can truncate a large repository. The
619
+ panel's card title changes to 源码关键词(回退) and the warning names the fallback,
620
+ so a text hit is never presented as a graph hit.
621
+ - **Panel effects and re-render are still not automated.** `react-dom` is not installed
622
+ on this machine, so rendering uses a single-pass shim: `useState` yields its initial
623
+ value and `useEffect` never runs. The pure view is covered; the fetch lifecycle is
624
+ not. The panel has now been checked once against the real right sidebar, and that
625
+ check is what exposed the two-column layout clipping the language card at a narrow
626
+ pane edge, the grey single-bucket chart, the invented `--dsw-color-*` tokens, the
627
+ raw English `index` cap source, and the `--skip-fts` config default. Those are
628
+ fixed, but a future prop or layout change can still pass every test here without
629
+ being seen in a browser.
630
+ - **Install → initialize has not been exercised end to end against a real npm install.**
631
+ The managed-prefix wiring, the auto-chain gate, and the analyze command are covered by
632
+ tests; the several-hundred-megabyte download and native compile are not.
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Search helper, run by the **system Node** rather than the DSH host.
4
+ *
5
+ * The host is Electron's Node and cannot load GitNexus's LadybugDB native addon
6
+ * (ABI mismatch: "A dynamic link library (DLL) initialization routine failed").
7
+ * This process is started with the platform Node that the CLI already uses, imports
8
+ * the same adapter, runs one read-only query, and prints exactly one JSON object for
9
+ * the host to parse.
10
+ *
11
+ * Invocation:
12
+ * node search-helper.js <repo> <sanitized-query> [limit] [managedPrefix]
13
+ *
14
+ * Success: `{ "ok": true, "report": SearchReport }`
15
+ * Failure: `{ "ok": false, "status": <number>, "code": <SearchErrorCode>, "message": string }`
16
+ *
17
+ * It always exits 0 for a reported failure, so the host reads a structured answer
18
+ * instead of inferring one from an exit code. A process-level crash (native fault)
19
+ * is the one case the host handles from an unparseable stdout/stderr pair.
20
+ */
21
+ export {};
22
+ //# sourceMappingURL=search-helper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-helper.d.ts","sourceRoot":"","sources":["../../src/bin/search-helper.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;GAkBG"}
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Search helper, run by the **system Node** rather than the DSH host.
4
+ *
5
+ * The host is Electron's Node and cannot load GitNexus's LadybugDB native addon
6
+ * (ABI mismatch: "A dynamic link library (DLL) initialization routine failed").
7
+ * This process is started with the platform Node that the CLI already uses, imports
8
+ * the same adapter, runs one read-only query, and prints exactly one JSON object for
9
+ * the host to parse.
10
+ *
11
+ * Invocation:
12
+ * node search-helper.js <repo> <sanitized-query> [limit] [managedPrefix]
13
+ *
14
+ * Success: `{ "ok": true, "report": SearchReport }`
15
+ * Failure: `{ "ok": false, "status": <number>, "code": <SearchErrorCode>, "message": string }`
16
+ *
17
+ * It always exits 0 for a reported failure, so the host reads a structured answer
18
+ * instead of inferring one from an exit code. A process-level crash (native fault)
19
+ * is the one case the host handles from an unparseable stdout/stderr pair.
20
+ */
21
+ import { SearchError, searchSymbols } from '../gitnexus/search.js';
22
+ /** Write the one object, then exit once stdout has flushed. */
23
+ function emit(payload) {
24
+ const text = JSON.stringify(payload);
25
+ process.stdout.write(text, () => process.exit(0));
26
+ // A native handle can keep the event loop alive; the payload is already queued.
27
+ setTimeout(() => process.exit(0), 2000).unref();
28
+ }
29
+ const [, , repoArg, queryArg, limitArg, managedArg] = process.argv;
30
+ if (typeof repoArg !== 'string' || repoArg === '' || typeof queryArg !== 'string') {
31
+ emit({
32
+ ok: false,
33
+ status: 400,
34
+ code: 'invalid-query',
35
+ message: 'usage: search-helper <repo> <query> [limit] [managedPrefix]',
36
+ });
37
+ }
38
+ else {
39
+ const parsedLimit = limitArg === undefined ? undefined : Number.parseInt(limitArg, 10);
40
+ const limit = parsedLimit === undefined || !Number.isFinite(parsedLimit) ? undefined : parsedLimit;
41
+ try {
42
+ const report = await searchSymbols(repoArg, queryArg, {
43
+ limit,
44
+ managedPrefix: managedArg === undefined || managedArg === '' ? undefined : managedArg,
45
+ });
46
+ emit({ ok: true, report });
47
+ }
48
+ catch (error) {
49
+ emit({
50
+ ok: false,
51
+ status: error instanceof SearchError ? error.status : 500,
52
+ code: error instanceof SearchError ? error.code : 'search-failed',
53
+ message: error instanceof Error ? error.message : String(error),
54
+ });
55
+ }
56
+ }
57
+ //# sourceMappingURL=search-helper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-helper.js","sourceRoot":"","sources":["../../src/bin/search-helper.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEnE,+DAA+D;AAC/D,SAAS,IAAI,CAAC,OAAgB;IAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACrC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAClD,gFAAgF;IAChF,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;AAClD,CAAC;AAED,MAAM,CAAC,EAAE,AAAD,EAAG,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;AAEnE,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;IAClF,IAAI,CAAC;QACH,EAAE,EAAE,KAAK;QACT,MAAM,EAAE,GAAG;QACX,IAAI,EAAE,eAAe;QACrB,OAAO,EAAE,6DAA6D;KACvE,CAAC,CAAC;AACL,CAAC;KAAM,CAAC;IACN,MAAM,WAAW,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACvF,MAAM,KAAK,GAAG,WAAW,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC;IACnG,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,QAAQ,EAAE;YACpD,KAAK;YACL,aAAa,EAAE,UAAU,KAAK,SAAS,IAAI,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU;SACtF,CAAC,CAAC;QACH,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC;YACH,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,KAAK,YAAY,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG;YACzD,IAAI,EAAE,KAAK,YAAY,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe;YACjE,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAChE,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}