cursor-route 0.1.8 → 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,18 @@
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
+
3
16
  ## 0.1.8 — 2026-08-14
4
17
 
5
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`.
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 |
@@ -113,10 +113,10 @@ docs/fixtures/generate-hero-demo.sh # regenerate docs/fixtures/hero-demo.lo
113
113
 
114
114
  ```text
115
115
  $ cursor-route --version
116
- 0.1.8
116
+ 0.1.9
117
117
 
118
118
  $ CURSOR_ROUTE_RELAXED=1 cursor-route health
119
- cursor-route v0.1.8
119
+ cursor-route v0.1.9
120
120
  health: OK
121
121
 
122
122
  $ cursor-route start --lane mid --model flash --dry-run "Add a unit test for shellQuote"
@@ -140,7 +140,7 @@ A real hero GIF is still pending — recording steps live in [docs/DEMO_GIF.md](
140
140
  | `mid` | `claude-ds` | Default implement on DeepSeek (**Flash** by default) |
141
141
  | `hard` | `grok` | Hard implement on Grok CLI |
142
142
 
143
- 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`.
144
144
 
145
145
  ### Mid models (Flash vs Pro)
146
146
 
@@ -163,7 +163,7 @@ export CURSOR_ROUTE_DS_MODEL=pro # also honors ANTHROPIC_MODEL; --model overri
163
163
 
164
164
  ## DeepSeek setup (the cheap mid-lane — this is the point)
165
165
 
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.
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.
167
167
 
168
168
  **Recommended (official DeepSeek → Claude Code):**
169
169
 
@@ -176,7 +176,7 @@ export ANTHROPIC_AUTH_TOKEN=YOUR_DEEPSEEK_API_KEY # from platform.deepseek.com
176
176
  export ANTHROPIC_DEFAULT_HAIKU_MODEL=deepseek-v4-flash
177
177
  export CLAUDE_CODE_SUBAGENT_MODEL=deepseek-v4-flash
178
178
 
179
- 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
180
180
  cursor-route start --lane mid "…" # Flash (default)
181
181
  cursor-route start --lane mid --model pro "…" # Pro when you need it
182
182
  ```
@@ -276,6 +276,7 @@ If you’d like to tip, use the **donation-only** addresses below (not trading o
276
276
 
277
277
  | Chain family | Address |
278
278
  |--------------|---------|
279
+ | **X Money** (fiat, US) | Request [@Cemini23](https://x.com/Cemini23) in the X app — scan the Request QR |
279
280
  | **EVM** (Ethereum, Polygon, Base, Arbitrum, …) | `0x444C5C2eC439E0382aa5a17F70313c536BcC5D58` |
280
281
  | **Solana / SVM** | `J4zNn4hK9jTrKBFY8sbAGJHLoZvXvQf4B9pQSbSrocZE` |
281
282
  | **Polymarket** (referral) | [polymarket.com/?r=Cemini23](https://polymarket.com/?r=Cemini23) |
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;
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>
@@ -356,11 +356,13 @@ async function main() {
356
356
  }
357
357
  })())
358
358
  : sessionExists(job.tmuxSession);
359
- const view = { ...job, sessionAlive: alive };
359
+ const evidence = jobEvidence(job, alive);
360
+ const view = { ...job, sessionAlive: alive, evidence };
360
361
  if (json)
361
362
  console.log(JSON.stringify(view, null, 2));
362
363
  else {
363
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)");
364
366
  if (job.error)
365
367
  console.log(`error: ${job.error}`);
366
368
  }
