Back to home

yabo083

dsh-document-review

DeepSeek Harness plugin: review Markdown documents in a local browser with annotations, replacements, and deletion suggestions. Supports single files and whole directories.

Stars
2
Language
TypeScript
Created
Aug 14, 2026
Updated
Aug 14, 2026

Introduction

dsh-document-review

A DeepSeek Harness plugin for reviewing Markdown documents in a local browser — word by word.

Select text in the rendered document to add a comment, suggest a replacement, or suggest deletion. The browser never edits the Markdown source; annotations are stored in one central append-only store at ~/.dsh-document-review/annotations.jsonl, so annotations on files anywhere on the machine — inside or outside the workspace — are all visible to the agent with a single tool call.

Adapted from opencode-document-review — the same review core (HTTP server, security model), wrapped as DSH model-facing tools instead of OpenCode tools, with annotations consolidated into a central store.

Why

DSH agents author and edit Markdown a lot — architecture docs, ADRs, READMEs, plans. "Review this document word by word" is a natural request, but DSH had no plugin for it (confirmed: no document-review/annotation plugin in the market as of 2026-08). This plugin fills that gap.

The agent opens the document in a local browser; the human reviews and annotates; the agent reads the annotations back and applies changes through its normal file-editing tools.

Tools

All four tools are registered on ctx.tools and become model-facing:

ToolPurpose
document_review_startStart or reuse a local review session for a Markdown file or directory (directory mode reviews every .md file inside). Returns the browser URL.
document_review_listRead review annotations (open/pending by default, or all). Without path, returns every annotation across all documents on the machine — the single call the agent needs after "审查完了你看下". Includes source-hash staleness. In directory mode, summarizes every document.
document_review_updateMark a comment (resolved/open) or suggestion (accepted/rejected/pending) as handled.
document_review_stopStop the local review session. Annotations remain in the central store.

Settings

Configuration follows the official DSH plugin pattern: export a Schemastery Config schema, configure from cordis.yml (per-entry config: block), and additionally register the same schema as a DSH settings namespace (document-review) for runtime overrides.

In the harness Web profile's cordis.patch.yml, add a config block to the plugin's entry:

- id: document-review
  name: dsh-document-review
  config:
    preferredPort: 15600
    idleTimeoutMinutes: 60
    indexIgnore:
      - node_modules
      - .git
      - dist

Changing the config block hot-replaces the plugin via HMR. A DSH settings user document (when present) overrides individual keys on top of the cordis.yml base; without one, the entry config applies as-is.

In-page settings panel (review page)

Open any review page and click the gear button (top-right) for a settings dialog with two levels:

  • 全局设置 — applies to every workspace. Persisted to ~/.dsh-document-review/config.json, layered above the cordis.yml base.
  • 当前工作区设置 — applies only to the workspace the review page is rooted at. Persisted to ~/.dsh-document-review/workspace-configs.json (keyed by root path), layered above the global settings.

Effective value = defaults → cordis.yml → 全局设置 → 当前工作区设置. Index limits apply immediately (next scan); port and idle-timeout values apply on the next session start. 恢复默认 clears the active level's overrides.

KeyDefaultEffect
preferredPort15580First port to try for the review server; auto-increments when taken
maxPortTries20Ports tried before giving up
idleTimeoutMinutes30Idle time before the singleton server auto-shuts down
openBrowserOnStarttrueOpen the review page when a session starts
indexMaxRoots64Filename-index roots remembered (LRU)
indexMaxEntries60000Filename-index entry cap
indexScanCooldownMs30000Min interval between full rescans of one root
indexIgnorenode_modules, .git, dist, …Directory names skipped by indexing, migration, and directory scans

Index limits apply immediately; port and idle-timeout values apply on the next session start.

Install (published package)

dsh plugin --profile web add dsh-document-review
dsh --profile web

