dsh-code-review-graph 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.
- package/README.en.md +143 -10
- package/README.md +127 -9
- package/dist/backend.js +2 -2
- package/dist/backend.js.map +2 -2
- package/dist/client.js +45 -2
- package/dist/client.js.map +3 -3
- package/dist/index.js +147 -33
- package/dist/index.js.map +4 -4
- package/dist/setup.js +30 -14
- package/dist/setup.js.map +2 -2
- package/docs/release-0.1.2.md +29 -0
- package/docs/validation.md +20 -0
- package/package.json +2 -2
- package/docs/release-0.1.1.md +0 -30
package/README.en.md
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
Native DeepSeek Harness tools and a Web/Desktop graph sidebar, powered by
|
|
6
6
|
[Code Review Graph](https://github.com/tirth8205/code-review-graph) **2.3.9**.
|
|
7
|
-
Preserves all 30 tools, five prompt workflows and seven skills.
|
|
8
|
-
|
|
9
|
-
See the [0.1.
|
|
7
|
+
Preserves all 30 tools, five prompt workflows and seven skills. Version **0.1.2**
|
|
8
|
+
adds one-click engine setup and graph generation, plus native commands without model inference.
|
|
9
|
+
See the [0.1.2 release notes](docs/release-0.1.2.md).
|
|
10
10
|
|
|
11
11
|
Supports Windows, Linux and macOS, with exact DSH baselines **0.1.5-rc.2** and
|
|
12
12
|
**0.2.0-rc.2**. Alpha compatibility is outside the contract.
|
|
@@ -16,13 +16,13 @@ Supports Windows, Linux and macOS, with exact DSH baselines **0.1.5-rc.2** and
|
|
|
16
16
|
Install the published npm package:
|
|
17
17
|
|
|
18
18
|
```sh
|
|
19
|
-
dsh plugin --profile web add dsh-code-review-graph@0.1.
|
|
20
|
-
npx dsh-code-review-graph@0.1.
|
|
19
|
+
dsh plugin --profile web add dsh-code-review-graph@0.1.2
|
|
20
|
+
npx dsh-code-review-graph@0.1.2 prepare
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Use `headless` or another ordinary profile for the command-line entrance. Desktop
|
|
24
24
|
profiles belong to the application: install through that release's native plugin
|
|
25
|
-
manager, using `dsh-code-review-graph@0.1.
|
|
25
|
+
manager, using `dsh-code-review-graph@0.1.2`, then click **Enable now**. Uninstall
|
|
26
26
|
0.1.0 through the manager before upgrading. For an unconfirmed installation,
|
|
27
27
|
first use **Check installation status**. See the [official DSH publishing guide](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/docs/user/develop/basic/publish.md).
|
|
28
28
|
|
|
@@ -42,26 +42,127 @@ npm ci
|
|
|
42
42
|
npm run typecheck
|
|
43
43
|
npm run build
|
|
44
44
|
npm pack
|
|
45
|
-
dsh plugin --profile web add ./dsh-code-review-graph-0.1.
|
|
45
|
+
dsh plugin --profile web add ./dsh-code-review-graph-0.1.2.tgz
|
|
46
46
|
node dist/setup.js prepare ../code-review-graph
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
## Workflows and graph
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
### First use on Desktop
|
|
52
|
+
|
|
53
|
+
Generate the interactive graph directly, **without a model, chat prompt or JSON parameters**:
|
|
54
|
+
|
|
55
|
+
1. Enable the plugin and create a session in your Git/SVN project workspace.
|
|
56
|
+
2. Open the right sidebar → New tab → Start → **Code graph / 代码图谱**.
|
|
57
|
+
3. Click **Set up and generate graph**. The plugin prepares the pinned Python
|
|
58
|
+
engine in DSH's isolated directory, parses the session's repository and loads
|
|
59
|
+
the graph. First preparation needs network access and uv or real Python 3.10+.
|
|
60
|
+
4. Use **Update graph** later, browse with search/filter/zoom/drag and save with
|
|
61
|
+
**Export HTML**. Operations support cancellation and retry; parse warnings appear in the result.
|
|
62
|
+
|
|
63
|
+
Two native DSH user commands also run **without parameters or model inference**:
|
|
64
|
+
|
|
65
|
+
| Command | Purpose |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| `/crg-setup` | Prepare/check the local graph engine separately |
|
|
68
|
+
| `/crg-graph` | Prepare the engine on first use, generate/update the graph and return its local HTML path |
|
|
69
|
+
|
|
70
|
+
The original seven `/crg-…` skills still ask the model to analyze code; these two
|
|
71
|
+
user commands execute directly. The session determines the repository path.
|
|
72
|
+
Custom engines remain user-managed. Because DSH's pnpm install scripts require
|
|
73
|
+
additional execution approval, Python downloads are not hidden in `postinstall`.
|
|
74
|
+
The first explicit UI action completes setup; ordinary startup and model tool
|
|
75
|
+
calls do not download dependencies. See the
|
|
76
|
+
[official packaging guide](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/docs/user/develop/basic/publish.md#installing-from-github-the-build-script-catch).
|
|
77
|
+
|
|
78
|
+
### Analyze code with a model
|
|
79
|
+
|
|
80
|
+
Browsing the graph does not require a model. For explanations or code review, use a skill in the chat input:
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
/crg-explore-codebase Explain the entry points, core modules and call relationships.
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Select the project workspace in DSH first; terminal `cd` does not change an existing session's binding. Actual tool-call records establish execution; mentioning a tool in prose does not.
|
|
87
|
+
|
|
88
|
+
### Mentions, slash skills and model tools
|
|
89
|
+
|
|
90
|
+
There is no `@dsh-code-review-graph` mention target and no `/crg_workflow`,
|
|
91
|
+
`/crg_visualize` or `/dsh-code-review-graph` command. `crg_workflow`, `crg_prompt`
|
|
92
|
+
and `crg_visualize` are model tools. Ask the model to invoke them using the chat
|
|
93
|
+
examples here.
|
|
94
|
+
|
|
95
|
+
Alongside the two direct user commands, the **seven model skills use `/crg-…` names**. Search `/crg-` in the session input, or
|
|
96
|
+
type the full skill name at the beginning of a message followed by your task:
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
/crg-explore-codebase Explain this repository's entry points and core modules.
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
/crg-review-changes Review uncommitted changes against HEAD; identify risks and missing tests.
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
| Skill | Purpose |
|
|
107
|
+
| --- | --- |
|
|
108
|
+
| `/crg-build-graph` | Build/update the graph and analyze structure |
|
|
109
|
+
| `/crg-explore-codebase` | Explore modules, dependencies and calls |
|
|
110
|
+
| `/crg-review-changes` | Review code changes |
|
|
111
|
+
| `/crg-review-delta` | Review subsequent changes incrementally |
|
|
112
|
+
| `/crg-review-pr` | Review a PR or branch diff |
|
|
113
|
+
| `/crg-debug-issue` | Trace a problem through call relationships |
|
|
114
|
+
| `/crg-refactor-safely` | Plan, preview and apply controlled refactors |
|
|
115
|
+
|
|
116
|
+
DSH caches the skill catalog per session. Create a new session after installing,
|
|
117
|
+
enabling or upgrading; search for `crg-`, rather than the npm package name. If
|
|
118
|
+
the menu is still empty, use the ordinary chat example above to check whether
|
|
119
|
+
`crg_workflow` is actually available. Both supported releases provide slash
|
|
120
|
+
skills: official source for
|
|
121
|
+
[0.1.5-rc.2](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.5-rc.2/packages/client/ui-skill/src/client/index.ts)
|
|
122
|
+
and [0.2.0-rc.2](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/client/ui-skill/src/client/index.ts).
|
|
123
|
+
|
|
124
|
+
### Workflow selection and common tasks
|
|
125
|
+
|
|
126
|
+
Ask the model to call `crg_workflow` with `context`,
|
|
52
127
|
`review`, `refactor`, `export`, `advanced`, or `all`. Restrictions are Agent-local
|
|
53
128
|
and affect only this plugin's tools. Original tools use the
|
|
54
129
|
`mcp__code_review_graph__` prefix. `crg_prompt` loads any of the original five
|
|
55
130
|
workflows and reveals the corresponding tools; skills use `crg-<original-name>`.
|
|
56
131
|
|
|
132
|
+
Two more ordinary chat examples:
|
|
133
|
+
|
|
134
|
+
```text
|
|
135
|
+
Call crg_prompt with {"name":"review_changes","base":"main"} and follow the returned workflow to review this branch.
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```text
|
|
139
|
+
Call crg_visualize with {"mode":"full","format":"html"} to generate this repository's interactive graph, then report the generated path.
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Replace `main` with an existing target branch; use `HEAD` for uncommitted changes.
|
|
143
|
+
The original context/change tools default to `HEAD~1`, which does not mean only
|
|
144
|
+
uncommitted changes. Specify the comparison ref when it matters.
|
|
145
|
+
|
|
146
|
+
These parameters are tool options: `mode: "full"` means the entire repository;
|
|
147
|
+
`format: "html"` means an interactive web page. The new graph button supplies
|
|
148
|
+
both defaults. `workflow: "context"` reveals the code-query tools; `task`
|
|
149
|
+
describes what to analyze; `base` is the Git branch/commit used for comparison.
|
|
150
|
+
Ordinary users can describe their task in plain language instead of typing JSON.
|
|
151
|
+
|
|
57
152
|
First use builds the graph; subsequent file changes are indexed incrementally.
|
|
58
153
|
Active Agents in the same canonical repository share workers and watchers. The
|
|
59
154
|
last owner releases them. A cancelled or timed-out call stops that repository's
|
|
60
155
|
shared worker; another concurrent call may also be interrupted. Later calls
|
|
61
156
|
reconnect. Failures and partial parsing warnings are retained in results.
|
|
62
157
|
|
|
63
|
-
|
|
64
|
-
|
|
158
|
+
### Open the interactive graph
|
|
159
|
+
|
|
160
|
+
Click **Set up and generate graph** in the sidebar, then **Update graph** later.
|
|
161
|
+
No chat message is required. **Refresh / 重新读取** only reloads existing HTML,
|
|
162
|
+
which is useful after a model invokes `crg_visualize`. The file is
|
|
163
|
+
`.code-review-graph/graph.html`; open the graph in the same session that generated it.
|
|
164
|
+
If the entry is missing, check the installed version and enable the plugin component.
|
|
165
|
+
The original renderer retains search, filters, zoom, dragging,
|
|
65
166
|
aggregation, paths and downloads. Supported options include `mode`,
|
|
66
167
|
file/symbol/changed-code/flow seeds, `path_from`/`path_to`, depth and node limits.
|
|
67
168
|
Export formats: HTML, JSON, GraphML, Cypher, Obsidian and SVG (optional engine
|
|
@@ -76,6 +177,38 @@ reader interfaces. Local assets are inlined into an iframe with scripts and
|
|
|
76
177
|
downloads enabled, without same-origin or network privilege. No fixed-port
|
|
77
178
|
server is started and no graph-database directory is exposed.
|
|
78
179
|
|
|
180
|
+
### Why does Desktop still show 0.1.0 after an upgrade?
|
|
181
|
+
|
|
182
|
+
Check the actual installed package before assuming stale UI metadata. The
|
|
183
|
+
default Desktop profile's installed manifest is:
|
|
184
|
+
|
|
185
|
+
```text
|
|
186
|
+
~/.dsh/profiles/desktop/node_modules/dsh-code-review-graph/package.json
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Its `version` is the installed version. The profile's own `package.json` records
|
|
190
|
+
a range such as `^0.1.0`: that range permits 0.1.2 but does not prove an upgrade.
|
|
191
|
+
Installation logs should also name 0.1.2 explicitly.
|
|
192
|
+
|
|
193
|
+
Official npm and npmmirror can have different `latest` versions. An unversioned
|
|
194
|
+
package name may still resolve to 0.1.0 on a stale mirror. An `npm install` in an
|
|
195
|
+
ordinary directory also does not update the application-owned Desktop profile.
|
|
196
|
+
Remove the old version through the native manager, then install the exact spec
|
|
197
|
+
`dsh-code-review-graph@0.1.2`. If your source lacks that version:
|
|
198
|
+
|
|
199
|
+
1. Download `dsh-code-review-graph-0.1.2.tgz` from the
|
|
200
|
+
[v0.1.2 release](https://github.com/sumingwang233/dsh-code-review-graph/releases/tag/v0.1.2).
|
|
201
|
+
Leave it compressed.
|
|
202
|
+
2. Enter its **absolute path** in the native **Add plugin** package-address
|
|
203
|
+
field, for example `D:\Downloads\dsh-code-review-graph-0.1.2.tgz`, install it
|
|
204
|
+
and choose **Enable now**.
|
|
205
|
+
3. Confirm that the installed package's `version` is 0.1.2 and create a new
|
|
206
|
+
session. Do not modify the Desktop profile through an external CLI.
|
|
207
|
+
|
|
208
|
+
If the installed file already says 0.1.2, refresh the manager's metadata by
|
|
209
|
+
reopening Desktop after saving your work. Use `prepare` for a missing engine;
|
|
210
|
+
reinstalling the JavaScript plugin does not install the Python engine.
|
|
211
|
+
|
|
79
212
|
## Permissions and refactors
|
|
80
213
|
|
|
81
214
|
Model inputs cannot supply `repo_root`. Execution uses the session's canonical
|
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
面向 DeepSeek Harness 的原生代码图谱插件。复用 [Code Review Graph](https://github.com/tirth8205/code-review-graph)
|
|
6
6
|
2.3.9 引擎,提供全部 30 个工具、5 个工作流 prompt、7 个技能,以及 Web/桌面侧栏交互图谱。
|
|
7
|
-
|
|
8
|
-
详见 [0.1.
|
|
7
|
+
当前版本为 **0.1.2**,支持一键准备引擎、生成交互图谱,以及无需模型的原生命令。
|
|
8
|
+
详见 [0.1.2 更新说明](docs/release-0.1.2.md)。
|
|
9
9
|
|
|
10
10
|
支持 Windows、Linux、macOS;兼容基线固定为 DSH **0.1.5-rc.2、0.2.0-rc.2**。
|
|
11
11
|
Web 和命令行通过官方 CLI 安装,桌面版通过对应版本的原生插件管理器安装。Alpha 版本不在兼容承诺内。
|
|
@@ -15,12 +15,12 @@ Web 和命令行通过官方 CLI 安装,桌面版通过对应版本的原生
|
|
|
15
15
|
安装已发布的 npm 包:
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
|
-
dsh plugin --profile web add dsh-code-review-graph@0.1.
|
|
19
|
-
npx dsh-code-review-graph@0.1.
|
|
18
|
+
dsh plugin --profile web add dsh-code-review-graph@0.1.2
|
|
19
|
+
npx dsh-code-review-graph@0.1.2 prepare
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
命令行入口将 `web` 替换为 `headless` 或你使用的普通 profile。
|
|
23
|
-
桌面版在插件管理器中安装 `dsh-code-review-graph@0.1.
|
|
23
|
+
桌面版在插件管理器中安装 `dsh-code-review-graph@0.1.2`,安装完成后点击“立即启用”。
|
|
24
24
|
已有 0.1.0 的用户先通过该管理器卸载旧版,再安装新版;尚未确认的安装先点击“核对安装状态”。
|
|
25
25
|
桌面 profile 由应用持有,外部 CLI 不直接修改。
|
|
26
26
|
参考 [DSH 官方打包指南](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/docs/user/develop/basic/publish.md)。
|
|
@@ -28,7 +28,7 @@ npx dsh-code-review-graph@0.1.1 prepare
|
|
|
28
28
|
`prepare` 是主动触发的一次性依赖准备:优先复用 uv 或真实 Python 3.10+,在
|
|
29
29
|
`$DSH_HOME/code-review-graph/engine`(默认 `~/.dsh/code-review-graph/engine`)安装隔离引擎。
|
|
30
30
|
Windows Store 的 Python 占位程序不算可用运行时,可用 `--python /path/to/python` 明确指定。
|
|
31
|
-
|
|
31
|
+
正常启动和模型工具调用不自动安装依赖、不下载嵌入模型、不默认使用云端服务。
|
|
32
32
|
没有本地嵌入依赖时,语义搜索使用引擎自带的 FTS/关键词降级。
|
|
33
33
|
|
|
34
34
|
源码开发安装:
|
|
@@ -38,7 +38,7 @@ npm ci
|
|
|
38
38
|
npm run typecheck
|
|
39
39
|
npm run build
|
|
40
40
|
npm pack
|
|
41
|
-
dsh plugin --profile web add ./dsh-code-review-graph-0.1.
|
|
41
|
+
dsh plugin --profile web add ./dsh-code-review-graph-0.1.2.tgz
|
|
42
42
|
node dist/setup.js prepare ../code-review-graph
|
|
43
43
|
```
|
|
44
44
|
|
|
@@ -47,7 +47,77 @@ node dist/setup.js prepare ../code-review-graph
|
|
|
47
47
|
|
|
48
48
|
## 使用
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
### 桌面端第一次使用
|
|
51
|
+
|
|
52
|
+
交互图谱可以通过界面直接生成,**无需模型、聊天提示词或 JSON 参数**:
|
|
53
|
+
|
|
54
|
+
1. 启用插件,在你的 Git/SVN 项目工作区新建会话。
|
|
55
|
+
2. 打开右侧栏 → 新标签页 → 开始 → **Code graph / 代码图谱**。
|
|
56
|
+
3. 点击 **准备引擎并生成图谱**。插件在 DSH 的隔离目录一次性准备固定版本的
|
|
57
|
+
Python 引擎,再解析当前会话的仓库并显示交互图谱。首次需要联网,以及 uv 或真实 Python 3.10+。
|
|
58
|
+
4. 后续点击 **更新图谱**;用搜索、过滤、缩放、拖动浏览,用 **导出 HTML** 保存可独立打开的图谱。
|
|
59
|
+
操作期间可取消,失败后可重试;解析警告会显示在结果中。
|
|
60
|
+
|
|
61
|
+
也提供两个真正的 DSH 用户命令,均**不需要参数、不调用模型**:
|
|
62
|
+
|
|
63
|
+
| 命令 | 用途 |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| `/crg-setup` | 单独准备/检查本地图谱引擎 |
|
|
66
|
+
| `/crg-graph` | 首次准备引擎并生成图谱,之后更新图谱;返回本地 HTML 路径 |
|
|
67
|
+
|
|
68
|
+
七个原有 `/crg-…` 技能仍用于让模型分析代码。它们与上面两个直接执行的用户命令不同。
|
|
69
|
+
当前会话决定仓库路径,用户无需传入目录。自定义引擎配置由用户自行准备,按钮不会替换它。
|
|
70
|
+
DSH 的 pnpm 安装脚本需要额外执行审批,因此不把 Python 下载隐藏在 npm `postinstall` 中;
|
|
71
|
+
安装后的首次界面操作即可完成准备,日常启动和模型工具调用不自动下载依赖。
|
|
72
|
+
详见 [DSH 官方打包指南](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/docs/user/develop/basic/publish.md#installing-from-github-the-build-script-catch)。
|
|
73
|
+
|
|
74
|
+
### 使用模型分析代码
|
|
75
|
+
|
|
76
|
+
图谱浏览不需要模型;需要模型解释结构或审查代码时,在聊天输入框直接使用技能,例如:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
/crg-explore-codebase 介绍当前仓库的入口、核心模块和调用关系。
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
先在 DSH 选择项目工作区;终端中 `cd` 不会改变已有会话绑定的仓库。
|
|
83
|
+
聊天里应出现实际工具调用记录;模型只提到工具名,不表示已经执行。
|
|
84
|
+
|
|
85
|
+
### `@`、`/` 和工具名的区别
|
|
86
|
+
|
|
87
|
+
本插件没有注册 `@dsh-code-review-graph` 引用目标,也没有 `/crg_workflow`、`/crg_visualize`
|
|
88
|
+
或 `/dsh-code-review-graph` 命令。`crg_workflow`、`crg_prompt`、`crg_visualize` 是供模型调用的工具,
|
|
89
|
+
可以通过上面的自然语言提示词明确要求模型使用。
|
|
90
|
+
|
|
91
|
+
除上面两个直接执行的命令外,还提供**七个 `crg-` 前缀的技能**。在会话输入框键入 `/crg-` 搜索,也可在消息开头
|
|
92
|
+
直接写完整技能名,再接任务描述,例如:
|
|
93
|
+
|
|
94
|
+
```text
|
|
95
|
+
/crg-explore-codebase 介绍当前仓库的入口和核心模块。
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
/crg-review-changes 请审查当前未提交改动,以 HEAD 为基准,指出风险和缺失测试。
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
| 技能 | 用途 |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| `/crg-build-graph` | 建立/更新图谱并分析结构 |
|
|
105
|
+
| `/crg-explore-codebase` | 探索模块、依赖和调用关系 |
|
|
106
|
+
| `/crg-review-changes` | 审查代码变化 |
|
|
107
|
+
| `/crg-review-delta` | 对后续修改做增量复查 |
|
|
108
|
+
| `/crg-review-pr` | 审查 PR 或分支差异 |
|
|
109
|
+
| `/crg-debug-issue` | 沿调用关系定位问题 |
|
|
110
|
+
| `/crg-refactor-safely` | 规划、预览和受控重构 |
|
|
111
|
+
|
|
112
|
+
技能目录由 DSH 按会话缓存;安装、启用或升级后,先新建会话再搜索。菜单中应搜索 `crg-`,
|
|
113
|
+
不是 npm 包名。若仍无候选,先用上面的普通提示词确认 `crg_workflow` 是否实际可用。
|
|
114
|
+
DSH 的两版基线均提供技能斜杠入口,见官方源码
|
|
115
|
+
[0.1.5-rc.2](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.5-rc.2/packages/client/ui-skill/src/client/index.ts)、
|
|
116
|
+
[0.2.0-rc.2](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/client/ui-skill/src/client/index.ts)。
|
|
117
|
+
|
|
118
|
+
### 工作流和常用任务
|
|
119
|
+
|
|
120
|
+
需要明确选择工具范围时,在聊天中要求模型调用 `crg_workflow`,指定下面的 `workflow` 参数:
|
|
51
121
|
|
|
52
122
|
| 工作流 | 用途 |
|
|
53
123
|
| --- | --- |
|
|
@@ -62,11 +132,34 @@ node dist/setup.js prepare ../code-review-graph
|
|
|
62
132
|
原工具名使用 `mcp__code_review_graph__` 前缀。`crg_prompt` 加载原有五个工作流并展开对应工具;
|
|
63
133
|
七个技能注册为 `crg-<原技能名>`。
|
|
64
134
|
|
|
135
|
+
另外两个可直接复制的普通聊天提示词:
|
|
136
|
+
|
|
137
|
+
```text
|
|
138
|
+
请调用 crg_prompt,参数为 {"name":"review_changes","base":"main"},按返回的工作流审查当前分支的改动。
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```text
|
|
142
|
+
请调用 crg_visualize,参数为 {"mode":"full","format":"html"},生成当前仓库的交互代码图谱,并告诉我生成文件的路径。
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`main` 要换成仓库实际存在的目标分支;审查当前未提交改动可用 `HEAD`。
|
|
146
|
+
原有上下文/变化工具的默认基准是 `HEAD~1`,并不等同于只看未提交改动,因此需要时应明确指定。
|
|
147
|
+
|
|
148
|
+
示例里的参数是给工具的选项,不是需要记忆的口令:`mode: "full"` 表示显示整个仓库,
|
|
149
|
+
`format: "html"` 表示生成可交互网页;新图谱按钮已选好这两个默认值。
|
|
150
|
+
`workflow: "context"` 是让模型先使用代码查询这一组工具,`task` 是你想让它分析的任务,
|
|
151
|
+
`base` 是审查时用来对比的 Git 分支或提交。日常使用只需描述任务;这些参数供精确控制时参考。
|
|
152
|
+
|
|
65
153
|
首次图谱调用自动建图,之后增量监听。相同仓库的活跃 Agent 共享后端及监听进程;
|
|
66
154
|
最后一个 Agent 释放后清理进程。取消或超时会停止该仓库的共享后端;后续调用重新建立连接,
|
|
67
155
|
因此同仓库的并发调用也可能收到中断结果。失败和解析警告保留在结果中。
|
|
68
156
|
|
|
69
|
-
|
|
157
|
+
### 打开交互图谱
|
|
158
|
+
|
|
159
|
+
在图谱页点击 **准备引擎并生成图谱**,之后点击 **更新图谱**,无需先发聊天消息。
|
|
160
|
+
**重新读取**只读取已有 HTML,适合模型调用 `crg_visualize` 生成文件后刷新页面。
|
|
161
|
+
图谱位于当前仓库的 `.code-review-graph/graph.html`;在生成它的同一会话中打开图谱页。
|
|
162
|
+
右侧入口缺失时,检查实际版本是否已升级,以及插件组件是否已启用。
|
|
70
163
|
原渲染器的搜索、过滤、缩放、拖动、聚合、调用路径和下载保持可用。
|
|
71
164
|
支持 `mode`、文件/符号/变更/执行流种子、`path_from`/`path_to` 和深度;
|
|
72
165
|
`format` 可选 `html`、`json`、`graphml`、`cypher`、`obsidian`、`svg`。
|
|
@@ -78,6 +171,31 @@ node dist/setup.js prepare ../code-review-graph
|
|
|
78
171
|
图谱资产通过 DSH 已有工作区文件读取接口取得,兼容两版接口差异。
|
|
79
172
|
本地资源内联到限制权限的 iframe;不启动固定端口服务器、不公开数据库目录。
|
|
80
173
|
|
|
174
|
+
### 安装了新版,管理器为什么仍显示 0.1.0?
|
|
175
|
+
|
|
176
|
+
先区分实际安装版本和界面缓存。默认桌面 profile 的实际包信息位于:
|
|
177
|
+
|
|
178
|
+
```text
|
|
179
|
+
~/.dsh/profiles/desktop/node_modules/dsh-code-review-graph/package.json
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
查看其中的 `version`;profile 自己的 `package.json` 只记录版本范围,例如 `^0.1.0`,
|
|
183
|
+
该范围允许 0.1.2,但不能证明实际已经升级。安装日志也应明确记录 0.1.2。
|
|
184
|
+
|
|
185
|
+
官方 npm 的 `latest` 和 npmmirror 的 `latest` 可能不同。镜像未同步时,输入不带版本号的包名
|
|
186
|
+
可能仍装到 0.1.0;在普通目录执行 `npm install` 也不会更新应用持有的桌面 profile。
|
|
187
|
+
使用桌面原生管理器卸载旧版,再安装明确的 `dsh-code-review-graph@0.1.2`。
|
|
188
|
+
若源里还没有这个版本,可按以下方式安装已验证的本地包:
|
|
189
|
+
|
|
190
|
+
1. 从 [v0.1.2 发行页](https://github.com/sumingwang233/dsh-code-review-graph/releases/tag/v0.1.2)
|
|
191
|
+
下载 `dsh-code-review-graph-0.1.2.tgz`,无需解压。
|
|
192
|
+
2. 在桌面管理器的“添加插件”包地址输入文件的**绝对路径**,例如
|
|
193
|
+
`D:\Downloads\dsh-code-review-graph-0.1.2.tgz`,完成安装并点击“立即启用”。
|
|
194
|
+
3. 核对实际包的 `version` 为 0.1.2,再新建会话。不要用外部 CLI 修改 `desktop` profile。
|
|
195
|
+
|
|
196
|
+
如果实际包已经是 0.1.2,才考虑管理器元数据尚未刷新;保存当前工作后重新打开应用再核对。
|
|
197
|
+
引擎未准备的错误用前面的 `prepare` 解决,重新安装 JavaScript 插件不会自动安装 Python 引擎。
|
|
198
|
+
|
|
81
199
|
## 权限和重构
|
|
82
200
|
|
|
83
201
|
模型不能指定 `repo_root`,执行器使用会话的规范化仓库路径,拒绝目录越界和逃逸符号链接。
|
package/dist/backend.js
CHANGED
|
@@ -57,13 +57,13 @@ async function connect(engine, cwd) {
|
|
|
57
57
|
const transport = new StdioClientTransport({ command: engine.command, args: [...engine.args, "serve", "--repo", cwd], cwd, env: environment(engine), stderr: "pipe" });
|
|
58
58
|
transport.stderr?.on("data", () => {
|
|
59
59
|
});
|
|
60
|
-
const client = new Client({ name: "dsh-code-review-graph", version: "0.1.
|
|
60
|
+
const client = new Client({ name: "dsh-code-review-graph", version: "0.1.2" });
|
|
61
61
|
try {
|
|
62
62
|
await client.connect(transport);
|
|
63
63
|
return client;
|
|
64
64
|
} catch (error) {
|
|
65
65
|
await transport.close();
|
|
66
|
-
throw new Error(`CRG engine unavailable.
|
|
66
|
+
throw new Error(`CRG engine unavailable. Open Code graph and click Set up engine, or run /crg-setup (CLI: dsh-crg prepare). ${String(error)}`);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
async function stop(child) {
|
package/dist/backend.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/backend.ts", "../src/safety.ts"],
|
|
4
|
-
"sourcesContent": ["import { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';\nimport { spawn, type ChildProcess } from 'node:child_process';\nimport { checkedData, scrubEnv } from './safety.ts';\n\nexport interface Engine { command: string; args: string[]; timeoutMs: number; env?: Record<string, string> }\nconst environment = (engine: Engine) => ({ ...scrubEnv(process.env), ...engine.env });\nexport function valueOf(result: unknown): Record<string, unknown> {\n const r = result as { isError?: boolean; structuredContent?: Record<string, unknown>; content?: { type: string; text?: string }[] };\n const text = r.content?.filter(x => x.type === 'text').map(x => x.text).join('\\n') ?? '';\n if (r.isError) throw new Error(text || 'Engine tool failed');\n const value = r.structuredContent ?? (() => { try { return JSON.parse(text); } catch { return { text }; } })();\n if (value.status === 'error') throw new Error(String(value.error));\n return value;\n}\n\nexport async function connect(engine: Engine, cwd: string): Promise<Client> {\n await checkedData(cwd);\n const transport = new StdioClientTransport({ command: engine.command, args: [...engine.args, 'serve', '--repo', cwd], cwd, env: environment(engine), stderr: 'pipe' });\n transport.stderr?.on('data', () => {});\n const client = new Client({ name: 'dsh-code-review-graph', version: '0.1.
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';\nimport { spawn, type ChildProcess } from 'node:child_process';\nimport { checkedData, scrubEnv } from './safety.ts';\n\nexport interface Engine { command: string; args: string[]; timeoutMs: number; env?: Record<string, string> }\nconst environment = (engine: Engine) => ({ ...scrubEnv(process.env), ...engine.env });\nexport function valueOf(result: unknown): Record<string, unknown> {\n const r = result as { isError?: boolean; structuredContent?: Record<string, unknown>; content?: { type: string; text?: string }[] };\n const text = r.content?.filter(x => x.type === 'text').map(x => x.text).join('\\n') ?? '';\n if (r.isError) throw new Error(text || 'Engine tool failed');\n const value = r.structuredContent ?? (() => { try { return JSON.parse(text); } catch { return { text }; } })();\n if (value.status === 'error') throw new Error(String(value.error));\n return value;\n}\n\nexport async function connect(engine: Engine, cwd: string): Promise<Client> {\n await checkedData(cwd);\n const transport = new StdioClientTransport({ command: engine.command, args: [...engine.args, 'serve', '--repo', cwd], cwd, env: environment(engine), stderr: 'pipe' });\n transport.stderr?.on('data', () => {});\n const client = new Client({ name: 'dsh-code-review-graph', version: '0.1.2' });\n try { await client.connect(transport); return client; }\n catch (error) { await transport.close(); throw new Error(`CRG engine unavailable. Open Code graph and click Set up engine, or run /crg-setup (CLI: dsh-crg prepare). ${String(error)}`); }\n}\n\nasync function stop(child?: ChildProcess): Promise<void> {\n if (!child || child.exitCode !== null || child.signalCode !== null) return;\n await new Promise<void>(resolve => {\n const timer = setTimeout(() => { child.kill('SIGKILL'); resolve(); }, 3000);\n child.once('exit', () => { clearTimeout(timer); resolve(); });\n child.kill();\n });\n}\ninterface Entry { client: Client; owners: Set<string>; ready?: Promise<void>; watcher?: ChildProcess; watchError?: string; queue: Promise<unknown>; update?: Record<string, unknown> }\nexport class BackendPool {\n private entries = new Map<string, Promise<Entry>>();\n private retired = new Set<string>();\n private closed = false;\n constructor(readonly engine: Engine) {}\n activate(owner: string): void { this.retired.delete(owner); }\n\n async acquire(root: string, owner: string): Promise<Entry> {\n if (this.closed || this.retired.has(owner)) throw new Error('Session has been released');\n let promise = this.entries.get(root);\n if (!promise) {\n promise = connect(this.engine, root).then(client => ({ client, owners: new Set<string>(), queue: Promise.resolve() }));\n this.entries.set(root, promise);\n promise.catch(() => { if (this.entries.get(root) === promise) this.entries.delete(root); });\n }\n const entry = await promise;\n if (this.closed || this.retired.has(owner)) throw new Error('Session has been released');\n entry.owners.add(owner);\n return entry;\n }\n\n async call(root: string, owner: string, name: string, args: Record<string, unknown>, signal: AbortSignal) {\n const entry = await this.acquire(root, owner);\n // One mutation at a time per SQLite graph; cancellation restarts this repository's shared worker.\n const work = entry.queue.catch(() => {}).then(async () => {\n signal.throwIfAborted();\n const cancel = () => { this.entries.delete(root); void stop(entry.watcher); void entry.client.close(); };\n signal.addEventListener('abort', cancel, { once: true });\n let result: unknown;\n try {\n await checkedData(root);\n if (entry.watchError) throw new Error(`Incremental watcher stopped: ${entry.watchError}`);\n result = await entry.client.callTool({ name, arguments: args }, undefined, { signal, timeout: this.engine.timeoutMs });\n } catch (error) {\n // A timed-out Python thread cannot safely be left mutating the graph.\n this.entries.delete(root); await stop(entry.watcher); await entry.client.close();\n throw error;\n } finally { signal.removeEventListener('abort', cancel); }\n // A normal tool error must not discard another call's pending refactor preview.\n valueOf(result);\n return result;\n });\n entry.queue = work;\n return work;\n }\n\n async ensure(root: string, owner: string, signal: AbortSignal): Promise<Record<string, unknown> | undefined> {\n const entry = await this.acquire(root, owner);\n entry.ready ??= (async () => {\n // An existing database can be stale after all sessions were closed.\n const update = valueOf(await this.call(root, owner, 'build_or_update_graph_tool', { repo_root: root }, signal));\n if (update.status === 'partial' || Array.isArray(update.warnings) && update.warnings.length) entry.update = update;\n signal.throwIfAborted();\n const watcher = spawn(this.engine.command, [...this.engine.args, 'watch', '--repo', root], { cwd: root, env: environment(this.engine), stdio: 'ignore', windowsHide: true });\n entry.watcher = watcher;\n await new Promise<void>((resolve, reject) => { watcher.once('spawn', resolve); watcher.once('error', reject); });\n watcher.on('error', error => { entry.watchError = String(error); });\n watcher.on('exit', (code, reason) => { if (entry.watcher === watcher) entry.watchError = `exit ${code ?? reason}`; });\n })().catch(error => { entry.ready = undefined; throw error; });\n await entry.ready;\n if (entry.watchError) throw new Error(`Incremental watcher stopped: ${entry.watchError}`);\n return entry.update;\n }\n\n async release(owner: string): Promise<void> {\n this.retired.add(owner);\n for (const [root, promise] of this.entries) {\n const entry = await promise.catch(() => undefined);\n if (!entry) continue;\n entry.owners.delete(owner);\n if (!entry.owners.size) { this.entries.delete(root); const watcher = entry.watcher; entry.watcher = undefined; await stop(watcher); await entry.client.close(); }\n }\n }\n\n async dispose(): Promise<void> {\n this.closed = true;\n const entries = [...this.entries.values()]; this.entries.clear();\n for (const promise of entries) {\n const entry = await promise.catch(() => undefined);\n if (entry) { const watcher = entry.watcher; entry.watcher = undefined; await stop(watcher); await entry.client.close(); }\n }\n }\n}\n\nexport async function run(engine: Engine, args: string[], cwd: string, signal: AbortSignal): Promise<{ log: string }> {\n await checkedData(cwd);\n signal = AbortSignal.any([signal, AbortSignal.timeout(engine.timeoutMs)]);\n return new Promise((resolve, reject) => {\n let log = '';\n const child = spawn(engine.command, [...engine.args, ...args], { cwd, env: environment(engine), stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true, signal });\n const capture = (bytes: Buffer) => { log = (log + bytes.toString()).slice(-16000); };\n child.stdout?.on('data', capture); child.stderr?.on('data', capture);\n child.on('error', reject);\n child.on('exit', code => code === 0 ? resolve({ log }) : reject(new Error(`Engine command exited ${code}: ${log}`)));\n });\n}\n", "import { realpath, stat, lstat, readdir } from 'node:fs/promises';\nimport { isAbsolute, relative, resolve, sep, dirname, basename, join } from 'node:path';\nimport { createHash } from 'node:crypto';\n\nexport const sha256 = (bytes: Uint8Array | string): string => createHash('sha256').update(bytes).digest('hex');\n\nexport function contained(root: string, path: string): boolean {\n const part = relative(root, path);\n return part === '' || (!isAbsolute(part) && part !== '..' && !part.startsWith(`..${sep}`));\n}\n\nexport async function checkedPath(root: string, value: string, allowMissing = false): Promise<string> {\n const candidate = resolve(root, value);\n if (!contained(root, candidate)) throw new Error('Path is outside the authorized repository');\n let current = candidate;\n const suffix: string[] = [];\n while (allowMissing && !await lstat(current).then(() => true, () => false)) {\n suffix.unshift(basename(current)); current = dirname(current);\n }\n const canonical = join(await realpath(current), ...suffix);\n if (!contained(root, canonical)) throw new Error('Symlink escapes the authorized repository');\n return canonical;\n}\n\nexport async function repository(cwd: string | undefined): Promise<string> {\n if (!cwd || !isAbsolute(cwd)) throw new Error('This session has no local absolute workspace');\n const root = await realpath(cwd);\n if (!(await stat(root)).isDirectory()) throw new Error('Workspace is not a directory');\n for (const marker of ['.git', '.svn']) {\n if (await stat(resolve(root, marker)).then(() => true, () => false)) return root;\n }\n throw new Error('Select a Git or SVN repository before using the graph');\n}\n\nexport function scrubEnv(env: NodeJS.ProcessEnv): Record<string, string> {\n return { ...Object.fromEntries(Object.entries(env).filter(([key, value]) => value !== undefined && !/KEY|PASSWORD|SECRET|TOKEN|^(DSH|CRG|OPENAI|GOOGLE|GEMINI|MINIMAX|VOYAGE|HF)_/i.test(key))), HF_HUB_OFFLINE: '1', TRANSFORMERS_OFFLINE: '1', HF_HUB_DISABLE_TELEMETRY: '1' } as Record<string, string>;\n}\n\nexport async function checkedData(root: string): Promise<void> {\n const dir = resolve(root, '.code-review-graph');\n if (!await lstat(dir).then(() => true, () => false)) return;\n await checkedPath(root, dir);\n const entries = await readdir(dir, { withFileTypes: true, recursive: true });\n for (const entry of entries) {\n if (entry.isSymbolicLink()) await checkedPath(root, join(entry.parentPath, entry.name));\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["resolve"]
|
|
7
7
|
}
|
package/dist/client.js
CHANGED
|
@@ -54,7 +54,7 @@ async function inlineGraph(html, read) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
// src/client.tsx
|
|
57
|
-
var inject = ["slots", "sidebarRightTabs", "remote", "remote.workspaceFiles"];
|
|
57
|
+
var inject = ["slots", "sidebarRightTabs", "remote", "remote.workspaceFiles", "remote.commands"];
|
|
58
58
|
var name = "code-review-graph-client";
|
|
59
59
|
async function readAsset(reader, session, path, signal, base) {
|
|
60
60
|
const result = reader.readBytes ? await reader.readBytes(session, path, base ? { baseFile: base } : {}, signal) : base && reader.readRelated ? await reader.readRelated(session, base, path, signal) : reader.readAll ? await reader.readAll(session, path, signal) : (() => {
|
|
@@ -73,7 +73,50 @@ function apply(ctx) {
|
|
|
73
73
|
const { tab } = useTabInfo();
|
|
74
74
|
const [html, setHtml] = (0, import_react.useState)("");
|
|
75
75
|
const [error, setError] = (0, import_react.useState)("");
|
|
76
|
+
const [notice, setNotice] = (0, import_react.useState)("");
|
|
77
|
+
const [busy, setBusy] = (0, import_react.useState)(false);
|
|
78
|
+
const operation = (0, import_react.useRef)();
|
|
76
79
|
const [generation, refresh] = (0, import_react.useState)(0);
|
|
80
|
+
(0, import_react.useEffect)(() => {
|
|
81
|
+
setHtml("");
|
|
82
|
+
setNotice("");
|
|
83
|
+
setError("");
|
|
84
|
+
setBusy(false);
|
|
85
|
+
return () => {
|
|
86
|
+
operation.current?.abort();
|
|
87
|
+
operation.current = void 0;
|
|
88
|
+
};
|
|
89
|
+
}, [sessionId, tab.signal]);
|
|
90
|
+
const execute = async (command) => {
|
|
91
|
+
if (operation.current) return;
|
|
92
|
+
const current = new AbortController();
|
|
93
|
+
operation.current = current;
|
|
94
|
+
const signal = AbortSignal.any([current.signal, tab.signal]);
|
|
95
|
+
setBusy(true);
|
|
96
|
+
setError("");
|
|
97
|
+
setNotice(command === "crg-setup" ? "\u6B63\u5728\u51C6\u5907\u5F15\u64CE\uFF1B\u9996\u6B21\u9700\u8981\u4E0B\u8F7D\u4F9D\u8D56\u2026 / Preparing engine; first use downloads dependencies\u2026" : "\u6B63\u5728\u51C6\u5907\u5F15\u64CE\u5E76\u751F\u6210\u56FE\u8C31\u2026 / Preparing engine and generating graph\u2026");
|
|
98
|
+
try {
|
|
99
|
+
const result = await ctx.remote.commands.execute(sessionId, `/${command}`, [], signal);
|
|
100
|
+
signal.throwIfAborted();
|
|
101
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
102
|
+
if (!result.value) throw new Error("Graph commands are unavailable. Enable the Host component. / \u56FE\u8C31\u547D\u4EE4\u4E0D\u53EF\u7528\uFF0C\u8BF7\u542F\u7528\u63D2\u4EF6\u7EC4\u4EF6\u3002");
|
|
103
|
+
if (result.value.result.kind === "error") throw new Error(result.value.result.text);
|
|
104
|
+
setNotice(result.value.result.text ?? "\u5B8C\u6210 / Done");
|
|
105
|
+
if (command === "crg-graph") refresh((n) => n + 1);
|
|
106
|
+
} catch (error2) {
|
|
107
|
+
if (operation.current !== current) return;
|
|
108
|
+
if (current.signal.aborted) setNotice("\u5DF2\u53D6\u6D88\uFF0C\u53EF\u91CD\u65B0\u5C1D\u8BD5\u3002 / Cancelled. You can retry.");
|
|
109
|
+
else if (!tab.signal.aborted) {
|
|
110
|
+
setNotice("");
|
|
111
|
+
setError(String(error2));
|
|
112
|
+
}
|
|
113
|
+
} finally {
|
|
114
|
+
if (operation.current === current) {
|
|
115
|
+
operation.current = void 0;
|
|
116
|
+
setBusy(false);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
77
120
|
const download = () => {
|
|
78
121
|
const url = URL.createObjectURL(new Blob([html], { type: "text/html;charset=utf-8" }));
|
|
79
122
|
const link = document.createElement("a");
|
|
@@ -95,7 +138,7 @@ function apply(ctx) {
|
|
|
95
138
|
});
|
|
96
139
|
return () => life.abort();
|
|
97
140
|
}, [sessionId, generation, tab.signal]);
|
|
98
|
-
return /* @__PURE__ */ import_react.default.createElement("section", { style: { height: "100%", display: "flex", flexDirection: "column", gap: 8 } }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("button", { type: "button", onClick: () => refresh((n) => n + 1) }, "Refresh / \
|
|
141
|
+
return /* @__PURE__ */ import_react.default.createElement("section", { style: { height: "100%", display: "flex", flexDirection: "column", gap: 8 } }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("button", { type: "button", disabled: busy, onClick: () => void execute("crg-graph") }, html ? "\u66F4\u65B0\u56FE\u8C31 / Update graph" : "\u51C6\u5907\u5F15\u64CE\u5E76\u751F\u6210\u56FE\u8C31 / Set up and generate graph"), " ", /* @__PURE__ */ import_react.default.createElement("button", { type: "button", disabled: busy, onClick: () => void execute("crg-setup") }, "\u51C6\u5907\u5F15\u64CE / Set up engine"), " ", busy && /* @__PURE__ */ import_react.default.createElement("button", { type: "button", onClick: () => operation.current?.abort() }, "\u53D6\u6D88 / Cancel"), " ", /* @__PURE__ */ import_react.default.createElement("button", { type: "button", disabled: busy, onClick: () => refresh((n) => n + 1) }, "Refresh / \u91CD\u65B0\u8BFB\u53D6"), " ", /* @__PURE__ */ import_react.default.createElement("button", { type: "button", disabled: !html, onClick: download }, "Export HTML / \u5BFC\u51FA HTML")), !html && !busy && /* @__PURE__ */ import_react.default.createElement("p", null, "\u70B9\u51FB\u201C\u51C6\u5907\u5F15\u64CE\u5E76\u751F\u6210\u56FE\u8C31\u201D\u5373\u53EF\u5F00\u59CB\uFF0C\u65E0\u9700\u804A\u5929\u6216\u586B\u5199\u53C2\u6570\u3002\u9996\u6B21\u4F1A\u5728 DSH \u7684\u9694\u79BB\u76EE\u5F55\u4E0B\u8F7D\u5F15\u64CE\u4F9D\u8D56\uFF0C\u9700\u8981 uv \u6216 Python 3.10+\u3002 / Click Set up and generate graph. No chat or parameters needed. First use downloads isolated engine dependencies; uv or Python 3.10+ is required."), notice && /* @__PURE__ */ import_react.default.createElement("p", { role: "status", style: { whiteSpace: "pre-wrap" } }, notice), error && /* @__PURE__ */ import_react.default.createElement("details", { open: true }, /* @__PURE__ */ import_react.default.createElement("summary", null, "\u65E0\u6CD5\u52A0\u8F7D\u6216\u751F\u6210\u56FE\u8C31 / Graph unavailable"), /* @__PURE__ */ import_react.default.createElement("p", { role: "alert", style: { whiteSpace: "pre-wrap" } }, error)), html && /* @__PURE__ */ import_react.default.createElement("iframe", { title: "Interactive code graph / \u4EA4\u4E92\u4EE3\u7801\u56FE\u8C31", sandbox: "allow-scripts allow-downloads", srcDoc: html, style: { width: "100%", flex: 1, border: 0 } }));
|
|
99
142
|
}
|
|
100
143
|
ctx.effect(() => ctx.slots.inject("sidebar.right.pane.tab", () => ctx.slots.register({ name: "sidebar.right.pane.tab", key: id }, Graph)));
|
|
101
144
|
}
|