cursor-route 0.1.7 → 0.1.9

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 CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ _(none)_
6
+
7
+ ## 0.1.9 — 2026-08-18
8
+
9
+ - Health proves mid DeepSeek: dedicated `lane:mid` check is ✓ only when mid is a `claude-ds` / `deepseek-claude` shim or stock `claude` with DeepSeek `ANTHROPIC_BASE_URL`. `CURSOR_ROUTE_ALLOW_ANTHROPIC=1` is not proof. Overall `ok` still uses the existing OR-gate (any worker + `CURSOR_ROUTE_RELAXED=1`); grok-only installs stay OK with `lane:mid` ✗ plus a fix tip.
10
+ - Health JSON includes `lanes.mid.{ worker, deepseek, detail }` (`worker` is always `"claude-ds"`).
11
+ - `status --json` adds an evidence tree (`spawn` / `execute` / `verify`). `verify.claim` is always `"unverified"` — the parent must capture; status never auto-greens.
12
+ - Headless pid liveness: if `ps` is unreadable (`EPERM` / non-zero), treat the pid as **alive** (fail-safe), not dead — avoids false-completed kills on sandboxed `ps`.
13
+ - `route-orch` skill: health-before-mid, evidence-tree closeout, always-approve is coding-worktrees-only (not LIVE Discord/trading), do not fork the mid harness; `--worker deepseek` stays experimental / cheap-to-abandon.
14
+ - Docs: `route-orch` **Eval & skill hygiene** (folded from Unreleased) — external eval contract (AutoDesign): never rewrite Verify / Success criteria mid-run to make a failing job look green (capture + exit status are the contract); skill misevolution: no auto-edit / promote skill variants from worker trajectories without operator HITL (write-time approval ≠ safe retrieval later); verify fail → prefer reconsidering the plan/definition over grinding the same tactic, with stage attribution when possible (spawn vs execute vs verify). Mid stays `claude-ds`.
15
+
16
+ ## 0.1.8 — 2026-08-14
17
+
18
+ - Experimental `--worker deepseek`: official DeepSeek Harness (`dsh`, `@deepseek-ai/dsh`) runs headless — `dsh --profile headless` with a per-job Cordis patch pinning the model. Not a mid default: `laneWorkers.mid` stays `claude-ds`.
19
+ - `--model flash|pro` (and `deepseek-v4-pro[1m]`) now applies to both `claude-ds` and `deepseek`; patch sets `agent-default-model` → `provider: deepseek-official` (never rewrites `~/.dsh/settings.yaml`, so parallel jobs don't race).
20
+ - Always-approve → `DSH_PERMISSION_MODE=danger-full-access`; `--ask` → `workspace-write`. `DEEPSEEK_API_KEY` travels via env only — never in the launch command or patch.
21
+ - Health: `worker:deepseek` ✓ only when `dsh` is on PATH (override `CURSOR_ROUTE_DSH_BIN`) **and** `DEEPSEEK_API_KEY` is set.
22
+ - Dry-run for `--worker deepseek` works without `dsh` installed (falls back to plain `dsh` in the printed command); real starts stay gated by health.
23
+ - `clean` also removes leftover `.dsh-patch.yml` files; patch path never overwrites a non-`.prompt` prompt file.
24
+
3
25
  ## 0.1.7 — 2026-08-12
4
26
 
5
27
  - Fix: `CURSOR_ROUTE_DS_MODEL` / `ANTHROPIC_MODEL` now apply on the `start` product path (live default)
package/CONTRIBUTING.md CHANGED
@@ -11,6 +11,7 @@ CURSOR_ROUTE_RELAXED=1 ./bin/cursor-route health # without tmux
11
11
  ./bin/cursor-route start --dry-run --worker grok "ping"
12
12
  ./bin/cursor-route start --dry-run --lane mid --model flash "ping"
13
13
  ./bin/cursor-route start --dry-run --lane mid --model pro "ping"
14
+ ./bin/cursor-route start --dry-run --worker deepseek --model flash "ping" # needs dsh + DEEPSEEK_API_KEY for real starts
14
15
  ```
15
16
 
16
17
  Keep `skills/route-orch/SKILL.md` and `.cursor/skills/route-orch/SKILL.md` identical when editing the skill.
package/README.md CHANGED
@@ -91,10 +91,10 @@ Working notes for this repo (edit in place): [docs/briefs/WORKING.md](./docs/bri
91
91
 
92
92
  | Command | Description |
93
93
  |---------|-------------|
94
- | `health` | Gate #1 — tmux, runtime, workers |
94
+ | `health` | Gate #1 — tmux, runtime, workers; proves `lane:mid` is DeepSeek (`lanes.mid` in `--json`) |
95
95
  | `start <prompt>` | Spawn worker job (`--worker` / `--lane` / `--model` / `--dir` / `--ask` / `--dry-run` / `--no-tmux`) |
96
96
  | `jobs [--json]` | List jobs |
97
- | `status <id>` | Job + sessionAlive |
97
+ | `status <id>` | Job + sessionAlive; `--json` includes evidence tree (`spawn` / `execute` / `verify`) |
98
98
  | `capture <id> [n]` | Last n pane/log lines |
99
99
  | `send <id> <msg>` | Mid-task redirect (tmux send-keys) |
100
100
  | `attach <id>` | Print `tmux attach` hint |
@@ -102,6 +102,36 @@ Working notes for this repo (edit in place): [docs/briefs/WORKING.md](./docs/bri
102
102
  | `sessions` | Managed tmux sessions |
103
103
  | `clean [--days N]` | Drop old job files |
104
104
 
105
+ ## Demo
106
+
107
+ A dry-run capture (no live workers, no secrets) is committed as a reproducible fixture:
108
+
109
+ ```bash
110
+ # Needs Bun or Node 20+ and a runnable ./bin/cursor-route (clone: bun install)
111
+ docs/fixtures/generate-hero-demo.sh # regenerate docs/fixtures/hero-demo.log
112
+ ```
113
+
114
+ ```text
115
+ $ cursor-route --version
116
+ 0.1.9
117
+
118
+ $ CURSOR_ROUTE_RELAXED=1 cursor-route health
119
+ cursor-route v0.1.9
120
+ health: OK
121
+
122
+ $ cursor-route start --lane mid --model flash --dry-run "Add a unit test for shellQuote"
123
+ dry-run job a1b2c3d4
124
+ worker: claude-ds
125
+ model: flash
126
+ command: cd '~/Projects/cursor-route' && '~/.local/bin/claude-ds' -PromptFile '~/.local/share/cursor-route/jobs/a1b2c3d4.prompt' -Model 'deepseek-v4-flash' --dangerously-skip-permissions
127
+
128
+ $ cursor-route jobs --json
129
+ []
130
+ ```
131
+
132
+ Full capture: [docs/fixtures/hero-demo.log](./docs/fixtures/hero-demo.log).
133
+ A real hero GIF is still pending — recording steps live in [docs/DEMO_GIF.md](./docs/DEMO_GIF.md).
134
+
105
135
  ## Lanes
106
136
 
107
137
  | Lane | Worker | Intent |
@@ -110,7 +140,7 @@ Working notes for this repo (edit in place): [docs/briefs/WORKING.md](./docs/bri
110
140
  | `mid` | `claude-ds` | Default implement on DeepSeek (**Flash** by default) |
111
141
  | `hard` | `grok` | Hard implement on Grok CLI |
112
142
 
113
- Always-approve is **on** by default. Opt out: `--ask` or `CURSOR_ROUTE_ASK=1`.
143
+ Always-approve is **on** by default for **coding worktrees only**. It does not authorize LIVE Discord, trading, or irreversible SaaS. Opt out: `--ask` or `CURSOR_ROUTE_ASK=1`.
114
144
 
115
145
  ### Mid models (Flash vs Pro)
116
146
 
@@ -133,7 +163,7 @@ export CURSOR_ROUTE_DS_MODEL=pro # also honors ANTHROPIC_MODEL; --model overri
133
163
 
134
164
  ## DeepSeek setup (the cheap mid-lane — this is the point)
135
165
 
136
- `cursor-route` mid lane runs **DeepSeek**, not Anthropic. Claude Code is only the terminal harness; bills go to DeepSeek when `ANTHROPIC_BASE_URL` points at them.
166
+ `cursor-route` mid lane runs **DeepSeek**, not Anthropic. Claude Code is only the terminal harness; bills go to DeepSeek when `ANTHROPIC_BASE_URL` points at them. `cursor-route health` shows `lane:mid` ✓ only when DeepSeek is proven (`claude-ds` / `deepseek-claude` shim, or stock `claude` with DeepSeek `ANTHROPIC_BASE_URL`) — `CURSOR_ROUTE_ALLOW_ANTHROPIC=1` is not proof.
137
167
 
138
168
  **Recommended (official DeepSeek → Claude Code):**
139
169
 
@@ -146,7 +176,7 @@ export ANTHROPIC_AUTH_TOKEN=YOUR_DEEPSEEK_API_KEY # from platform.deepseek.com
146
176
  export ANTHROPIC_DEFAULT_HAIKU_MODEL=deepseek-v4-flash
147
177
  export CLAUDE_CODE_SUBAGENT_MODEL=deepseek-v4-flash
148
178
 
149
- cursor-route health # worker:claude-ds should be ✓
179
+ cursor-route health # worker:claude-ds should be ✓; `lane:mid` is only when DeepSeek is proven
150
180
  cursor-route start --lane mid "…" # Flash (default)
151
181
  cursor-route start --lane mid --model pro "…" # Pro when you need it
152
182
  ```
@@ -155,7 +185,18 @@ Persist the same vars under `~/.claude/settings.json` → `"env": { … }` if yo
155
185
 
156
186
  **Also accepted:** `claude-ds` or `deepseek-claude` on PATH (Cemini shims). The adapter passes `-Model deepseek-v4-flash|deepseek-v4-pro`.
157
187
 
158
- **Reserved:** `--worker deepseek` is a slot for an official DeepSeek coding harness not wired yet. Mid stays on `claude-ds`.
188
+ **Experimental:** `--worker deepseek` runs the official DeepSeek Harness (`dsh`, npm `@deepseek-ai/dsh`) as an opt-in worker **mid stays on `claude-ds`**; don't default lanes here.
189
+
190
+ ```bash
191
+ npm i -g @deepseek-ai/dsh
192
+ export DEEPSEEK_API_KEY=YOUR_DEEPSEEK_API_KEY # from platform.deepseek.com
193
+
194
+ cursor-route health # worker:deepseek should be ✓
195
+ cursor-route start --worker deepseek "…" # Flash (default)
196
+ cursor-route start --worker deepseek --model pro "…" # Pro when you need it
197
+ ```
198
+
199
+ The adapter launches `dsh --profile headless` with a **per-job Cordis patch** (`jobs/<id>.dsh-patch.yml`, mode 0600) that pins `--model flash|pro` (`deepseek-v4-pro[1m]` preserved) — it never rewrites `~/.dsh/settings.yaml`, so parallel jobs don't race. Always-approve maps to `DSH_PERMISSION_MODE=danger-full-access`; `--ask` drops to `workspace-write`. Your `DEEPSEEK_API_KEY` travels via env only — never in the launch command or patch. Override the binary with `CURSOR_ROUTE_DSH_BIN`.
159
200
 
160
201
  **Not the default:** bare `claude` still talking to Anthropic. Health refuses that so a misconfigured install cannot silently burn frontier $ rates. Escape hatch only: `CURSOR_ROUTE_ALLOW_ANTHROPIC=1`.
161
202
 
@@ -235,6 +276,7 @@ If you’d like to tip, use the **donation-only** addresses below (not trading o
235
276
 
236
277
  | Chain family | Address |
237
278
  |--------------|---------|
279
+ | **X Money** (fiat, US) | Request [@Cemini23](https://x.com/Cemini23) in the X app — scan the Request QR |
238
280
  | **EVM** (Ethereum, Polygon, Base, Arbitrum, …) | `0x444C5C2eC439E0382aa5a17F70313c536BcC5D58` |
239
281
  | **Solana / SVM** | `J4zNn4hK9jTrKBFY8sbAGJHLoZvXvQf4B9pQSbSrocZE` |
240
282
  | **Polymarket** (referral) | [polymarket.com/?r=Cemini23](https://polymarket.com/?r=Cemini23) |
@@ -249,8 +291,8 @@ MIT © Cemini — see [LICENSE](LICENSE).
249
291
 
250
292
  ## Roadmap (explicitly later)
251
293
 
252
- - Homebrew tap
253
- - Native DeepSeek harness adapter
254
- - Codebase map injection (`--map`)
255
- - Cursor CLI `agent` as alternate supervisor
294
+ - Homebrew tap
295
+ - Stabilize the DeepSeek harness adapter (experimental `--worker deepseek` since 0.1.8)
296
+ - Codebase map injection (`--map`)
297
+ - Cursor CLI `agent` as alternate supervisor
256
298
  - Web UI / CAO-style MCP supervisor
package/SUPPORT.md CHANGED
@@ -6,6 +6,7 @@ Voluntary tips fund wikis, OSS tooling, and public research. **Donation-only add
6
6
 
7
7
  | Chain family | Address |
8
8
  |--------------|---------|
9
+ | **X Money** (fiat, US) | Request [@Cemini23](https://x.com/Cemini23) in the X app — scan the Request QR |
9
10
  | **EVM** (Ethereum, Polygon, Base, Arbitrum, …) | `0x444C5C2eC439E0382aa5a17F70313c536BcC5D58` |
10
11
  | **Solana / SVM** | `J4zNn4hK9jTrKBFY8sbAGJHLoZvXvQf4B9pQSbSrocZE` |
11
12
  | **Polymarket** (referral) | [polymarket.com/?r=Cemini23](https://polymarket.com/?r=Cemini23) |
@@ -108,6 +108,24 @@ function resolveClaudeDs() {
108
108
  }
109
109
  return null;
110
110
  }
111
+ /**
112
+ * Mid lane is proven DeepSeek only via shim (`claude-ds` / `deepseek-claude`)
113
+ * or stock `claude` routed to DeepSeek. The Anthropic escape hatch is not proof.
114
+ */
115
+ export function isMidDeepSeekProven() {
116
+ const resolved = resolveClaudeDs();
117
+ if (!resolved)
118
+ return false;
119
+ return !resolved.mode.startsWith("claude → Anthropic");
120
+ }
121
+ /** Human detail for health `lane:mid` (mode when proven; install hint otherwise). */
122
+ export function midDeepSeekProofDetail() {
123
+ const resolved = resolveClaudeDs();
124
+ if (!resolved || resolved.mode.startsWith("claude → Anthropic")) {
125
+ return "mid not proven DeepSeek — install claude-ds / set ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic. CURSOR_ROUTE_ALLOW_ANTHROPIC=1 is not proof.";
126
+ }
127
+ return `DeepSeek proven (${resolved.mode})`;
128
+ }
111
129
  function pickModel(requested, modelId) {
112
130
  if (modelId) {
113
131
  const alias = requested ?? resolveDsModel(modelId).alias;
@@ -1,19 +1,138 @@
1
+ import { execSync } from "node:child_process";
2
+ import { existsSync, writeFileSync, unlinkSync } from "node:fs";
3
+ import { shellQuote } from "../util.js";
4
+ import { DS_MODEL_IDS, resolveDsModel, } from "../config.js";
1
5
  /**
2
- * Reserved slot for the official DeepSeek coding harness when it ships.
3
- * Mid lane stays on claude-ds until then do not route jobs here.
6
+ * Experimental official DeepSeek Harness (`dsh`, npm @deepseek-ai/dsh) as a
7
+ * coding worker `dsh --profile headless` with a per-job Cordis patch that
8
+ * pins the model. Mid lane stays on claude-ds; this is an opt-in worker only
9
+ * (`--worker deepseek`), not a mid replacement.
10
+ *
11
+ * We never write ~/.dsh/settings.yaml (parallel jobs would race) and never
12
+ * put DEEPSEEK_API_KEY in the command or patch — the key travels via plan.env.
4
13
  */
14
+ function findDsh() {
15
+ // Env override lets tests pin a fake dsh — but it must exist, so a stale
16
+ // override cannot pass health with a dangling path.
17
+ const override = process.env.CURSOR_ROUTE_DSH_BIN;
18
+ if (override)
19
+ return existsSync(override) ? override : null;
20
+ try {
21
+ return (execSync("command -v dsh", {
22
+ encoding: "utf8",
23
+ stdio: ["ignore", "pipe", "ignore"],
24
+ // Bun may ignore mutated process.env.PATH unless env is passed explicitly
25
+ env: { ...process.env },
26
+ }).trim() || null);
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ }
32
+ /** Same resolution as claude-ds: passed model/modelId wins, else env, else Flash. */
33
+ function pickModel(requested, modelId) {
34
+ if (modelId) {
35
+ const alias = requested ?? resolveDsModel(modelId).alias;
36
+ return { alias, id: modelId };
37
+ }
38
+ if (requested) {
39
+ return { alias: requested, id: DS_MODEL_IDS[requested] };
40
+ }
41
+ // Env default (startJob normally resolves this; kept for direct buildLaunch callers)
42
+ return resolveDsModel(process.env.CURSOR_ROUTE_DS_MODEL || process.env.ANTHROPIC_MODEL);
43
+ }
44
+ /** Whitelist model ids before interpolating into YAML (no newlines / injection). */
45
+ function assertPatchModelId(modelId) {
46
+ if (!/^[a-z0-9][a-z0-9.\-[\]]*$/i.test(modelId)) {
47
+ throw new Error(`Invalid DeepSeek model id for dsh patch: ${modelId}`);
48
+ }
49
+ return modelId;
50
+ }
51
+ /** Per-job Cordis patch path. Never reuse the prompt path (would overwrite it). */
52
+ export function patchPathForPrompt(promptFile) {
53
+ return promptFile.endsWith(".prompt")
54
+ ? promptFile.replace(/\.prompt$/, ".dsh-patch.yml")
55
+ : `${promptFile}.dsh-patch.yml`;
56
+ }
57
+ /** Per-job Cordis patch (whole-row replace). `name` is required or dsh silently skips. */
58
+ function patchYaml(modelId) {
59
+ const id = assertPatchModelId(modelId);
60
+ return [
61
+ "- id: agent-default-model",
62
+ " name: '@deepseek-ai/dsh-agent-default-model'",
63
+ " config:",
64
+ " provider: deepseek-official",
65
+ ` model: '${id}'`,
66
+ ].join("\n") + "\n";
67
+ }
5
68
  export const deepseekAdapter = {
6
69
  kind: "deepseek",
7
- label: "Official DeepSeek harness (unreleased)",
70
+ label: "Official DeepSeek Harness (dsh)",
8
71
  health() {
72
+ const binary = findDsh();
73
+ if (!binary) {
74
+ return {
75
+ worker: "deepseek",
76
+ ok: false,
77
+ binary: null,
78
+ detail: "dsh (@deepseek-ai/dsh) not found — install: npm i -g @deepseek-ai/dsh. Mid default remains claude-ds.",
79
+ };
80
+ }
81
+ if (!process.env.DEEPSEEK_API_KEY) {
82
+ return {
83
+ worker: "deepseek",
84
+ ok: false,
85
+ binary,
86
+ detail: "DEEPSEEK_API_KEY not set — export your DeepSeek API key to use dsh (@deepseek-ai/dsh). Mid default remains claude-ds.",
87
+ };
88
+ }
9
89
  return {
10
90
  worker: "deepseek",
11
- ok: false,
12
- binary: null,
13
- detail: "unreleased mid lane uses claude-ds (DeepSeek behind Claude Code). See README.",
91
+ ok: true,
92
+ binary,
93
+ detail: "ok (dsh @deepseek-ai/dsh headless; mid default remains claude-ds)",
14
94
  };
15
95
  },
16
- buildLaunch() {
17
- throw new Error("Official DeepSeek harness is not available yet use --lane mid / --worker claude-ds");
96
+ buildLaunch({ promptFile, cwd, alwaysApprove, model, modelId, dryRun }) {
97
+ // Missing dsh is tolerated here so `--dry-run` can still print the command;
98
+ // real starts are gated by the health preflight (binary + DEEPSEEK_API_KEY).
99
+ const binary = findDsh() || "dsh";
100
+ const choice = pickModel(model, modelId);
101
+ // Per-job patch next to the prompt file (never touch ~/.dsh/settings.yaml).
102
+ const patchFile = patchPathForPrompt(promptFile);
103
+ writeFileSync(patchFile, patchYaml(choice.id), { mode: 0o600 });
104
+ if (dryRun) {
105
+ // Dry-run keeps no durable artifacts (jobs.ts removes the prompt likewise).
106
+ try {
107
+ unlinkSync(patchFile);
108
+ }
109
+ catch {
110
+ /* ignore */
111
+ }
112
+ }
113
+ // Launcher flags before the task; prompt inlined via cat (never the key).
114
+ const parts = [
115
+ shellQuote(binary),
116
+ "--profile",
117
+ "headless",
118
+ "--patch",
119
+ shellQuote(patchFile),
120
+ `"$(cat ${shellQuote(promptFile)})"`,
121
+ ];
122
+ const ask = process.env.CURSOR_ROUTE_ASK === "1";
123
+ const skip = alwaysApprove && !ask;
124
+ const env = {
125
+ DSH_PERMISSION_MODE: skip ? "danger-full-access" : "workspace-write",
126
+ };
127
+ // Key travels via env only — never interpolated into command or patch.
128
+ if (process.env.DEEPSEEK_API_KEY) {
129
+ env.DEEPSEEK_API_KEY = process.env.DEEPSEEK_API_KEY;
130
+ }
131
+ return {
132
+ worker: "deepseek",
133
+ command: `cd ${shellQuote(cwd)} && ${parts.join(" ")}`,
134
+ alwaysApprove: skip,
135
+ env,
136
+ };
18
137
  },
19
138
  };
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import { readFileSync, existsSync, realpathSync, statSync } from "node:fs";
6
6
  import { resolve, basename } from "node:path";
7
7
  import { config, WORKERS, LANES, resolveDsModel, } from "./config.js";
8
8
  import { runHealth, printHealth } from "./health.js";
9
- import { startJob, listJobs, readJob, killJob, cleanJobs, jobPaths, refreshStatus, } from "./jobs.js";
9
+ import { startJob, listJobs, readJob, killJob, cleanJobs, jobPaths, refreshStatus, jobEvidence, } from "./jobs.js";
10
10
  import { capturePane, sendKeys, attachHint, listManagedSessions, sessionExists, } from "./tmux.js";
11
11
  import { looksLikeSecretMaterial, redactSecrets } from "./secrets.js";
12
12
  function usage(exitCode = 0) {
@@ -16,11 +16,11 @@ Cursor stays the brain. Grok CLI + DeepSeek (claude-ds) + OpenRouter (easy) are
16
16
 
17
17
  Usage:
18
18
  cursor-route --version
19
- cursor-route health [--json]
19
+ cursor-route health [--json] # JSON includes lanes.mid (DeepSeek proof)
20
20
  cursor-route start <prompt> [options]
21
21
  cursor-route start --prompt-file <path> [options]
22
22
  cursor-route jobs [--json] [--limit N]
23
- cursor-route status <jobId> [--json]
23
+ cursor-route status <jobId> [--json] # JSON includes evidence spawn/execute/verify
24
24
  cursor-route capture <jobId> [lines]
25
25
  cursor-route send <jobId> <message>
26
26
  cursor-route attach <jobId>
@@ -29,9 +29,9 @@ Usage:
29
29
  cursor-route clean [--days N]
30
30
 
31
31
  Start options:
32
- --worker <grok|claude-ds|openrouter> Worker adapter (default: grok; deepseek = reserved slot)
32
+ --worker <grok|claude-ds|openrouter|deepseek> Worker adapter (default: grok; deepseek = experimental official dsh)
33
33
  --lane <easy|mid|hard> Lane → worker (easy=openrouter, mid=claude-ds, hard=grok)
34
- --model <flash|pro> Mid DeepSeek only (default: flash / CURSOR_ROUTE_DS_MODEL). Parsed only for claude-ds
34
+ --model <flash|pro> Mid DeepSeek only (default: flash / CURSOR_ROUTE_DS_MODEL). Parsed for claude-ds + deepseek
35
35
  --dir <path> Working directory (default: cwd)
36
36
  --ask Disable always-approve for this job
37
37
  --dry-run Print launch command; do not start
@@ -47,6 +47,8 @@ Env:
47
47
  CURSOR_ROUTE_DS_MODEL Default mid model flash|pro (or deepseek-v4-pro[1m]); overridden by --model
48
48
  CURSOR_ROUTE_GROK_BIN Override the grok binary path (tests / power users)
49
49
  CURSOR_ROUTE_CLAUDE_DS_BIN Override the claude-ds binary path (tests / power users)
50
+ CURSOR_ROUTE_DSH_BIN Override the dsh binary path (tests / power users)
51
+ DEEPSEEK_API_KEY DeepSeek API key (required for --worker deepseek)
50
52
  OPENROUTER_API_KEY OpenRouter key (required for --worker openrouter / --lane easy)
51
53
  CURSOR_ROUTE_OPENROUTER_MODEL OpenRouter model (default: openrouter/free)
52
54
  OPENROUTER_BASE_URL OpenRouter API base (default: https://openrouter.ai/api/v1)
@@ -221,8 +223,9 @@ async function main() {
221
223
  const resolvedWorker = worker ?? (lane ? config.laneWorkers[lane] : config.defaultWorker);
222
224
  let model;
223
225
  let modelId;
224
- // --model is DeepSeek/claude-ds only; ignore (do not validate) for other workers
225
- if (f.model !== undefined && resolvedWorker === "claude-ds") {
226
+ // --model is DeepSeek-only (claude-ds mid lane + experimental deepseek worker);
227
+ // ignore (do not validate) for other workers
228
+ if (f.model !== undefined && (resolvedWorker === "claude-ds" || resolvedWorker === "deepseek")) {
226
229
  try {
227
230
  const choice = asDsModelChoice(f.model);
228
231
  if (choice) {
@@ -353,11 +356,13 @@ async function main() {
353
356
  }
354
357
  })())
355
358
  : sessionExists(job.tmuxSession);
356
- const view = { ...job, sessionAlive: alive };
359
+ const evidence = jobEvidence(job, alive);
360
+ const view = { ...job, sessionAlive: alive, evidence };
357
361
  if (json)
358
362
  console.log(JSON.stringify(view, null, 2));
359
363
  else {
360
364
  console.log(`${job.id} ${job.status} worker=${job.worker}${job.model ? ` model=${job.model}` : ""} sessionAlive=${alive}`);
365
+ console.log("evidence: spawn/execute/verify (claim=unverified)");
361
366
  if (job.error)
362
367
  console.log(`error: ${job.error}`);
363
368
  }
package/dist/config.js CHANGED
@@ -65,13 +65,16 @@ function maxConcurrentJobsFromEnv() {
65
65
  */
66
66
  export const config = {
67
67
  product: "cursor-route",
68
- version: "0.1.7",
68
+ version: "0.1.9",
69
69
  get jobsDir() {
70
70
  return defaultJobsDir();
71
71
  },
72
72
  tmuxPrefix: "cursor-route",
73
73
  defaultWorker: "grok",
74
- /** Lane → default worker (Cemini /route public core). */
74
+ /**
75
+ * Lane → default worker (Cemini /route public core).
76
+ * `deepseek` is experimental only — mid stays on claude-ds.
77
+ */
75
78
  laneWorkers: {
76
79
  easy: "openrouter",
77
80
  mid: "claude-ds",
package/dist/health.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { execSync } from "node:child_process";
2
2
  import { allAdapters } from "./adapters/index.js";
3
+ import { isMidDeepSeekProven, midDeepSeekProofDetail } from "./adapters/claude-ds.js";
3
4
  import { config } from "./config.js";
4
5
  import { isTmuxAvailable } from "./tmux.js";
5
6
  import { commandExists } from "./util.js";
@@ -42,6 +43,14 @@ export function runHealth() {
42
43
  detail: h.binary ? `${h.detail} @ ${h.binary}` : h.detail,
43
44
  });
44
45
  }
46
+ // Informational: mid is proven DeepSeek (not the overall OR-gate).
47
+ const midOk = isMidDeepSeekProven();
48
+ const midDetail = midDeepSeekProofDetail();
49
+ checks.push({
50
+ name: "lane:mid",
51
+ ok: midOk,
52
+ detail: midDetail,
53
+ });
45
54
  // Optional supervisor probe (v0 skill-only; Cursor CLI agent is informational)
46
55
  const agentBin = (commandExists("agent") && "agent") ||
47
56
  (commandExists("cursor-agent") && "cursor-agent") ||
@@ -78,6 +87,9 @@ export function runHealth() {
78
87
  product: config.product,
79
88
  version: config.version,
80
89
  checks,
90
+ lanes: {
91
+ mid: { worker: "claude-ds", deepseek: midOk, detail: midDetail },
92
+ },
81
93
  };
82
94
  }
83
95
  export function printHealth(report, asJson) {
@@ -97,4 +109,8 @@ export function printHealth(report, asJson) {
97
109
  console.log("Fix the ✗ items, then re-run: cursor-route health");
98
110
  console.log("Tip: start with one worker (grok OR claude-ds) before parallel demos.");
99
111
  }
112
+ else if (report.checks.some((c) => c.name === "lane:mid" && !c.ok)) {
113
+ console.log("");
114
+ console.log("Tip: health OK without a DeepSeek mid — --lane mid will fail until lane:mid is ✓.");
115
+ }
100
116
  }
package/dist/jobs.js CHANGED
@@ -59,6 +59,29 @@ export function readJob(id) {
59
59
  export function writeJob(job) {
60
60
  writeSecure(jobPaths(job.id).json, JSON.stringify(job, null, 2));
61
61
  }
62
+ export function jobEvidence(job, sessionAlive) {
63
+ return {
64
+ spawn: {
65
+ jobId: job.id,
66
+ worker: job.worker,
67
+ lane: job.lane ?? null,
68
+ model: job.model ?? null,
69
+ startedAt: job.startedAt ?? job.createdAt,
70
+ },
71
+ execute: {
72
+ status: job.status,
73
+ exitCode: job.exitCode ?? null,
74
+ tmuxSession: job.tmuxSession,
75
+ pid: job.pid ?? null,
76
+ sessionAlive,
77
+ },
78
+ verify: {
79
+ captureHint: `cursor-route capture ${job.id}`,
80
+ logBytes: job.logBytes ?? null,
81
+ claim: "unverified",
82
+ },
83
+ };
84
+ }
62
85
  function pidAlive(pid) {
63
86
  try {
64
87
  process.kill(pid, 0);
@@ -73,6 +96,9 @@ function pidAlive(pid) {
73
96
  encoding: "utf8",
74
97
  stdio: ["ignore", "pipe", "ignore"],
75
98
  });
99
+ // spawnSync does not throw on EPERM (macOS sandbox); empty stdout is not "dead".
100
+ if (r.error || r.status !== 0)
101
+ return true;
76
102
  const state = (r.stdout || "").trim();
77
103
  return state !== "" && !state.startsWith("Z");
78
104
  }
@@ -219,7 +245,7 @@ export function startJob(opts) {
219
245
  writeSecure(paths.prompt, opts.prompt);
220
246
  let model;
221
247
  let modelId;
222
- if (worker === "claude-ds") {
248
+ if (worker === "claude-ds" || worker === "deepseek") {
223
249
  if (opts.model) {
224
250
  model = opts.model;
225
251
  modelId = opts.modelId ?? DS_MODEL_IDS[opts.model];
@@ -249,6 +275,7 @@ export function startJob(opts) {
249
275
  alwaysApprove,
250
276
  model,
251
277
  modelId,
278
+ dryRun: Boolean(opts.dryRun),
252
279
  });
253
280
  }
254
281
  catch (e) {
@@ -421,7 +448,7 @@ export function cleanJobs(olderThanDays = 7) {
421
448
  if (Number.isFinite(t) &&
422
449
  t < cutoff &&
423
450
  (job.status === "completed" || job.status === "failed" || job.status === "killed")) {
424
- for (const ext of [".json", ".prompt", ".log"]) {
451
+ for (const ext of [".json", ".prompt", ".log", ".dsh-patch.yml"]) {
425
452
  const fp = underJobsDir(id, ext);
426
453
  if (existsSync(fp))
427
454
  unlinkSync(fp);
package/docs/DEMO_GIF.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Hero demo recording (GIF)
2
2
 
3
+ ## Status
4
+
5
+ No real GIF yet — the checkbox stays open until a `docs/fixtures/hero.gif` exists.
6
+
7
+ Committed fixture for now: [docs/fixtures/hero-demo.log](./fixtures/hero-demo.log),
8
+ a reproducible dry-run capture (no live workers, no secrets). Regenerate it with:
9
+
10
+ ```bash
11
+ docs/fixtures/generate-hero-demo.sh
12
+ ```
13
+
14
+ Also see [docs/fixtures/claude-ds-smoke.log](./fixtures/claude-ds-smoke.log)
15
+ (headless `claude-ds` smoke proof). Both are machine-agnostic.
16
+
17
+ ## Recording the real GIF (later)
18
+
3
19
  tmux is required for the viral attach/send demo. On this laptop brew needs sudo — record after:
4
20
 
5
21
  ```bash
@@ -27,4 +43,4 @@ If you previously exported `$HOME/.cursor-route/bin`, that dir is stale — remo
27
43
 
28
44
  Record with [asciinema](https://asciinema.org/) or CleanShot → export GIF → `docs/fixtures/hero.gif`.
29
45
 
30
- Until then, use `docs/fixtures/claude-ds-smoke.log` as the committed proof fixture.
46
+ When a real GIF lands, check off the **Hero GIF** item in `docs/briefs/WORKING.md` and reference it here.
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  title: cursor-route workspace — working brief (edit in place)
3
3
  repo: ~/Projects/cursor-route
4
- npm: cursor-route@0.1.7 (LIVE on npm latest)
4
+ npm: cursor-route@0.1.9 (not published this WorkDir only)
5
5
  created: 2026-08-12
6
- updated: 2026-08-12
6
+ updated: 2026-08-18
7
7
  ---
8
8
 
9
9
  # cursor-route — living brief
@@ -22,9 +22,9 @@ Cursor Agent plans. Workers run in tmux via `cursor-route`:
22
22
  | `mid` | claude-ds (DeepSeek behind Claude Code) | Default implement (**Flash**; `--model pro` when needed) |
23
23
  | `hard` | Grok CLI | Hard implement |
24
24
 
25
- Always-approve on (`--ask` / `CURSOR_ROUTE_ASK=1` to opt out). Jobs live in `~/.local/share/cursor-route/jobs`, not in this clone.
25
+ Always-approve on for coding worktrees (`--ask` / `CURSOR_ROUTE_ASK=1` to opt out) — not LIVE Discord/trading. Jobs live in `~/.local/share/cursor-route/jobs`, not in this clone.
26
26
 
27
- Install: `npm i -g cursor-route` → **0.1.7**. Release notes: [CHANGELOG.md](../../CHANGELOG.md).
27
+ Install: `npm i -g cursor-route` → **0.1.9** once published. Release notes: [CHANGELOG.md](../../CHANGELOG.md).
28
28
 
29
29
  ## Open (edit / check off)
30
30
 
@@ -33,9 +33,12 @@ Install: `npm i -g cursor-route` → **0.1.7**. Release notes: [CHANGELOG.md](..
33
33
  - [x] add `--model flash|pro` on `start`
34
34
  - [x] document Grok **auth** ≠ usage-out (`grok login`) vs quota → Pro stand-in
35
35
  - [x] **Skill `route-orch`** — Flash/Pro table in `skills/` + `.cursor/skills/`
36
- - [x] **Official DeepSeek Harness** — `deepseek` adapter slot present; mid stays on claude-ds
36
+ - [x] **Official DeepSeek Harness** — `deepseek` adapter slot present; `@deepseek-ai/dsh` 0.1.0-rc.6 (2026-08-13, github.com/deepseek-ai/deepseek-harness, MIT) is a developer-preview plugin kernel (web UI + `dsh --profile headless "job"`), not a Claude Code replacement; mid does not swap
37
37
  - [x] **0.1.7 debug fixes** — env default wired through `startJob`; Anthropic hatch omits DS ids; preserve `[1m]`
38
- - [ ] **Hero GIF**still outstanding (`docs/DEMO_GIF.md`)
38
+ - [x] **Experimental `--worker deepseek`** 0.1.8: real dsh adapter (`dsh --profile headless` + per-job Cordis patch pins the model; never writes `~/.dsh/settings.yaml`). Always-approve → `DSH_PERMISSION_MODE=danger-full-access`, `--ask` → `workspace-write`; key via env only. Health ✓ needs `dsh` + `DEEPSEEK_API_KEY` (override `CURSOR_ROUTE_DSH_BIN`). Mid stays **claude-ds**.
39
+ - [x] **route-orch brief steals (2026-08-14)** — AutoDesign / misevolution / Vero habits into the public skill: **Verify / claim closeout** (external eval contract; activity ≠ verification), **Eval & skill hygiene** (mid-run Verify-rewrite ban; skill misevolution HITL — no auto-promotion of worker-trajectory variants; verify-fail → reconsider plan/definition + stage attribution spawn/execute/verify); handoff shape Success criteria + Verify + NEVER; skills synced; mid stays claude-ds.
40
+ - [x] **Health proves mid DeepSeek + evidence tree** — 0.1.9: `lane:mid` ✓ only when DeepSeek is proven (shim or DeepSeek `ANTHROPIC_BASE_URL`; Anthropic hatch is not proof); health JSON `lanes.mid`; `status --json` evidence tree (`spawn` / `execute` / `verify.claim=unverified`); skill health-before-mid + evidence-tree closeout. Mid stays **claude-ds**.
41
+ - [ ] **Hero GIF** — still outstanding; dry-run fixture ships as the substitute for now (`docs/fixtures/hero-demo.log` — see `docs/DEMO_GIF.md`)
39
42
  - [x] **Do not** paste private `ROUTE_KIT`, SIP, prod paths, or hang-watchdog env into this public repo
40
43
 
41
44
  ## Repo map
@@ -44,7 +47,7 @@ Install: `npm i -g cursor-route` → **0.1.7**. Release notes: [CHANGELOG.md](..
44
47
  |------|------|
45
48
  | `src/cli.ts` | `health` / `start` / `jobs` / `capture` / `send` / `kill` |
46
49
  | `src/adapters/claude-ds.ts` | Mid worker; DeepSeek URL required; `--model` → `-Model` |
47
- | `src/adapters/deepseek.ts` | Reserved unreleased harness slot |
50
+ | `src/adapters/deepseek.ts` | Experimental dsh worker (`--worker deepseek`; mid stays claude-ds) |
48
51
  | `src/adapters/grok.ts` | Hard worker |
49
52
  | `src/adapters/openrouter.ts` | Easy worker |
50
53
  | `skills/route-orch/SKILL.md` | Cursor skill — spawn CLI, do not implement in-session |
@@ -59,3 +62,9 @@ Install: `npm i -g cursor-route` → **0.1.7**. Release notes: [CHANGELOG.md](..
59
62
  | 2026-08-12 | Shipped Flash default + `--model pro`, deepseek slot, skill table → 0.1.6. |
60
63
  | 2026-08-12 | npm `cursor-route@0.1.6` LIVE; CHANGELOG + README skill-install path fixed. |
61
64
  | 2026-08-12 | Grok debug → 0.1.7: env DS default, Anthropic hatch, `[1m]` preserve. |
65
+ | 2026-08-13 | Hero demo dry-run fixture + docs: `docs/fixtures/generate-hero-demo.sh` → `hero-demo.log`; README Demo, DEMO_GIF.md, demo-notes pointer. Real GIF still open. |
66
+ | 2026-08-13 | GPTSOL fixes: scrub inherited env/paths, stable job ids, ignore `docs/briefs/handoffs/`, GIF checkbox wording. |
67
+ | 2026-08-14 | DeepSeek Harness eval: `@deepseek-ai/dsh` 0.1.0-rc.6 is a developer-preview plugin kernel, not a mid replacement; `--worker deepseek` stays unhealthy; mid remains claude-ds (docs-only, no version bump). |
68
+ | 2026-08-14 | Experimental `--worker deepseek` wired to official dsh (headless + per-job patch + `DSH_PERMISSION_MODE` + key-via-env); `--model` applies to claude-ds + deepseek; mid stays claude-ds → 0.1.8 LIVE. |
69
+ | 2026-08-14 | route-orch brief steals (AutoDesign / misevolution / Vero): **Verify / claim closeout** + **Eval & skill hygiene**; handoff Success criteria + Verify + NEVER; both skill copies synced; mid stays claude-ds. Docs-only, no version bump. |
70
+ | 2026-08-18 | Health `lane:mid` + `lanes.mid` prove DeepSeek; status evidence tree (`verify.claim` stays unverified); skill health-before-mid + closeout tree; mid stays claude-ds → 0.1.9 (not published). |
@@ -1,13 +1,15 @@
1
1
  # Demo fixture log (no secrets)
2
2
 
3
+ > **Current fixture:** [docs/fixtures/hero-demo.log](./fixtures/hero-demo.log) — generated, dry-run, machine-agnostic (regenerate: `docs/fixtures/generate-hero-demo.sh`). Real GIF still pending: [DEMO_GIF.md](./DEMO_GIF.md).
4
+
3
5
  Simulated capture output for README / tweet assets — replace with a real GIF once tmux is available.
4
6
 
5
7
  ```
6
8
  $ cursor-route --version
7
- 0.1.6
9
+ 0.1.9
8
10
 
9
11
  $ cursor-route health
10
- cursor-route v0.1.6
12
+ cursor-route v0.1.9
11
13
  health: OK
12
14
  ✓ tmux
13
15
  ✓ runtime bun ok
@@ -15,7 +17,8 @@ health: OK
15
17
  ✓ worker:grok
16
18
  ✓ worker:claude-ds
17
19
  ✓ worker:openrouter
18
- ✗ worker:deepseek unreleased — mid lane uses claude-ds
20
+ ✗ worker:deepseek dsh not found — mid default remains claude-ds
21
+ ✓ lane:mid DeepSeek proven (claude-ds (DeepSeek shim))
19
22
  ✓ jobs_dir ~/.local/share/cursor-route/jobs
20
23
 
21
24
  $ cursor-route start --lane mid "Add a failing test then make it pass"