The plugin declares dsh.bundle (host tools + HTTP route) and dsh.client (浏览器「审阅」面板): the review page, the model-facing tools, and the per-Workspace「文档审阅」menu entries in the DSH sidebar all ship in one package.

Install (development)

From a DSH repository checkout:

dsh web --patch /absolute/path/to/dsh-document-review/cordis.yml

Open http://127.0.0.1:3080 and ask the agent: "Review the document at /path/to/file.md word by word."

The cordis.yml overlay inserts the plugin into the Web profile. The path in cordis.yml must be absolute; edit it to match your checkout location.

Browser review page

The review page renders the Markdown read-only. Select any text to annotate:

  • Comment — a note with no replacement.
  • Suggest replacement — propose new text for the selection.
  • Suggest deletion — mark the selection for removal.

Annotations appear in a side panel. Each one carries textQuote anchors and a SHA-256 hash of the source, so the agent can verify the document hasn't changed underneath the review (stale: true otherwise). The agent reads annotations back via document_review_list, applies changes to the source, and marks them resolved/accepted/rejected.

Directory mode

Pass a directory path to document_review_start (or the HTTP route) to review many Markdown files in one session:

  • The page lists every .md/.markdown file (recursive, sorted; node_modules, .git, and build-output directories are skipped; capped at 500 files).
  • Click a file to render it; navigation (back/forward/breadcrumb/path jump) is the same file-manager UI as a single-file open.
  • Each document keeps its own <document>.review.jsonl sidecar.
  • document_review_update accepts a documentPath argument (relative within the directory) to address a specific file's annotation.

HTTP routes (GUI launcher)

The client launcher calls host routes to start the review page without the model:

GET  /api/v2/document-review/scan?path=<directory>   (compat; the page browses via /api/fs/list)
POST /api/v2/document-review/start
     { "path": "/abs/or/relative/path", "openBrowser": false }

start returns { reused, mode, documentPath, sidecarPath, url, documents }. Both routes are registered on the harness web server (ctx.webServer, with an httpServer fallback for older deployments) and are re-registered idempotently when the web-server service binds after this plugin's apply.

Inside the review page (port 15580), the settings dialog uses:

GET  /api/config                       (merged snapshot for this root)
POST /api/config/global                (write global overrides; needs review token)
POST /api/config/workspace             (write this root's overrides; needs review token)

Browser launcher (client plugin)

Every Workspace row in the DSH sidebar gets a「文档审阅」entry inside its ellipsis menu:

  • Open a Workspace row's menu and choose 文档审阅 to open the review page rooted at that workspace's directory (ctx.workspaces → workspace path).
  • The sidebar exposes no per-row slot, so the entry is injected into the portaled menu list via a MutationObserver keyed on the workspace row menu button (工作区“{name}”的操作 / Workspace actions for {name}), which exists only on real Workspace rows — the ungrouped bucket is skipped, and session-row menus never get the entry.
  • The review server is a singleton — one server, one port (15580, with fallbacks), reused across opens. It shuts down automatically after 30 minutes without traffic, and the page keeps it alive with a lightweight heartbeat while open, so no port is permanently occupied.
  • Styling rides the DSH --dsw-* design tokens, so the injected menu item matches the host chrome in both light and dark themes.

Review page — file-manager browser + review

The review page is a lightweight two-mode UI:

  • File browser (default): toolbar with back / forward / up / refresh, a history dropdown, and a quick-open search box (VS Code style — Ctrl+P to focus). Breadcrumbs run from the filesystem root through a「此电脑」level (all drives), so you can browse anywhere on the machine — including drives outside the workspace (e.g. OneDrive on C: while the workspace lives on E:). Directories and Markdown files are listed; clicking a directory enters it, clicking a document opens the review view. The workspace is just the starting point, not a boundary.
  • Review view: the rendered document with the full annotation UI — select text to comment / suggest replacement / suggest deletion; the side panel lists annotations with locate, resolve, accept, and reject actions.
  • Quick search is backed by an Everything-style filename index (names only, never contents) whose scope is the directories you actually browse, persisted to ~/.dsh-document-review/index-state.json. Typing filters the in-memory name map (prefix beats substring); ↑/↓ navigate, Enter opens, Esc closes. Common noise directories (node_modules, .git, dist, cache, dot-entries, …) and .review.jsonl sidecars are skipped.
  • Back / forward history is a front-end stack (Alt+← / Alt+→, mouse side buttons, and the 🕘 dropdown all work), so navigation never touches the server.
  • A status dot shows the singleton server's health; if it idles out, refresh the page to restart it.

The page consumes these APIs on the singleton server:

GET  /api/health              singleton status (root, document count); heartbeat target
GET  /api/fs/list?path=<dir>  one directory: subdirectories + Markdown files, breadcrumb ancestry, parent;
                             `::computer` lists every drive (the「此电脑」level)
GET  /api/fs/search?q=<q>     quick search over the persisted filename index (max 50 hits)
GET  /api/document?path=<abs> render one Markdown document + its annotations
POST /api/annotations         add an annotation (X-Review-Token required)
PATCH /api/annotations/:id    update an annotation status (X-Review-Token required)
DELETE /api/annotations/:id   permanently delete an annotation (X-Review-Token required)

Data contract

  • Any explicit absolute .md or .markdown path readable by the current OS user can be reviewed.
  • Relative paths resolve from the agent's current working directory.
  • Review records live in one central append-only JSONL store: ~/.dsh-document-review/annotations.jsonl.
  • Status updates append a new record with the same annotation ID; reads dedupe by ID, latest wins.
  • Deleting an annotation rewrites the store without it (physical removal).
  • Legacy <document>.review.jsonl sidecars from older versions are auto-imported and removed on first start.
  • Records contain quote anchors, a source SHA-256 hash, status, timestamps, and optional replacement text.
  • Source changes are applied by the agent through normal DSH file-editing tools, not by the browser.

Security

The HTTP server binds only to 127.0.0.1, validates the loopback Host and same-origin requests, uses a random write token, disables CORS, and serves the selected Markdown rendering, one-level directory listings, and bundled static assets. The browser only lists directory names and reads Markdown files; every other filesystem surface is closed. Store writes use a cross-process lock and reject symbolic-link sidecars.

Structure

dsh-document-review/
├── src/
│   ├── index.ts          DSH host entry: 4 tools on ctx.tools + HTTP start route + Config schema
│   ├── config.ts         Schemastery Config schema + defaults (cordis.yml + DSH settings)
│   ├── review.ts         Review core: HTTP server, central JSONL store, security
│   ├── web-server.ts     Minimal structural type for ctx.webServer routes
│   └── client/
│       └── index.tsx     Browser entry: per-Workspace「审阅」buttons (dsh.client)
├── public/
│   ├── index.html        Review page shell
│   ├── app.js            Selection → annotation UI (file + directory modes)
│   └── styles.css        Review page styles
├── cordis.patch.yml      Bundle patch: inserts the plugin row (dsh plugin add)
├── cordis.yml            DSH Web dev overlay (edit path to match your location)
├── tsconfig.json         Host program (src/, excludes src/client)
├── tsconfig.client.json  Browser program (src/client, jsx: react-jsx)
├── tsdown.config.ts      Client bundle build (DSH __ModuleLoader__ protocol)
├── scripts/copy-assets.mjs
├── package.json
└── README.md

Build

npm install          # dev tooling (tsdown, lightningcss, react, typescript)
npm run build        # host tsc → lib/, client tsc → lib/client/, tsdown → lib/client.js
npm run check        # type-check both programs without emitting

The client bundle is emitted as a CJS closure-factory artifact: window.__ModuleLoader__.load({ id: "dsh-document-review", factory: (require) => … }) with react, react-dom, and the other platform modules resolved through the shell's frozen module table, exactly like DSH's own packages/client/ui-* bundles.

License

MIT