JayYuen666/dsh-ocr-review

dsh-ocr-review

把外部 `ocr`(open-code-review)CLI 包成五个模型工具——单次评审、全树扫描、确定性的范围与规则脚手架、会话查询——并把「OCR 发现了什么」与「这次跑有没有覆盖完选中的文件」分成两套口径回报。

待分类dshdsh-plugins

dsh-ocr-review

A code-review bridge for dsh: seven model tools (`ocr_review`, `ocr_scan`, delegate preview & rule, `ocr_session`, `ocr_rules_check`, `ocr_llm`) plus a settings card that applies the host's provider/model selection to the external CLI's own config — as a runtime command, never a plaintext key.

Contents

  • What it does
  • Install
  • The external CLI
  • Verify it is live
  • What it does not provide
  • Enabling it in dsh
  • Tools exposed to the model
  • Settings
  • Public interface
  • Credentials and the OCR config file
  • Background runs as host jobs
  • Data and privacy
  • Known boundaries
  • FAQ
  • Development
  • License

What it does

  • One settings card also carries the admin surfaces: a validated MCP servers JSON editor (saving touches only mcp_servers) and a ChatGPT section that reports ocr auth status and switches OCR's provider/model. Both are human-managed; neither is a model tool (a stdio MCP server runs commands, and OAuth sign-in needs a browser plus a local callback).
  • Wraps the external `ocr` (open-code-review) CLI into seven model tools. The host validates tool arguments not at all, so every shape, type and enum gate lives in this package: user-controlled strings are single-quote escaped, enums are whitelist-clamped, and wrong-typed values error instead of silently narrowing or widening a review.
  • One settings card (the web client half) picks provider/model from the host's own llm-pi-ai settings entry and key status from the host credentials service. When either service is absent the card shows the degradation reason; tools and settings keep working.
  • Applying a selection writes the CLI's own `~/.opencodereview/config.json` — as an api_key_cmd command that reads the host credential ref at OCR runtime. A plaintext key is never written anywhere.
  • Results are a structured summary with two distinct notions: what OCR found (status, aggregation, topComments, warnings) and whether the run actually covered the selected files (coverage, budgetExceeded, toolCalls). The CLI is invoked with --audience agent, which suppresses its progress output — the second notion is only reachable through those fields.
  • CLI availability is one judgement (resolveOcrAvailability in lib/cli.ts): pure filesystem, never a spawn, re-read per call. The card status row, the detail-page badge and the activation guidance all render that one answer, so "which copy is in use" cannot be told three ways.
  • Background runs (`wait: false`) are official host jobs: job_list / job_output / job_kill can observe and stop a review that outlives the tool call.

Install

dsh plugin --profile web add @jayyuen66/dsh-ocr-review

