dsh
Akimiya-z/codex-guard在 DeepSeek Harness 内做提交前的 Pull Request 卫生检查:扫描当前改动中的 TODO 残留、硬编码密钥与非规范提交信息。
把对话里的 git diff 一键打开为独立标签页的双列代码评审页:行号、增删配色、字符级高亮、hunk 感知的多文件分段、文件导航侧栏与统计条,行内评论可提交回 DSH 对话框,界面跟随 DSH 语言(中英)。
English | 中文
dsh-code-review is a browser plugin for the DSH (DeepSeek Harness) web UI that turns git diff output in a conversation into a one-click code review page in its own browser tab — a two-column, human-friendly view (old file on the left, new file on the right, both with line numbers), free from the chat window's width/height limits.
Since 0.3.0 it can also delegate the review itself: a one-click AI Review button spawns a clean-context subagent that independently reviews the change and discusses with the main agent until they reach consensus — a second pair of eyes that never saw the conversation that produced the code.
The conversation itself stays untouched: when a git diff shows up, a small "open in new tab" button appears on the block (a markdown diff code block, or the bash tool row that produced the diff — even while collapsed). One click opens the review page.
When the conversation mentions a local `.diff` / `.patch` file path, the path automatically becomes a view link button — a path inside backticks gets an inline pill right after it, a bare-text path gets a right-aligned button row under the message. One click opens that file's code review page (the browser cannot read local files, so the plugin host half reads them — absolute paths only, .diff/.patch suffix only, ≤ 5 MB).
useSession((s) => s.chat) to the session-standard useChat hook; markdown code-block DOM wrapped in a bannerWrap layer) is handled on both detection paths: markdown diff blocks and collapsed bash tool rows. Verified against dsh 0.1.3-alpha.2 since 0.2.1, and against dsh 0.2.0-rc.1 since 0.2.2, and against dsh 0.2.1-rc.2 since 0.3.0 (AI review staging route + composer auto-submit verified there).spawn_teammate(context: "fresh") (Agent Teams profiles) or the subagent tool plus send_message (plain delegation setups). On hosts without any delegation tool the agent is told to say so and review with fresh eyes itself. Auto-submit uses the composer submit action (dsh 0.2.x); on older hosts the instruction is filled into the draft and you press Enter yourself.@deepseek-ai/dsh-* peer range against the *runtime version* and skips any plugin whose ranges do not match (^0.1.3-alpha.2 excludes 0.2.0-rc.1). No plugin code changed: 0.2.2 only widens those ranges to ^0.1.3-alpha.2 || ^0.2.0-rc.1. On 0.2.0-rc.1 an already-installed 0.2.1 can also be admitted without upgrading, by granting the exact-version exemption (dsh plugin --profile allow-version @yangzhe1991/dsh-code-review@0.2.1 --dsh-version 0.2.0-rc.1 --accept-risk).ReferenceError (the copy itself still worked); the texts are now defined globally before use.@@ hunk header is kept as a separator row with its original section name; multiple files in one diff are split into per-file sections with a status badge (modified / new file / deleted / renamed / binary).+N −M · F files in a sticky top bar, with a "copy raw diff" button and a collapsible raw-text section at the bottom.file:line — comment lines, ready to send to the agent.spawn_teammate with context: "fresh" or the subagent tool, never fork — (2) has it review the diff as an independent reviewer (it may read sources and run tests), and (3) discusses every disputed finding via `send_message` until both sides agree on each conclusion, finally reporting the jointly confirmed findings (P0/P1/P2), the suspicions ruled out, and the verdict (LGTM / needs changes). Fixes are only applied after you confirm.…/xxx.diff / …/xxx.patch paths mentioned in the conversation become link buttons: backtick-wrapped paths get an inline pill right after them, bare-text paths get a right-aligned button row under the message, and paths listed in bash tool output join the tool row's button row. Clicking opens that file's code review page; on read failure (missing / not a diff / over 5 MB) the button briefly turns red. diff / patch fence, or any code block whose content looks like a git diff (e.g. pasted inside a text` fence). The block is collapsed by default (just the title row + buttons), with an "expand/collapse" toggle if you want to read the raw text inline.git diff in a bash tool, the button is added to the tool row (visible even while the row is collapsed; detected from the conversation data, since a collapsed row has no output in the DOM). The terminal card in the details panel gets one too..diff/.patch paths in assistant or user messages: /Users/me/proj/patch.diff `) get an inline pill button right after the path;Streaming output is handled gracefully: the button only appears once the content has been stable for ~1 second, so half-streamed diffs never produce a broken page.
When a path button is clicked, the plugin's host half (a route registered on webServer) reads the local file (absolute paths only, ~ expansion, .diff/.patch suffix, ≤ 5 MB, no CORS), and the browser half renders it into the standalone review page; on read failure the button briefly turns red. The route registration requires a `dsh web` restart (the host half registers it at startup); browser-half changes only need a hard refresh.
The browser has no official API to create subagents — the delegation tools (spawn_teammate / subagent) live with the main agent. So the button drives the main agent through the composer: the diff text is POSTed to a second host route (/dsh-code-review/ai-review), which stages it as a temp file (server-named, 0600, ≤ 5 MB, validated to look like a git diff, auto-pruned after 24 h, capped at 1000 files) — keeping the instruction short and the big diff out of the main conversation's context. The browser half then writes a structured instruction into the composer and auto-submits it (on older hosts without the submit action it only fills the draft, and the button honestly reports "Draft filled"). The instruction requires the main agent to: spawn a subagent with a fresh context (never fork), hand it the staged file path, have it verify suspicions against real sources/tests, dispute findings via `send_message` until consensus on every item, and summarize the agreed conclusions — waiting for your confirmation before fixing anything. If the staging route is unavailable (host half not restarted after upgrade) and the diff is small, the diff is inlined into the instruction as a fallback; otherwise the button shows a brief error. The write route is same-origin guarded (requires Content-Type: application/json and rejects Sec-Fetch-Site: cross-site; a missing header passes for curl compatibility), and the standalone page's AI button auto-recovers with a failure hint after 10 s without an ack.
dsh plugin --profile web add @yangzhe1991/dsh-code-reviewRestart the Web GUI (Ctrl+C the dsh web process and run it again) and refresh the browser tab. (dsh plugin runs pnpm add and auto-appends the bundle to dsh.profile.bundles.)
For local development, install from a path instead — the link: spec keeps a live symlink so edits take effect after a rebuild + restart:
dsh plugin --profile web add link:/path/to/@yangzhe1991/dsh-code-reviewThe plugin registers a root-scope shell.overlay seat that renders nothing and watches the whole document with a MutationObserver. For every .md-code-block and [data-terminal] element it runs a stability-debounced check; candidates get a small button appended to the block's banner/header (never replacing official nodes, so React's reconciliation stays safe). Collapsed bash tool rows are handled from the data layer: a session-scope seat subscribes to the conversation snapshot and records settled bash results that look like a diff.
Local path chips are split across the two layers: message rows (assistant/user) are probed by the same scanner — backtick-wrapped paths get an inline button inserted right after the code element, bare-text paths get a button row appended to the row; paths in bash tool results go through the data layer (same pipeline as the diff buttons). Text nodes are only read, never split (splitting React-managed text nodes would break streaming rendering), so bare-text paths get row-level buttons instead of in-place edits. On click, the browser half first opens a placeholder tab synchronously (beats popup blockers), then fetches GET /dsh-code-review/diff?path=… from the host route and navigates the tab to the standalone review page.
Clicking the button synchronously builds a self-contained HTML page (inline styles + inherited theme variables + the two-column content, all HTML-escaped) and opens it via a Blob URL — no server route needed, and the synchronous window.open inside the click gesture is not popup-blocked. The parser is a pure module (src/client/diff-parse.ts, unit-tested in test/parse.test.mjs) and supports: multi-file diffs, hunks with line-number tracking, delete/add pairing into changed rows (uneven counts degrade to single-side rows), \ No newline at end of file markers, new/deleted/renamed/binary files, bare patches without a diff --git header, and commit-message prefixes (git show output).
npm install
npm run build # esbuild → lib/index.js (host half) + lib/client.js (browser half)
node test/parse.test.mjs # parser + standalone HTML unit tests
node test/path-detect.test.mjs # local path-detection regex unit tests
node test/node-route.test.mjs # host-half route (read/validation/error codes) tests
node test/client-smoke.test.mjs # bundle smoke test (mock __ModuleLoader__)Browser-side changes only need a rebuild; the host serves lib/ live, so a hard refresh (⌘+Shift+R) picks them up. Profile-level changes (package name/bundles) require restarting dsh web.