dsh-gitnexus-plus 0.0.0-stage → 0.1.1

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 (78) hide show
  1. package/LICENSE +38 -0
  2. package/README.md +483 -2
  3. package/cordis.patch.yml +18 -0
  4. package/lib/bin/gitnexus-coverage.d.ts +14 -0
  5. package/lib/bin/gitnexus-coverage.d.ts.map +1 -0
  6. package/lib/bin/gitnexus-coverage.js +171 -0
  7. package/lib/bin/gitnexus-coverage.js.map +1 -0
  8. package/lib/client.js +1642 -0
  9. package/lib/gitnexus/analyze.d.ts +148 -0
  10. package/lib/gitnexus/analyze.d.ts.map +1 -0
  11. package/lib/gitnexus/analyze.js +283 -0
  12. package/lib/gitnexus/analyze.js.map +1 -0
  13. package/lib/gitnexus/commands.d.ts +96 -0
  14. package/lib/gitnexus/commands.d.ts.map +1 -0
  15. package/lib/gitnexus/commands.js +132 -0
  16. package/lib/gitnexus/commands.js.map +1 -0
  17. package/lib/gitnexus/coverage.d.ts +340 -0
  18. package/lib/gitnexus/coverage.d.ts.map +1 -0
  19. package/lib/gitnexus/coverage.js +552 -0
  20. package/lib/gitnexus/coverage.js.map +1 -0
  21. package/lib/gitnexus/format.d.ts +16 -0
  22. package/lib/gitnexus/format.d.ts.map +1 -0
  23. package/lib/gitnexus/format.js +178 -0
  24. package/lib/gitnexus/format.js.map +1 -0
  25. package/lib/gitnexus/git-activity.d.ts +136 -0
  26. package/lib/gitnexus/git-activity.d.ts.map +1 -0
  27. package/lib/gitnexus/git-activity.js +345 -0
  28. package/lib/gitnexus/git-activity.js.map +1 -0
  29. package/lib/gitnexus/git.d.ts +70 -0
  30. package/lib/gitnexus/git.d.ts.map +1 -0
  31. package/lib/gitnexus/git.js +121 -0
  32. package/lib/gitnexus/git.js.map +1 -0
  33. package/lib/gitnexus/index-meta.d.ts +150 -0
  34. package/lib/gitnexus/index-meta.d.ts.map +1 -0
  35. package/lib/gitnexus/index-meta.js +156 -0
  36. package/lib/gitnexus/index-meta.js.map +1 -0
  37. package/lib/gitnexus/install.d.ts +97 -0
  38. package/lib/gitnexus/install.d.ts.map +1 -0
  39. package/lib/gitnexus/install.js +228 -0
  40. package/lib/gitnexus/install.js.map +1 -0
  41. package/lib/gitnexus/languages.d.ts +73 -0
  42. package/lib/gitnexus/languages.d.ts.map +1 -0
  43. package/lib/gitnexus/languages.js +217 -0
  44. package/lib/gitnexus/languages.js.map +1 -0
  45. package/lib/gitnexus/root.d.ts +68 -0
  46. package/lib/gitnexus/root.d.ts.map +1 -0
  47. package/lib/gitnexus/root.js +162 -0
  48. package/lib/gitnexus/root.js.map +1 -0
  49. package/lib/gitnexus/scan.d.ts +67 -0
  50. package/lib/gitnexus/scan.d.ts.map +1 -0
  51. package/lib/gitnexus/scan.js +247 -0
  52. package/lib/gitnexus/scan.js.map +1 -0
  53. package/lib/gitnexus/toolchain.d.ts +136 -0
  54. package/lib/gitnexus/toolchain.d.ts.map +1 -0
  55. package/lib/gitnexus/toolchain.js +251 -0
  56. package/lib/gitnexus/toolchain.js.map +1 -0
  57. package/lib/index.d.ts +79 -0
  58. package/lib/index.d.ts.map +1 -0
  59. package/lib/index.js +211 -0
  60. package/lib/index.js.map +1 -0
  61. package/lib/routes/analyze.d.ts +35 -0
  62. package/lib/routes/analyze.d.ts.map +1 -0
  63. package/lib/routes/analyze.js +71 -0
  64. package/lib/routes/analyze.js.map +1 -0
  65. package/lib/routes/coverage.d.ts +44 -0
  66. package/lib/routes/coverage.d.ts.map +1 -0
  67. package/lib/routes/coverage.js +68 -0
  68. package/lib/routes/coverage.js.map +1 -0
  69. package/lib/routes/guard.d.ts +92 -0
  70. package/lib/routes/guard.d.ts.map +1 -0
  71. package/lib/routes/guard.js +187 -0
  72. package/lib/routes/guard.js.map +1 -0
  73. package/lib/routes/install.d.ts +27 -0
  74. package/lib/routes/install.d.ts.map +1 -0
  75. package/lib/routes/install.js +46 -0
  76. package/lib/routes/install.js.map +1 -0
  77. package/package.json +87 -4
  78. package/scripts/link-runtime.mjs +146 -0