package/dist/config.js CHANGED
@@ -65,7 +65,7 @@ function maxConcurrentJobsFromEnv() {
65
65
  */
66
66
  export const config = {
67
67
  product: "cursor-route",
68
- version: "0.1.8",
68
+ version: "0.1.9",
69
69
  get jobsDir() {
70
70
  return defaultJobsDir();
71
71
  },
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
  }
@@ -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.8 (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-14
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.8**. 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
 
@@ -36,6 +36,8 @@ Install: `npm i -g cursor-route` → **0.1.8**. Release notes: [CHANGELOG.md](..
36
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
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**.
39
41
  - [ ] **Hero GIF** — still outstanding; dry-run fixture ships as the substitute for now (`docs/fixtures/hero-demo.log` — see `docs/DEMO_GIF.md`)
40
42
  - [x] **Do not** paste private `ROUTE_KIT`, SIP, prod paths, or hang-watchdog env into this public repo
41
43
 
@@ -64,3 +66,5 @@ Install: `npm i -g cursor-route` → **0.1.8**. Release notes: [CHANGELOG.md](..
64
66
  | 2026-08-13 | GPTSOL fixes: scrub inherited env/paths, stable job ids, ignore `docs/briefs/handoffs/`, GIF checkbox wording. |
65
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). |
66
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). |
@@ -6,10 +6,10 @@ Simulated capture output for README / tweet assets — replace with a real GIF o
6
6
 
7
7
  ```
8
8
  $ cursor-route --version
9
- 0.1.8
9
+ 0.1.9
10
10
 
11
11
  $ cursor-route health
12
- cursor-route v0.1.8
12
+ cursor-route v0.1.9
13
13
  health: OK
14
14
  ✓ tmux
15
15
  ✓ runtime bun ok
@@ -18,6 +18,7 @@ health: OK
18
18
  ✓ worker:claude-ds
19
19
  ✓ worker:openrouter
20
20
  ✗ worker:deepseek dsh not found — mid default remains claude-ds
21
+ ✓ lane:mid DeepSeek proven (claude-ds (DeepSeek shim))
21
22
  ✓ jobs_dir ~/.local/share/cursor-route/jobs
22
23
 
23
24
  $ cursor-route start --lane mid "Add a failing test then make it pass"
@@ -1,8 +1,8 @@
1
1
  $ cursor-route --version
2
- 0.1.8
2
+ 0.1.9
3
3
 
4
4
  $ CURSOR_ROUTE_RELAXED=1 cursor-route health
5
- cursor-route v0.1.8
5
+ cursor-route v0.1.9
6
6
  health: OK
7
7
 
8
8
  ✓ tmux ok
@@ -12,6 +12,7 @@ health: OK
12
12
  ✓ worker:claude-ds ok (claude-ds (DeepSeek shim); default model deepseek-v4-flash) @ ~/.local/bin/claude-ds
13
13
  ✗ worker:openrouter OPENROUTER_API_KEY not set — export your OpenRouter key (easy lane model defaults to openrouter/free) @ node '~/Projects/cursor-route/dist/openrouter-run.js'
14
14
  ✗ worker:deepseek dsh (@deepseek-ai/dsh) not found — install: npm i -g @deepseek-ai/dsh. Mid default remains claude-ds.
15
+ ✓ lane:mid DeepSeek proven (claude-ds (DeepSeek shim))
15
16
  ✓ cursor_cli optional ok (agent on PATH) — v0 supervisor is Cursor skill, not CLI
16
17
  ✓ relaxed CURSOR_ROUTE_RELAXED=1 — tmux optional (headless OK)
17
18
  ✓ jobs_dir ~/.local/share/cursor-route/jobs
package/llms.txt CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Cursor stays the planner. DeepSeek (mid), Grok CLI (hard), and OpenRouter free models (easy) run parallel coding workers in tmux.
4
4
 
5
- MIT CLI + Cursor skill. npm: https://www.npmjs.com/package/cursor-route (latest **0.1.8**)
5
+ MIT CLI + Cursor skill. npm: https://www.npmjs.com/package/cursor-route (latest **0.1.9**)
6
6
  GitHub: https://github.com/cemini23/cursor-route
7
7
 
8
8
  ## FAQ
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursor-route",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Cursor stays the brain. Grok CLI + DeepSeek (claude-ds) + OpenRouter easy lane are the parallel army \u2014 lane-aware /route orchestration in tmux.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -18,7 +18,7 @@ You are the **orchestrator**. Do **not** implement bulk code in this Cursor sess
18
18
  - Mid/hard implementation that should run on a subscription worker (Grok CLI / claude-ds)
19
19
  - Multi-file investigation that benefits from parallel panes
20
20
 
21
- **Do not steal federation `/route`.** Private Cemini `/route` (route-task → SIP → verify → Grok/claude-ds chain) is a different skill. This public skill only drives the `cursor-route` CLI.
21
+ **Do not steal federation `/route`.** Private Cemini `/route` (route-task → verify → Grok/claude-ds chain) is a different skill. This public skill only drives the `cursor-route` CLI.
22
22
 
23
23
  ## Lanes (public core)
24
24
 
@@ -61,8 +61,8 @@ Health ✓ needs `dsh` on PATH and `DEEPSEEK_API_KEY` set. The adapter pins `--m
61
61
 
62
62
  ## Workflow
63
63
 
64
- 1. Run `cursor-route health` (or `CURSOR_ROUTE_RELAXED=1` for headless). If the **target worker** is unhealthy, fix before spawning.
65
- 2. Write a clear handoff prompt with **verify criteria** (no secrets, no LIVE Discord).
64
+ 1. Run `cursor-route health` (or `CURSOR_ROUTE_RELAXED=1` for headless). If the **target worker** is unhealthy, fix before spawning. If targeting **mid**, require `lane:mid` ✓ (or health JSON `lanes.mid.deepseek`) before spawn — `CURSOR_ROUTE_ALLOW_ANTHROPIC=1` is not DeepSeek proof.
65
+ 2. Write a clear handoff prompt with **Success criteria** + **Verify** + **NEVER** (no secrets, no LIVE Discord).
66
66
  3. Spawn:
67
67
 
68
68
  ```bash
@@ -70,8 +70,14 @@ cursor-route start --lane hard --dir "$PWD" "$(cat <<'EOF'
70
70
  ## Task
71
71
  ...
72
72
 
73
+ ## Success criteria
74
+ - [ ] ...
75
+
73
76
  ## Verify
74
77
  - [ ] ...
78
+
79
+ ## NEVER
80
+ - ...
75
81
  EOF
76
82
  )"
77
83
  ```
@@ -79,15 +85,33 @@ EOF
79
85
  Or `--worker grok` / `--worker claude-ds` / `--worker deepseek` (experimental) / `--worker openrouter` (or `--lane easy`). Use `--no-tmux` only when tmux is unavailable.
80
86
 
81
87
  4. Monitor: `cursor-route jobs --json` · `cursor-route capture <id>` · `cursor-route send <id> "…"` (tmux only).
82
- 5. Summarize worker results with **verify evidence** — no status-only “done”. If verify fails, `send` a correction or spawn a follow-up do not invent success.
88
+ 5. Summarize worker results with **verify evidence** — no status-only “done (see Verify / claim closeout). If verify fails, reconsider the plan/definition (not only retry) — `send` a correction or spawn a follow-up; do not invent success.
89
+
90
+ ## Verify / claim closeout
91
+
92
+ Verify criteria are an **external eval contract** (AutoDesign pattern), fixed by the parent — not a checklist the worker may rewrite:
93
+
94
+ - Workers must **not rewrite Success criteria / Verify** to claim done
95
+ - Parent closeout is an **evidence tree**: report **spawn** (job id, worker, lane, model) + **execute** (status, exit) + **verify** (`capture` excerpt / exit). A single “done” scalar is not enough.
96
+ - Parent closes a job only on **capture / exit evidence** (`cursor-route capture <id>`, job exit status)
97
+ - `cursor-route status --json` `.evidence.verify.claim` stays `"unverified"` until the parent reads capture
98
+ - **activity ≠ verification** — busy panes, many tool calls, or long transcripts do not make a claim true
99
+
100
+ ## Eval & skill hygiene
101
+
102
+ - **External eval contract (AutoDesign):** do not rewrite Verify / Success criteria mid-run to make a failing job look green — capture + exit status are the contract (see Verify / claim closeout).
103
+ - **Skill misevolution:** do not auto-edit `route-orch` or promote skill variants from worker trajectories without operator HITL — write-time approval ≠ safe retrieval later.
104
+ - **On verify fail:** prefer reconsidering the plan/definition (wrong approach) over grinding the same tactic; attribute failure to stage when possible (spawn vs execute vs verify).
83
105
 
84
106
  ## Always-approve
85
107
 
86
- Defaults on for workers. Opt out: `cursor-route start … --ask` or `CURSOR_ROUTE_ASK=1`.
108
+ Defaults on for workers. Opt out: `cursor-route start … --ask` or `CURSOR_ROUTE_ASK=1`. Always-approve is for **coding worktrees only** — it does not authorize LIVE Discord, trading, or irreversible SaaS.
87
109
 
88
110
  ## Anti-patterns
89
111
 
90
112
  - Do not paste API keys / private keys into prompts or `send`
91
- - Do not claim the official DeepSeek harness (`@deepseek-ai/dsh`) is the mid default — `--worker deepseek` is experimental only; mid stays **claude-ds**
92
- - Do not open-source or dump private Cemini `agent-toolkit` paths into public handoffs
113
+ - Do not claim the official DeepSeek harness (`@deepseek-ai/dsh`) is the mid default — `--worker deepseek` is an opt-in experiment (cheap to abandon), not a product fork; mid stays **claude-ds**
114
+ - Do not fork a second mid harness
115
+ - Do not open-source or dump private cemini `agent-toolkit` paths into public handoffs
93
116
  - Do not mark done without reading `capture` / exit status
117
+ - When editing this skill itself, treat changes as **skill-evolution** — do not auto-promote harmful instructions; prefer **HITL** (no unattended promote from worker trajectories)
@@ -124,6 +124,25 @@ function resolveClaudeDs(): { binary: string; mode: string } | null {
124
124
  return null;
125
125
  }
126
126
 
127
+ /**
128
+ * Mid lane is proven DeepSeek only via shim (`claude-ds` / `deepseek-claude`)
129
+ * or stock `claude` routed to DeepSeek. The Anthropic escape hatch is not proof.
130
+ */
131
+ export function isMidDeepSeekProven(): boolean {
132
+ const resolved = resolveClaudeDs();
133
+ if (!resolved) return false;
134
+ return !resolved.mode.startsWith("claude → Anthropic");
135
+ }
136
+
137
+ /** Human detail for health `lane:mid` (mode when proven; install hint otherwise). */
138
+ export function midDeepSeekProofDetail(): string {
139
+ const resolved = resolveClaudeDs();
140
+ if (!resolved || resolved.mode.startsWith("claude → Anthropic")) {
141
+ 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.";
142
+ }
143
+ return `DeepSeek proven (${resolved.mode})`;
144
+ }
145
+
127
146
  function pickModel(requested?: DsModelAlias, modelId?: string): { alias: DsModelAlias; id: string } {
128
147
  if (modelId) {
129
148
  const alias = requested ?? resolveDsModel(modelId).alias;
package/src/cli.test.ts CHANGED
@@ -10,12 +10,17 @@ import {
10
10
  } from "node:fs";
11
11
  import { join } from "node:path";
12
12
  import { tmpdir } from "node:os";
13
- import { resolveWorker, startJob } from "./jobs.ts";
13
+ import { resolveWorker, startJob, jobEvidence, type Job } from "./jobs.ts";
14
14
  import { config, resolveDsModel, resolveDsModelAlias } from "./config.ts";
15
15
  import { shellQuote, newJobId } from "./util.ts";
16
16
  import { runHealth } from "./health.ts";
17
17
  import { looksLikeSecretMaterial, redactSecrets } from "./secrets.ts";
18
- import { isDeepSeekRouted, isDeepSeekBaseUrl, claudeDsAdapter } from "./adapters/claude-ds.ts";
18
+ import {
19
+ isDeepSeekRouted,
20
+ isDeepSeekBaseUrl,
21
+ claudeDsAdapter,
22
+ isMidDeepSeekProven,
23
+ } from "./adapters/claude-ds.ts";
19
24
  import { deepseekAdapter, patchPathForPrompt } from "./adapters/deepseek.ts";
20
25
  import { grokAdapter } from "./adapters/grok.ts";
21
26
 
@@ -610,14 +615,18 @@ describe("health", () => {
610
615
  test("returns structured report", () => {
611
616
  const r = runHealth();
612
617
  expect(r.product).toBe("cursor-route");
613
- expect(r.version).toBe("0.1.8");
618
+ expect(r.version).toBe("0.1.9");
614
619
  expect(r.checks.length).toBeGreaterThan(3);
615
620
  expect(r.checks.some((c) => c.name === "tmux")).toBe(true);
616
621
  expect(r.checks.some((c) => c.name === "cursor_cli")).toBe(true);
622
+ const mid = r.checks.find((c) => c.name === "lane:mid");
623
+ expect(mid).toBeDefined();
624
+ expect(r.lanes.mid.worker).toBe("claude-ds");
625
+ expect(r.lanes.mid.deepseek).toBe(mid!.ok);
617
626
  });
618
627
 
619
- test("config version is 0.1.8", () => {
620
- expect(config.version).toBe("0.1.8");
628
+ test("config version is 0.1.9", () => {
629
+ expect(config.version).toBe("0.1.9");
621
630
  });
622
631
 
623
632
  test("OR-gate: ok can be true while worker:deepseek is false", () => {
@@ -657,4 +666,98 @@ describe("health", () => {
657
666
  else process.env.CURSOR_ROUTE_RELAXED = prev;
658
667
  }
659
668
  });
669
+
670
+ test("CURSOR_ROUTE_CLAUDE_DS_BIN pointing at an existing file proves mid DeepSeek", () => {
671
+ const prev = process.env.CURSOR_ROUTE_CLAUDE_DS_BIN;
672
+ const fake = join(tmpdir(), `cr-mid-proof-${process.pid}`);
673
+ writeFileSync(fake, "#!/bin/sh\necho ok\n");
674
+ chmodSync(fake, 0o755);
675
+ process.env.CURSOR_ROUTE_CLAUDE_DS_BIN = fake;
676
+ try {
677
+ expect(isMidDeepSeekProven()).toBe(true);
678
+ } finally {
679
+ if (prev === undefined) delete process.env.CURSOR_ROUTE_CLAUDE_DS_BIN;
680
+ else process.env.CURSOR_ROUTE_CLAUDE_DS_BIN = prev;
681
+ rmSync(fake, { force: true });
682
+ }
683
+ });
684
+
685
+ test("Anthropic hatch is not DeepSeek proof (shim on PATH still is)", () => {
686
+ const shimDir = join(tmpdir(), `cr-mid-hatch-${process.pid}`);
687
+ mkdirSync(shimDir, { recursive: true });
688
+ const claude = join(shimDir, "claude");
689
+ writeFileSync(claude, "#!/bin/sh\necho ok\n");
690
+ chmodSync(claude, 0o755);
691
+
692
+ const prev = {
693
+ bin: process.env.CURSOR_ROUTE_CLAUDE_DS_BIN,
694
+ allow: process.env.CURSOR_ROUTE_ALLOW_ANTHROPIC,
695
+ base: process.env.ANTHROPIC_BASE_URL,
696
+ crBase: process.env.CURSOR_ROUTE_ANTHROPIC_BASE_URL,
697
+ path: process.env.PATH,
698
+ };
699
+ delete process.env.CURSOR_ROUTE_CLAUDE_DS_BIN;
700
+ delete process.env.CURSOR_ROUTE_ANTHROPIC_BASE_URL;
701
+ process.env.ANTHROPIC_BASE_URL = "https://api.anthropic.com";
702
+ process.env.CURSOR_ROUTE_ALLOW_ANTHROPIC = "1";
703
+ process.env.PATH = `${shimDir}:/usr/bin:/bin`;
704
+ try {
705
+ const detail = claudeDsAdapter.health().detail;
706
+ if (detail.includes("Anthropic")) {
707
+ expect(isMidDeepSeekProven()).toBe(false);
708
+ const r = runHealth();
709
+ const mid = r.checks.find((c) => c.name === "lane:mid");
710
+ expect(mid?.ok).toBe(false);
711
+ expect(r.lanes.mid.deepseek).toBe(false);
712
+ } else {
713
+ // Real claude-ds / deepseek-claude on PATH is proof (hatch never engages).
714
+ expect(isMidDeepSeekProven()).toBe(true);
715
+ }
716
+ } finally {
717
+ if (prev.bin === undefined) delete process.env.CURSOR_ROUTE_CLAUDE_DS_BIN;
718
+ else process.env.CURSOR_ROUTE_CLAUDE_DS_BIN = prev.bin;
719
+ if (prev.allow === undefined) delete process.env.CURSOR_ROUTE_ALLOW_ANTHROPIC;
720
+ else process.env.CURSOR_ROUTE_ALLOW_ANTHROPIC = prev.allow;
721
+ if (prev.base === undefined) delete process.env.ANTHROPIC_BASE_URL;
722
+ else process.env.ANTHROPIC_BASE_URL = prev.base;
723
+ if (prev.crBase === undefined) delete process.env.CURSOR_ROUTE_ANTHROPIC_BASE_URL;
724
+ else process.env.CURSOR_ROUTE_ANTHROPIC_BASE_URL = prev.crBase;
725
+ process.env.PATH = prev.path || "";
726
+ rmSync(shimDir, { recursive: true, force: true });
727
+ }
728
+ });
729
+ });
730
+
731
+ describe("jobEvidence", () => {
732
+ test("verify.claim is always unverified; spawn/execute keys exist", () => {
733
+ const job: Job = {
734
+ id: "abcd1234",
735
+ schema: "cursor-route.job.v1",
736
+ status: "running",
737
+ worker: "claude-ds",
738
+ lane: "mid",
739
+ model: "flash",
740
+ prompt: "ping",
741
+ cwd: "/tmp",
742
+ alwaysApprove: true,
743
+ tmuxSession: "cursor-route-abcd1234",
744
+ createdAt: "2026-08-18T00:00:00.000Z",
745
+ startedAt: "2026-08-18T00:00:01.000Z",
746
+ logBytes: 42,
747
+ };
748
+ const ev = jobEvidence(job, true);
749
+ expect(ev.verify.claim).toBe("unverified");
750
+ expect(ev.verify.captureHint).toBe("cursor-route capture abcd1234");
751
+ expect(ev.verify.logBytes).toBe(42);
752
+ expect(ev.spawn.jobId).toBe("abcd1234");
753
+ expect(ev.spawn.worker).toBe("claude-ds");
754
+ expect(ev.spawn.lane).toBe("mid");
755
+ expect(ev.spawn.model).toBe("flash");
756
+ expect(ev.spawn.startedAt).toBe("2026-08-18T00:00:01.000Z");
757
+ expect(ev.execute.status).toBe("running");
758
+ expect(ev.execute.exitCode).toBe(null);
759
+ expect(ev.execute.tmuxSession).toBe("cursor-route-abcd1234");
760
+ expect(ev.execute.pid).toBe(null);
761
+ expect(ev.execute.sessionAlive).toBe(true);
762
+ });
660
763
  });
package/src/cli.ts CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  cleanJobs,
23
23
  jobPaths,
24
24
  refreshStatus,
25
+ jobEvidence,
25
26
  } from "./jobs.ts";
26
27
  import {
27
28
  capturePane,
@@ -39,11 +40,11 @@ Cursor stays the brain. Grok CLI + DeepSeek (claude-ds) + OpenRouter (easy) are
39
40
 
40
41
  Usage:
41
42
  cursor-route --version
42
- cursor-route health [--json]
43
+ cursor-route health [--json] # JSON includes lanes.mid (DeepSeek proof)
43
44
  cursor-route start <prompt> [options]
44
45
  cursor-route start --prompt-file <path> [options]
45
46
  cursor-route jobs [--json] [--limit N]
46
- cursor-route status <jobId> [--json]
47
+ cursor-route status <jobId> [--json] # JSON includes evidence spawn/execute/verify
47
48
  cursor-route capture <jobId> [lines]
48
49
  cursor-route send <jobId> <message>
49
50
  cursor-route attach <jobId>
@@ -389,12 +390,14 @@ async function main() {
389
390
  }
390
391
  })())
391
392
  : sessionExists(job.tmuxSession);
392
- const view = { ...job, sessionAlive: alive };
393
+ const evidence = jobEvidence(job, alive);
394
+ const view = { ...job, sessionAlive: alive, evidence };
393
395
  if (json) console.log(JSON.stringify(view, null, 2));
394
396
  else {
395
397
  console.log(
396
398
  `${job.id} ${job.status} worker=${job.worker}${job.model ? ` model=${job.model}` : ""} sessionAlive=${alive}`,
397
399
  );
400
+ console.log("evidence: spawn/execute/verify (claim=unverified)");
398
401
  if (job.error) console.log(`error: ${job.error}`);
399
402
  }
400
403
  return;
package/src/config.ts CHANGED
@@ -89,7 +89,7 @@ function maxConcurrentJobsFromEnv(): number {
89
89
  */
90
90
  export const config = {
91
91
  product: "cursor-route",
92
- version: "0.1.8",
92
+ version: "0.1.9",
93
93
  get jobsDir(): string {
94
94
  return defaultJobsDir();
95
95
  },
package/src/health.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { execSync } from "node:child_process";
2
2
  import { allAdapters } from "./adapters/index.ts";
3
+ import { isMidDeepSeekProven, midDeepSeekProofDetail } from "./adapters/claude-ds.ts";
3
4
  import { config } from "./config.ts";
4
5
  import { isTmuxAvailable } from "./tmux.ts";
5
6
  import { commandExists } from "./util.ts";
@@ -13,6 +14,9 @@ export interface HealthReport {
13
14
  ok: boolean;
14
15
  detail: string;
15
16
  }>;
17
+ lanes: {
18
+ mid: { worker: "claude-ds"; deepseek: boolean; detail: string };
19
+ };
16
20
  }
17
21
 
18
22
  export function runHealth(): HealthReport {
@@ -58,6 +62,15 @@ export function runHealth(): HealthReport {
58
62
  });
59
63
  }
60
64
 
65
+ // Informational: mid is proven DeepSeek (not the overall OR-gate).
66
+ const midOk = isMidDeepSeekProven();
67
+ const midDetail = midDeepSeekProofDetail();
68
+ checks.push({
69
+ name: "lane:mid",
70
+ ok: midOk,
71
+ detail: midDetail,
72
+ });
73
+
61
74
  // Optional supervisor probe (v0 skill-only; Cursor CLI agent is informational)
62
75
  const agentBin =
63
76
  (commandExists("agent") && "agent") ||
@@ -99,6 +112,9 @@ export function runHealth(): HealthReport {
99
112
  product: config.product,
100
113
  version: config.version,
101
114
  checks,
115
+ lanes: {
116
+ mid: { worker: "claude-ds", deepseek: midOk, detail: midDetail },
117
+ },
102
118
  };
103
119
  }
104
120
 
@@ -118,5 +134,10 @@ export function printHealth(report: HealthReport, asJson: boolean): void {
118
134
  console.log("");
119
135
  console.log("Fix the ✗ items, then re-run: cursor-route health");
120
136
  console.log("Tip: start with one worker (grok OR claude-ds) before parallel demos.");
137
+ } else if (report.checks.some((c) => c.name === "lane:mid" && !c.ok)) {
138
+ console.log("");
139
+ console.log(
140
+ "Tip: health OK without a DeepSeek mid — --lane mid will fail until lane:mid is ✓.",
141
+ );
121
142
  }
122
143
  }
package/src/jobs.ts CHANGED
@@ -105,6 +105,53 @@ export function writeJob(job: Job): void {
105
105
  writeSecure(jobPaths(job.id).json, JSON.stringify(job, null, 2));
106
106
  }
107
107
 
108
+ /** Evidence tree for status --json. Parent must capture; claim is never auto-green. */
109
+ export interface JobEvidence {
110
+ spawn: {
111
+ jobId: string;
112
+ worker: WorkerKind;
113
+ lane: Lane | null;
114
+ model: DsModelAlias | null;
115
+ startedAt: string;
116
+ };
117
+ execute: {
118
+ status: JobStatus;
119
+ exitCode: number | null;
120
+ tmuxSession: string;
121
+ pid: number | null;
122
+ sessionAlive: boolean;
123
+ };
124
+ verify: {
125
+ captureHint: string;
126
+ logBytes: number | null;
127
+ claim: "unverified";
128
+ };
129
+ }
130
+
131
+ export function jobEvidence(job: Job, sessionAlive: boolean): JobEvidence {
132
+ return {
133
+ spawn: {
134
+ jobId: job.id,
135
+ worker: job.worker,
136
+ lane: job.lane ?? null,
137
+ model: job.model ?? null,
138
+ startedAt: job.startedAt ?? job.createdAt,
139
+ },
140
+ execute: {
141
+ status: job.status,
142
+ exitCode: job.exitCode ?? null,
143
+ tmuxSession: job.tmuxSession,
144
+ pid: job.pid ?? null,
145
+ sessionAlive,
146
+ },
147
+ verify: {
148
+ captureHint: `cursor-route capture ${job.id}`,
149
+ logBytes: job.logBytes ?? null,
150
+ claim: "unverified",
151
+ },
152
+ };
153
+ }
154
+
108
155
  function pidAlive(pid: number): boolean {
109
156
  try {
110
157
  process.kill(pid, 0);
@@ -118,6 +165,8 @@ function pidAlive(pid: number): boolean {
118
165
  encoding: "utf8",
119
166
  stdio: ["ignore", "pipe", "ignore"],
120
167
  });
168
+ // spawnSync does not throw on EPERM (macOS sandbox); empty stdout is not "dead".
169
+ if (r.error || r.status !== 0) return true;
121
170
  const state = (r.stdout || "").trim();
122
171
  return state !== "" && !state.startsWith("Z");
123
172
  } catch {