dsh-graft-plugin 0.1.1 → 0.2.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/README.md +54 -8
- package/package.json +1 -2
- package/scripts/test-harness.mjs +9 -1
- package/src/client.js +72 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dsh-graft-plugin
|
|
2
2
|
|
|
3
|
-
            
|
|
4
4
|
|
|
5
5
|
A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin that puts [graft](https://github.com/trailhq/Graft) — a prebuilt graph of every symbol, its `file:line` span, and who calls what — in front of both you and the model.
|
|
6
6
|
|
|
@@ -9,7 +9,7 @@ It does four things:
|
|
|
9
9
|
- **A chip in the composer** naming the graft index the current session is actually serving, whether it still matches the code, and how many tokens graft has saved in that repo.
|
|
10
10
|
- **Five tools for the model** — `graft_ask`, `graft_grep`, `graft_callers`, `graft_skeleton`, `graft_map` — resolved per session, so one dsh can serve several workspaces at once.
|
|
11
11
|
- **`/graft`** to build or rebuild the index, and **auto-rebuild** after the model edits files.
|
|
12
|
-
- **A `graft viz` button** that opens the graph
|
|
12
|
+
- **A `graft viz` button** that opens the graph in dsh's built-in right sidebar (0.1.5+), in [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) if you still run it, and in a browser tab as the last resort.
|
|
13
13
|
|
|
14
14
|
## Install
|
|
15
15
|
|
|
@@ -18,12 +18,29 @@ dsh plugin --profile web add dsh-graft-plugin
|
|
|
18
18
|
dsh --profile web
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
**pnpm 10 and later block `postinstall` scripts from unapproved packages**, and dsh skips writing the plugin's bundle row whenever the install exits non-zero — the dependency lands, the plugin stays invisible, and dsh starts up cleanly with no error to search for. If `add` ends with `ERR_PNPM_IGNORED_BUILDS`, approve and re-run it:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
cd ~/.dsh/profiles/web && pnpm approve-builds # tick dsh-graft-plugin
|
|
25
|
+
dsh plugin --profile web add dsh-graft-plugin # re-run so the bundle row gets written
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Confirm it mounted — the plugin must appear in **both** `dependencies` and `dsh.profile.bundles` in the profile's `package.json`:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
cd ~/.dsh/profiles/web && node -e "const p=require('./package.json');
|
|
32
|
+
console.log(p.dependencies['dsh-graft-plugin'],
|
|
33
|
+
p.dsh.profile.bundles.includes('dsh-graft-plugin'))"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Every tool here shells out to the `graft` CLI, so it needs `@nanonets/graft` on PATH:
|
|
22
37
|
|
|
23
38
|
```sh
|
|
24
39
|
npm install -g @nanonets/graft
|
|
25
40
|
```
|
|
26
41
|
|
|
42
|
+
A `postinstall` does this for you **when your package manager runs lifecycle scripts** — under pnpm 10+ they are blocked by default, so treat the command above as the required step there, not a fallback. If your global npm prefix needs elevation, the postinstall says so and prints the one command to run by hand.
|
|
43
|
+
|
|
27
44
|
Then index a repo once:
|
|
28
45
|
|
|
29
46
|
```sh
|
|
@@ -33,13 +50,17 @@ graft build
|
|
|
33
50
|
|
|
34
51
|
### The sidebar tab
|
|
35
52
|
|
|
36
|
-
|
|
53
|
+
On dsh `0.1.5` and later the button opens the graph in **dsh's own right sidebar** — a tab titled Graft, also listed on the sidebar's guide page as "The graft graph for this workspace" — and a second click focuses the tab that is already open. No install needed for that path: the registration is soft, so an older dsh without the right sidebar falls through to `dsh-better-sidebar` when it is mounted, and to a browser tab when neither is there.
|
|
54
|
+
|
|
55
|
+
If your profile still runs `dsh-better-sidebar`, that path is tried **first** and behaves exactly as before. It ships as a dependency of this plugin (0.18.x), so it is already downloaded — but dsh only mounts what the profile lists:
|
|
37
56
|
|
|
38
57
|
```sh
|
|
39
58
|
dsh plugin --profile web add dsh-better-sidebar
|
|
40
59
|
```
|
|
41
60
|
|
|
42
|
-
|
|
61
|
+
> **Compatibility.** `dsh-better-sidebar` 0.18.x requires `@deepseek-ai/dsh-client-ui-primitives`, which dsh removed in `0.1.5-rc.2`. On that version or later it will not mount — which is exactly the profiles where the built-in right sidebar takes over instead. Everything else in this plugin — chip, tools, `/graft`, auto-rebuild — is unaffected.
|
|
62
|
+
|
|
63
|
+
The chip, the tools, `/graft` and the auto-rebuild all work without any sidebar; the button simply falls back to a browser tab when neither one is present.
|
|
43
64
|
|
|
44
65
|
> This plugin does **not** insert a `dsh-better-sidebar` row itself, on purpose. That package's own bundle row carries a guard that disables it when another enabled row already mounts the same package. A second row with a different id makes that guard circular, and the losing outcome is *your existing sidebar silently disabling itself*. One explicit command is worth more than that risk.
|
|
45
66
|
|
|
@@ -63,7 +84,7 @@ The root is resolved from **the session's own workspace**, not from wherever dsh
|
|
|
63
84
|
|
|
64
85
|
### The visualiser
|
|
65
86
|
|
|
66
|
-
The `graft viz` button opens the graph as a tab in [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar). The context view lays out the whole graph:
|
|
87
|
+
The `graft viz` button opens the graph as a tab — in dsh's built-in right sidebar on `0.1.5`+, in [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) if that is what you run. The context view lays out the whole graph:
|
|
67
88
|
|
|
68
89
|

|
|
69
90
|
|
|
@@ -110,9 +131,15 @@ All optional, all on the plugin's row in your profile's `cordis.patch.yml`:
|
|
|
110
131
|
|
|
111
132
|
## Requirements
|
|
112
133
|
|
|
113
|
-
|
|
134
|
+
| dsh | status |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `0.1.2-rc.1` – `0.1.0-rc.7` | tested, full feature set — the `graft viz` tab via `dsh-better-sidebar` |
|
|
137
|
+
| `0.1.5-rc.2` / `0.1.5` | tested — chip, tools, `/graft` and auto-rebuild all work; on `0.1.5` the `graft viz` button opens the graph in the built-in right sidebar |
|
|
138
|
+
|
|
114
139
|
- Node 22+
|
|
115
|
-
- the `graft` CLI on PATH (
|
|
140
|
+
- the `graft` CLI on PATH (`npm install -g @nanonets/graft`)
|
|
141
|
+
|
|
142
|
+
The client bundle requires only `react`, which is why it survives the dsh `0.1.5-rc.2` client-package removals that broke several neighbouring plugins.
|
|
116
143
|
|
|
117
144
|
## Development
|
|
118
145
|
|
|
@@ -124,6 +151,25 @@ npm run test:live # drives the real graft CLI against a real repo
|
|
|
124
151
|
|
|
125
152
|
`npm test` needs no graft index and no graft install anywhere on the machine. The one suite that does build a real index skips itself, loudly, when graft is absent — a missing CLI is a setup state, not a defect.
|
|
126
153
|
|
|
154
|
+
### Two identifiers, deliberately different
|
|
155
|
+
|
|
156
|
+
The cordis row `id` (`graft-status`) is this plugin's own handle — it keys the storage file, the prompt section and every effect label, and is stable across renames.
|
|
157
|
+
|
|
158
|
+
The client bundle's `__ModuleLoader__.load({ id })` is **not** that handle. It must be the published package name: dsh builds each client row from `entry.options.name` — the `name:` in `cordis.patch.yml` — serves the bundle at `/plugins/<name>/client.js`, then asserts that a factory registered under that same name. Using the row id there fails every boot with:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
bundle /plugins/dsh-graft-plugin/client.js loaded without
|
|
162
|
+
registering "dsh-graft-plugin" via __ModuleLoader__.load
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`npm test` guards this both ways (the guard was added with the 0.1.1 fix).
|
|
166
|
+
|
|
167
|
+
## Troubleshooting
|
|
168
|
+
|
|
169
|
+
**No chip appears and dsh started without errors.** The plugin is in the profile's `package.json` `dependencies` but not in `dsh.profile.bundles` — which happens under pnpm 10+ when the `add` ends non-zero on `ERR_PNPM_IGNORED_BUILDS`; dsh skips the bundle row in that case and starts up looking healthy. Fix: `pnpm approve-builds` in the profile directory (tick `dsh-graft-plugin`), then re-run `dsh plugin --profile web add dsh-graft-plugin`. If `pnpm approve-builds` reports nothing awaiting approval, check `pnpm-workspace.yaml` for an unanswered `dsh-graft-plugin` entry (pnpm's approval placeholder) and set its value to `true` first.
|
|
170
|
+
|
|
171
|
+
**`/graft` says the graft CLI is not on PATH.** The postinstall was blocked or skipped (see Install): `npm install -g @nanonets/graft`.
|
|
172
|
+
|
|
127
173
|
## Licence
|
|
128
174
|
|
|
129
175
|
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-graft-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Shows which graft index a dsh session is serving and whether it still matches the code, and gives the model graft_ask, graft_grep, graft_callers, graft_skeleton and graft_map as native tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -41,7 +41,6 @@
|
|
|
41
41
|
"client": {
|
|
42
42
|
"platform": "web",
|
|
43
43
|
"inject": [
|
|
44
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
45
44
|
"@deepseek-ai/dsh-api-remotes"
|
|
46
45
|
]
|
|
47
46
|
}
|
package/scripts/test-harness.mjs
CHANGED
|
@@ -246,7 +246,15 @@ check(
|
|
|
246
246
|
clientSource.includes('ctx.inject(["betterSidebar"]') && /const inject = \[[^\]]*\]/.exec(clientSource)?.[0].includes('betterSidebar') === false,
|
|
247
247
|
/const inject = \[[^\]]*\]/.exec(clientSource)?.[0],
|
|
248
248
|
)
|
|
249
|
-
|
|
249
|
+
// The open order: better-sidebar if a profile still has it, then dsh's own
|
|
250
|
+
// right sidebar (0.1.5+), and a browser tab only when neither is there.
|
|
251
|
+
check('dsh right sidebar comes after better-sidebar', clientSource.indexOf('ctx.get("betterSidebar")') < clientSource.indexOf('ctx.get("sidebarRight")'))
|
|
252
|
+
check('it opens by kind, which also reveals the column', clientSource.includes('right.openTab(VIZ_KIND)'))
|
|
253
|
+
check('a kind nothing registered falls through, not dead', clientSource.includes('openTab throws for a kind nothing registered'))
|
|
254
|
+
check('the type and its body register under the same id', clientSource.includes('sidebarRightTabs.register({') && clientSource.includes('id: TAB_TYPE,') && clientSource.includes('name: "sidebar.right.pane.tab", key: TAB_TYPE'))
|
|
255
|
+
check('that dependency is soft too', clientSource.includes('ctx.inject(["sidebarRightTabs", "slots"]') && !/const inject = [[]/.test('') && clientSource.indexOf('const inject = ["slots", "remote"]') !== -1)
|
|
256
|
+
check('the body reads the session from either host', clientSource.includes('props?.sessionId ?? props?.scope?.sessionId'))
|
|
257
|
+
check('the browser tab survives only as the last resort', clientSource.includes('neither sidebar'))
|
|
250
258
|
|
|
251
259
|
await rm(fixture, { recursive: true, force: true })
|
|
252
260
|
|
package/src/client.js
CHANGED
|
@@ -324,9 +324,19 @@ window.__ModuleLoader__.load({
|
|
|
324
324
|
].join("");
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
-
/**
|
|
327
|
+
/** This viewer's identity: better-sidebar's tab type, and dsh's tab id. */
|
|
328
328
|
const TAB_TYPE = "graft-status:viz";
|
|
329
329
|
|
|
330
|
+
/**
|
|
331
|
+
* The kind dsh's own right sidebar opens this page type by.
|
|
332
|
+
*
|
|
333
|
+
* dsh 0.1.5 splits a tab type in two: a static registration naming the kind
|
|
334
|
+
* (`ctx.sidebarRightTabs`), and the body under the registration's id in the
|
|
335
|
+
* keyed `sidebar.right.pane.tab` seat. `ctx.sidebarRight.openTab(kind)`
|
|
336
|
+
* then opens it AND expands a collapsed column in the same step.
|
|
337
|
+
*/
|
|
338
|
+
const VIZ_KIND = "graft-viz";
|
|
339
|
+
|
|
330
340
|
/**
|
|
331
341
|
* The sidebar tab: `graft viz` in an iframe.
|
|
332
342
|
*
|
|
@@ -370,10 +380,14 @@ window.__ModuleLoader__.load({
|
|
|
370
380
|
const [status, setStatus] = React.useState(null);
|
|
371
381
|
const [reload, setReload] = React.useState(0);
|
|
372
382
|
const [busy, setBusy] = React.useState(false);
|
|
383
|
+
// Visible by default: better-sidebar passes `visible: false` for a tab
|
|
384
|
+
// that exists but is not focused (and asks live views to pause then);
|
|
385
|
+
// dsh's sidebar mounts only what it draws and passes nothing.
|
|
373
386
|
const visible = props?.visible !== false;
|
|
374
|
-
//
|
|
375
|
-
//
|
|
376
|
-
|
|
387
|
+
// Which workspace to resolve. Both hosts supply the session, in their
|
|
388
|
+
// own shape: dsh passes `sessionId` straight to the body, while
|
|
389
|
+
// better-sidebar wraps it in the tab's `scope`.
|
|
390
|
+
const sessionId = props?.sessionId ?? props?.scope?.sessionId;
|
|
377
391
|
|
|
378
392
|
React.useEffect(() => {
|
|
379
393
|
if (!visible || viz !== null) return;
|
|
@@ -630,7 +644,25 @@ window.__ModuleLoader__.load({
|
|
|
630
644
|
return;
|
|
631
645
|
}
|
|
632
646
|
|
|
633
|
-
//
|
|
647
|
+
// Next: dsh's own right sidebar (0.1.5+). `openTab` names the KIND,
|
|
648
|
+
// reveals the column, and reuses the tab that is already open, so a
|
|
649
|
+
// second click focuses the graph rather than stacking another.
|
|
650
|
+
// The body starts the server itself on mount, so nothing is awaited
|
|
651
|
+
// here and no popup-blocker timing applies.
|
|
652
|
+
const right = ctx.get("sidebarRight");
|
|
653
|
+
if (right !== undefined && right !== null && typeof right.openTab === "function") {
|
|
654
|
+
try {
|
|
655
|
+
right.openTab(VIZ_KIND);
|
|
656
|
+
return;
|
|
657
|
+
} catch (error) {
|
|
658
|
+
// openTab throws for a kind nothing registered — which is the
|
|
659
|
+
// case when the tab registry was absent at load. Fall through to
|
|
660
|
+
// the browser tab rather than leaving the click dead.
|
|
661
|
+
console.warn("[graft-status] right sidebar refused the viz tab:", error);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Last resort, for a profile with neither sidebar: a browser tab.
|
|
634
666
|
|
|
635
667
|
// Opened SYNCHRONOUSLY, before any await. A tab opened from a
|
|
636
668
|
// resolved promise is a popup as far as the browser is concerned and
|
|
@@ -812,6 +844,41 @@ window.__ModuleLoader__.load({
|
|
|
812
844
|
// plugin — chip included — from activating in a profile that does not
|
|
813
845
|
// have the sidebar installed. This way the tab appears when the service
|
|
814
846
|
// does, and the button falls back to a browser tab when it never comes.
|
|
847
|
+
// dsh's own right sidebar (0.1.5+). Two registrations: the static type,
|
|
848
|
+
// and the body in the keyed seat under the SAME id. `ctx.inject` rather
|
|
849
|
+
// than the module-level list for the same reason as below — a profile
|
|
850
|
+
// without this sidebar must still get the chip and the button.
|
|
851
|
+
ctx.inject(["sidebarRightTabs", "slots"], (scope) => {
|
|
852
|
+
scope.effect(
|
|
853
|
+
() =>
|
|
854
|
+
scope.sidebarRightTabs.register({
|
|
855
|
+
id: TAB_TYPE,
|
|
856
|
+
kind: VIZ_KIND,
|
|
857
|
+
// A page type, opened by kind: it recognises no resource address,
|
|
858
|
+
// so it declares no patterns. "extension" is the band for a type
|
|
859
|
+
// from outside the product, and the default.
|
|
860
|
+
priority: "extension",
|
|
861
|
+
title: () => "Graft",
|
|
862
|
+
guide: [
|
|
863
|
+
{
|
|
864
|
+
order: 60,
|
|
865
|
+
title: () => "Graft",
|
|
866
|
+
description: () => "The graft graph for this workspace",
|
|
867
|
+
icon: GraphIcon,
|
|
868
|
+
},
|
|
869
|
+
],
|
|
870
|
+
}),
|
|
871
|
+
"graft-status: right sidebar tab type",
|
|
872
|
+
);
|
|
873
|
+
scope.effect(
|
|
874
|
+
() =>
|
|
875
|
+
scope.slots.inject("sidebar.right.pane.tab", () =>
|
|
876
|
+
scope.slots.register({ name: "sidebar.right.pane.tab", key: TAB_TYPE }, makeVizTab(ctx)),
|
|
877
|
+
),
|
|
878
|
+
"graft-status: right sidebar tab body",
|
|
879
|
+
);
|
|
880
|
+
});
|
|
881
|
+
|
|
815
882
|
ctx.inject(["betterSidebar"], (scope) => {
|
|
816
883
|
scope.effect(
|
|
817
884
|
() =>
|