package/LICENSE ADDED
@@ -0,0 +1,38 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Moowningstar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ This licence covers THIS PROJECT ONLY.
26
+
27
+ This project is a visualization and automation layer for GitNexus. It does not
28
+ fork GitNexus, does not modify it, and does not redistribute its source. GitNexus
29
+ is a separate project distributed under PolyForm-Noncommercial-1.0.0 and
30
+ remains the property of its author, Abhigyan Patwari — see
31
+ https://github.com/abhigyanpatwari/GitNexus.
32
+
33
+ GitNexus is declared as a PEER, not a dependency: installing this project does
34
+ not install GitNexus, and nothing under that licence ships inside this package.
35
+ But **your use of GitNexus through this project is subject to GitNexus's
36
+ licence**: noncommercial use is granted, commercial use requires a separate
37
+ grant from its author. A
38
+ summary is in README.md under "License and what this project is not".
package/README.md CHANGED
@@ -1,3 +1,484 @@
1
- # Temporary Holding Version
1
+ # dsh-gitnexus-plus
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A DeepSeek Harness plugin for **GitNexus index visibility** — starting with the part
4
+ GitNexus itself cannot tell you: **what the index left out**.
5
+
6
+ ## The problem it solves
7
+
8
+ `gitnexus analyze` silently drops files. Its entire surface for saying so is one
9
+ line on stderr, printed once:
10
+
11
+ ```
12
+ Skipped 3 large files (>512KB, likely generated/vendored)
13
+ - dist/app.min.js
14
+ ...and 2 more (set GITNEXUS_VERBOSE=1 to list them all)
15
+ ```
16
+
17
+ That line is not returned, not persisted, not queryable, and gone when the terminal
18
+ scrolls. Meanwhile the files it names can be the difference between an index that
19
+ represents your codebase and one that does not — measured on this machine,
20
+ `material-ui` skips 4.4 MB (12.4 % of scanned bytes), and those files contribute
21
+ **zero** symbols to the graph.
22
+
23
+ This plugin turns that line into data, a tool, and a route.
24
+
25
+ ## What this project is, and what it is not
26
+
27
+ [GitNexus](https://github.com/abhigyanpatwari/GitNexus) is a code-intelligence engine by
28
+ **Abhigyan Patwari**. It parses a repository into symbols, relationships and execution
29
+ flows and writes them to a local graph database. It is a genuinely good piece of work,
30
+ and this project would have nothing to show without it.
31
+
32
+ What GitNexus does not give an everyday user is **visibility** and **automation**:
33
+
34
+ - **Visibility** — how much of the repository does the index actually cover? Which files
35
+ were skipped for being too large? Has the index fallen behind `HEAD`, and by how much?
36
+ The CLI does not answer those unasked, and the answer is invisible exactly when it
37
+ matters.
38
+ - **Automation** — once an index is stale, somebody has to remember to refresh it.
39
+
40
+ **This project is a layer that adds those two things and nothing else.** It reads the
41
+ index GitNexus wrote, reports what the index left out, and can start an incremental
42
+ refresh through GitNexus's own CLI.
43
+
44
+ It does **not**:
45
+
46
+ - fork GitNexus, patch it, or reimplement its parser or graph database;
47
+ - redistribute GitNexus source. Nothing from that project is inside this package's
48
+ tarball. `gitnexus` is declared as a **peer**, not a dependency, so installing this
49
+ plugin installs nothing from it — the operator supplies GitNexus, by whatever means,
50
+ and the two stay separate;
51
+ - claim authorship of, or any contribution to, GitNexus. All rights in GitNexus belong
52
+ to its author;
53
+ - replace GitNexus. With no GitNexus index there is nothing here to display.
54
+
55
+ ## Licence — read this before a commercial use
56
+
57
+ This project is **MIT** (see [LICENSE](LICENSE)).
58
+
59
+ **It depends on GitNexus, which is `PolyForm-Noncommercial-1.0.0`.** That licence grants
60
+ noncommercial use and restricts commercial use. GitNexus is **not** a dependency of this
61
+ package — it is a peer, so installing this plugin does not install it, and the plugin
62
+ never bundles or redistributes it. Installing this plugin's package therefore installs
63
+ nothing under that licence. But every GitNexus-backed feature — reading an index,
64
+ refreshing one, installing one on request — puts your use of *GitNexus* under
65
+ *GitNexus's* licence:
66
+
67
+ - **your use of this plugin's GitNexus-backed features is subject to GitNexus's
68
+ licence**, not to this project's MIT grant;
69
+ - **commercial use requires a separate grant from GitNexus's author.** If your use is
70
+ commercial, read
71
+ <https://github.com/abhigyanpatwari/GitNexus/blob/main/LICENSE> or contact the author
72
+ before relying on any of it.
73
+
74
+ This project cannot and does not change that: it only calls the interfaces GitNexus
75
+ provides. The read-only half — reading `.gitnexus/gitnexus.json` and reporting on it —
76
+ is this project's own code and carries no GitNexus code, but it also has nothing to
77
+ report on unless GitNexus produced that file.
78
+
79
+ A one-line summary: **the MIT licence here covers my code; GitNexus's licence covers
80
+ GitNexus, and installing this plugin installs GitNexus.**
81
+
82
+ ## What it reports
83
+
84
+ - **Skipped large files** — path, size, share of its language, and two independent
85
+ signals for *why* it is not source: a single-line (minified) heuristic, and a
86
+ build-output path heuristic. Binary assets are detected separately and never
87
+ called minified.
88
+ - **Index freshness** — the indexed commit against HEAD, classified `current` /
89
+ `behind` / `diverged` / `unknown` the same way `gitnexus status` does, plus the
90
+ uncommitted-change count.
91
+ - **Graph composition** — nodes, edges, communities, processes, embeddings, and
92
+ edges-per-node, all read from the manifest.
93
+ - **Query surfaces** — which of graph / keyword search / semantic search are
94
+ actually available, with the reason when one is not. An index with no FTS cannot
95
+ answer keyword queries, and hiding that is how a reader concludes otherwise.
96
+ - **Git activity, crossed with the index** — recent commits, authors (bots marked),
97
+ branches, most-changed files and most-recent files, each carrying a three-way
98
+ verdict: *indexed*, *excluded by design*, or **missing from the index**. That last
99
+ one is the point: of the files people are actively editing, which does the graph
100
+ not see?
101
+ - **Language distribution by bytes**, split three ways: languages GitNexus can
102
+ *parse* into the graph, formats it merely *recognizes*, and everything else.
103
+ - **Resolver confidence** — untyped receiver sites, which make `impact` and
104
+ `context` a lower bound rather than an exact answer.
105
+
106
+ ## Why the git three-way split matters
107
+
108
+ A two-way "indexed / not indexed" count is misleading. Measured on a real
109
+ repository, 1 635 files had been touched in 90 days: 491 indexed, 1 143 absent, and
110
+ reporting that as "70 % missing" is wrong — 1 143 of them are lockfiles, dotfiles
111
+ and binary assets that no scan would select either. Exactly **one** was a genuine
112
+ gap (`src/stores/windowModeStore.ts`). The panel reports the three numbers
113
+ separately, and only the third raises a warning.
114
+
115
+
116
+ ## Design commitments
117
+
118
+ **Reuse, don't reimplement.** "Which files did GitNexus skip?" has exactly one
119
+ correct answer: the set `walkRepositoryPaths` produced. This plugin deep-imports
120
+ GitNexus's own `createIgnoreFilter`, `glob` call and `getLanguageFromFilename`
121
+ rather than approximating them, and `test/run.mjs` cross-checks its scan against
122
+ `walkRepositoryPaths` on real repositories. If upstream reshapes those modules the
123
+ plugin degrades to a documented fallback scan (`ScanReport.primitives === 'fallback'`)
124
+ instead of reporting a wrong number.
125
+
126
+ **Absent is reported as absent.** The size cap an index was built under is read from
127
+ that index. Legacy indexes (`meta.json`, no `indexCoverage`) are reported with the
128
+ cap *unknown* and a note — never defaulted to today's value, which would misattribute
129
+ covered files as skipped.
130
+
131
+ **Read-only.** Nothing here triggers `analyze`, writes to `.gitnexus/`, or takes the
132
+ index lock. A status check must never start a 30-second rebuild.
133
+
134
+ ## Getting GitNexus — the prerequisite, and the two ways to satisfy it
135
+
136
+ This plugin reads an index. **Only GitNexus produces one.** So the plugin checks
137
+ whether GitNexus is present, and when it is not, the panel says so above everything else
138
+ rather than letting you wonder why the numbers are missing.
139
+
140
+ Two ways out, in the order the panel offers them:
141
+
142
+ **① Install it yourself.** The panel shows a copyable `npm install -g gitnexus`, and
143
+ that is the recommendation. You know your network, your registry and your toolchain; a
144
+ command you can run is more useful than a download this plugin performs on your behalf,
145
+ and it works behind whatever mirror or feed your machine actually reaches.
146
+
147
+ **② Let the plugin install it.** Second in the list and behind a confirmation, because
148
+ it is a much larger decision. It runs, through the host's shell executor:
149
+
150
+ ```
151
+ npm install --prefix <DSH_HOME>/gitnexus-plus/gitnexus gitnexus@^1.6.12
152
+ ```
153
+
154
+ and the confirmation states what that means before anything starts:
155
+
156
+ - **about 500 MB to 1 GB** downloaded, from whatever registry your `.npmrc` already
157
+ names — this plugin hardcodes none, because a hardcoded one breaks exactly the users
158
+ whose network is why they need this path;
159
+ - **several minutes**, longer on a slow link or when the native modules have to compile;
160
+ - **this machine's build toolchain is used** — `@ladybugdb/core`, `tree-sitter` and
161
+ `tree-sitter-*` compile on install. That is why this is `npm` and not `pnpm`: pnpm 10+
162
+ refuses dependency build scripts by default, and the failure it produces is one you
163
+ cannot act on. GitNexus's own message names the grants it needs:
164
+ `--allow-build=@ladybugdb/core --allow-build=gitnexus --allow-build=tree-sitter`;
165
+ - it installs into a **plugin-owned directory**, never your global prefix, so it needs no
166
+ elevation and cannot collide with a GitNexus you already have.
167
+
168
+ Everything runs through `ctx.shell`, so a sandboxed deployment confines it exactly as it
169
+ confines your own commands. **A refusal is reported as a refusal and never retried
170
+ wider** — the plugin never asks for more scope than the session already granted.
171
+
172
+ If your GitNexus lives somewhere unusual, name it directly and none of this applies:
173
+
174
+ ```sh
175
+ GITNEXUS_CLI=/path/to/gitnexus/dist/cli/index.js
176
+ ```
177
+
178
+ That path is checked first, and it is the answer for an offline or air-gapped machine:
179
+ put a copy anywhere, point at it, done.
180
+
181
+ ## Usage
182
+
183
+ ```sh
184
+ # CLI, no install
185
+ npm run build # tsc (host) + tsc (client) + wrap
186
+ node lib/bin/gitnexus-coverage.js <repo-path>
187
+ node lib/bin/gitnexus-coverage.js -r material-ui # by registry alias
188
+ node lib/bin/gitnexus-coverage.js --all --json # every indexed repository
189
+ node lib/bin/gitnexus-coverage.js <repo> --fail-on-large # exit 2 — CI gate
190
+
191
+ # type check
192
+ npm run check
193
+
194
+ # tests
195
+ npm test
196
+ ```
197
+
198
+ In DSH the plugin exposes:
199
+
200
+ - **tool** `gitnexus_coverage` — `{ repo, includeLanguages? }`
201
+ - **route** `GET /api/gitnexus/coverage?repo=<project>` — one report, plus the
202
+ maintenance commands for that project
203
+ - **panel** — a right-sidebar tab type `gitnexus`, opened from the sidebar's **+** →
204
+ guide page ("GitNexus 索引")
205
+
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.
210
+
211
+ 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
214
+ route so the read-only half keeps a method policy that cannot mutate — the shared guard
215
+ takes the accepted methods from the route rather than hard-coding one set for both. The
216
+ mutating verbs additionally pass the write guard, which refuses cross-origin requests;
217
+ loopback alone is not enough, because any page the operator visits can reach
218
+ `127.0.0.1`. See **Maintenance** below for what it will and will not do.
219
+
220
+ ## TypeScript, and why the build looks the way it does
221
+
222
+ The source is TypeScript under `strict`, including `noImplicitAny`. That is not
223
+ decoration: an implicit `any` is a **silent** failure — it does not report an error,
224
+ it just makes the checker stop working exactly where it was needed. Migrating this
225
+ codebase from JS+JSDoc to real types surfaced six places where the documentation and
226
+ the code disagreed, none of which produced a runtime error until the affected branch
227
+ was reached:
228
+
229
+ | Where | JSDoc claimed | Actually |
230
+ | --- | --- | --- |
231
+ | `LargeFile.generatedHint` | `string \| null` | `boolean` — consumed as `if (x)` |
232
+ | `LargeFile.minified` | non-null | nullable, when the probe budget runs out |
233
+ | `analyzer.primitives` | `number` | a **string**, compared against `'gitnexus'` |
234
+ | `MinifiedVerdict` | a type name | **the type did not exist** |
235
+ | `activityView()` result | — | missing `truncated`, which callers read |
236
+ | `analyzeCoverage` options | 3 fields | 5 — two were undocumented |
237
+
238
+ ### Two compile targets, no bundler
239
+
240
+ ```
241
+ src/index.ts ───────────┐
242
+ src/gitnexus/*.ts ──────┤ tsc (NodeNext) → lib/**/*.js + *.d.ts ESM
243
+ src/routes/*.ts ────────┤
244
+ src/bin/*.ts ───────────┘
245
+
246
+ src/client/index.ts ────→ tsc (CommonJS) → build/client/index.js
247
+ ↓ text wrap (scripts/build-client.mjs)
248
+ lib/client.js
249
+ ```
250
+
251
+ The browser half needs no bundler because it **is** one module: its only imports are
252
+ platform baseline modules (`react`, the UI primitives) supplied by the loader's own
253
+ `require` at runtime, and it has no relative imports at all. `tsc --module commonjs`
254
+ therefore emits exactly one self-contained file, and the wrap is a text operation. A
255
+ bundler would add a dependency and a resolution graph to solve a problem this package
256
+ does not have.
257
+
258
+ `esModuleInterop` is **off** for the client target on purpose: it makes
259
+ `import * as React from 'react'` compile to a bare `const React = require("react")`,
260
+ which is precisely what the loader factory provides. With interop on, tsc injects an
261
+ `__importStar` helper that would ride along in every page load.
262
+
263
+ ### `scripts/*.mjs` stays JavaScript (deliberate)
264
+
265
+ `scripts/link-runtime.mjs` runs from `postinstall`, and `scripts/build-client.mjs` is
266
+ the build driver itself. A bootstrap script **must not depend on the build it
267
+ bootstraps** — if those needed compiling, a fresh clone's `npm install` would fail
268
+ because `lib/` does not exist yet. `src/**` and `bin/**` are TypeScript; `scripts/`
269
+ is plain ESM, and that line is intentional rather than unfinished.
270
+
271
+ ### Runtime packages ship no type declarations
272
+
273
+ `@deepseek-ai/schemastery`, `@deepseek-ai/dsh-tools`, `@deepseek-ai/cordis` and
274
+ `@deepseek-ai/dsh-client-ui-primitives` each declare `"types": "lib/types/index.d.ts"`
275
+ in their manifest and ship no such file. Every plugin author has to write these
276
+ declarations; `types/` holds the four, with the surface limited to what this plugin
277
+ actually uses. The primitive **props** were read off real call sites in shipped
278
+ plugins rather than guessed, because a wrong prop name fails silently at runtime —
279
+ the control renders with a default and nothing reports anything.
280
+
281
+ ## Client half
282
+
283
+ `src/client/index.ts` is one CommonJS module using nothing but baseline modules
284
+ (`react`), so `scripts/build-client.mjs` wraps it into the loader's factory
285
+ artifact with no bundler at all — a text wrap cannot drift from its source the way
286
+ a bundle can.
287
+
288
+ The panel splits fetching (`CoveragePanel`) from presentation (`CoverageView`).
289
+ That is not stylistic: a component that fetches cannot be rendered against a
290
+ fixture, and the states worth asserting on — a report with skipped files, one
291
+ without, an error, missing language data — are exactly the ones a fixture supplies.
292
+ `test/client.mjs` renders each of them through a minimal single-pass hooks shim.
293
+
294
+ Styling is theme-agnostic by construction: host elements only, `color` inherited,
295
+ every border derived from `currentColor`. `@deepseek-ai/dsh-client-ui-primitives` is
296
+ a baseline module but ships no JS in the installed runtime, so its exports cannot
297
+ be verified from outside — guessing at component props would fail at mount time in
298
+ a browser, where no test in this repository can see it.
299
+
300
+ ## Verified end to end
301
+
302
+ Installed into the `web` profile and booted; the route answered with real data:
303
+
304
+ ```
305
+ GET /api/gitnexus/coverage?repo=D:\Project\material-ui HTTP 200
306
+ manifest meta.json · scanned 28927 · selected 28924 · large 3 · 11 languages
307
+ 2.3 MB packages/mui-icons-material/lib/index.js generated
308
+ 1.4 MB CHANGELOG.old.md
309
+ 739.9 KB packages/mui-icons-material/lib/index.mjs generated
310
+ GET /api/gitnexus/coverage?repo=C:\Windows HTTP 404 (refused)
311
+ ```
312
+
313
+ ## Scope: the project you are in, not a global list
314
+
315
+ The panel reports **the project the session's workspace points at**. It does not
316
+ list indexed repositories and has no picker.
317
+
318
+ That is a deliberate reversal. The first design read `~/.gitnexus/registry.json`
319
+ and offered a dropdown, which made a global file a hard dependency — so a fresh
320
+ clone, a second working tree, or an index built with `GITNEXUS_HOME` redirected
321
+ could not be shown at all, which is exactly when a coverage panel is most useful.
322
+ The workspace comes from the slot's session-scoped props (`useWorkspaces` matched
323
+ by `sessionId`); the host route accepts any directory **that is already a GitNexus
324
+ index**, which is the bound that keeps it from being a filesystem probe.
325
+
326
+ ## Maintenance: the refresh runs here, the escape hatches do not
327
+
328
+ Two situations the read-only half cannot resolve on its own:
329
+
330
+ - **初始化索引 / 增量刷新** — the index is missing or behind HEAD. This one the plugin
331
+ runs for you.
332
+ - **强制全量重构** — 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.
335
+
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:
340
+
341
+ DSH's approval service governs **sandbox escalation** — widening the scope a session was
342
+ granted. Its audit pair must be turn-enclosed, which is why `ApprovalService.request()`
343
+ throws outside an open turn; a panel route has no turn, so it cannot ask. It also has
344
+ nothing to ask about here: this command writes `.gitnexus/` **inside the workspace**,
345
+ already within the granted scope.
346
+
347
+ Confinement is not something a caller opts into. It is what `ctx.shell` *is* in a given
348
+ deployment — `dsh-pwsh-local` exposes the argv seam that `dsh-pwsh-sandbox` wraps
349
+ through `ctx.sandbox.confine`. So routing through the executor gives this plugin exactly
350
+ the isolation your own shell commands get.
351
+
352
+ What the plugin commits to, and what is tested:
353
+
354
+ - **It never sets `sandboxPolicy`.** Requesting one would be this plugin choosing its
355
+ own confinement — escalation, asked for through a door that cannot ask. A denial comes
356
+ back as `denied` and **is never retried wider**.
357
+ - **One run per repository.** Two concurrent `analyze` processes contend for the same
358
+ store, and the second reports a failure that says nothing about the repository.
359
+ - **`--index-only` always.** Without it `analyze` rewrites `AGENTS.md`, `CLAUDE.md` and
360
+ the agent skill files in your tree — a far larger change than "refresh the index".
361
+ - **A confirmation before it starts**, stating the exact command, what it touches and
362
+ how long it takes.
363
+ - **Cancellable**, with a ten-minute ceiling by default (`analyzeTimeoutMs`).
364
+
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.
368
+
369
+ The commands handed over are not a bare `gitnexus analyze`, because that does not work
370
+ in every deployment:
371
+
372
+ ```
373
+ $env:GITNEXUS_MEMORY='off'; $env:NODE_OPTIONS='--max-old-space-size=8192'; \
374
+ gitnexus analyze --index-only '<project>'
375
+
376
+ $env:GITNEXUS_MEMORY='off'; $env:NODE_OPTIONS='--max-old-space-size=8192'; \
377
+ gitnexus analyze --index-only --force '<project>'
378
+ ```
379
+
380
+ - `GITNEXUS_MEMORY=off` — the CLI otherwise re-spawns itself with a larger heap over
381
+ piped stdio, which a confined sandbox denies (`spawn EPERM`).
382
+ - `NODE_OPTIONS=--max-old-space-size=8192` — supplies the heap that respawn wanted.
383
+ - `--index-only` — the default `analyze` also rewrites `AGENTS.md`, `CLAUDE.md` and
384
+ agent skill files inside your repository. A maintenance action must not edit the
385
+ working tree.
386
+
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.
392
+
393
+ The panel prints these rationales next to the commands, so they do not look
394
+ arbitrary. `src/gitnexus/commands.js` is pure and asserted verbatim by tests.
395
+
396
+
397
+ The CLI cannot manage the `desktop` profile — Electron owns it. Use the Web
398
+ sidebar's Plugins page there. `web` is CLI-managed:
399
+
400
+ ```sh
401
+ dsh plugin --profile web add <absolute path to this package>
402
+ dsh --profile web --dump-config | grep gitnexus-plus # layer composes
403
+ dsh --profile web # restart (bundle set changed)
404
+ ```
405
+
406
+ `dsh plugin add` also adds the package to `dsh.profile.bundles` automatically,
407
+ because this package declares `dsh.bundle`.
408
+
409
+ ## Known limits
410
+
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.
422
+ - **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.
429
+ - **Installing GitNexus from the panel has never been run against a real network.** The
430
+ command shape is verified (`npm install --prefix … --no-audit --no-fund`, no hardcoded
431
+ registry), the runner's state machine is covered by fourteen assertions, and the
432
+ failure paths name the two likely causes — unreachable registry, missing C++ toolchain.
433
+ What is **not** verified is the download and compile itself: it is several hundred
434
+ megabytes and was never executed from inside a confined DSH process. Treat the first
435
+ 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.
437
+ - **`--omit=optional` saves about 486 MB of that install, and is not used by default.**
438
+ Measured: a full GitNexus tree is 998.9 MB, and omitting the optional dependencies
439
+ gives 513.2 MB by dropping `onnxruntime-node` (287 MB), `onnxruntime-web` (139 MB) and
440
+ `@huggingface/transformers`. Those serve vector search and embeddings, which this
441
+ plugin never enables and reports as `unavailable`. The default does not pass the flag
442
+ because the plugin cannot verify that analysis still succeeds without them on every
443
+ platform, and a refresh that fails in an unexplained way is worse than one that is
444
+ large. A deployment that wants the smaller tree can set `gitnexusInstallDir` aside and
445
+ install by hand with `--omit=optional`.
446
+ - **The refresh runs outside the conversation, and that has a real consequence.** A
447
+ command started from the panel goes through `ctx.shell` and is confined exactly as
448
+ your own shell commands are, but it is **not** a tool call: it writes no
449
+ `approval/asked` + `approval/decided` pair into the session log, and it does not
450
+ appear in the transcript. That pair could not be written even if it were wanted —
451
+ `ApprovalService.request()` requires an open turn and throws without one, which is
452
+ also why there is nothing to approve: the command does not escalate. The audit record
453
+ is the run state the panel holds, which is per-process and is not persisted. If you
454
+ want a refresh on the conversation's record, run the handed-over command in the
455
+ session instead; that path is unchanged.
456
+ - **Walk cost scales with file count.** ~29 000 files takes ~1.4 s warm, ~13 s on a
457
+ cold filesystem cache. The host half caches reports for 15 s; a panel that polls
458
+ faster than that gains nothing.
459
+ - **The minified heuristic reads a 64 KB head and extrapolates.** It flags
460
+ single-line bundles reliably and correctly *declines* generated-but-multi-line
461
+ output (verified: a 2.3 MB, 75 263-line file averages 31 chars/line). The
462
+ generated-path heuristic covers that gap, as a hint rather than a verdict.
463
+ - **Binary assets are detected and reported separately, and that is not cosmetic.**
464
+ A first revision tested only "no newlines ⇒ one enormous line", so a real 380 MB
465
+ `.glb` was reported as *"single-line ≈ 399 230 584 chars — probably minified
466
+ rather than source"*. The number was absurd; the conclusion was worse, because it
467
+ implied that 97 % of one repository's skipped bytes were compressed **source**
468
+ when 414 of 417 MB were 3D models that could never hold a symbol. Detection is
469
+ git's own rule (a NUL byte in the head means binary), plus a control-byte ratio;
470
+ binary short-circuits both the minified and generated claims, and the headline
471
+ warning now separates binary from text so the figure worth acting on is visible.
472
+ Verified end to end on the repository that exposed it: the actionable remainder
473
+ went from an implied 416.9 MB to **2.6 MB**.
474
+ - **`git ls-tree` is not used.** An earlier design compared the index against
475
+ git-tracked files; real data killed it — 8 of the "missing" tracked files in a
476
+ 37-file repo were dotfiles excluded by the walker's `dot: false`, and 2 indexed
477
+ 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.
@@ -0,0 +1,18 @@
1
+ # dsh-gitnexus-plus — bundle layer.
2
+ #
3
+ # Applied when a profile lists this package in `dsh.profile.bundles`. The row id
4
+ # is what a profile-level patch targets to change this plugin's config, so it is
5
+ # deliberately stable and matched by `name`.
6
+ - insert:
7
+ - id: gitnexus-plus
8
+ name: dsh-gitnexus-plus
9
+ config:
10
+ # Cap for *reporting* only. The index's own recorded cap wins when present,
11
+ # because judging an index by a threshold it was not built with would
12
+ # misreport covered files as skipped.
13
+ scanDefaultCapKb: 512
14
+ # Sampling budget for the minified heuristic: how many oversized files to
15
+ # probe by reading their head. Bounded because a repository may hold
16
+ # hundreds of skipped bundles and this is a heuristic, not a verdict.
17
+ minifiedSampleLimit: 40
18
+ serveRoutes: true
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `gitnexus-coverage` — report what an index left out.
4
+ *
5
+ * Usage:
6
+ * dsh-gitnexus-coverage [repo-path | -r <alias>] [--json]
7
+ * dsh-gitnexus-coverage --all # every registered repository
8
+ *
9
+ * Exit code is 0 even when files were skipped: a skipped bundle is a fact about
10
+ * the repository, not a failure of this tool. Pass `--fail-on-large` to make it a
11
+ * CI gate instead.
12
+ */
13
+ export {};
14
+ //# sourceMappingURL=gitnexus-coverage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitnexus-coverage.d.ts","sourceRoot":"","sources":["../../src/bin/gitnexus-coverage.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG"}