Remove with dsh plugin --profile web remove @jayyuen66/dsh-ocr-review. The package is on the public npm registry, so installation needs no credentials.

  • Upgrading an installed copy is remove-then-add. On a profile running HMR (the shipped ones do), add on a package the profile already depends on refuses before pnpm runs (already-installed) — run remove first, then add the target version; the new one starts without a restart. Without HMR the same add runs pnpm and reports restart-required.
  • Requires Node.js `>= 22.12` and host `dsh` on the `0.2.1-rc.3` line. The host reads engines.dsh (^0.2.1-rc.3) and decides on it alone; prereleases participate in the comparison. An incompatible host needs the explicit risk grant dsh plugin --profile allow-version @jayyuen66/dsh-ocr-review@ --dsh-version --accept-risk.
  • **No @deepseek-ai/* under dependencies, by contract (`test/publish-manifest.test.ts` enforces it). Consumer profiles install with `nodeLinker: hoisted` and `autoInstallPeers: false`, so a host package there is materialized as a real directory next to the copy the running host already loaded. `@deepseek-ai/dsh-tools` keys its scheduler with a plain `Symbol` (not `Symbol.for`), so symbol identity is per instance and every native tool call in that profile throws** — while this package's build, typecheck and coverage gates stay green right up to that moment.
  • The five host packages `host.js` imports as values are required peers: @deepseek-ai/schemastery (the host fork — only it resolves .volatile() fields), @deepseek-ai/dsh-credentials, @deepseek-ai/dsh-home-paths, @deepseek-ai/dsh-atomic-write and @deepseek-ai/dsh-typert-protocol (the Remote service base). Each is repeated in devDependencies at the same pin so this repository's own typecheck resolves them; the build externalizes all manifest tables, keeping bare specifiers in the artifact.
  • What ships: host.js, client.js, cordis.patch.yml, scripts/, icon.svg, CHANGELOG.md and THIRD_PARTY_NOTICES.md. prepack rebuilds the bundles, so nothing stale is published.

Shared version floor

If your profile pins @jayyuen66/dsh-plugin-shared with a pnpm overrides entry, that pin must be at least the version this package declares. A lower pin installs a copy without the names imported at runtime, and the host then reports N entries did not activate together with does not provide an export named ... at boot — the package installs fine and never loads, and no per-repository CI run can see it.

The external CLI

This package spawns ocr; it never installs or upgrades it. The command contract is verified against 1.13.3 (EXPECTED_OCR_CLI_VERSION in lib/cli.ts, declared once as ocrCliVersion in package.json — tests pin the manifest, the code constant and both READMEs to the same string).

brew install open-code-review                        # the formula tracks the newest release
npm i -g @alibaba-group/open-code-review@1.13.3      # pinned to the version this plugin verifies against
  • brew cannot pin. homebrew/core carries one stable formula and no versioned variant, so brew install / brew upgrade always yields whatever is newest upstream. The npm command is the only one that can carry the verified version.
  • PATH wins, and the refusal is honest. Command resolution takes the first ocr on PATH, then a copy inside this package's own dependency tree (skipped on Windows — a shebang script pwsh/cmd cannot execute); with neither, the tools refuse before spawning and hand the model the install commands above with the version filled in. "How it was installed" is read off the real target of the bin symlink: npm-style under node_modules, a formula under Cellar / Homebrew, otherwise reported as unrecognised rather than guessed.
  • Version is measured, not assumed. Reading it spawns ocr version (15 s wall clock, always with OCR_NO_UPDATE=1) — on the card's Detect button, and once at apply when startupVerify is on. Rendering a page never spawns: the card shows the last measurement, or nothing measured yet. A disagreement names both numbers, flips the badge and appends a note to the review summary's hint; it is never a hard failure, and this package runs no installer.
  • Upstream self-update is real. The npm wrapper runs npm i -g @alibaba-group/open-code-review@ during invocations on its own. The version probe always suppresses that; ocrNoAutoUpdate extends the suppression to every command this package spawns (see Settings).
  • Upgrading from a release that bundled the CLI (optionalDependencies, up to and including 0.1.5-rc): nothing to do — PATH now wins, so the stale copy stops shadowing yours. To clean the profile's tree, remove the package there and delete the allowBuilds entry that release needed in the profile's pnpm-workspace.yaml.
  • Platforms. macOS and Linux are the targets: the process reaper and the HOME= injection behind a custom ocrConfigPath are POSIX-only. Windows runs the bare command without reaping (a hard host exit can leave an orphaned OCR process) and requires a global ocr on PATH.

Verify it is live

  1. Mounted: dsh --profile web --dump-config ends with this package's layer (# == @jayyuen666/dsh-ocr-review).
  • That command is the only accepted "it is in the layer stack" signal, and it is not read-only — it rewrites the profile's root assembly file. Read layer lines from stdout and check stderr separately: an unmatched patch target appears only on stderr, so the exit code alone proves nothing. Accept both # == and # == , patched by .
  1. The card renders: the settings page shows the card, and its External CLI row names which copy is in use, how it was installed, and which version this plugin verifies against.
  2. Detect version works: pressing it runs one ocr version and fills the measured version into the row — or names the failure arm (command missing, non-zero exit, timeout, nothing parseable).
  3. Tools answer honestly without the CLI: on a machine with no ocr, ocr_review refuses before spawning and returns the install commands with the verified version — not an exit-127 to reverse-derive.

What it does not provide

  • It does not install or upgrade the CLI. The brew/npm commands are shown, never run; there is no update action and no clipboard API (a non-secure context would fail silently, looking like a dead button).
  • No HTTP route of its own. The data plane is one official Typert Remote namespace (ocrReview) through the host /api gateway; authentication, Host / DNS-rebinding / Origin checks and body limits are the gateway's. This package holds no CSRF token, no body cap and no route disposer.
  • No CLI subcommand. dsh ocr … does not exist.
  • It reads no file outside the CLI's config path, the temp files it creates itself, and the host settings / credentials services it is handed.
  • What it *does* consume: the host settings and credentials services (provider list, key status), shell (every ocr subprocess), jobs (background runs) and systemPrompt (one routing section). A missing settings or credentials service degrades the card only — the tools keep working.

Enabling it in dsh

  • dsh.bundle.patch in package.json points at cordis.patch.yml, whose single registration line is - id: ocr-review with name set to the bare package name — the bare name is what makes the host scan the client half.
  • The card needs a web profile (dsh.client.platform: web, immediately: true). The five ocrReview methods are installed unconditionally on the apply fiber, so a host whose web layer never comes up (a TUI) still has the tools and settings.
  • The gateway exposes the methods only to clients of this profile; this package adds nothing in front of it.
  • Deployment defaults go on the registration line's config: (cordis validates through the exported Config schema); precedence: settings-card runtime value > line config > built-in default.

Tools exposed to the model

Tool · Arguments and constraints ocr_review · repo (absolute path, default = the session workspace), scope workspace/commit/branch (branch needs both from+to and is mutually exclusive with commit), effort, background, exclude, provider/model/resume — resume only with commit/branch; a workspace run cannot be resumed. preview (file-selection only), noFilter, maxGitProcs, rule/backgroundFile (repo-root files only), wait Numeric group (review and scan) · concurrency / timeoutMinutes / maxTools / maxTokens / maxTokensBudget, mapped to --concurrency --timeout --max-tools --max-tokens --max-tokens-budget. Values pass through faithfully; min floors are 1 for concurrency and 0 for the rest, and non-integers or below-floor values error instead of clamping ocr_scan · repo, path, exclude, batch none/by-language/by-directory, provider/model (the CLI's native shared flags), the same numeric group, preview, noPlan/noDedup/noSummary, resume, maxGitProcs, rule (repo-root file), wait. Whole-repo scanning, no git diff required ocr_delegate_preview / ocr_delegate_rule · preview: repo, scope/commit/from/to, exclude, background, backgroundFile/rule/maxGitProcs. rule: repo plus paths — required, up to 200 entries, positional after --, each escaped — plus rule. Zero LLM cost on the OCR side: they return the file list and the rule groups ocr_session · repo, action list/show/comments/compare/export, id, before/after (compare), severity/category (comments filter), limit — default 10; an explicit value must be an integer in 1–100 and out-of-range errors rather than clamping. compare returns the four buckets; export returns an HTML path ocr_rules_check · repo, path (required), rule (repo-root file): print the winning rule layer, glob and body; no LLM call ocr_llm · action providers/models: read-only LLM catalogue queries; no review and no connectivity test List caps · exclude and path take up to 2000 entries each; exceeding the cap rejects the call. Entries are never silently dropped — "reviewed fewer files than the list" must not be reportable as success

wait: false starts a background run with no host deadline (poll ocr_session; the subprocess is also an official job, see Background runs as host jobs), and the reaper guard takes the OCR process tree down when dsh exits in any way. Cancelling the tool call stops the corresponding background process.

Settings

The namespace is the entry id ocr-review; the card fields are the .volatile() ones.

Field · Values, default, use effort · auto/low/medium/high, default auto; auto omits --effort so OCR's own configured effort wins. Used when ocr_review is called without an explicit effort language · 中文/English, default 中文; written as the top-level language key of the CLI config when a selection is applied autoVerify · default true; a successful select also runs ocr llm test (capped by llmTestTimeoutMs) maxComments · default 12, the number of comments kept in the summary; 0 = no truncation, non-integers and negatives fall back to 12 timeoutMinutes · default 0 = request the host cap (the host clamps to min(request, shell.maxTimeoutMs)); above 0 is a number of minutes ocrConfigPath · Where the CLI config lives; empty derives /.opencodereview/config.json, ~ and ~/ prefixes accepted, relative paths error. A custom value must follow the /.opencodereview/config.json layout — the CLI has no file-level path override, so the plugin injects HOME= into the subprocess (config and sessions redirect together); anything else is rejected at the setting layer. POSIX-only: Windows has no such channel

The deployment keys below are deliberately not on the card — they are plain (non-volatile) fields of the exported Config, settable only on the profile's config: line and taking effect on restart:

Key · Default · Use llmTestTimeoutMs · 90000 · Wall clock for ocr llm test, shared by autoVerify and the card's test connection; raise it on slow machines startupVerify · true · Run the endpoint self-check (and one version measurement) at apply. The same switch governs both, because the criterion is "may a load spawn processes"; turn it off if loads must stay process-free ocrBackgroundMaxMs · 0 · Backstop window for background reviews (registry wait to the deadline, then kill); 0 = no window — a run ends only when the host exits, the session stops, the plugin unloads or the job is killed stdoutMaxBytes · 400000 · Shell executor stdout cap, shared by foreground and background runs (review results go through the --output temp file precisely to step outside it) ocrNoAutoUpdate · false · true puts OCR_NO_UPDATE=1 on every command this package spawns, stopping upstream's mid-review npm i -g of a newer release; false keeps upstream behaviour. The version probe sets the variable regardless ocrInstallHint · both · Which install command to lead with when nothing is installed: both / brew / npm. A fallback, not an override — an existing copy is always described by its own source, because telling a formula user to run npm i -g leaves a second copy behind while PATH still resolves the old one. The npm command always carries the verified version; the brew one never can

Public interface

One official Typert Remote namespace, ocrReview (OCR_REMOTE_NAMESPACE in lib/remote-shape.ts), its nine methods marked programmatically in host.ts. The service registers at construction and is withdrawn with its fiber, so a hot reload cannot leave a stale namespace behind.

Method · Input · Success payload providers() · — · {providers (each with a hasKey boolean), current, getCredReady, source, endpointVerify, ocrBinary} — never a key. ocrBinary carries the filesystem-only availability verdict plus the version context (source, channel, expectedVersion, measuredVersion: string \ · null, installHint); reading this method never spawns select(request) · {provider, model}, both must hit the list describe() returns · {ok, applied, llmTest} (llmTest is null when autoVerify is off) migrate() · — · {ok, migrated, skippedUnknown} test() · — · {ok, output} — ok is the test verdict, not whether the call ran; output is redacted first, then cut to 8000 characters probe() · — · {ok, version, expectedVersion, consistent, output, reason, changeText} — one ocr version under a 15 s wall clock, with OCR_NO_UPDATE=1. consistent is three-state (null = nothing was measured, where claiming a mismatch would be claiming without evidence); reason names the failure arm; changeText carries the install/upgrade sentence only when there is a command to give mcp() · — · {ok, json} — only the mcp_servers object as pretty JSON, never providers or keys saveMcp(request) · {json} — raw JSON text, at most 64 KiB; stdio/remote entries are validated field by field · {ok, count} — writes only mcp_servers; other config is preserved auth() · — · {ok, output, loginCommand} — read-only ocr auth status --provider chatgpt; sign-in itself stays a terminal action because it needs a browser and a local callback chatGpt(request) · {model} · {ok, model} — sets provider: "chatgpt" and providers.chatgpt.model, writes no key

Refusals answer {ok: false, error, status} where status is a retained semantic code, not an HTTP status (400 for an empty or off-list selection, 500 for a failed write or a missing credential script) — the number lets the tests pin which rule fired, and the client renders error only. There is deliberately no client-side wall clock on these calls: the timeout belongs to the gateway, and a second one in the card is how a slow write gets reported lost while it actually landed.

Credentials and the OCR config file

Aspect · Fact What lands in the config · A command, not a secret: api_key_cmd = node '' '' '' — single-quote escaped on POSIX, double-quote escaped on Windows (the CLI runs credential commands through cmd.exe there). The data directory is the host's current resolved home, so get-cred's fallback layers stay correct when HOME is redirected; the REF name must satisfy the official ref-name rule or the command is refused | Layer order | get-cred reads the same three layers in the same order as the official credentials-local: refs. in the managed credentials file first, then the inherited