dsh-code-review-graph 0.1.0 → 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.
- package/README.en.md +10 -7
- package/README.md +9 -6
- package/dist/backend.js +1 -1
- package/dist/backend.js.map +1 -1
- package/dist/client.js +46 -13
- package/dist/client.js.map +3 -3
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/docs/release-0.1.1.md +30 -0
- package/docs/validation.md +59 -11
- package/package.json +3 -2
package/README.en.md
CHANGED
|
@@ -4,24 +4,27 @@
|
|
|
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
|
-
|
|
7
|
+
Preserves all 30 tools, five prompt workflows and seven skills. Patch **0.1.1**
|
|
8
|
+
fixes native Web/Desktop client loading and sidebar display, with actual Desktop application validation.
|
|
9
|
+
See the [0.1.1 release notes](docs/release-0.1.1.md).
|
|
9
10
|
|
|
10
11
|
Supports Windows, Linux and macOS, with exact DSH baselines **0.1.5-rc.2** and
|
|
11
12
|
**0.2.0-rc.2**. Alpha compatibility is outside the contract.
|
|
12
13
|
|
|
13
14
|
## Installation
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
Install the published npm package:
|
|
16
17
|
|
|
17
18
|
```sh
|
|
18
|
-
dsh plugin --profile web add dsh-code-review-graph@0.1.
|
|
19
|
-
npx dsh-code-review-graph@0.1.
|
|
19
|
+
dsh plugin --profile web add dsh-code-review-graph@0.1.1
|
|
20
|
+
npx dsh-code-review-graph@0.1.1 prepare
|
|
20
21
|
```
|
|
21
22
|
|
|
22
23
|
Use `headless` or another ordinary profile for the command-line entrance. Desktop
|
|
23
24
|
profiles belong to the application: install through that release's native plugin
|
|
24
|
-
manager
|
|
25
|
+
manager, using `dsh-code-review-graph@0.1.1`, then click **Enable now**. Uninstall
|
|
26
|
+
0.1.0 through the manager before upgrading. For an unconfirmed installation,
|
|
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).
|
|
25
28
|
|
|
26
29
|
`prepare` explicitly creates an isolated Python engine under
|
|
27
30
|
`$DSH_HOME/code-review-graph/engine` (default `~/.dsh/code-review-graph/engine`),
|
|
@@ -39,7 +42,7 @@ npm ci
|
|
|
39
42
|
npm run typecheck
|
|
40
43
|
npm run build
|
|
41
44
|
npm pack
|
|
42
|
-
dsh plugin --profile web add ./dsh-code-review-graph-0.1.
|
|
45
|
+
dsh plugin --profile web add ./dsh-code-review-graph-0.1.1.tgz
|
|
43
46
|
node dist/setup.js prepare ../code-review-graph
|
|
44
47
|
```
|
|
45
48
|
|
package/README.md
CHANGED
|
@@ -4,22 +4,25 @@
|
|
|
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
|
-
|
|
7
|
+
当前补丁版本为 **0.1.1**,修复 Web/桌面原生客户端加载和侧栏显示,补充真实桌面应用验证。
|
|
8
|
+
详见 [0.1.1 更新说明](docs/release-0.1.1.md)。
|
|
8
9
|
|
|
9
10
|
支持 Windows、Linux、macOS;兼容基线固定为 DSH **0.1.5-rc.2、0.2.0-rc.2**。
|
|
10
11
|
Web 和命令行通过官方 CLI 安装,桌面版通过对应版本的原生插件管理器安装。Alpha 版本不在兼容承诺内。
|
|
11
12
|
|
|
12
13
|
## 安装
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
安装已发布的 npm 包:
|
|
15
16
|
|
|
16
17
|
```sh
|
|
17
|
-
dsh plugin --profile web add dsh-code-review-graph@0.1.
|
|
18
|
-
npx dsh-code-review-graph@0.1.
|
|
18
|
+
dsh plugin --profile web add dsh-code-review-graph@0.1.1
|
|
19
|
+
npx dsh-code-review-graph@0.1.1 prepare
|
|
19
20
|
```
|
|
20
21
|
|
|
21
22
|
命令行入口将 `web` 替换为 `headless` 或你使用的普通 profile。
|
|
22
|
-
桌面版在插件管理器中安装 `dsh-code-review-graph@0.1.
|
|
23
|
+
桌面版在插件管理器中安装 `dsh-code-review-graph@0.1.1`,安装完成后点击“立即启用”。
|
|
24
|
+
已有 0.1.0 的用户先通过该管理器卸载旧版,再安装新版;尚未确认的安装先点击“核对安装状态”。
|
|
25
|
+
桌面 profile 由应用持有,外部 CLI 不直接修改。
|
|
23
26
|
参考 [DSH 官方打包指南](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/docs/user/develop/basic/publish.md)。
|
|
24
27
|
|
|
25
28
|
`prepare` 是主动触发的一次性依赖准备:优先复用 uv 或真实 Python 3.10+,在
|
|
@@ -35,7 +38,7 @@ npm ci
|
|
|
35
38
|
npm run typecheck
|
|
36
39
|
npm run build
|
|
37
40
|
npm pack
|
|
38
|
-
dsh plugin --profile web add ./dsh-code-review-graph-0.1.
|
|
41
|
+
dsh plugin --profile web add ./dsh-code-review-graph-0.1.1.tgz
|
|
39
42
|
node dist/setup.js prepare ../code-review-graph
|
|
40
43
|
```
|
|
41
44
|
|
package/dist/backend.js
CHANGED
|
@@ -57,7 +57,7 @@ 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.1" });
|
|
61
61
|
try {
|
|
62
62
|
await client.connect(transport);
|
|
63
63
|
return client;
|
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.
|
|
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.1' });\n try { await client.connect(transport); return client; }\n catch (error) { await transport.close(); throw new Error(`CRG engine unavailable. Run dsh-crg prepare explicitly. ${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
5
|
"mappings": "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",
|
|
6
6
|
"names": ["resolve"]
|
|
7
7
|
}
|
package/dist/client.js
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({ id: "dsh-code-review-graph", factory: (require) => { var module = { exports: {} }; var exports = module.exports;
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name2 in all)
|
|
11
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
1
31
|
// src/client.tsx
|
|
2
|
-
|
|
32
|
+
var client_exports = {};
|
|
33
|
+
__export(client_exports, {
|
|
34
|
+
apply: () => apply,
|
|
35
|
+
inject: () => inject,
|
|
36
|
+
name: () => name,
|
|
37
|
+
readAsset: () => readAsset
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(client_exports);
|
|
40
|
+
var import_react = __toESM(require("react"), 1);
|
|
3
41
|
|
|
4
42
|
// src/html.ts
|
|
5
43
|
async function inlineGraph(html, read) {
|
|
@@ -33,9 +71,9 @@ function apply(ctx) {
|
|
|
33
71
|
ctx.effect(() => ctx.sidebarRightTabs.register({ id, kind: "code-review-graph", priority: "builtin", title: () => "Code graph / \u4EE3\u7801\u56FE\u8C31", guide: [{ id: "graph", order: 30, title: () => "Code graph / \u4EE3\u7801\u56FE\u8C31", description: () => "Explore dependencies, changes and execution paths locally." }] }));
|
|
34
72
|
function Graph({ sessionId, useTabInfo }) {
|
|
35
73
|
const { tab } = useTabInfo();
|
|
36
|
-
const [html, setHtml] = useState("");
|
|
37
|
-
const [error, setError] = useState("");
|
|
38
|
-
const [generation, refresh] = useState(0);
|
|
74
|
+
const [html, setHtml] = (0, import_react.useState)("");
|
|
75
|
+
const [error, setError] = (0, import_react.useState)("");
|
|
76
|
+
const [generation, refresh] = (0, import_react.useState)(0);
|
|
39
77
|
const download = () => {
|
|
40
78
|
const url = URL.createObjectURL(new Blob([html], { type: "text/html;charset=utf-8" }));
|
|
41
79
|
const link = document.createElement("a");
|
|
@@ -44,7 +82,7 @@ function apply(ctx) {
|
|
|
44
82
|
link.click();
|
|
45
83
|
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
46
84
|
};
|
|
47
|
-
useEffect(() => {
|
|
85
|
+
(0, import_react.useEffect)(() => {
|
|
48
86
|
const life = new AbortController();
|
|
49
87
|
const signal = AbortSignal.any([life.signal, tab.signal]);
|
|
50
88
|
const base = ".code-review-graph/graph.html";
|
|
@@ -57,14 +95,9 @@ function apply(ctx) {
|
|
|
57
95
|
});
|
|
58
96
|
return () => life.abort();
|
|
59
97
|
}, [sessionId, generation, tab.signal]);
|
|
60
|
-
return /* @__PURE__ */
|
|
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 / \u5237\u65B0"), " ", /* @__PURE__ */ import_react.default.createElement("button", { type: "button", disabled: !html, onClick: download }, "Export HTML / \u5BFC\u51FA HTML")), error && /* @__PURE__ */ import_react.default.createElement("p", { role: "status" }, "Generate the graph with crg_visualize, then refresh. / \u4F7F\u7528 crg_visualize \u751F\u6210\u56FE\u8C31\u540E\u5237\u65B0\u3002", /* @__PURE__ */ import_react.default.createElement("br", null), 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 } }));
|
|
61
99
|
}
|
|
62
|
-
ctx.effect(() => ctx.slots.inject("sidebar.right.pane.tab", () => ctx.slots.register({ name: "sidebar.right.pane.tab", key:
|
|
100
|
+
ctx.effect(() => ctx.slots.inject("sidebar.right.pane.tab", () => ctx.slots.register({ name: "sidebar.right.pane.tab", key: id }, Graph)));
|
|
63
101
|
}
|
|
64
|
-
|
|
65
|
-
apply,
|
|
66
|
-
inject,
|
|
67
|
-
name,
|
|
68
|
-
readAsset
|
|
69
|
-
};
|
|
102
|
+
return module.exports; } });
|
|
70
103
|
//# sourceMappingURL=client.js.map
|
package/dist/client.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/client.tsx", "../src/html.ts"],
|
|
4
|
-
"sourcesContent": ["import React, { useEffect, useState } from 'react';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type {} from '@deepseek-ai/dsh-client-ui-sidebar-right/client';\nimport type {} from '@deepseek-ai/dsh-client-ui-slots';\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client';\nimport type {} from '@deepseek-ai/dsh-api-remotes/client';\nimport type {} from '@deepseek-ai/dsh-api-workspace-files/client';\nimport { inlineGraph } from './html.ts';\n\nexport const inject = ['slots', 'sidebarRightTabs', 'remote', 'remote.workspaceFiles'];\nexport const name = 'code-review-graph-client';\ntype ReadResult = { ok: true; value: { data: Uint8Array | string } } | { ok: false; error: { message: string } };\ntype WorkspaceReader = {\n readBytes?: (session: string, path: string, options: object, signal: AbortSignal) => Promise<ReadResult>;\n readAll?: (session: string, path: string, signal: AbortSignal) => Promise<ReadResult>;\n readRelated?: (session: string, base: string, path: string, signal: AbortSignal) => Promise<ReadResult>;\n};\nexport async function readAsset(reader: WorkspaceReader, session: string, path: string, signal: AbortSignal, base?: string): Promise<string> {\n const result = reader.readBytes ? await reader.readBytes(session, path, base ? { baseFile: base } : {}, signal)\n : base && reader.readRelated ? await reader.readRelated(session, base, path, signal)\n : reader.readAll ? await reader.readAll(session, path, signal) : (() => { throw new Error('Unsupported DSH file reader'); })();\n if (!result.ok) throw new Error(result.error.message);\n if (result.value.data instanceof Uint8Array) return new TextDecoder().decode(result.value.data);\n const base64 = result.value.data;\n if (typeof base64 !== 'string') throw new Error('Invalid graph asset response');\n return new TextDecoder().decode(Uint8Array.from(atob(base64), c => c.charCodeAt(0)));\n}\n\nexport function apply(ctx: Context): void {\n const id = 'dsh-code-review-graph';\n ctx.effect(() => ctx.sidebarRightTabs.register({ id, kind: 'code-review-graph', priority: 'builtin', title: () => 'Code graph / \u4EE3\u7801\u56FE\u8C31', guide: [{ id: 'graph', order: 30, title: () => 'Code graph / \u4EE3\u7801\u56FE\u8C31', description: () => 'Explore dependencies, changes and execution paths locally.' }] }));\n function Graph({ sessionId, useTabInfo }: { sessionId: string; useTabInfo: () => { tab: { signal: AbortSignal } } }) {\n const { tab } = useTabInfo();\n const [html, setHtml] = useState('');\n const [error, setError] = useState('');\n const [generation, refresh] = useState(0);\n const download = () => {\n const url = URL.createObjectURL(new Blob([html], { type: 'text/html;charset=utf-8' }));\n const link = document.createElement('a'); link.href = url; link.download = 'code-review-graph.html'; link.click();\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n };\n useEffect(() => {\n const life = new AbortController();\n const signal = AbortSignal.any([life.signal, tab.signal]);\n const base = '.code-review-graph/graph.html';\n const reader = ctx.remote.workspaceFiles as unknown as WorkspaceReader;\n setError('');\n void readAsset(reader, sessionId, base, signal).then(html => inlineGraph(html, path => readAsset(reader, sessionId, path, signal, base))).then(result => {\n if (!signal.aborted) setHtml(result);\n }).catch(error => { if (!signal.aborted) setError(String(error)); });\n return () => life.abort();\n }, [sessionId, generation, tab.signal]);\n return <section style={{ height: '100%', display: 'flex', flexDirection: 'column', gap: 8 }}>\n <div><button type=\"button\" onClick={() => refresh(n => n + 1)}>Refresh / \u5237\u65B0</button> <button type=\"button\" disabled={!html} onClick={download}>Export HTML / \u5BFC\u51FA HTML</button></div>\n {error && <p role=\"status\">Generate the graph with crg_visualize, then refresh. / \u4F7F\u7528 crg_visualize \u751F\u6210\u56FE\u8C31\u540E\u5237\u65B0\u3002<br />{error}</p>}\n {html && <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 }} />}\n </section>;\n }\n ctx.effect(() => ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({ name: 'sidebar.right.pane.tab', key:
|
|
5
|
-
"mappings": ";AAAA,
|
|
6
|
-
"names": ["html", "error"]
|
|
4
|
+
"sourcesContent": ["import React, { useEffect, useState } from 'react';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type {} from '@deepseek-ai/dsh-client-ui-sidebar-right/client';\nimport type {} from '@deepseek-ai/dsh-client-ui-slots';\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client';\nimport type {} from '@deepseek-ai/dsh-api-remotes/client';\nimport type {} from '@deepseek-ai/dsh-api-workspace-files/client';\nimport { inlineGraph } from './html.ts';\n\nexport const inject = ['slots', 'sidebarRightTabs', 'remote', 'remote.workspaceFiles'];\nexport const name = 'code-review-graph-client';\ntype ReadResult = { ok: true; value: { data: Uint8Array | string } } | { ok: false; error: { message: string } };\ntype WorkspaceReader = {\n readBytes?: (session: string, path: string, options: object, signal: AbortSignal) => Promise<ReadResult>;\n readAll?: (session: string, path: string, signal: AbortSignal) => Promise<ReadResult>;\n readRelated?: (session: string, base: string, path: string, signal: AbortSignal) => Promise<ReadResult>;\n};\nexport async function readAsset(reader: WorkspaceReader, session: string, path: string, signal: AbortSignal, base?: string): Promise<string> {\n const result = reader.readBytes ? await reader.readBytes(session, path, base ? { baseFile: base } : {}, signal)\n : base && reader.readRelated ? await reader.readRelated(session, base, path, signal)\n : reader.readAll ? await reader.readAll(session, path, signal) : (() => { throw new Error('Unsupported DSH file reader'); })();\n if (!result.ok) throw new Error(result.error.message);\n if (result.value.data instanceof Uint8Array) return new TextDecoder().decode(result.value.data);\n const base64 = result.value.data;\n if (typeof base64 !== 'string') throw new Error('Invalid graph asset response');\n return new TextDecoder().decode(Uint8Array.from(atob(base64), c => c.charCodeAt(0)));\n}\n\nexport function apply(ctx: Context): void {\n const id = 'dsh-code-review-graph';\n ctx.effect(() => ctx.sidebarRightTabs.register({ id, kind: 'code-review-graph', priority: 'builtin', title: () => 'Code graph / \u4EE3\u7801\u56FE\u8C31', guide: [{ id: 'graph', order: 30, title: () => 'Code graph / \u4EE3\u7801\u56FE\u8C31', description: () => 'Explore dependencies, changes and execution paths locally.' }] }));\n function Graph({ sessionId, useTabInfo }: { sessionId: string; useTabInfo: () => { tab: { signal: AbortSignal } } }) {\n const { tab } = useTabInfo();\n const [html, setHtml] = useState('');\n const [error, setError] = useState('');\n const [generation, refresh] = useState(0);\n const download = () => {\n const url = URL.createObjectURL(new Blob([html], { type: 'text/html;charset=utf-8' }));\n const link = document.createElement('a'); link.href = url; link.download = 'code-review-graph.html'; link.click();\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n };\n useEffect(() => {\n const life = new AbortController();\n const signal = AbortSignal.any([life.signal, tab.signal]);\n const base = '.code-review-graph/graph.html';\n const reader = ctx.remote.workspaceFiles as unknown as WorkspaceReader;\n setError('');\n void readAsset(reader, sessionId, base, signal).then(html => inlineGraph(html, path => readAsset(reader, sessionId, path, signal, base))).then(result => {\n if (!signal.aborted) setHtml(result);\n }).catch(error => { if (!signal.aborted) setError(String(error)); });\n return () => life.abort();\n }, [sessionId, generation, tab.signal]);\n return <section style={{ height: '100%', display: 'flex', flexDirection: 'column', gap: 8 }}>\n <div><button type=\"button\" onClick={() => refresh(n => n + 1)}>Refresh / \u5237\u65B0</button> <button type=\"button\" disabled={!html} onClick={download}>Export HTML / \u5BFC\u51FA HTML</button></div>\n {error && <p role=\"status\">Generate the graph with crg_visualize, then refresh. / \u4F7F\u7528 crg_visualize \u751F\u6210\u56FE\u8C31\u540E\u5237\u65B0\u3002<br />{error}</p>}\n {html && <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 }} />}\n </section>;\n }\n ctx.effect(() => ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({ name: 'sidebar.right.pane.tab', key: id }, Graph)));\n}\n", "/** Inline only bundled scripts and local data; the iframe never fetches remote assets. */\nexport async function inlineGraph(html: string, read: (path: string) => Promise<string>): Promise<string> {\n // The upstream CDN fallback contains a literal script tag inside a JS string.\n // Local D3 is required here, so remove that loader before inspecting HTML tags.\n html = html.replace(/<script>\\s*window\\.d3\\s*\\|\\|\\s*document\\.write\\([\\s\\S]*?\\);\\s*<\\/script>/gi, '');\n const scripts = [...html.matchAll(/<script\\b([^>]*?)\\bsrc=[\"']([^\"']+)[\"']([^>]*)>[\\s\\S]*?<\\/script>/gi)];\n for (const match of scripts) {\n const path = match[2];\n if (/^(?:https?:|data:|\\/|\\\\)|\\.\\./i.test(path)) throw new Error('Graph contains a nonlocal script');\n const script = await read(path);\n html = html.replace(match[0], () => `<script>${script.replace(/<\\/script/gi, '<\\\\/script')}</script>`);\n }\n const policy = `<meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data: blob:; connect-src 'none'; font-src 'none'\">`;\n return html.replace(/<head>/i, `<head>${policy}`);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["html", "error", "React"]
|
|
7
7
|
}
|
package/dist/index.js
CHANGED
|
@@ -75,7 +75,7 @@ async function connect(engine, cwd) {
|
|
|
75
75
|
const transport = new StdioClientTransport({ command: engine.command, args: [...engine.args, "serve", "--repo", cwd], cwd, env: environment(engine), stderr: "pipe" });
|
|
76
76
|
transport.stderr?.on("data", () => {
|
|
77
77
|
});
|
|
78
|
-
const client = new Client({ name: "dsh-code-review-graph", version: "0.1.
|
|
78
|
+
const client = new Client({ name: "dsh-code-review-graph", version: "0.1.1" });
|
|
79
79
|
try {
|
|
80
80
|
await client.connect(transport);
|
|
81
81
|
return client;
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts", "../src/backend.ts", "../src/safety.ts", "../src/refactor.ts", "../src/workflows.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Context } from '@deepseek-ai/cordis';\nimport type { ToolDefinition, ToolRunContext } from '@deepseek-ai/dsh-tools';\nimport type {} from '@deepseek-ai/dsh-agent';\nimport type {} from '@deepseek-ai/dsh-skill';\nimport type {} from '@deepseek-ai/dsh-system-prompt';\nimport z from '@deepseek-ai/schemastery';\nimport { Ajv } from 'ajv';\nimport { readFile, readdir, stat } from 'node:fs/promises';\nimport { join, basename } from 'node:path';\nimport { homedir } from 'node:os';\nimport { BackendPool, run, valueOf, type Engine } from './backend.ts';\nimport { repository, checkedPath, sha256 } from './safety.ts';\nimport { commitPlan, type EditPlan } from './refactor.ts';\nimport { exposed, groups, publicName, skillWorkflow, type Workflow } from './workflows.ts';\n\nexport const name = 'code-review-graph';\nexport const inject = ['tools', 'agents', 'fs', 'sandboxPolicy', 'skills', 'systemPrompt'];\nconst engineDir = join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'code-review-graph', 'engine');\nexport const Config = z.object({\n engineCommand: z.string().default(join(engineDir, process.platform === 'win32' ? 'Scripts/code-review-graph.exe' : 'bin/code-review-graph')),\n engineArgs: z.array(z.string()).default([]),\n timeoutMs: z.number().min(1000).default(120000),\n authorizedRepos: z.array(z.string()).default([]),\n embeddingProviders: z.array(z.string()).default(['local']),\n embeddingEnv: z.array(z.string()).default([]),\n});\nexport interface Configuration { engineCommand: string; engineArgs: string[]; timeoutMs: number; authorizedRepos: string[]; embeddingProviders: string[]; embeddingEnv: string[] }\ninterface Capability { name: string; description?: string; inputSchema: Record<string, unknown> & { properties?: Record<string, unknown>; required?: string[] } }\nexport const output: ToolDefinition['output'] = {\n schema: { type: 'object', additionalProperties: true },\n render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }],\n};\n\nexport async function apply(ctx: Context, config: Configuration): Promise<void> {\n const engine: Engine = { command: config.engineCommand, args: config.engineArgs, timeoutMs: config.timeoutMs, env: Object.fromEntries(config.embeddingEnv.filter(key => /^(OPENAI|GOOGLE|GEMINI|MINIMAX|VOYAGE|CRG)_/.test(key) && process.env[key] !== undefined).map(key => [key, process.env[key]!])) };\n const pool = new BackendPool(engine);\n const snapshot = JSON.parse(await readFile(new URL('../capabilities.json', import.meta.url), 'utf8')) as { tools: Capability[] };\n const tools = snapshot.tools.filter(t => t.name !== 'get_refactor_edit_plan_tool');\n const names = tools.map(t => t.name);\n const masks = new Map<string, () => void>();\n const ajv = new Ajv({ strict: false, allErrors: true });\n const life = new AbortController();\n ctx.effect(() => () => { life.abort(); for (const dispose of masks.values()) dispose(); return pool.dispose(); });\n const switchWorkflow = (agent: NonNullable<ToolRunContext['agent']>, workflow?: Workflow) => {\n pool.activate(agent.id);\n const available = workflow ? exposed(workflow, names) : [];\n masks.get(agent.id)?.();\n masks.set(agent.id, agent.ctx.tools.restrict({ deny: names.filter(n => !available.includes(n)).map(publicName) }));\n return available.map(publicName);\n };\n\n async function rootFor(exec: ToolRunContext) {\n if (!exec.agent) throw new Error('Graph tools require a session-owned agent');\n return repository(exec.agent.session.header.cwd);\n }\n\n async function invoke(raw: string, args: Record<string, unknown>, exec: ToolRunContext) {\n const root = await rootFor(exec);\n const owner = exec.agent!.id;\n const signal = AbortSignal.any([exec.signal, life.signal]);\n const providerKey = ['build_or_update_graph_tool', 'run_postprocess_tool'].includes(raw) ? 'embedding_provider' : 'provider';\n const provider = args[providerKey];\n if (raw === 'get_docs_section_tool') await checkedPath(root, 'docs/LLM-OPTIMIZED-REFERENCE.md', true);\n if (provider && (typeof provider !== 'string' || !config.embeddingProviders.includes(provider))) throw new Error('Embedding provider has not been authorized in plugin configuration');\n if (['semantic_search_nodes_tool', 'embed_graph_tool'].includes(raw) && !provider) args = { ...args, provider: 'local' };\n for (const key of ['file_path', 'file', 'path']) {\n if (typeof args[key] === 'string') await checkedPath(root, args[key] as string);\n }\n for (const key of ['changed_files', 'files']) {\n if (Array.isArray(args[key])) for (const path of args[key] as string[]) await checkedPath(root, path, true);\n }\n if (raw === 'list_repos_tool') {\n const roots = [...new Set([root, ...await Promise.all(config.authorizedRepos.map(repository))])];\n return { status: 'ok', repos: roots.map(path => ({ alias: roots.filter(r => basename(r) === basename(path)).length > 1 ? `${basename(path)}@${sha256(path).slice(0, 8)}` : basename(path), path })) };\n }\n if (raw === 'cross_repo_search_tool') {\n const roots = [...new Set([root, ...await Promise.all(config.authorizedRepos.map(repository))])];\n const aliases = roots.map(path => ({ path, alias: roots.filter(r => basename(r) === basename(path)).length > 1 ? `${basename(path)}@${sha256(path).slice(0, 8)}` : basename(path) }));\n const selected = Array.isArray(args.repos) ? args.repos as string[] : [];\n if (selected.some(n => !aliases.some(r => r.alias === n))) throw new Error('Repository is not authorized');\n const results: Record<string, unknown>[] = [];\n for (const { path: repo, alias } of aliases.filter(r => !selected.length || selected.includes(r.alias))) {\n if (repo !== root && !await stat(join(repo, '.code-review-graph/graph.db')).then(() => true, () => false)) throw new Error(`Build ${repo} in its own session first`);\n await pool.ensure(repo, owner, signal);\n const data = valueOf(await pool.call(repo, owner, 'semantic_search_nodes_tool', { query: args.query, kind: args.kind, limit: args.limit ?? 20, provider: 'local', repo_root: repo }, signal));\n for (const result of data.results as Record<string, unknown>[]) results.push({ ...result, repo: alias });\n }\n results.sort((a, b) => Number(b.score ?? 0) - Number(a.score ?? 0));\n const limit = Math.min(Number(args.max_results ?? 50), 100);\n return { status: 'ok', results: results.slice(0, limit), total: results.length, results_omitted: Math.max(0, results.length - limit), truncated: results.length > limit };\n }\n const graphUpdate = raw !== 'build_or_update_graph_tool' ? await pool.ensure(root, owner, signal) : undefined;\n if (raw === 'apply_refactor_tool' && !args.dry_run) {\n const plan = valueOf(await pool.call(root, owner, 'get_refactor_edit_plan_tool', { refactor_id: args.refactor_id, repo_root: root }, signal)) as unknown as EditPlan;\n const result = await commitPlan(ctx, exec, root, plan);\n if (result.status === 'ok') {\n try { return { ...result, graph_update: valueOf(await pool.call(root, owner, 'build_or_update_graph_tool', { repo_root: root }, signal)) }; }\n catch (error) { return { ...result, graph_update_error: String(error) }; }\n }\n return result;\n }\n if (raw === 'apply_refactor_tool') args = { ...args, dry_run: true };\n const result = valueOf(await pool.call(root, owner, raw, { ...args, repo_root: root }, signal));\n if (raw === 'build_or_update_graph_tool') await pool.ensure(root, owner, signal);\n return graphUpdate ? { ...result, graph_update: graphUpdate } : result;\n }\n\n for (const tool of tools) {\n const parameters = structuredClone(tool.inputSchema);\n delete parameters.properties?.repo_root;\n parameters.required = parameters.required?.filter(n => n !== 'repo_root');\n parameters.additionalProperties = false;\n const validate = ajv.compile(parameters);\n const definition = {\n name: publicName(tool.name), description: tool.description ?? tool.name, parameters, output,\n timeoutMs: config.timeoutMs,\n execute: async (args: unknown, exec: ToolRunContext) => {\n if (!validate(args)) throw new Error(ajv.errorsText(validate.errors));\n return invoke(tool.name, args as Record<string, unknown>, exec);\n },\n } as ToolDefinition;\n ctx.effect(() => ctx.tools.register(definition));\n }\n\n ctx.effect(() => ctx.tools.register({ name: 'crg_workflow', description: 'Select code graph tools: context, review, refactor, export, advanced or all. Begin with get_minimal_context.',\n parameters: { type: 'object', properties: { workflow: { type: 'string', enum: [...groups] } }, required: ['workflow'], additionalProperties: false }, output,\n execute: async (args, exec) => {\n const workflow = (args as { workflow: Workflow }).workflow;\n if (!groups.includes(workflow) || !exec.agent) throw new Error('Invalid workflow or missing agent');\n return { tools: switchWorkflow(exec.agent, workflow) };\n },\n }));\n\n const visualizationSchema = { type: 'object', properties: {\n mode: { type: 'string', enum: ['auto', 'file', 'community', 'full'] },\n format: { type: 'string', enum: ['html', 'json', 'graphml', 'cypher', 'obsidian', 'svg'] },\n seed_file: { type: 'array', items: { type: 'string' } }, seed_symbol: { type: 'string' }, seed_changed: { type: 'boolean' }, seed_changed_base: { type: 'string' }, seed_flow: { type: 'string' },\n path_from: { type: 'string' }, path_to: { type: 'string' }, depth: { type: 'integer', minimum: 0 }, render_depth: { type: 'integer', minimum: 0 }, max_nodes: { type: 'integer', minimum: 1 },\n }, additionalProperties: false };\n const validateVisualization = ajv.compile(visualizationSchema);\n ctx.effect(() => ctx.tools.register({ name: 'crg_visualize', description: 'Generate the local interactive graph for the DSH Code graph sidebar, or export HTML, JSON, GraphML, Cypher, Obsidian or SVG. Supports file, community, symbol, changed-code, flow and shortest-path views.',\n parameters: visualizationSchema, output,\n timeoutMs: config.timeoutMs,\n execute: async (value, exec) => {\n if (!validateVisualization(value)) throw new Error(ajv.errorsText(validateVisualization.errors));\n const args = value as Record<string, unknown>;\n const root = await rootFor(exec);\n const signal = AbortSignal.any([exec.signal, life.signal]);\n await pool.ensure(root, exec.agent!.id, signal);\n for (const path of (args.seed_file ?? []) as string[]) await checkedPath(root, path, true);\n const command = ['visualize', '--repo', root];\n for (const [key, value] of Object.entries(args)) {\n const flag = `--${key.replaceAll('_', '-')}`;\n if (Array.isArray(value)) for (const item of value) command.push(flag, String(item));\n else if (value === true) command.push(flag);\n else if (value !== undefined && value !== false) command.push(flag, String(value));\n }\n const { log } = await run(engine, command, root, signal);\n const format = String(args.format ?? 'html');\n return { status: 'ok', path: join(root, '.code-review-graph', format === 'obsidian' ? 'obsidian' : `graph.${format}`), ...(format === 'html' ? { sidebar: 'code-review-graph' } : {}), log };\n },\n }));\n const promptNames = ['review_changes', 'architecture_map', 'debug_issue', 'onboard_developer', 'pre_merge_check'];\n const promptSchema = { type: 'object', properties: { name: { type: 'string', enum: promptNames }, base: { type: 'string' }, description: { type: 'string' } }, required: ['name'], additionalProperties: false };\n const validatePrompt = ajv.compile(promptSchema);\n ctx.effect(() => ctx.tools.register({ name: 'crg_prompt', description: 'Load one of the five original CRG workflows and reveal its graph tools.', parameters: promptSchema, output,\n execute: async (args, exec) => {\n if (!validatePrompt(args) || !exec.agent) throw new Error('Invalid workflow prompt or missing agent');\n const { name, ...parameters } = args as { name: string; base?: string; description?: string };\n const workflow: Workflow = name === 'pre_merge_check' ? 'all' : name === 'review_changes' ? 'review' : name === 'debug_issue' ? 'review' : 'advanced';\n switchWorkflow(exec.agent, workflow);\n const root = await rootFor(exec);\n const { client } = await pool.acquire(root, exec.agent.id);\n const result = await client.getPrompt({ name, arguments: parameters }, { signal: AbortSignal.any([exec.signal, life.signal]), timeout: config.timeoutMs });\n return { ...result, messages: result.messages.map(message => ({ ...message, content: message.content.type === 'text' ? { ...message.content, text: message.content.text.replace(/\\b([a-z_]+_tool)\\b/g, raw => names.includes(raw) ? publicName(raw) : raw) } : message.content })) };\n },\n }));\n ctx.on('agent/created', ({ agent }) => { switchWorkflow(agent); return undefined; });\n ctx.on('agent/disposed', ({ agent }) => { masks.get(agent.id)?.(); masks.delete(agent.id); return pool.release(agent.id); });\n for (const agent of ctx.agents.list()) switchWorkflow(agent);\n ctx.effect(() => ctx.systemPrompt.section({ name: 'code-review-graph', order: 80, interpolate: false,\n text: 'Use crg_workflow to reveal task-specific local code graph tools, then get_minimal_context_tool before broader queries. Prefer minimal detail. crg_visualize opens the local graph in the Code graph sidebar. Refactors use session filesystem permissions.' }));\n\n const skillsDir = new URL('../skills/', import.meta.url);\n for (const slug of await readdir(skillsDir)) {\n const path = new URL(`${slug}/SKILL.md`, skillsDir);\n const body = await readFile(path, 'utf8');\n const content = body.replace(/^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n/, '').replace(/\\b([a-z_]+_tool)\\b/g, raw => names.includes(raw) ? publicName(raw) : raw);\n if (!skillWorkflow[slug]) throw new Error(`Unmapped CRG skill: ${slug}`);\n ctx.effect(() => ctx.skills.register({ name: `crg-${slug}`, description: `Code review graph: ${slug}`, source: 'bundled', content: `First call crg_workflow with workflow \"${skillWorkflow[slug]}\". Tool names use mcp__code_review_graph__ prefixes.\\n\\n${content}` }));\n }\n}\n", "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.0' });\n try { await client.connect(transport); return client; }\n catch (error) { await transport.close(); throw new Error(`CRG engine unavailable. Run dsh-crg prepare explicitly. ${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", "import type { Context } from '@deepseek-ai/cordis';\nimport type { ToolRunContext } from '@deepseek-ai/dsh-tools';\nimport type {} from '@deepseek-ai/dsh-fs';\nimport type {} from '@deepseek-ai/dsh-sandbox-policy';\nimport { checkedPath, sha256 } from './safety.ts';\n\nexport interface EditPlan { files: { path: string; before_sha256: string; after_content: string; edit_count: number }[] }\nexport async function commitPlan(ctx: Context, exec: ToolRunContext, root: string, plan: EditPlan) {\n if (!exec.agent) throw new Error('Refactor requires a session-owned agent');\n const policy = ctx.sandboxPolicy.resolve({ session: exec.agent.session });\n if (policy.mode === 'read-only') throw new Error('Refactor is denied by read-only policy');\n if (!Array.isArray(plan.files)) throw new Error('Invalid edit plan');\n const prepared = [];\n const seen = new Set<string>();\n for (const file of plan.files) {\n if (typeof file.path !== 'string' || !/^[a-f0-9]{64}$/.test(file.before_sha256) || typeof file.after_content !== 'string' || !Number.isInteger(file.edit_count) || file.edit_count < 0) throw new Error('Invalid edit plan file');\n exec.signal.throwIfAborted();\n const path = await checkedPath(root, file.path);\n if (seen.has(path)) throw new Error('Duplicate edit target');\n seen.add(path);\n const target = await ctx.fs.resolve(path, { cwd: root, signal: exec.signal });\n const before = await ctx.fs.stat(target, exec.signal);\n if (!before || before.type !== 'file') throw new Error('Edit target is not a regular file');\n const bytes = await ctx.fs.readBytes(target, exec.signal, 32 * 1024 * 1024);\n if (sha256(bytes) !== file.before_sha256) throw new Error(`Stale edit plan: ${path}`);\n const after = await ctx.fs.stat(target, exec.signal);\n if (after?.version !== before.version) throw new Error(`File changed during preflight: ${path}`);\n const intent = await ctx.waterfall('fs/write-intent', target, exec, () => undefined);\n if (intent && (intent.kind !== 'replaceIfVersion' || intent.version !== before.version)) throw new Error(`File intent disagrees with edit plan: ${path}`);\n prepared.push({ file, path, target, original: Buffer.from(bytes).toString('utf8'), intent: intent ?? { kind: 'replaceIfVersion' as const, version: before.version } });\n }\n const applied: string[] = [];\n const recovery: EditPlan['files'] = [];\n try {\n for (const item of prepared) {\n exec.signal.throwIfAborted();\n await checkedPath(root, item.path);\n const outcome = await ctx.fs.writeText(item.target, item.file.after_content, item.intent, exec.signal, policy);\n ctx.emit('fs/observed', item.target, { kind: 'present', version: outcome.version }, exec);\n applied.push(item.path);\n recovery.push({ path: item.path, before_sha256: sha256(item.file.after_content), after_content: item.original, edit_count: item.file.edit_count });\n }\n } catch (error) {\n // Return the complete recovery patch through the tool output; never silently roll back a later edit.\n return { status: 'partial_failure', error: String(error), files_modified: applied, recovery_patch: { files: recovery } };\n }\n return { status: 'ok', files_modified: applied, edits_applied: plan.files.reduce((n, f) => n + f.edit_count, 0) };\n}\n", "export const groups = ['context', 'review', 'refactor', 'export', 'advanced', 'all'] as const;\nexport type Workflow = typeof groups[number];\nconst context = ['build_or_update_graph_tool', 'get_minimal_context_tool', 'query_graph_tool', 'semantic_search_nodes_tool', 'list_graph_stats_tool', 'get_docs_section_tool'];\nconst members: Record<Exclude<Workflow, 'all'>, string[]> = {\n context,\n review: [...context, 'detect_changes_tool', 'get_review_context_tool', 'get_impact_radius_tool', 'get_affected_flows_tool', 'list_flows_tool', 'get_flow_tool', 'list_communities_tool'],\n refactor: [...context, 'refactor_tool', 'apply_refactor_tool', 'find_large_functions_tool', 'get_impact_radius_tool', 'get_affected_flows_tool', 'detect_changes_tool'],\n export: [...context, 'generate_wiki_tool', 'get_wiki_page_tool'],\n advanced: [...context, 'embed_graph_tool', 'run_postprocess_tool', 'list_repos_tool', 'cross_repo_search_tool', 'list_communities_tool', 'get_community_tool', 'get_architecture_overview_tool', 'get_hub_nodes_tool', 'get_bridge_nodes_tool', 'get_knowledge_gaps_tool', 'get_surprising_connections_tool', 'get_suggested_questions_tool', 'traverse_graph_tool', 'detect_changes_tool', 'list_flows_tool', 'get_flow_tool', 'find_large_functions_tool'],\n};\nexport const skillWorkflow: Record<string, Workflow> = {\n 'build-graph': 'advanced', 'debug-issue': 'review', 'explore-codebase': 'advanced',\n 'refactor-safely': 'refactor', 'review-changes': 'review', 'review-delta': 'review', 'review-pr': 'review',\n};\nexport const publicName = (raw: string): string => `mcp__code_review_graph__${raw}`;\nexport function exposed(workflow: Workflow, names: string[]): string[] {\n return workflow === 'all' ? names : names.filter(name => members[workflow].includes(name));\n}\n"],
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@deepseek-ai/cordis';\nimport type { ToolDefinition, ToolRunContext } from '@deepseek-ai/dsh-tools';\nimport type {} from '@deepseek-ai/dsh-agent';\nimport type {} from '@deepseek-ai/dsh-skill';\nimport type {} from '@deepseek-ai/dsh-system-prompt';\nimport z from '@deepseek-ai/schemastery';\nimport { Ajv } from 'ajv';\nimport { readFile, readdir, stat } from 'node:fs/promises';\nimport { join, basename } from 'node:path';\nimport { homedir } from 'node:os';\nimport { BackendPool, run, valueOf, type Engine } from './backend.ts';\nimport { repository, checkedPath, sha256 } from './safety.ts';\nimport { commitPlan, type EditPlan } from './refactor.ts';\nimport { exposed, groups, publicName, skillWorkflow, type Workflow } from './workflows.ts';\n\nexport const name = 'code-review-graph';\nexport const inject = ['tools', 'agents', 'fs', 'sandboxPolicy', 'skills', 'systemPrompt'];\nconst engineDir = join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'code-review-graph', 'engine');\nexport const Config = z.object({\n engineCommand: z.string().default(join(engineDir, process.platform === 'win32' ? 'Scripts/code-review-graph.exe' : 'bin/code-review-graph')),\n engineArgs: z.array(z.string()).default([]),\n timeoutMs: z.number().min(1000).default(120000),\n authorizedRepos: z.array(z.string()).default([]),\n embeddingProviders: z.array(z.string()).default(['local']),\n embeddingEnv: z.array(z.string()).default([]),\n});\nexport interface Configuration { engineCommand: string; engineArgs: string[]; timeoutMs: number; authorizedRepos: string[]; embeddingProviders: string[]; embeddingEnv: string[] }\ninterface Capability { name: string; description?: string; inputSchema: Record<string, unknown> & { properties?: Record<string, unknown>; required?: string[] } }\nexport const output: ToolDefinition['output'] = {\n schema: { type: 'object', additionalProperties: true },\n render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }],\n};\n\nexport async function apply(ctx: Context, config: Configuration): Promise<void> {\n const engine: Engine = { command: config.engineCommand, args: config.engineArgs, timeoutMs: config.timeoutMs, env: Object.fromEntries(config.embeddingEnv.filter(key => /^(OPENAI|GOOGLE|GEMINI|MINIMAX|VOYAGE|CRG)_/.test(key) && process.env[key] !== undefined).map(key => [key, process.env[key]!])) };\n const pool = new BackendPool(engine);\n const snapshot = JSON.parse(await readFile(new URL('../capabilities.json', import.meta.url), 'utf8')) as { tools: Capability[] };\n const tools = snapshot.tools.filter(t => t.name !== 'get_refactor_edit_plan_tool');\n const names = tools.map(t => t.name);\n const masks = new Map<string, () => void>();\n const ajv = new Ajv({ strict: false, allErrors: true });\n const life = new AbortController();\n ctx.effect(() => () => { life.abort(); for (const dispose of masks.values()) dispose(); return pool.dispose(); });\n const switchWorkflow = (agent: NonNullable<ToolRunContext['agent']>, workflow?: Workflow) => {\n pool.activate(agent.id);\n const available = workflow ? exposed(workflow, names) : [];\n masks.get(agent.id)?.();\n masks.set(agent.id, agent.ctx.tools.restrict({ deny: names.filter(n => !available.includes(n)).map(publicName) }));\n return available.map(publicName);\n };\n\n async function rootFor(exec: ToolRunContext) {\n if (!exec.agent) throw new Error('Graph tools require a session-owned agent');\n return repository(exec.agent.session.header.cwd);\n }\n\n async function invoke(raw: string, args: Record<string, unknown>, exec: ToolRunContext) {\n const root = await rootFor(exec);\n const owner = exec.agent!.id;\n const signal = AbortSignal.any([exec.signal, life.signal]);\n const providerKey = ['build_or_update_graph_tool', 'run_postprocess_tool'].includes(raw) ? 'embedding_provider' : 'provider';\n const provider = args[providerKey];\n if (raw === 'get_docs_section_tool') await checkedPath(root, 'docs/LLM-OPTIMIZED-REFERENCE.md', true);\n if (provider && (typeof provider !== 'string' || !config.embeddingProviders.includes(provider))) throw new Error('Embedding provider has not been authorized in plugin configuration');\n if (['semantic_search_nodes_tool', 'embed_graph_tool'].includes(raw) && !provider) args = { ...args, provider: 'local' };\n for (const key of ['file_path', 'file', 'path']) {\n if (typeof args[key] === 'string') await checkedPath(root, args[key] as string);\n }\n for (const key of ['changed_files', 'files']) {\n if (Array.isArray(args[key])) for (const path of args[key] as string[]) await checkedPath(root, path, true);\n }\n if (raw === 'list_repos_tool') {\n const roots = [...new Set([root, ...await Promise.all(config.authorizedRepos.map(repository))])];\n return { status: 'ok', repos: roots.map(path => ({ alias: roots.filter(r => basename(r) === basename(path)).length > 1 ? `${basename(path)}@${sha256(path).slice(0, 8)}` : basename(path), path })) };\n }\n if (raw === 'cross_repo_search_tool') {\n const roots = [...new Set([root, ...await Promise.all(config.authorizedRepos.map(repository))])];\n const aliases = roots.map(path => ({ path, alias: roots.filter(r => basename(r) === basename(path)).length > 1 ? `${basename(path)}@${sha256(path).slice(0, 8)}` : basename(path) }));\n const selected = Array.isArray(args.repos) ? args.repos as string[] : [];\n if (selected.some(n => !aliases.some(r => r.alias === n))) throw new Error('Repository is not authorized');\n const results: Record<string, unknown>[] = [];\n for (const { path: repo, alias } of aliases.filter(r => !selected.length || selected.includes(r.alias))) {\n if (repo !== root && !await stat(join(repo, '.code-review-graph/graph.db')).then(() => true, () => false)) throw new Error(`Build ${repo} in its own session first`);\n await pool.ensure(repo, owner, signal);\n const data = valueOf(await pool.call(repo, owner, 'semantic_search_nodes_tool', { query: args.query, kind: args.kind, limit: args.limit ?? 20, provider: 'local', repo_root: repo }, signal));\n for (const result of data.results as Record<string, unknown>[]) results.push({ ...result, repo: alias });\n }\n results.sort((a, b) => Number(b.score ?? 0) - Number(a.score ?? 0));\n const limit = Math.min(Number(args.max_results ?? 50), 100);\n return { status: 'ok', results: results.slice(0, limit), total: results.length, results_omitted: Math.max(0, results.length - limit), truncated: results.length > limit };\n }\n const graphUpdate = raw !== 'build_or_update_graph_tool' ? await pool.ensure(root, owner, signal) : undefined;\n if (raw === 'apply_refactor_tool' && !args.dry_run) {\n const plan = valueOf(await pool.call(root, owner, 'get_refactor_edit_plan_tool', { refactor_id: args.refactor_id, repo_root: root }, signal)) as unknown as EditPlan;\n const result = await commitPlan(ctx, exec, root, plan);\n if (result.status === 'ok') {\n try { return { ...result, graph_update: valueOf(await pool.call(root, owner, 'build_or_update_graph_tool', { repo_root: root }, signal)) }; }\n catch (error) { return { ...result, graph_update_error: String(error) }; }\n }\n return result;\n }\n if (raw === 'apply_refactor_tool') args = { ...args, dry_run: true };\n const result = valueOf(await pool.call(root, owner, raw, { ...args, repo_root: root }, signal));\n if (raw === 'build_or_update_graph_tool') await pool.ensure(root, owner, signal);\n return graphUpdate ? { ...result, graph_update: graphUpdate } : result;\n }\n\n for (const tool of tools) {\n const parameters = structuredClone(tool.inputSchema);\n delete parameters.properties?.repo_root;\n parameters.required = parameters.required?.filter(n => n !== 'repo_root');\n parameters.additionalProperties = false;\n const validate = ajv.compile(parameters);\n const definition = {\n name: publicName(tool.name), description: tool.description ?? tool.name, parameters, output,\n timeoutMs: config.timeoutMs,\n execute: async (args: unknown, exec: ToolRunContext) => {\n if (!validate(args)) throw new Error(ajv.errorsText(validate.errors));\n return invoke(tool.name, args as Record<string, unknown>, exec);\n },\n } as ToolDefinition;\n ctx.effect(() => ctx.tools.register(definition));\n }\n\n ctx.effect(() => ctx.tools.register({ name: 'crg_workflow', description: 'Select code graph tools: context, review, refactor, export, advanced or all. Begin with get_minimal_context.',\n parameters: { type: 'object', properties: { workflow: { type: 'string', enum: [...groups] } }, required: ['workflow'], additionalProperties: false }, output,\n execute: async (args, exec) => {\n const workflow = (args as { workflow: Workflow }).workflow;\n if (!groups.includes(workflow) || !exec.agent) throw new Error('Invalid workflow or missing agent');\n return { tools: switchWorkflow(exec.agent, workflow) };\n },\n }));\n\n const visualizationSchema = { type: 'object', properties: {\n mode: { type: 'string', enum: ['auto', 'file', 'community', 'full'] },\n format: { type: 'string', enum: ['html', 'json', 'graphml', 'cypher', 'obsidian', 'svg'] },\n seed_file: { type: 'array', items: { type: 'string' } }, seed_symbol: { type: 'string' }, seed_changed: { type: 'boolean' }, seed_changed_base: { type: 'string' }, seed_flow: { type: 'string' },\n path_from: { type: 'string' }, path_to: { type: 'string' }, depth: { type: 'integer', minimum: 0 }, render_depth: { type: 'integer', minimum: 0 }, max_nodes: { type: 'integer', minimum: 1 },\n }, additionalProperties: false };\n const validateVisualization = ajv.compile(visualizationSchema);\n ctx.effect(() => ctx.tools.register({ name: 'crg_visualize', description: 'Generate the local interactive graph for the DSH Code graph sidebar, or export HTML, JSON, GraphML, Cypher, Obsidian or SVG. Supports file, community, symbol, changed-code, flow and shortest-path views.',\n parameters: visualizationSchema, output,\n timeoutMs: config.timeoutMs,\n execute: async (value, exec) => {\n if (!validateVisualization(value)) throw new Error(ajv.errorsText(validateVisualization.errors));\n const args = value as Record<string, unknown>;\n const root = await rootFor(exec);\n const signal = AbortSignal.any([exec.signal, life.signal]);\n await pool.ensure(root, exec.agent!.id, signal);\n for (const path of (args.seed_file ?? []) as string[]) await checkedPath(root, path, true);\n const command = ['visualize', '--repo', root];\n for (const [key, value] of Object.entries(args)) {\n const flag = `--${key.replaceAll('_', '-')}`;\n if (Array.isArray(value)) for (const item of value) command.push(flag, String(item));\n else if (value === true) command.push(flag);\n else if (value !== undefined && value !== false) command.push(flag, String(value));\n }\n const { log } = await run(engine, command, root, signal);\n const format = String(args.format ?? 'html');\n return { status: 'ok', path: join(root, '.code-review-graph', format === 'obsidian' ? 'obsidian' : `graph.${format}`), ...(format === 'html' ? { sidebar: 'code-review-graph' } : {}), log };\n },\n }));\n const promptNames = ['review_changes', 'architecture_map', 'debug_issue', 'onboard_developer', 'pre_merge_check'];\n const promptSchema = { type: 'object', properties: { name: { type: 'string', enum: promptNames }, base: { type: 'string' }, description: { type: 'string' } }, required: ['name'], additionalProperties: false };\n const validatePrompt = ajv.compile(promptSchema);\n ctx.effect(() => ctx.tools.register({ name: 'crg_prompt', description: 'Load one of the five original CRG workflows and reveal its graph tools.', parameters: promptSchema, output,\n execute: async (args, exec) => {\n if (!validatePrompt(args) || !exec.agent) throw new Error('Invalid workflow prompt or missing agent');\n const { name, ...parameters } = args as { name: string; base?: string; description?: string };\n const workflow: Workflow = name === 'pre_merge_check' ? 'all' : name === 'review_changes' ? 'review' : name === 'debug_issue' ? 'review' : 'advanced';\n switchWorkflow(exec.agent, workflow);\n const root = await rootFor(exec);\n const { client } = await pool.acquire(root, exec.agent.id);\n const result = await client.getPrompt({ name, arguments: parameters }, { signal: AbortSignal.any([exec.signal, life.signal]), timeout: config.timeoutMs });\n return { ...result, messages: result.messages.map(message => ({ ...message, content: message.content.type === 'text' ? { ...message.content, text: message.content.text.replace(/\\b([a-z_]+_tool)\\b/g, raw => names.includes(raw) ? publicName(raw) : raw) } : message.content })) };\n },\n }));\n ctx.on('agent/created', ({ agent }) => { switchWorkflow(agent); return undefined; });\n ctx.on('agent/disposed', ({ agent }) => { masks.get(agent.id)?.(); masks.delete(agent.id); return pool.release(agent.id); });\n for (const agent of ctx.agents.list()) switchWorkflow(agent);\n ctx.effect(() => ctx.systemPrompt.section({ name: 'code-review-graph', order: 80, interpolate: false,\n text: 'Use crg_workflow to reveal task-specific local code graph tools, then get_minimal_context_tool before broader queries. Prefer minimal detail. crg_visualize opens the local graph in the Code graph sidebar. Refactors use session filesystem permissions.' }));\n\n const skillsDir = new URL('../skills/', import.meta.url);\n for (const slug of await readdir(skillsDir)) {\n const path = new URL(`${slug}/SKILL.md`, skillsDir);\n const body = await readFile(path, 'utf8');\n const content = body.replace(/^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n/, '').replace(/\\b([a-z_]+_tool)\\b/g, raw => names.includes(raw) ? publicName(raw) : raw);\n if (!skillWorkflow[slug]) throw new Error(`Unmapped CRG skill: ${slug}`);\n ctx.effect(() => ctx.skills.register({ name: `crg-${slug}`, description: `Code review graph: ${slug}`, source: 'bundled', content: `First call crg_workflow with workflow \"${skillWorkflow[slug]}\". Tool names use mcp__code_review_graph__ prefixes.\\n\\n${content}` }));\n }\n}\n", "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.1' });\n try { await client.connect(transport); return client; }\n catch (error) { await transport.close(); throw new Error(`CRG engine unavailable. Run dsh-crg prepare explicitly. ${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", "import type { Context } from '@deepseek-ai/cordis';\nimport type { ToolRunContext } from '@deepseek-ai/dsh-tools';\nimport type {} from '@deepseek-ai/dsh-fs';\nimport type {} from '@deepseek-ai/dsh-sandbox-policy';\nimport { checkedPath, sha256 } from './safety.ts';\n\nexport interface EditPlan { files: { path: string; before_sha256: string; after_content: string; edit_count: number }[] }\nexport async function commitPlan(ctx: Context, exec: ToolRunContext, root: string, plan: EditPlan) {\n if (!exec.agent) throw new Error('Refactor requires a session-owned agent');\n const policy = ctx.sandboxPolicy.resolve({ session: exec.agent.session });\n if (policy.mode === 'read-only') throw new Error('Refactor is denied by read-only policy');\n if (!Array.isArray(plan.files)) throw new Error('Invalid edit plan');\n const prepared = [];\n const seen = new Set<string>();\n for (const file of plan.files) {\n if (typeof file.path !== 'string' || !/^[a-f0-9]{64}$/.test(file.before_sha256) || typeof file.after_content !== 'string' || !Number.isInteger(file.edit_count) || file.edit_count < 0) throw new Error('Invalid edit plan file');\n exec.signal.throwIfAborted();\n const path = await checkedPath(root, file.path);\n if (seen.has(path)) throw new Error('Duplicate edit target');\n seen.add(path);\n const target = await ctx.fs.resolve(path, { cwd: root, signal: exec.signal });\n const before = await ctx.fs.stat(target, exec.signal);\n if (!before || before.type !== 'file') throw new Error('Edit target is not a regular file');\n const bytes = await ctx.fs.readBytes(target, exec.signal, 32 * 1024 * 1024);\n if (sha256(bytes) !== file.before_sha256) throw new Error(`Stale edit plan: ${path}`);\n const after = await ctx.fs.stat(target, exec.signal);\n if (after?.version !== before.version) throw new Error(`File changed during preflight: ${path}`);\n const intent = await ctx.waterfall('fs/write-intent', target, exec, () => undefined);\n if (intent && (intent.kind !== 'replaceIfVersion' || intent.version !== before.version)) throw new Error(`File intent disagrees with edit plan: ${path}`);\n prepared.push({ file, path, target, original: Buffer.from(bytes).toString('utf8'), intent: intent ?? { kind: 'replaceIfVersion' as const, version: before.version } });\n }\n const applied: string[] = [];\n const recovery: EditPlan['files'] = [];\n try {\n for (const item of prepared) {\n exec.signal.throwIfAborted();\n await checkedPath(root, item.path);\n const outcome = await ctx.fs.writeText(item.target, item.file.after_content, item.intent, exec.signal, policy);\n ctx.emit('fs/observed', item.target, { kind: 'present', version: outcome.version }, exec);\n applied.push(item.path);\n recovery.push({ path: item.path, before_sha256: sha256(item.file.after_content), after_content: item.original, edit_count: item.file.edit_count });\n }\n } catch (error) {\n // Return the complete recovery patch through the tool output; never silently roll back a later edit.\n return { status: 'partial_failure', error: String(error), files_modified: applied, recovery_patch: { files: recovery } };\n }\n return { status: 'ok', files_modified: applied, edits_applied: plan.files.reduce((n, f) => n + f.edit_count, 0) };\n}\n", "export const groups = ['context', 'review', 'refactor', 'export', 'advanced', 'all'] as const;\nexport type Workflow = typeof groups[number];\nconst context = ['build_or_update_graph_tool', 'get_minimal_context_tool', 'query_graph_tool', 'semantic_search_nodes_tool', 'list_graph_stats_tool', 'get_docs_section_tool'];\nconst members: Record<Exclude<Workflow, 'all'>, string[]> = {\n context,\n review: [...context, 'detect_changes_tool', 'get_review_context_tool', 'get_impact_radius_tool', 'get_affected_flows_tool', 'list_flows_tool', 'get_flow_tool', 'list_communities_tool'],\n refactor: [...context, 'refactor_tool', 'apply_refactor_tool', 'find_large_functions_tool', 'get_impact_radius_tool', 'get_affected_flows_tool', 'detect_changes_tool'],\n export: [...context, 'generate_wiki_tool', 'get_wiki_page_tool'],\n advanced: [...context, 'embed_graph_tool', 'run_postprocess_tool', 'list_repos_tool', 'cross_repo_search_tool', 'list_communities_tool', 'get_community_tool', 'get_architecture_overview_tool', 'get_hub_nodes_tool', 'get_bridge_nodes_tool', 'get_knowledge_gaps_tool', 'get_surprising_connections_tool', 'get_suggested_questions_tool', 'traverse_graph_tool', 'detect_changes_tool', 'list_flows_tool', 'get_flow_tool', 'find_large_functions_tool'],\n};\nexport const skillWorkflow: Record<string, Workflow> = {\n 'build-graph': 'advanced', 'debug-issue': 'review', 'explore-codebase': 'advanced',\n 'refactor-safely': 'refactor', 'review-changes': 'review', 'review-delta': 'review', 'review-pr': 'review',\n};\nexport const publicName = (raw: string): string => `mcp__code_review_graph__${raw}`;\nexport function exposed(workflow: Workflow, names: string[]): string[] {\n return workflow === 'all' ? names : names.filter(name => members[workflow].includes(name));\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["readdir", "stat", "join", "basename", "resolve", "name", "name", "join", "basename", "stat", "result", "value", "name", "readdir"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# 0.1.1
|
|
2
|
+
|
|
3
|
+
修复 DSH Web/桌面客户端的原生加载和图谱侧栏显示:
|
|
4
|
+
|
|
5
|
+
- Client 发行文件改用 DSH 官方 closure-factory 格式,解决 `Cannot use import statement outside a module`。
|
|
6
|
+
- 侧栏正文按插件 provider ID 注册,解决打开图谱后显示“这类内容还没有可用的查看方式”。
|
|
7
|
+
- 浏览器回归直接执行发行文件;补充真实 Windows Desktop 0.2.0-rc.2 的隔离安装、启用、图谱读取、搜索、HTML 导出和卸载/重装验证。
|
|
8
|
+
|
|
9
|
+
桌面安装 `dsh-code-review-graph@0.1.1`,完成后点击“立即启用”。已有旧版先通过
|
|
10
|
+
原生插件管理器卸载;安装状态尚未确认时先核对状态。
|
|
11
|
+
|
|
12
|
+
用户报告的 pnpm 十分钟无输出超时未能复现。相同旧包及原有依赖组合均可正常安装;
|
|
13
|
+
本次修复不代表该安装器超时已解决。其他桌面平台/版本未整机实测,详细范围见
|
|
14
|
+
[验证记录](validation.md)。没有运行模型推理,也未修改用户的 DSH 配置。
|
|
15
|
+
|
|
16
|
+
## English
|
|
17
|
+
|
|
18
|
+
- Emit the Client in DSH's native closure-factory format, fixing `Cannot use import statement outside a module` in the actual loader.
|
|
19
|
+
- Register the sidebar body under the plugin provider ID, fixing the unavailable-view fallback when opening a graph.
|
|
20
|
+
- Execute the published Client in browser regressions; add isolated installation, enablement, native graph reading/search/HTML export, removal and reinstallation checks using the actual Windows Desktop 0.2.0-rc.2 application.
|
|
21
|
+
|
|
22
|
+
Install `dsh-code-review-graph@0.1.1` in the native Desktop plugin manager, then
|
|
23
|
+
choose **Enable now**. Remove an existing old version through that manager first;
|
|
24
|
+
reconcile an unconfirmed installation before proceeding.
|
|
25
|
+
|
|
26
|
+
The reported ten-minute pnpm no-output timeout did not reproduce with the same
|
|
27
|
+
old package and dependency combination. This release does not claim to resolve
|
|
28
|
+
that installer timeout. Other Desktop platforms/releases have not been tested as
|
|
29
|
+
complete applications. See [validation](validation.md) for scope. No model
|
|
30
|
+
inference or changes to the user's DSH configuration were involved.
|
package/docs/validation.md
CHANGED
|
@@ -4,16 +4,21 @@ Local checkpoint: **2026-10-08**, Windows, Node **24.14.1**, Python **3.11.16**.
|
|
|
4
4
|
CRG base **2.3.9**; exact contribution engine commit is in `engine.json`.
|
|
5
5
|
DSH packages are the real published releases **0.1.5-rc.2** and **0.2.0-rc.2**.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
0.1.1 修复了真实原生加载器才会暴露的两个问题:Client 发行文件必须使用
|
|
8
|
+
`window.__ModuleLoader__.load({ id, factory })`,侧栏正文必须按 provider ID 注册。
|
|
9
|
+
0.1.0 的浏览器测试重新打包了源文件,并错误地接受了按 kind 注册正文,未发现这两个问题。
|
|
10
|
+
回归测试现在直接执行发行 Client 文件,并检查 provider ID。
|
|
11
|
+
|
|
12
|
+
Windows 本地增加实际安装的 DSH Desktop **0.2.0-rc.2** 验证;三平台、两版 DSH
|
|
13
|
+
的 CI 覆盖原生运行时、CLI 和浏览器组件。Desktop 0.1.5-rc.2、Linux/macOS
|
|
14
|
+
桌面应用尚未整机实测。未运行真实模型推理。
|
|
10
15
|
|
|
11
16
|
## Results
|
|
12
17
|
|
|
13
18
|
| Check | Local result | Scope |
|
|
14
19
|
| --- | --- | --- |
|
|
15
|
-
| TypeScript and Host/Client build | Passed |
|
|
16
|
-
| Plugin safety contracts |
|
|
20
|
+
| TypeScript and Host/Client build | Passed | ESM Host/CLI; native closure-factory browser Client |
|
|
21
|
+
| Plugin safety and native Client contracts | 5 passed | Root/linked-directory fences, all 30 tools, both readers, offline iframe, full refactor recovery; actual published Client executes as a classic script |
|
|
17
22
|
| Real engine worker lifecycle | 3 passed | Shared root worker, last owner cleanup, resumed owner, cancellation, timeout, missing engine |
|
|
18
23
|
| DSH 0.1.5-rc.2 session | Passed | 52 recorded deterministic tool calls, including 9 expected refusals |
|
|
19
24
|
| DSH 0.2.0-rc.2 session | Passed | Same native session and real engine checks |
|
|
@@ -24,7 +29,8 @@ Windows 本地验证及 Windows/Linux/macOS 的远程 CI 均已通过。桌面
|
|
|
24
29
|
| Ruff for changed Python sources/tests | Passed | All modified Python files |
|
|
25
30
|
| Windows/Linux/macOS × both DSH releases | 6 CI jobs passed | Real pinned engine, isolated native installation, session calls, browser graph and explicit setup |
|
|
26
31
|
| Core Windows/Linux/macOS regression matrix | 3 CI jobs passed | 808 passed, 1 platform-specific skip on each OS |
|
|
27
|
-
|
|
|
32
|
+
| Windows Desktop 0.2.0-rc.2 | Passed | Actual packaged Electron application, native install/enable/remove/reinstall, graph reading/search/HTML download, unrelated configuration retained; no model |
|
|
33
|
+
| Other Desktop releases/platforms | Not run | Browser/CLI checks do not establish complete Electron coverage |
|
|
28
34
|
| Real model inference | Not run | Deterministic automation only, no paid inference jobs |
|
|
29
35
|
|
|
30
36
|
The four local core skips comprise three existing symlink checks requiring
|
|
@@ -62,10 +68,41 @@ the transcript retains the structured value and omits duplicate rendering;
|
|
|
62
68
|
error content is kept. Upstream reviewers decide whether deterministic evidence
|
|
63
69
|
satisfies their contribution requirement.
|
|
64
70
|
|
|
65
|
-
The Playwright harness
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
The Playwright browser harness executes **`dist/client.js` as published**, through
|
|
72
|
+
the native classic-script factory contract, and mounts its graph in Chromium.
|
|
73
|
+
Only the host services and each release's workspace reader are fixtures. It
|
|
74
|
+
checks provider ID/slot registration. This remains a component test, separate
|
|
75
|
+
from the real Desktop test.
|
|
76
|
+
|
|
77
|
+
## Desktop regression and reported timeout
|
|
78
|
+
|
|
79
|
+
- [Clean Desktop profile evidence](evidence/desktop-0.2.0-rc.2-win32-clean.json)
|
|
80
|
+
- [Existing dependency combination evidence](evidence/desktop-0.2.0-rc.2-win32-dependency-combination.json)
|
|
81
|
+
|
|
82
|
+
`tests/desktop.mjs` launches the actual installed Electron executable with a
|
|
83
|
+
temporary DSH_HOME, userData, HOME/USERPROFILE and APPDATA. It uses the native
|
|
84
|
+
plugin manager to install the packed tarball, enable it, uninstall it, reinstall
|
|
85
|
+
it and uninstall it again. It checks that existing dependencies, bundles and an
|
|
86
|
+
unrelated user setting survive. The real engine CLI builds a small Python graph;
|
|
87
|
+
the Desktop sidebar reads it through the actual workspace-file service, searches
|
|
88
|
+
it and exports the HTML through Electron's download pipeline. Session creation
|
|
89
|
+
is deterministic, without model inference. This complements the real DSH tool
|
|
90
|
+
session transcripts; it does not claim that the model drove the Desktop UI.
|
|
91
|
+
|
|
92
|
+
The official Desktop Host hardcodes port 19387. A test-only preload changes that
|
|
93
|
+
test process's listener to an OS-assigned port so the user's running Desktop is
|
|
94
|
+
untouched. Test windows stay hidden; the native welcome flow uses “Set up later”
|
|
95
|
+
without signing in, copying credentials or configuring a model key. No test
|
|
96
|
+
settings are written to the user's profile.
|
|
97
|
+
|
|
98
|
+
The reported pnpm log said `Done in 4.1s`, followed by the Host's 600000 ms
|
|
99
|
+
no-output timeout. The same 0.1.0 package completed successfully with bundled
|
|
100
|
+
Electron/pnpm, a clean native Desktop profile, and a native Desktop profile
|
|
101
|
+
containing the three existing plugins and package-manager policy files. The
|
|
102
|
+
latter reproduced the same `+98 -27` dependency change. The original hang has
|
|
103
|
+
**not been reproduced**. 0.1.1 fixes the two independently confirmed Client
|
|
104
|
+
defects and the missing regression coverage; it does not claim to fix that
|
|
105
|
+
unresolved package-manager timeout.
|
|
69
106
|
|
|
70
107
|
## Reproduction
|
|
71
108
|
|
|
@@ -90,6 +127,17 @@ installation use the publicly available exact source in `engine.json`.
|
|
|
90
127
|
Test profiles use temporary
|
|
91
128
|
`DSH_HOME` values and never modify the user's DSH profiles or credentials.
|
|
92
129
|
|
|
130
|
+
For the separate real Desktop check, first run `npm pack`, set
|
|
131
|
+
`DSH_DESKTOP_EXECUTABLE` to a supported installed Desktop application and
|
|
132
|
+
`CRG_COMMAND` to a prepared real engine, then run `npm run test:desktop`.
|
|
133
|
+
`DSH_VERSION` must match the actual application version (default 0.2.0-rc.2).
|
|
134
|
+
Missing application/engine inputs fail explicitly. To reproduce a dependency
|
|
135
|
+
combination, additionally set `DSH_DESKTOP_PROFILE_FIXTURE` to a profile directory
|
|
136
|
+
and `DSH_DESKTOP_PNPM` to the application's bundled pnpm entry. Only
|
|
137
|
+
`package.json`, `pnpm-lock.yaml` and `pnpm-workspace.yaml` are copied to the
|
|
138
|
+
temporary profile. Evidence is written to `test-results/desktop-*.json` with the
|
|
139
|
+
tested package SHA-256 and native operation results.
|
|
140
|
+
|
|
93
141
|
Core regression command:
|
|
94
142
|
|
|
95
143
|
```sh
|
|
@@ -101,7 +149,7 @@ Linux and macOS using a real pinned engine, isolated profile lifecycle, browser
|
|
|
101
149
|
checks and dependency preparation. The core's `dsh-platform.yml` runs the Python
|
|
102
150
|
contract/regression set on the same three OS families.
|
|
103
151
|
|
|
104
|
-
- [
|
|
152
|
+
- [Historical 0.1.0 adapter CI: six combinations passed](https://github.com/sumingwang233/dsh-code-review-graph/actions/runs/37716247823), source `9c478191b743ecc443cc0eede5d67927a367d7ed`; the old browser harness missed the Client packaging/slot defects described above.
|
|
105
153
|
- [Core CI: all three OS jobs passed](https://github.com/sumingwang233/code-review-graph/actions/runs/37717599685), source `8ac789138463cd3b8282688bb94d682e90ca1df3`.
|
|
106
154
|
|
|
107
155
|
Each adapter CI artifact contains its native session transcript, browser report,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-code-review-graph",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Local code intelligence, review workflows and interactive graphs for DeepSeek Harness",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"engines": { "node": ">=22.19" },
|
|
9
9
|
"exports": { ".": "./dist/index.js", "./client": "./dist/client.js", "./package.json": "./package.json" },
|
|
10
10
|
"bin": { "dsh-crg": "./dist/setup.js" },
|
|
11
|
-
"files": ["dist", "cordis.patch.yml", "engine.json", "capabilities.json", "skills", "LICENSE", "NOTICE", "README.md", "README.en.md", "docs/capabilities.md", "docs/validation.md"],
|
|
11
|
+
"files": ["dist", "cordis.patch.yml", "engine.json", "capabilities.json", "skills", "LICENSE", "NOTICE", "README.md", "README.en.md", "docs/capabilities.md", "docs/validation.md", "docs/release-0.1.1.md"],
|
|
12
12
|
"dsh": { "bundle": { "patch": "./cordis.patch.yml" }, "client": { "platform": "web", "inject": ["@deepseek-ai/dsh-api-workspace-files", "@deepseek-ai/dsh-client-ui-sidebar-right", "@deepseek-ai/dsh-api-remotes"] } },
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "node scripts/build.mjs",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"test": "node --test tests/*.test.mjs",
|
|
17
17
|
"test:integration": "node --test tests/integration.mjs",
|
|
18
18
|
"test:browser": "node --test tests/browser.mjs",
|
|
19
|
+
"test:desktop": "node --test tests/desktop.mjs",
|
|
19
20
|
"test:install": "node --test tests/install.mjs",
|
|
20
21
|
"test:setup": "node --test tests/setup.mjs",
|
|
21
22
|
"test:backend": "node --test tests/backend.mjs",
|