@jayyuen66/dsh-ocr-review 0.1.6 → 0.1.8
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/CHANGELOG.md +289 -0
- package/LICENSE +1 -1
- package/README.md +203 -163
- package/README.zh-CN.md +209 -170
- package/THIRD_PARTY_NOTICES.md +10 -7
- package/client.js +477 -134
- package/cordis.patch.yml +4 -2
- package/host.js +2382 -624
- package/package.json +46 -46
- package/scripts/get-cred.mjs +156 -56
package/README.md
CHANGED
|
@@ -1,200 +1,240 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://cdn.jsdelivr.net/gh/JayYuen666/dsh-ocr-review@main/icon.svg" width="96" height="96" alt="dsh-ocr-review logo" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">dsh-ocr-review</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center"><em>The external `ocr` (open-code-review) CLI as seven dsh model tools and one settings card — you install the CLI, the plugin only discovers, verifies and drives it.</em></p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/@jayyuen66/dsh-ocr-review"><img src="https://img.shields.io/npm/v/@jayyuen66/dsh-ocr-review?style=flat-square&logo=npm&logoColor=white" alt="npm version" /></a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/@jayyuen66/dsh-ocr-review"><img src="https://img.shields.io/npm/dm/@jayyuen66/dsh-ocr-review?style=flat-square" alt="npm downloads" /></a>
|
|
12
|
+
<a href="https://github.com/JayYuen666/dsh-ocr-review/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/JayYuen666/dsh-ocr-review/ci.yml?branch=main&style=flat-square&label=ci" alt="ci" /></a>
|
|
13
|
+
<img src="https://img.shields.io/badge/license-MIT-4c1?style=flat-square" alt="MIT" />
|
|
14
|
+
<img src="https://img.shields.io/badge/node-%3E%3D22.12-4c1?style=flat-square&logo=nodedotjs&logoColor=white" alt="node" />
|
|
15
|
+
<img src="https://img.shields.io/badge/dsh-%5E0.2.1--rc.3-658EFF?style=flat-square" alt="dsh" />
|
|
16
|
+
<a href="https://github.com/deepseek-ai/deepseek-harness"><img src="https://img.shields.io/badge/powered_by_dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white" alt="powered by dsh" /></a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<img src="https://skillicons.dev/icons?i=typescript,nodejs,react,vitest,githubactions" alt="tech stack" />
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
<p align="center">
|
|
24
|
+
<a href="https://github.com/JayYuen666"><img src="https://img.shields.io/badge/GitHub-JayYuen666-181717?style=flat-square&logo=github&logoColor=white" alt="GitHub" /></a>
|
|
25
|
+
<a href="mailto:cqwkkj@gmail.com"><img src="https://img.shields.io/badge/Email-cqwkkj@gmail.com-EA4335?style=flat-square&logo=gmail&logoColor=white" alt="Email" /></a>
|
|
26
|
+
<a href="https://twitter.com/JayYuen666"><img src="https://img.shields.io/badge/Twitter-X%2FJayYuen666-000000?style=flat-square&logo=x&logoColor=white" alt="Twitter/X" /></a>
|
|
27
|
+
<a href="./README.zh-CN.md"><img src="https://img.shields.io/badge/lang-简体中文-658EFF?style=flat-square" alt="简体中文" /></a>
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
**A code-review bridge for [dsh](https://github.com/deepseek-ai/deepseek-harness): 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.**
|
|
31
|
+
|
|
32
|
+
## <img src="https://api.iconify.design/lucide/file-text.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Contents
|
|
33
|
+
|
|
34
|
+
- [What it does](#what-it-does)
|
|
35
|
+
- [Install](#install)
|
|
36
|
+
- [The external CLI](#the-external-cli)
|
|
37
|
+
- [Verify it is live](#verify-it-is-live)
|
|
38
|
+
- [What it does not provide](#what-it-does-not-provide)
|
|
39
|
+
- [Enabling it in dsh](#enabling-it-in-dsh)
|
|
40
|
+
- [Tools exposed to the model](#tools-exposed-to-the-model)
|
|
41
|
+
- [Settings](#settings)
|
|
42
|
+
- [Public interface](#public-interface)
|
|
43
|
+
- [Credentials and the OCR config file](#credentials-and-the-ocr-config-file)
|
|
44
|
+
- [Background runs as host jobs](#background-runs-as-host-jobs)
|
|
45
|
+
- [Data and privacy](#data-and-privacy)
|
|
46
|
+
- [Known boundaries](#known-boundaries)
|
|
47
|
+
- [FAQ](#faq)
|
|
48
|
+
- [Development](#development)
|
|
49
|
+
- [License](#license)
|
|
50
|
+
|
|
51
|
+
## <img src="https://api.iconify.design/lucide/shield.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> What it does
|
|
52
|
+
|
|
53
|
+
- **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).
|
|
54
|
+
- **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.
|
|
55
|
+
- **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.
|
|
56
|
+
- **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.
|
|
57
|
+
- **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.
|
|
58
|
+
- **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.
|
|
59
|
+
- **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.
|
|
60
|
+
|
|
61
|
+
## <img src="https://api.iconify.design/lucide/package.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Install
|
|
54
62
|
|
|
55
63
|
```sh
|
|
56
64
|
dsh plugin --profile web add @jayyuen66/dsh-ocr-review
|
|
57
65
|
```
|
|
58
66
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
- What
|
|
67
|
+
Remove with `dsh plugin --profile web remove @jayyuen66/dsh-ocr-review`. The package is on the public npm registry, so installation needs no credentials.
|
|
68
|
+
|
|
69
|
+
- **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`.
|
|
70
|
+
- **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 <p> allow-version @jayyuen66/dsh-ocr-review@<version> --dsh-version <exact> --accept-risk`.
|
|
71
|
+
- **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.
|
|
72
|
+
- **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.
|
|
73
|
+
- **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.
|
|
74
|
+
|
|
75
|
+
### <img src="https://api.iconify.design/lucide/arrow-down-to-line.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Shared version floor
|
|
66
76
|
|
|
67
|
-
|
|
77
|
+
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.
|
|
68
78
|
|
|
69
|
-
|
|
79
|
+
## <img src="https://api.iconify.design/lucide/terminal.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> The external CLI
|
|
80
|
+
|
|
81
|
+
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).
|
|
70
82
|
|
|
71
83
|
```sh
|
|
72
84
|
brew install open-code-review # the formula tracks the newest release
|
|
73
|
-
npm i -g @alibaba-group/open-code-review@1.13.
|
|
85
|
+
npm i -g @alibaba-group/open-code-review@1.13.3 # pinned to the version this plugin verifies against
|
|
74
86
|
```
|
|
75
87
|
|
|
76
|
-
- **brew cannot pin
|
|
77
|
-
- **
|
|
78
|
-
- **
|
|
79
|
-
- **
|
|
80
|
-
-
|
|
88
|
+
- **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.
|
|
89
|
+
- **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.
|
|
90
|
+
- **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.
|
|
91
|
+
- **Upstream self-update is real.** The npm wrapper runs `npm i -g @alibaba-group/open-code-review@<latest>` during invocations on its own. The version probe always suppresses that; `ocrNoAutoUpdate` extends the suppression to every command this package spawns (see Settings).
|
|
92
|
+
- **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`.
|
|
93
|
+
- **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.
|
|
81
94
|
|
|
82
|
-
##
|
|
95
|
+
## <img src="https://api.iconify.design/lucide/circle-check.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Verify it is live
|
|
83
96
|
|
|
84
|
-
|
|
85
|
-
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
- Deployment defaults can go on the profile's `config:` line (cordis validates and fills defaults through the exported `Config` schema); precedence: runtime settings value > line config > built-in default.
|
|
97
|
+
1. **Mounted**: `dsh --profile web --dump-config` ends with this package's layer (`# == @jayyuen666/dsh-ocr-review`).
|
|
98
|
+
- 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 `# == <layer>` and `# == <layer>, patched by <path>`.
|
|
99
|
+
2. **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.
|
|
100
|
+
3. **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).
|
|
101
|
+
4. **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.
|
|
90
102
|
|
|
91
|
-
##
|
|
103
|
+
## <img src="https://api.iconify.design/lucide/circle-slash.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> What it does not provide
|
|
92
104
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
| `ocr_delegate_preview` / `ocr_delegate_rule` | The former: repo, scope/commit/from/to, exclude 50, background. The latter: repo, paths required, 1–200 entries, positional arguments after `--`, each escaped. Zero LLM cost on the OCR side, they return only the file list and the rule groups |
|
|
99
|
-
| `ocr_session` | repo, action list/show/comments (whitelist, anything else errors), id, limit (clamped to 1–100, default 10); show/comments without an id falls back to list |
|
|
100
|
-
| Background and reaper | `wait: false` (background start, no host deadline, no time limit by default, no `--output`, poll results with `ocr_session`) and the host reaper guard apply only to these two minute-scale commands, review and scan - that subprocess is also an official job (see the job-surface section below); that change left the tool's JSON untouched (a later output-contract rework switched it to the direct canonical object, see below) |
|
|
105
|
+
- **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).
|
|
106
|
+
- **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.
|
|
107
|
+
- **No CLI subcommand.** `dsh ocr …` does not exist.
|
|
108
|
+
- **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.
|
|
109
|
+
- 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.
|
|
101
110
|
|
|
102
|
-
##
|
|
111
|
+
## <img src="https://api.iconify.design/lucide/plug.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Enabling it in dsh
|
|
103
112
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
| `autoVerify` | default true; when true, a successful `/select` also runs `ocr llm test --color never` (capped at 90 seconds) |
|
|
109
|
-
| `maxComments` | default 12, the number of comments kept in the summary; 0 = no truncation, non-integers and negatives fall back to 12 |
|
|
110
|
-
| `timeoutMinutes` | default 0 = request the host cap (the host clamps to min(request, shell.maxTimeoutMs)), >0 is a number of minutes |
|
|
111
|
-
| `ocrConfigPath` | where the external ocr CLI config lives; left empty it derives `os.homedir()/.opencodereview/config.json`, `~` and `~/` prefixes are accepted, relative paths error out. **A custom value must follow the `<X>/.opencodereview/config.json` layout**: OCR has no file-level path override (it only ever looks at `<HOME>/.opencodereview/config.json`, `config_cmd.go:92-99`), so the plugin makes it effective by injecting `HOME=<X>` into the ocr subprocess (config and sessions are redirected together, and `api_key_cmd` carries the host-resolved data directory as get-cred's explicit first-tier locator); any path outside that layout is rejected outright at the setting layer rather than silently mismatching (the HOME injection is a POSIX assignment prefix; Windows has no such channel, so custom paths only take effect on macOS/Linux) |
|
|
113
|
+
- `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.
|
|
114
|
+
- 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.
|
|
115
|
+
- The gateway exposes the methods only to clients of this profile; this package adds nothing in front of it.
|
|
116
|
+
- 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.
|
|
112
117
|
|
|
113
|
-
|
|
118
|
+
## <img src="https://api.iconify.design/lucide/hammer.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Tools exposed to the model
|
|
114
119
|
|
|
115
|
-
|
|
|
116
|
-
|
|
|
117
|
-
| `
|
|
118
|
-
|
|
|
119
|
-
| `
|
|
120
|
+
| Tool | Arguments and constraints |
|
|
121
|
+
| --- | --- |
|
|
122
|
+
| `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 |
|
|
123
|
+
| 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 |
|
|
124
|
+
| `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 |
|
|
125
|
+
| `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 |
|
|
126
|
+
| `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 |
|
|
127
|
+
| `ocr_rules_check` | repo, path (required), rule (repo-root file): print the winning rule layer, glob and body; no LLM call |
|
|
128
|
+
| `ocr_llm` | action providers/models: read-only LLM catalogue queries; no review and no connectivity test |
|
|
129
|
+
| 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 |
|
|
120
130
|
|
|
121
|
-
|
|
131
|
+
`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](#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.
|
|
122
132
|
|
|
123
|
-
|
|
124
|
-
| ------------------ | ------- | ------------------------------------------------------------------------------------------------------ |
|
|
125
|
-
| `ocrNoAutoUpdate` | `false` | When true, every `ocr` command this package spawns carries `OCR_NO_UPDATE=1`, which stops upstream's wrapper from running `npm i -g @alibaba-group/open-code-review@<latest>` in the middle of a review. `false` keeps upstream behaviour (18-minute cooldown, pid lock). The version probe sets the variable regardless of this switch |
|
|
133
|
+
## <img src="https://api.iconify.design/lucide/sliders-horizontal.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Settings
|
|
126
134
|
|
|
127
|
-
The
|
|
135
|
+
The namespace is the entry id `ocr-review`; the card fields are the `.volatile()` ones.
|
|
128
136
|
|
|
129
|
-
|
|
|
130
|
-
|
|
|
131
|
-
| `
|
|
137
|
+
| Field | Values, default, use |
|
|
138
|
+
| --- | --- |
|
|
139
|
+
| `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 |
|
|
140
|
+
| `language` | 中文/English, default 中文; written as the top-level `language` key of the CLI config when a selection is applied |
|
|
141
|
+
| `autoVerify` | default true; a successful select also runs `ocr llm test` (capped by `llmTestTimeoutMs`) |
|
|
142
|
+
| `maxComments` | default 12, the number of comments kept in the summary; 0 = no truncation, non-integers and negatives fall back to 12 |
|
|
143
|
+
| `timeoutMinutes` | default 0 = request the host cap (the host clamps to min(request, shell.maxTimeoutMs)); above 0 is a number of minutes |
|
|
144
|
+
| `ocrConfigPath` | Where the CLI config lives; empty derives `<home>/.opencodereview/config.json`, `~` and `~/` prefixes accepted, relative paths error. A custom value must follow the `<X>/.opencodereview/config.json` layout — the CLI has no file-level path override, so the plugin injects `HOME=<X>` into the subprocess (config and sessions redirect together); anything else is rejected at the setting layer. POSIX-only: Windows has no such channel |
|
|
132
145
|
|
|
133
|
-
|
|
146
|
+
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:
|
|
134
147
|
|
|
135
|
-
|
|
|
136
|
-
|
|
|
137
|
-
|
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
141
|
-
| `
|
|
142
|
-
| `
|
|
148
|
+
| Key | Default | Use |
|
|
149
|
+
| --- | --- | --- |
|
|
150
|
+
| `llmTestTimeoutMs` | `90000` | Wall clock for `ocr llm test`, shared by autoVerify and the card's test connection; raise it on slow machines |
|
|
151
|
+
| `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 |
|
|
152
|
+
| `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 |
|
|
153
|
+
| `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) |
|
|
154
|
+
| `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 |
|
|
155
|
+
| `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 |
|
|
143
156
|
|
|
144
|
-
## Public
|
|
157
|
+
## <img src="https://api.iconify.design/lucide/network.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Public interface
|
|
145
158
|
|
|
146
|
-
|
|
159
|
+
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.
|
|
147
160
|
|
|
148
161
|
| Method | Input | Success payload |
|
|
149
162
|
| --- | --- | --- |
|
|
150
|
-
| `providers()` | — | `{providers (each with a hasKey boolean), current, getCredReady, source, endpointVerify, ocrBinary}
|
|
151
|
-
| `select(request)` | `{provider, model}`, both must hit the list
|
|
163
|
+
| `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 |
|
|
164
|
+
| `select(request)` | `{provider, model}`, both must hit the list `describe()` returns | `{ok, applied, llmTest}` (`llmTest` is null when `autoVerify` is off) |
|
|
152
165
|
| `migrate()` | — | `{ok, migrated, skippedUnknown}` |
|
|
153
|
-
| `test()` | — | `{ok, output}`
|
|
154
|
-
| `probe()` | — | `{ok, version, expectedVersion, consistent, output, reason, changeText}` —
|
|
155
|
-
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
|
165
|
-
|
|
|
166
|
-
|
|
|
167
|
-
|
|
|
168
|
-
|
|
|
169
|
-
|
|
|
170
|
-
|
|
|
171
|
-
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
166
|
+
| `test()` | — | `{ok, output}` — `ok` is the **test verdict**, not whether the call ran; output is redacted first, then cut to 8000 characters |
|
|
167
|
+
| `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 |
|
|
168
|
+
| `mcp()` | — | `{ok, json}` — only the `mcp_servers` object as pretty JSON, never providers or keys |
|
|
169
|
+
| `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 |
|
|
170
|
+
| `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 |
|
|
171
|
+
| `chatGpt(request)` | `{model}` | `{ok, model}` — sets `provider: "chatgpt"` and `providers.chatgpt.model`, writes no key |
|
|
172
|
+
|
|
173
|
+
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.
|
|
174
|
+
|
|
175
|
+
## <img src="https://api.iconify.design/lucide/key-round.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Credentials and the OCR config file
|
|
176
|
+
|
|
177
|
+
| Aspect | Fact |
|
|
178
|
+
| --- | --- |
|
|
179
|
+
| What lands in the config | A command, not a secret: `api_key_cmd` = `node '<path of scripts/get-cred.mjs in this package>' '<REF>' '<dsh data dir>'` — 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 |
|
|
180
|
+
| Layer order | get-cred reads the same three layers in the same order as the official credentials-local: `refs.<REF>` in the managed credentials file first, then the inherited process environment, then `$DSH_HOME/.env`. The host treats the managed file as the effective credential, so a wrong order fails silently — OCR authenticates against a different key than the card reports while both sides show success |
|
|
181
|
+
| Reachability gate | select and migrate refuse a key the CLI cannot read at runtime: dsh strips `KEY`/`PASSWORD`/`SECRET`/`TOKEN`-named and `DSH_`-prefixed variables from spawned shells, and the reviewed repository's `.env` is deliberately not read. Keys living only in the dsh process environment or the workspace `.env` resolve on the host side yet fail at OCR runtime — previously a silent "select succeeded, OCR 401". The refusal happens before any write and names the fix |
|
|
182
|
+
| Writes | Atomic replacement through the official atomic-write helper: a random-suffixed temp sibling, then rename, with 0600 on the new inode; the `.bak` backup is copied by this package before the rename. The whole read→decide→write span runs under the official file lock — a short-lived `config.json.lock` appears and is removed in `finally`. An existing config that is invalid JSON or not an object aborts the write instead of being treated as empty |
|
|
183
|
+
| `migrate()` | Replaces the plaintext `api_key` of custom provider entries with `api_key_cmd` and deletes the plaintext; entries whose key source is unreachable at CLI runtime (same gate as select) or whose `apiKeyEnv` is missing from settings are left untouched and listed in `skippedUnknown`; nothing is written when nothing migrated |
|
|
184
|
+
| `scripts/get-cred.mjs` | Spawned by the CLI itself; the only place in this package that still reads host credential files. When all three layers miss it exits non-zero and names every location it checked |
|
|
185
|
+
|
|
186
|
+
## <img src="https://api.iconify.design/lucide/hourglass.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Background runs as host jobs
|
|
187
|
+
|
|
188
|
+
| Aspect | Fact |
|
|
189
|
+
| --- | --- |
|
|
190
|
+
| What the model gains | `job_list` shows the review, `job_output` reads its stdout/stderr (non-consuming — it never steals bytes from the session reader), `job_kill` stops it. Results still come from the `ocr_session` record |
|
|
191
|
+
| No registry, no background | A host without the jobs service makes `wait: false` throw an error naming the missing piece instead of starting a subprocess nobody can observe; foreground runs are unaffected |
|
|
192
|
+
| The shared bucket | The unowned job slots are shared host-wide and overflow refuses new starts, so one hung CLI parks a slot forever. That is what the optional `ocrBackgroundMaxMs` backstop is for — it uses the registry's own wait + kill, not a host timer |
|
|
193
|
+
| Cleanup | Settled records of this kind are pruned down to a fixed window before each start; live and stopping records are never pruned (the registry answers overflow by refusing, not by killing somebody's review). At unload, live jobs are killed through the registry and settled ones removed |
|
|
194
|
+
| Exposure | Unowned means any caller can list / read / kill it. Known and accepted for this adoption; closing it would mean rolling the job registration back |
|
|
195
|
+
| Nothing may throw | An error raised inside a host event handler takes the whole host process down, and the official package does not cover producers either — the cancel chain catches at two layers, each pinned by its own test |
|
|
196
|
+
|
|
197
|
+
## <img src="https://api.iconify.design/lucide/lock.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Data and privacy
|
|
198
|
+
|
|
199
|
+
| Aspect | Fact |
|
|
200
|
+
| --- | --- |
|
|
201
|
+
| Process boundary | The reviewed repository's paths and diff content go into the `ocr` child process, and the CLI sends them to the LLM endpoint configured in its own config.json — that egress is the review feature itself and does not pass through the dsh host. Plaintext keys never appear in tool results or Remote payloads; a value resolved by the host is used only to confirm "it resolves right now" and is discarded on the spot |
|
|
202
|
+
| Redaction | All raw CLI stdout/stderr leaving this package (failure details, llm test, parse-failure echoes, session passthrough) goes through `redactKeyMaterial()`: bearer and assignment forms strictly, the standalone-key form only with random-string features, so file paths handed to the model are not erased with it |
|
|
203
|
+
| On disk | Two places only: the CLI's `config.json` (plus its temp sibling, the `.bak` backup and the short-lived lock) and `ocr-review-*` / `ocr-scan-*` temp files, deleted right after reading. Pre-migration plaintext may persist in that `.bak` (mode 0600) — delete the backup yourself to be rid of it. Outside these two, the only writer is the CLI's own npm wrapper unless `ocrNoAutoUpdate` is on |
|
|
204
|
+
|
|
205
|
+
## <img src="https://api.iconify.design/lucide/triangle-alert.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Known boundaries
|
|
206
|
+
|
|
207
|
+
- **Windows gets no reaper and no custom config path.** Both rest on a POSIX toolchain (bash traps, process-group semantics, the `HOME=` assignment prefix); on Windows the package runs the bare command un-reaped, and a hard host exit can leave an orphaned OCR process.
|
|
208
|
+
- **A version mismatch is never a hard failure.** A newer CLI usually works; both numbers are shown, the badge flips, and the summary's `hint` says so. This package never runs an installer.
|
|
209
|
+
- **Version reads cost a spawn** and never happen on render; the card shows the last measurement or nothing — a page render cannot install, update or probe anything.
|
|
210
|
+
- **The atomic-write lock timeout surfaces in English.** Contention past the official wait budget throws the official package's own timeout text — the one user-visible error here that bypasses the bilingual message table.
|
|
211
|
+
- **`partial` is a terminal success state.** Some files failed but coverage stands and the comments are valid; it is neither upgraded to success nor folded into failure.
|
|
212
|
+
- **Unowned background jobs are visible to every session** (see Background runs) — accepted with the adoption, and the only way to close it is to roll the registration back.
|
|
213
|
+
|
|
214
|
+
## <img src="https://api.iconify.design/lucide/circle-help.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> FAQ
|
|
215
|
+
|
|
216
|
+
| Question / symptom | Answer |
|
|
217
|
+
| --- | --- |
|
|
218
|
+
| Exit 0 was taken as a successful review | Exit 0 is not success: any `status` outside success / complete / partial / completed_with_warnings / completed_with_errors / skipped reports as failure. `partial` means some files failed while coverage and the comments stand. A parse failure echoes the first 500 redacted UTF-16 code units of the raw output |
|
|
219
|
+
| Did the summary cover everything? | Two notions, neither replaces the other. `droppedCount` / `invalidCommentCount` say whether **this summary** listed every comment (raise `maxComments` or set 0 and re-check). Whether **the run** covered the selected files is `coverage` (`selected` is the denominator, `failedItems` names the files with their reasons) plus `budgetExceeded` and `toolCalls` |
|
|
220
|
+
| `add` was refused with `already-installed` | HMR profiles refuse `add` on an already-installed package before pnpm runs — `remove` first, then `add` the target version (see Install) |
|
|
221
|
+
| What happens to background processes when dsh exits? | The reaper guard takes the review/scan process tree down with TERM→1s→KILL on any host exit (including kill -9 followed by adoption); cancelling the tool call stops the corresponding background process |
|
|
222
|
+
| Where might a plaintext key still sit? | Nowhere this package writes. A pre-migration config's plaintext may survive in the `.bak` produced by the first write after you migrate (mode 0600); delete that backup to be rid of it |
|
|
223
|
+
|
|
224
|
+
## <img src="https://api.iconify.design/lucide/wrench.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> Development
|
|
176
225
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
| Places written | This package writes to only two places: the external ocr CLI's `config.json` (plus its random-suffixed `.tmp`, the `.bak` backup, and the short-lived `config.json.lock` held only for the duration of one read-modify-write) and `ocr-review-*`/`ocr-scan-*` in the system temp directory (removed right after reading). Plaintext that predates a migration may still sit in the `.bak` produced by that same write (mode 0600); removing it for good means deleting that backup yourself. Writes outside those two places can still happen **inside the CLI's own wrapper**: unless `ocrNoAutoUpdate` is on, an `ocr` invocation may trigger upstream's global `npm i -g` of a newer release. This package never runs an installer itself, and the version probe always suppresses that path |
|
|
182
|
-
|
|
183
|
-
## FAQ
|
|
184
|
-
|
|
185
|
-
| Symptom | Verdict |
|
|
186
|
-
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
187
|
-
| Exit 0 taken for a successful review | Exit 0 is not a successful review: any `status` outside success/complete/**partial**/completed_with_warnings/completed_with_errors/skipped is reported as failure (`{"error": …}` is of that kind; partial is one of the manifest terminal states — some files failed but coverage remains, and the comments still stand), and a parse failure echoes the first 500 redacted UTF-16 code units of the raw output (an emoji takes 2) |
|
|
188
|
-
| Whether the summary covered everything | Two different notions, neither replaces the other: `droppedCount` and `invalidCommentCount` only say whether *this summary* listed every comment OCR produced (raise `maxComments`, or set 0, and re-check). Whether *the run* covered everything is in `coverage` (`selected` is the denominator, `completed`/`reused`/`failed`/`waived` partition it, `failedItems` names the files with their classification and reason) plus `budgetExceeded` (the token budget stopped it early — `status` still only says `partial`); `toolCalls` reports how many of the model's tool calls failed. stdout has its own 400000-byte cap, which is why review results always go through the `--output` temp file |
|
|
189
|
-
| Background processes when dsh exits | When dsh exits in any way (including kill -9 followed by launchd adoption), the OCR process tree of review/scan is taken down by the reaper guard inside the command with TERM→1s→KILL; cancelling the tool call terminates the corresponding background process too |
|
|
190
|
-
|
|
191
|
-
## Development
|
|
226
|
+
```sh
|
|
227
|
+
pnpm install
|
|
228
|
+
pnpm run check # typecheck (both sides) → lint → client build → host build → tests with coverage → format
|
|
229
|
+
```
|
|
192
230
|
|
|
193
|
-
- `
|
|
194
|
-
-
|
|
195
|
-
-
|
|
231
|
+
- Coverage thresholds are **100%** across four metrics over the surface `vitest.config.ts` opts into (`coverageInclude`: `host.ts` + `lib/**`); two areas are deliberately outside it: the client half (`src/**`) is exercised through the bundle and the settings-card tests, and `scripts/get-cred.mjs` is driven by process-level tests spawning a child (v8 coverage cannot see a child process) — its behavioral gate lives in `test/get-cred.test.ts`.
|
|
232
|
+
- Adding this package's **local directory** to a profile (`pnpm add <path>`) needs pnpm ≥ 12.3.0: earlier pnpm 12 releases resolve an unaliased local path as a registry package name (upstream #14437, fixed in 12.3.0). Installing from the registry is unaffected.
|
|
233
|
+
- Bundle freshness is pinned by `test/build-host.test.ts`: a stale `host.js` / `client.js` fails the suite. Never hand-edit the bundles; `prepack` rebuilds them.
|
|
196
234
|
- The plugin source is TypeScript loaded directly by the host's cordis loader (Node ≥ 22.18 type stripping); the bundles exist for the published npm surface.
|
|
197
235
|
|
|
198
|
-
## License
|
|
236
|
+
## <img src="https://api.iconify.design/lucide/scale.svg?color=%23658EFF&height=22" height="22" width="22" align="absmiddle" alt="" /> License
|
|
237
|
+
|
|
238
|
+
MIT. Source at [github.com/JayYuen666/dsh-ocr-review](https://github.com/JayYuen666/dsh-ocr-review). Issues go to that repository's tracker.
|
|
199
239
|
|
|
200
|
-
|
|
240
|
+
Runtime dependencies carry their own licenses; [`THIRD_PARTY_NOTICES.md`](./THIRD_PARTY_NOTICES.md) records that artifact boundary and how to re-check it.
|