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 +13 -0
- package/README.md +8 -7
- package/SUPPORT.md +1 -0
- package/dist/adapters/claude-ds.js +18 -0
- package/dist/cli.js +6 -4
- package/dist/config.js +1 -1
- package/dist/health.js +16 -0
- package/dist/jobs.js +26 -0
- package/docs/briefs/WORKING.md +8 -4
- package/docs/demo-notes.md +3 -2
- package/docs/fixtures/hero-demo.log +3 -2
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/skills/route-orch/SKILL.md +31 -7
- package/src/adapters/claude-ds.ts +19 -0
- package/src/cli.test.ts +108 -5
- package/src/cli.ts +6 -3
- package/src/config.ts +1 -1
- package/src/health.ts +21 -0
- package/src/jobs.ts +49 -0
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.
|
|
116
|
+
0.1.9
|
|
117
117
|
|
|
118
118
|
$ CURSOR_ROUTE_RELAXED=1 cursor-route health
|
|
119
|
-
cursor-route v0.1.
|
|
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
|
|
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
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
|
}
|
package/docs/briefs/WORKING.md
CHANGED
|
@@ -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.
|
|
4
|
+
npm: cursor-route@0.1.9 (not published — this WorkDir only)
|
|
5
5
|
created: 2026-08-12
|
|
6
|
-
updated: 2026-08-
|
|
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.
|
|
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). |
|
package/docs/demo-notes.md
CHANGED
|
@@ -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.
|
|
9
|
+
0.1.9
|
|
10
10
|
|
|
11
11
|
$ cursor-route health
|
|
12
|
-
cursor-route v0.1.
|
|
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.
|
|
2
|
+
0.1.9
|
|
3
3
|
|
|
4
4
|
$ CURSOR_ROUTE_RELAXED=1 cursor-route health
|
|
5
|
-
cursor-route v0.1.
|
|
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.
|
|
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.
|
|
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 →
|
|
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 **
|
|
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
|
|
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
|
|
92
|
-
- Do not
|
|
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 {
|
|
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.
|
|
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.
|
|
620
|
-
expect(config.version).toBe("0.1.
|
|
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
|
|
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
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 {
|