omnilane 0.13.0 → 0.14.0

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
@@ -6,6 +6,37 @@ semantic version tags.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.14.0] - 2026-08-10
10
+
11
+ ### Added
12
+
13
+ - Optional `omnilane doctor --probe V` and read-only MCP `provider_probe` perform
14
+ one bounded advise-mode live capability check only when explicitly requested;
15
+ reports include routing/timing metadata but never the provider response body.
16
+ - Live Board retained-history search and state filters now drive a local
17
+ **Export visible** JSON download that excludes tokens, task/result bodies,
18
+ workdirs, and logs.
19
+ - `omnilane benchmark` provides a versioned fixed-workload quality/timing report,
20
+ transparent user-supplied per-call cost estimates, default no-call dry-run,
21
+ and an explicit `--run` provider-call gate.
22
+ - CI runs `omnilane doctor --strict --json` against an isolated, offline fixture
23
+ as a post-install acceptance contract.
24
+
25
+ - `omnilane jobs recommend` and the read-only MCP `jobs_recommend` tool derive
26
+ per-lane vendor suggestions from bounded completed-job metadata. A configurable
27
+ minimum sample gate prevents one-off successes from outranking established
28
+ evidence; neither surface reads task/result bodies or changes routing.
29
+ - `omnilane doctor --strict` keeps warning diagnostics intact but exits nonzero
30
+ when any warning is present, so local automation and CI can enforce a complete
31
+ runtime setup. JSON output now includes the applied `strict` policy and makes
32
+ `ok` reflect it.
33
+
34
+ ### Fixed
35
+
36
+ - `doctor` now validates macOS `stat -f` output before falling back to GNU
37
+ `stat -c`, so owner-only job-store permissions are classified consistently on
38
+ Linux CI instead of becoming a false strict-mode warning.
39
+
9
40
  ## [0.13.0] - 2026-08-03
10
41
 
11
42
  ### Changed
@@ -563,7 +594,9 @@ work to the wrong model, and records the evidence behind the shipped defaults.
563
594
  - Initial shared routing table, cross-vendor dispatcher, runners, installer,
564
595
  and baseline lint fixes.
565
596
 
566
- [Unreleased]: https://github.com/Seraphim0916/omnilane/compare/v0.13.0...HEAD
597
+ [Unreleased]: https://github.com/Seraphim0916/omnilane/compare/v0.14.0...HEAD
598
+
599
+ [0.14.0]: https://github.com/Seraphim0916/omnilane/compare/v0.13.0...v0.14.0
567
600
  [0.13.0]: https://github.com/Seraphim0916/omnilane/compare/v0.12.0...v0.13.0
568
601
  [0.12.0]: https://github.com/Seraphim0916/omnilane/compare/v0.11.0...v0.12.0
569
602
  [0.11.0]: https://github.com/Seraphim0916/omnilane/compare/v0.10.4...v0.11.0
package/README.ja.md CHANGED
@@ -516,6 +516,14 @@ scripts/dispatch.sh --dry-run hardest-coding "…" # 解決済みプラン、
516
516
 
517
517
  ## 📜 リリース履歴
518
518
 
519
+ ## v0.14.0 の新機能
520
+
521
+ - **根拠に基づくルーティング提案**:`jobs recommend` と MCP `jobs_recommend` は公開ジョブメタデータだけを読み、ルーティングを自動変更しません。
522
+ - **任意の実動プローブ**:`doctor --probe V` と MCP `provider_probe` は明示された場合だけプロバイダーを呼び出し、応答本文を報告しません。
523
+ - **Live Board の履歴検索・絞り込み・エクスポート**:直近 50 件を対象に、表示中の公開メタデータだけを出力します。
524
+ - **固定品質/コストベンチマーク**:`omnilane benchmark` は既定で dry-run、実呼び出しには `--run` が必要です。
525
+ - **厳格なインストール検証**:CI で隔離された `doctor --strict --json` を実行し、macOS/GNU `stat` の権限判定差も修正しました。
526
+
519
527
  ## v0.13.0 の新機能
520
528
 
521
529
  - **`long-context` を AA-LCR で順序付け** — Artificial Analysis の長文脈推論
package/README.ko.md CHANGED
@@ -499,6 +499,14 @@ scripts/dispatch.sh --dry-run hardest-coding "…" # 완전히 해석된 계
499
499
 
500
500
  ## 📜 릴리스 기록
501
501
 
502
+ ## v0.14.0 새 기능
503
+
504
+ - **근거 기반 라우팅 제안**: `jobs recommend`와 MCP `jobs_recommend`는 공개 작업 메타데이터만 읽고 라우팅을 자동 변경하지 않습니다.
505
+ - **선택형 실제 기능 탐지**: `doctor --probe V`와 MCP `provider_probe`는 명시적으로 요청할 때만 공급자를 호출하며 응답 본문을 보고하지 않습니다.
506
+ - **Live Board 기록 검색·필터·내보내기**: 최근 50개 작업을 검색하고 현재 표시된 공개 메타데이터만 내보냅니다.
507
+ - **고정 품질/비용 벤치마크**: `omnilane benchmark`는 기본적으로 dry-run이며 실제 호출에는 `--run`이 필요합니다.
508
+ - **엄격한 설치 검증**: CI에서 격리된 `doctor --strict --json`을 실행하고 macOS/GNU `stat` 권한 판정 차이도 수정했습니다.
509
+
502
510
  ## v0.13.0 새 기능
503
511
 
504
512
  - **`long-context` 를 AA-LCR 기준으로 정렬** — Artificial Analysis 의 장문맥 추론
package/README.md CHANGED
@@ -195,6 +195,10 @@ The reference is memory-only and disappears when the page closes. The board
195
195
  binds only to `127.0.0.1`, uses a random token, and is read-only. It shows
196
196
  `task.txt` and the public `out.txt`, but never raw worker or vendor logs.
197
197
 
198
+ Search and state filters apply to the latest 50 retained jobs. **Export visible**
199
+ downloads only the currently visible public metadata as local JSON; it excludes
200
+ tokens, task text, result bodies, workdirs, and raw logs.
201
+
198
202
  The board reads in English, Japanese, Korean, Traditional Chinese and Simplified
199
203
  Chinese. It follows the browser language on first load; the switcher in the
200
204
  header overrides that and the choice is remembered locally.
@@ -255,7 +259,8 @@ adding a routing reminder. Configure the host to launch the installed CLI:
255
259
 
256
260
  The server exposes `route` plus read-only introspection: `list_lanes`,
257
261
  `explain`, `validate`, `dry_run`, `jobs_list`, `jobs_status`, `jobs_result`,
258
- `jobs_stats`, `jobs_audit`, and `doctor`. `route` defaults to read-only `advise` mode. Calls that select `work` must also
262
+ `jobs_stats`, `jobs_recommend`, `jobs_audit`, `doctor`, and the explicitly opt-in
263
+ `provider_probe`. `route` defaults to read-only `advise` mode. Calls that select `work` must also
259
264
  provide an explicit `workdir`.
260
265
 
261
266
  Node.js is the only runtime requirement (no npm packages). If you prefer
@@ -299,7 +304,8 @@ omnilane ui start # start/reuse the local Live UI;
299
304
  omnilane ui status # report whether the Live UI is running
300
305
  omnilane ui url # print the current authenticated local URL
301
306
  omnilane ui stop # stop the Live UI
302
- omnilane doctor [--json] # read-only routing and runtime health report
307
+ omnilane doctor [--json] [--strict] [--probe V] [--probe-timeout SEC] # live probe is opt-in
308
+ omnilane benchmark [--json] [--run] [--vendor V] [--cost-per-call V=USD] # dry-run by default
303
309
  dispatch.sh [--background] [--dry-run] [--mode advise|work|sysops] [--workdir DIR]
304
310
  [--vendor V] [--model M] [--effort E] [--timeout SEC] [--job-timeout SEC]
305
311
  LANE "TASK" # "-" reads task from stdin
@@ -312,12 +318,24 @@ jobs.sh wait ID [--timeout N] # job exit; 124 timeout; 125 d
312
318
  jobs.sh cancel ID # stop a running job: group SIGTERM, then SIGKILL
313
319
  jobs.sh rm ID # delete one finished/dead job (refuses a running job)
314
320
  jobs.sh [--json] stats [--last N] [--lane L] [--vendor V] # local success and routing aggregates
321
+ jobs.sh [--json] recommend [--last N] [--lane L] [--min-samples N] # evidence-gated vendor suggestion
315
322
  jobs.sh audit [--last N] [--json] # read-only job integrity/privacy check
316
323
  jobs.sh prune [--keep N] [--apply] # preview by default; completed jobs only
317
324
  configure.sh # interactive lane menu
318
325
  configure.sh set|get|unset|list|diff LANE [SPEC] # script/inspect routing.local.yaml, no tty
319
326
  ```
320
327
 
328
+ `jobs recommend` reads only validated public metadata and exit codes. It ranks
329
+ eligible vendors by success rate, sample count, then name; the default minimum
330
+ is three completed jobs. It never reads task/result bodies or changes routing.
331
+
332
+ `doctor --probe V` makes one bounded advise-mode provider call and returns only
333
+ availability, selected model, timing, and response byte count—not the response
334
+ body. Without `--probe`, doctor remains offline. `benchmark` uses the fixed TSV
335
+ suite in `benchmarks/workloads.tsv`; its default dry-run resolves every route
336
+ without provider calls. `--run` is the explicit call gate, and cost totals are
337
+ estimates based only on values supplied with `--cost-per-call`.
338
+
321
339
  **Big decisions can get a panel, not a person.** The `arbitrate` lane ships
322
340
  **disabled** — a panel costs one call per voter per round, so it is opt-in.
323
341
  Enable it with `arbitrate: vote codex,claude,grok -` in `routing.local.yaml`,
@@ -547,6 +565,24 @@ working notes, including per-benchmark caveats, live in
547
565
 
548
566
  ## 📜 Release history
549
567
 
568
+ ## What's new in v0.14.0
569
+
570
+ - **Evidence-based routing recommendations** — `jobs recommend` and MCP
571
+ `jobs_recommend` rank vendors from completed public job metadata, enforce a
572
+ minimum sample gate, and never change routing automatically.
573
+ - **Opt-in live capability probes** — `doctor --probe V` and MCP
574
+ `provider_probe` distinguish an installed CLI from a working provider call.
575
+ Default doctor remains offline; probe reports never include response bodies.
576
+ - **Search, filter, and export Live Board history** — search and state filters
577
+ cover the latest 50 jobs, while **Export visible** downloads only filtered
578
+ public metadata.
579
+ - **Repeatable quality/cost benchmark** — `omnilane benchmark` ships a fixed
580
+ workload suite, defaults to provider-free dry-run, and requires `--run` for
581
+ actual calls. Cost estimates use only explicit `--cost-per-call` values.
582
+ - **Strict installation acceptance** — CI runs isolated
583
+ `omnilane doctor --strict --json`, catching incomplete runtime wiring without
584
+ contacting providers.
585
+
550
586
  ## What's new in v0.13.0
551
587
 
552
588
  - **`long-context` is ordered on AA-LCR** — Artificial Analysis's long-context
package/README.zh-CN.md CHANGED
@@ -473,6 +473,14 @@ vendor 一律当成 `work`,而且它只能逐次明确指定,永远不是 lane
473
473
 
474
474
  ## 📜 版本历程
475
475
 
476
+ ## v0.14.0 新功能
477
+
478
+ - **基于证据的路由建议**:`jobs recommend` 与 MCP `jobs_recommend` 只读取公开作业元数据,并且不会自动修改路由。
479
+ - **可选的真实能力探测**:`doctor --probe V` 与 MCP `provider_probe` 仅在明确请求时调用供应商,报告不包含回答正文。
480
+ - **Live Board 历史搜索、筛选与导出**:支持最近 50 条作业,并且只导出当前可见的公开元数据。
481
+ - **固定质量/成本基准**:`omnilane benchmark` 默认只做 dry-run,`--run` 才实际调用供应商。
482
+ - **严格安装验收**:CI 在隔离环境执行 `doctor --strict --json`,并修正 macOS/GNU `stat` 权限检测差异。
483
+
476
484
  ## v0.13.0 新功能
477
485
 
478
486
  - **`long-context` 改用 AA-LCR 排序**——那是 Artificial Analysis 的长脉络推理基准,
package/README.zh-TW.md CHANGED
@@ -178,6 +178,9 @@ omnilane ui stop # 正常停止
178
178
  `127.0.0.1`、用隨機 token 保護、全程唯讀。畫面只顯示 `task.txt` 與公開的
179
179
  `out.txt`,不顯示工作端或廠商原始 log。
180
180
 
181
+ 搜尋與狀態篩選會套用到最近保留的 50 筆工作歷史。按「匯出目前結果」只會把畫面上
182
+ 目前可見的公開中繼資料下載成 JSON;不含 token、任務本文、結果本文、工作目錄與原始 log。
183
+
181
184
  畫面提供英文、日文、韓文、繁體中文與簡體中文。首次載入依瀏覽器語言決定,可用標題列
182
185
  的切換器覆寫,選擇會記在本機。
183
186
 
@@ -228,7 +231,7 @@ omnilane ui stop # 正常停止
228
231
  }
229
232
  ```
230
233
 
231
- Server 提供 `route`,以及一組唯讀查詢工具:`list_lanes`、`explain`、`validate`、`dry_run`、`jobs_list`、`jobs_status`、`jobs_result`、`jobs_stats`、`jobs_audit`、`doctor`。
234
+ Server 提供 `route`,以及一組唯讀查詢工具:`list_lanes`、`explain`、`validate`、`dry_run`、`jobs_list`、`jobs_status`、`jobs_result`、`jobs_stats`、`jobs_recommend`、`jobs_audit`、`doctor`,以及必須明確選用的 `provider_probe`。
232
235
  `route` 預設唯讀 `advise` 模式;選 `work` 的呼叫必須同時提供明確的
233
236
  `workdir`。
234
237
 
@@ -266,7 +269,8 @@ omnilane ui start # 啟動或沿用本機 Live UI,
266
269
  omnilane ui status # 查看 Live UI 是否運作中
267
270
  omnilane ui url # 印出目前通過驗證的本機網址
268
271
  omnilane ui stop # 停止 Live UI
269
- omnilane doctor [--json] # 唯讀檢查路由與本機執行環境
272
+ omnilane doctor [--json] [--strict] [--probe V] [--probe-timeout SEC] # 實際探測必須明確選用
273
+ omnilane benchmark [--json] [--run] [--vendor V] [--cost-per-call V=USD] # 預設只乾跑
270
274
  dispatch.sh [--background] [--dry-run] [--mode advise|work|sysops] [--workdir 目錄]
271
275
  [--vendor V] [--model M] [--effort E] [--timeout SEC] [--job-timeout SEC]
272
276
  通道 "任務" # "-" 表示從 stdin 讀任務
@@ -279,6 +283,7 @@ jobs.sh wait 工作ID [--timeout N] # 工作結束碼;124 逾
279
283
  jobs.sh cancel 工作ID # 停止執行中的工作:整組 SIGTERM,再 SIGKILL
280
284
  jobs.sh rm 工作ID # 刪除單一已完成/已死工作(執行中會被拒絕)
281
285
  jobs.sh [--json] stats [--last N] [--lane L] [--vendor V] # 本機成功率與路由彙整
286
+ jobs.sh [--json] recommend [--last N] [--lane L] [--min-samples N] # 有證據門檻的廠商建議
282
287
  jobs.sh audit [--last N] [--json] # 唯讀檢查工作完整性與隱私
283
288
  jobs.sh prune [--keep N] [--apply] # 預設只預覽;只清理已完成工作
284
289
  omnilane mcp # MCP stdio server(需 Node.js)
@@ -287,6 +292,15 @@ configure.sh # 互動通道選單
287
292
  configure.sh set|get|unset|list|diff LANE [SPEC] # 非互動編輯/檢視 routing.local.yaml
288
293
  ```
289
294
 
295
+ `jobs recommend` 只讀取通過驗證的公開中繼資料與退出碼。候選達到最低樣本數後,
296
+ 依成功率、樣本數、廠商名稱排序;預設至少三筆已完成工作。它不讀任務/結果本文,
297
+ 也不修改路由。
298
+
299
+ `doctor --probe V` 只會做一次有逾時上限的 advise 模式供應商呼叫,回傳可用性、
300
+ 選到的模型、耗時與回應位元組數,不回傳回答本文;未帶 `--probe` 時仍完全離線。
301
+ `benchmark` 使用 `benchmarks/workloads.tsv` 的固定題組,預設只解析路由、不呼叫供應商;
302
+ `--run` 才是實際呼叫閘門,成本總額也只依 `--cost-per-call` 明確提供的估值計算。
303
+
290
304
  **重大決定可以開評審團,不是問一個人。**`arbitrate` 通道**預設關閉**——
291
305
  評審團每評審每輪燒一次額度,所以做成選配。要用就在 `routing.local.yaml`
292
306
  寫 `arbitrate: vote codex,claude,grok -`,或跑設定選單,從
@@ -483,6 +497,19 @@ vendor 一律當成 `work`,而且它只能逐次明確指定,永遠不是 lane
483
497
 
484
498
  ## 📜 版本歷程
485
499
 
500
+ ## v0.14.0 新功能
501
+
502
+ - **依證據產生路由建議**:`jobs recommend` 與 MCP `jobs_recommend` 只用已完成工作的
503
+ 公開中繼資料排序供應商,設有最低樣本門檻,也不會自動修改路由。
504
+ - **選配實際能力探測**:`doctor --probe V` 與 MCP `provider_probe` 可分辨「CLI 已安裝」
505
+ 和「供應商呼叫真的可用」。doctor 預設仍離線,探測報告不含回答本文。
506
+ - **Live Board 歷史搜尋、篩選與匯出**:搜尋與狀態篩選涵蓋最近 50 筆工作;
507
+ 「匯出目前結果」只下載篩選後的公開中繼資料。
508
+ - **可重現的品質/成本基準**:`omnilane benchmark` 內建固定題組,預設不呼叫供應商;
509
+ `--run` 才會實際執行,成本也只依明確提供的 `--cost-per-call` 估值計算。
510
+ - **嚴格安裝驗收**:CI 以隔離環境執行 `omnilane doctor --strict --json`,
511
+ 不接觸供應商也能抓出 runtime 接線不完整。
512
+
486
513
  ## v0.13.0 新功能
487
514
 
488
515
  - **`long-context` 改用 AA-LCR 排序**——那是 Artificial Analysis 的長脈絡推理基準,
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.13.0
1
+ 0.14.0
@@ -0,0 +1,4 @@
1
+ # id<TAB>preferred_lane<TAB>weight<TAB>Python regex<TAB>prompt
2
+ exact_alpha consult 1 ^ALPHA\s*$ Return exactly ALPHA and nothing else.
3
+ arithmetic consult 2 ^42\s*$ Return only the integer result of 17 + 25.
4
+ json_contract hard-judgment 2 ^\{"status":"pass","count":3\}\s*$ Return exactly this compact JSON object with the stated key order: {"status":"pass","count":3}
package/bin/omnilane CHANGED
@@ -52,6 +52,13 @@ case "$cmd" in
52
52
  ;;
53
53
  release-audit) exec bash "$REPO/scripts/release-audit.sh" "$@" ;;
54
54
  doctor) exec bash "$REPO/scripts/doctor.sh" "$@" ;;
55
+ benchmark)
56
+ command -v python3 >/dev/null 2>&1 || {
57
+ echo "omnilane: benchmark requires Python 3.9 or newer" >&2
58
+ exit 1
59
+ }
60
+ exec python3 "$REPO/scripts/benchmark.py" "$@"
61
+ ;;
55
62
  ui)
56
63
  command -v python3 >/dev/null 2>&1 || {
57
64
  echo "omnilane: ui requires Python 3.9 or newer" >&2
@@ -68,11 +75,12 @@ omnilane — one routing table, every harness
68
75
  omnilane list effective routing table
69
76
  omnilane route [--vendor V] [flags] LANE "TASK"
70
77
  dispatch or consult a model
71
- omnilane jobs [--json] COMMAND [ARGS] list, status, result, stats, wait, or audit
78
+ omnilane jobs [--json] COMMAND [ARGS] list, status, result, stats, recommend, wait, or audit
72
79
  omnilane mcp start the MCP stdio server
73
80
  omnilane completion bash|zsh|fish print a shell completion script
74
81
  omnilane release-audit [flags] offline, read-only release gate
75
- omnilane doctor [--json] read-only local health report
82
+ omnilane doctor [--json] [--strict] [--probe V] local health / opt-in live probe
83
+ omnilane benchmark [--json] [--run] [--vendor V] fixed quality/cost comparison
76
84
  omnilane ui start|status|url|stop
77
85
  omnilane configure interactive lane menu
78
86
  EOF
package/bin/omnilane-mcp CHANGED
@@ -10,6 +10,8 @@ const ROOT = path.resolve(__dirname, '..');
10
10
  const DISPATCH_SCRIPT = path.join(ROOT, 'scripts', 'dispatch.sh');
11
11
  const JOBS_SCRIPT = path.join(ROOT, 'scripts', 'jobs.sh');
12
12
  const DOCTOR_SCRIPT = path.join(ROOT, 'scripts', 'doctor.sh');
13
+ const PROVIDER_PROBE_SCRIPT = process.env.OMNILANE_PROVIDER_PROBE_SCRIPT ||
14
+ path.join(ROOT, 'scripts', 'provider-probe.sh');
13
15
  const LATEST_PROTOCOL_VERSION = '2025-11-25';
14
16
  const SUPPORTED_PROTOCOL_VERSIONS = new Set([
15
17
  '2025-11-25',
@@ -285,6 +287,37 @@ const tools = [
285
287
  openWorldHint: false,
286
288
  },
287
289
  },
290
+ {
291
+ name: 'provider_probe',
292
+ description: 'Explicitly invoke one configured provider with a bounded fixed prompt to verify live inference. Incurs one provider call; never returns the provider response.',
293
+ inputSchema: {
294
+ type: 'object',
295
+ required: ['vendor'],
296
+ properties: {
297
+ vendor: {
298
+ type: 'string',
299
+ enum: ['codex', 'claude', 'grok', 'gemini', 'kimi', 'qwen', 'opencode', 'openrouter', 'deepseek', 'zai', 'mistral', 'groq', 'cerebras'],
300
+ },
301
+ timeout: {
302
+ type: 'integer',
303
+ minimum: 1,
304
+ maximum: 300,
305
+ default: 30,
306
+ },
307
+ json: {
308
+ type: 'boolean',
309
+ default: true,
310
+ },
311
+ },
312
+ additionalProperties: false,
313
+ },
314
+ annotations: {
315
+ readOnlyHint: true,
316
+ destructiveHint: false,
317
+ idempotentHint: false,
318
+ openWorldHint: true,
319
+ },
320
+ },
288
321
  {
289
322
  name: 'jobs_stats',
290
323
  description: 'Aggregate local background-job outcomes and lane/vendor distribution from bounded public metadata. Never reads task or result bodies.',
@@ -311,6 +344,45 @@ const tools = [
311
344
  openWorldHint: false,
312
345
  },
313
346
  },
347
+ {
348
+ name: 'jobs_recommend',
349
+ description: 'Recommend a vendor per lane from bounded local completed-job outcomes. Read-only, evidence-gated, and never reads task or result bodies.',
350
+ inputSchema: {
351
+ type: 'object',
352
+ properties: {
353
+ last: {
354
+ type: 'integer',
355
+ minimum: 1,
356
+ maximum: 10000,
357
+ description: 'Limit analysis to the most recent N jobs.',
358
+ },
359
+ lane: {
360
+ type: 'string',
361
+ pattern: '^[a-z][a-z0-9-]*$',
362
+ description: 'Optionally analyze one lane.',
363
+ },
364
+ min_samples: {
365
+ type: 'integer',
366
+ minimum: 1,
367
+ maximum: 1000,
368
+ default: 3,
369
+ description: 'Completed samples required before a vendor is eligible for recommendation.',
370
+ },
371
+ json: {
372
+ type: 'boolean',
373
+ default: false,
374
+ description: 'Return the versioned JSON envelope instead of the human report.',
375
+ },
376
+ },
377
+ additionalProperties: false,
378
+ },
379
+ annotations: {
380
+ readOnlyHint: true,
381
+ destructiveHint: false,
382
+ idempotentHint: true,
383
+ openWorldHint: false,
384
+ },
385
+ },
314
386
  {
315
387
  name: 'jobs_audit',
316
388
  description: 'Bounded read-only integrity and privacy scan of the local job store. Reports findings without printing task or result content.',
@@ -435,6 +507,24 @@ function validateJsonOnlyArguments(args) {
435
507
  return null;
436
508
  }
437
509
 
510
+ function validateProviderProbeArguments(args) {
511
+ const error = validationError(args, ['vendor', 'timeout', 'json']);
512
+ if (error) return error;
513
+ const vendors = new Set([
514
+ 'codex', 'claude', 'grok', 'gemini', 'kimi', 'qwen', 'opencode',
515
+ 'openrouter', 'deepseek', 'zai', 'mistral', 'groq', 'cerebras',
516
+ ]);
517
+ if (typeof args.vendor !== 'string' || !vendors.has(args.vendor)) {
518
+ return 'vendor must name one probe-capable provider';
519
+ }
520
+ if (OWN(args, 'timeout') &&
521
+ (!Number.isInteger(args.timeout) || args.timeout < 1 || args.timeout > 300)) {
522
+ return 'timeout must be an integer from 1 to 300';
523
+ }
524
+ if (OWN(args, 'json') && typeof args.json !== 'boolean') return 'json must be a boolean';
525
+ return null;
526
+ }
527
+
438
528
  function validateJobsQueryArguments(args) {
439
529
  const error = validationError(args, ['last', 'json']);
440
530
  if (error) return error;
@@ -445,6 +535,23 @@ function validateJobsQueryArguments(args) {
445
535
  return null;
446
536
  }
447
537
 
538
+ function validateJobsRecommendArguments(args) {
539
+ const error = validationError(args, ['last', 'lane', 'min_samples', 'json']);
540
+ if (error) return error;
541
+ if (OWN(args, 'last') && (!Number.isInteger(args.last) || args.last < 1 || args.last > 10000)) {
542
+ return 'last must be an integer from 1 to 10000';
543
+ }
544
+ if (OWN(args, 'lane') && (typeof args.lane !== 'string' || !LANE_PATTERN.test(args.lane))) {
545
+ return 'lane must match ^[a-z][a-z0-9-]*$';
546
+ }
547
+ if (OWN(args, 'min_samples') &&
548
+ (!Number.isInteger(args.min_samples) || args.min_samples < 1 || args.min_samples > 1000)) {
549
+ return 'min_samples must be an integer from 1 to 1000';
550
+ }
551
+ if (OWN(args, 'json') && typeof args.json !== 'boolean') return 'json must be a boolean';
552
+ return null;
553
+ }
554
+
448
555
  function validateExplainArguments(args) {
449
556
  const error = validationError(args, ['lane', 'json']);
450
557
  if (error) return error;
@@ -645,6 +752,14 @@ async function callTool(name, args) {
645
752
  const argv = args.json === true ? ['--json'] : [];
646
753
  return runToolScript('doctor', DOCTOR_SCRIPT, argv);
647
754
  }
755
+ if (name === 'provider_probe') {
756
+ const error = validateProviderProbeArguments(args);
757
+ if (error) return toolError(error);
758
+ const argv = ['--vendor', args.vendor];
759
+ if (OWN(args, 'timeout')) argv.push('--timeout', String(args.timeout));
760
+ if (args.json !== false) argv.push('--json');
761
+ return runToolScript('provider_probe', PROVIDER_PROBE_SCRIPT, argv);
762
+ }
648
763
  if (name === 'jobs_stats' || name === 'jobs_audit') {
649
764
  const error = validateJobsQueryArguments(args);
650
765
  if (error) return toolError(error);
@@ -654,6 +769,17 @@ async function callTool(name, args) {
654
769
  if (OWN(args, 'last')) argv.push('--last', String(args.last));
655
770
  return runToolScript(name, JOBS_SCRIPT, argv);
656
771
  }
772
+ if (name === 'jobs_recommend') {
773
+ const error = validateJobsRecommendArguments(args);
774
+ if (error) return toolError(error);
775
+ const argv = [];
776
+ if (args.json === true) argv.push('--json');
777
+ argv.push('recommend');
778
+ if (OWN(args, 'last')) argv.push('--last', String(args.last));
779
+ if (OWN(args, 'lane')) argv.push('--lane', args.lane);
780
+ if (OWN(args, 'min_samples')) argv.push('--min-samples', String(args.min_samples));
781
+ return runToolScript(name, JOBS_SCRIPT, argv);
782
+ }
657
783
  throw new RpcError(-32602, `Unknown tool: ${name}`);
658
784
  }
659
785
 
@@ -53,7 +53,7 @@ _omnilane() {
53
53
  local command="${words[2]:-}" sub sub_index=3
54
54
  local -a lanes ids
55
55
  if (( CURRENT == 2 )); then
56
- _values 'command' version list route dispatch jobs doctor release-audit ui configure completion help
56
+ _values 'command' version list route dispatch jobs doctor benchmark release-audit ui configure completion help
57
57
  return
58
58
  fi
59
59
  case "$command" in
@@ -78,7 +78,7 @@ _omnilane() {
78
78
  sub="${words[4]:-}"
79
79
  fi
80
80
  if (( CURRENT == sub_index )); then
81
- _values 'job command' list status result tail retry stats wait audit prune help
81
+ _values 'job command' list status result tail retry stats recommend wait audit prune help
82
82
  elif (( CURRENT == sub_index + 1 )) &&
83
83
  [[ "$sub" == status || "$sub" == result || "$sub" == wait ||
84
84
  "$sub" == tail || "$sub" == retry ]]; then
@@ -92,7 +92,13 @@ _omnilane() {
92
92
  _arguments '--background[re-dispatch in the background]'
93
93
  elif [[ "$sub" == wait ]]; then
94
94
  _arguments '--timeout[maximum wait seconds]:seconds:'
95
- elif [[ "$sub" == stats || "$sub" == audit ]]; then
95
+ elif [[ "$sub" == stats ]]; then
96
+ _arguments '--last[maximum jobs]:count:' '--lane[filter by lane]:lane:' \
97
+ '--vendor[filter by vendor]:vendor:' '--json[versioned metadata output]'
98
+ elif [[ "$sub" == recommend ]]; then
99
+ _arguments '--last[maximum jobs]:count:' '--lane[filter by lane]:lane:' \
100
+ '--min-samples[minimum completed samples]:count:' '--json[versioned metadata output]'
101
+ elif [[ "$sub" == audit ]]; then
96
102
  _arguments '--last[maximum jobs]:count:' '--json[versioned metadata output]'
97
103
  elif [[ "$sub" == prune ]]; then
98
104
  _arguments '--keep[completed jobs to retain]:count:' \
@@ -100,7 +106,18 @@ _omnilane() {
100
106
  '--apply[delete eligible jobs]'
101
107
  fi
102
108
  ;;
103
- doctor) _values 'option' --json ;;
109
+ doctor)
110
+ _arguments '--json[versioned JSON output]' '--strict[fail on warnings]' \
111
+ '--probe[run one opt-in live provider probe]:vendor:' \
112
+ '--probe-timeout[probe timeout in seconds]:seconds:'
113
+ ;;
114
+ benchmark)
115
+ _arguments '--json[versioned JSON output]' '--run[invoke providers in advise mode]' \
116
+ '*--vendor[vendor to compare]:vendor:(codex claude grok gemini kimi qwen opencode openrouter deepseek zai mistral groq cerebras)' \
117
+ '--timeout[per-workload timeout]:seconds:' \
118
+ '--workloads[TSV workload file]:file:_files' \
119
+ '*--cost-per-call[user-supplied vendor USD estimate]:vendor=USD:'
120
+ ;;
104
121
  release-audit)
105
122
  _arguments '--target[release version]:version:' '--allow-dirty[permit a dirty worktree]' \
106
123
  '--require-tag[require an annotated tag at HEAD]' '--manifest[include tracked inventory]' \
@@ -58,7 +58,7 @@ _omnilane() {
58
58
  prev="${COMP_WORDS[COMP_CWORD-1]:-}"
59
59
  command="${COMP_WORDS[1]:-}"
60
60
  if [[ "$COMP_CWORD" -eq 1 ]]; then
61
- words="version list route dispatch jobs doctor release-audit ui configure completion help"
61
+ words="version list route dispatch jobs doctor benchmark release-audit ui configure completion help"
62
62
  else
63
63
  case "$command" in
64
64
  route|dispatch)
@@ -84,7 +84,7 @@ _omnilane() {
84
84
  sub="${COMP_WORDS[3]:-}"
85
85
  fi
86
86
  if [[ "$COMP_CWORD" -eq "$sub_index" ]]; then
87
- words="list status result tail retry stats wait audit prune help"
87
+ words="list status result tail retry stats recommend wait audit prune help"
88
88
  elif [[ "$COMP_CWORD" -eq $((sub_index + 1)) &&
89
89
  ( "$sub" == status || "$sub" == result || "$sub" == wait ||
90
90
  "$sub" == tail || "$sub" == retry ) ]]; then
@@ -99,7 +99,11 @@ _omnilane() {
99
99
  words="--background"
100
100
  elif [[ "$sub" == wait ]]; then
101
101
  words="--timeout"
102
- elif [[ "$sub" == stats || "$sub" == audit ]]; then
102
+ elif [[ "$sub" == stats ]]; then
103
+ words="--last --lane --vendor --json"
104
+ elif [[ "$sub" == recommend ]]; then
105
+ words="--last --lane --min-samples --json"
106
+ elif [[ "$sub" == audit ]]; then
103
107
  words="--last --json"
104
108
  elif [[ "$sub" == prune ]]; then
105
109
  words="--keep --older-than --apply"
@@ -107,7 +111,18 @@ _omnilane() {
107
111
  return
108
112
  fi
109
113
  ;;
110
- doctor) words="--json" ;;
114
+ doctor) words="--json --strict --probe --probe-timeout" ;;
115
+ benchmark)
116
+ case "$prev" in
117
+ --vendor) words="codex claude grok gemini kimi qwen opencode openrouter deepseek zai mistral groq cerebras" ;;
118
+ --workloads)
119
+ COMPREPLY=( $(compgen -f -- "$cur") )
120
+ return
121
+ ;;
122
+ --timeout|--cost-per-call) return ;;
123
+ *) words="--json --run --vendor --timeout --workloads --cost-per-call" ;;
124
+ esac
125
+ ;;
111
126
  release-audit) words="--target --allow-dirty --require-tag --manifest --json" ;;
112
127
  ui) words="start status url stop" ;;
113
128
  completion) words="bash zsh" ;;
@@ -10,6 +10,7 @@ complete -c omnilane -f -n __fish_use_subcommand -a dispatch -d 'dispatch o
10
10
  complete -c omnilane -f -n __fish_use_subcommand -a jobs -d 'inspect background jobs'
11
11
  complete -c omnilane -f -n __fish_use_subcommand -a mcp -d 'MCP stdio server'
12
12
  complete -c omnilane -f -n __fish_use_subcommand -a doctor -d 'read-only health report'
13
+ complete -c omnilane -f -n __fish_use_subcommand -a benchmark -d 'fixed quality/cost comparison'
13
14
  complete -c omnilane -f -n __fish_use_subcommand -a release-audit -d 'offline release gate'
14
15
  complete -c omnilane -f -n __fish_use_subcommand -a ui -d 'Live Board server'
15
16
  complete -c omnilane -f -n __fish_use_subcommand -a configure -d 'lane routing (menu or set/get/unset/list/diff)'
@@ -29,19 +30,30 @@ complete -c omnilane -n '__fish_seen_subcommand_from route dispatch' -l dry-run
29
30
 
30
31
  # jobs subcommands (only before a jobs subcommand is chosen).
31
32
  complete -c omnilane -f \
32
- -n '__fish_seen_subcommand_from jobs; and not __fish_seen_subcommand_from list status result tail retry stats wait audit prune cancel rm help' \
33
- -a 'list status result tail retry stats wait audit prune cancel rm help' -d 'jobs subcommand'
33
+ -n '__fish_seen_subcommand_from jobs; and not __fish_seen_subcommand_from list status result tail retry stats recommend wait audit prune cancel rm help' \
34
+ -a 'list status result tail retry stats recommend wait audit prune cancel rm help' -d 'jobs subcommand'
34
35
  complete -c omnilane -n '__fish_seen_subcommand_from jobs' -l json -d 'JSON output'
35
36
  # jobs list / stats filters.
36
- complete -c omnilane -n '__fish_seen_subcommand_from jobs; and __fish_seen_subcommand_from list stats' -l lane -x -d 'filter by lane'
37
+ complete -c omnilane -n '__fish_seen_subcommand_from jobs; and __fish_seen_subcommand_from list stats recommend' -l lane -x -d 'filter by lane'
37
38
  complete -c omnilane -n '__fish_seen_subcommand_from jobs; and __fish_seen_subcommand_from list stats' -l vendor -x -a 'codex claude grok gemini kimi qwen opencode openrouter deepseek zai mistral groq cerebras exec' -d 'filter by vendor'
38
39
  complete -c omnilane -n '__fish_seen_subcommand_from jobs; and __fish_seen_subcommand_from list' -l status -x -a 'running done' -d 'filter by status'
40
+ complete -c omnilane -n '__fish_seen_subcommand_from jobs; and __fish_seen_subcommand_from stats recommend audit' -l last -x -d 'maximum recent jobs'
41
+ complete -c omnilane -n '__fish_seen_subcommand_from jobs; and __fish_seen_subcommand_from recommend' -l min-samples -x -d 'minimum completed samples'
39
42
 
40
43
  # configure subcommands (non-interactive).
41
44
  complete -c omnilane -f -n '__fish_seen_subcommand_from configure; and not __fish_seen_subcommand_from set get unset list diff' -a 'set get unset list diff' -d 'configure action'
42
45
 
43
46
  # doctor / release-audit / ui / completion.
44
47
  complete -c omnilane -n '__fish_seen_subcommand_from doctor' -l json -d 'JSON output'
48
+ complete -c omnilane -n '__fish_seen_subcommand_from doctor' -l strict -d 'fail when any warning is reported'
49
+ complete -c omnilane -n '__fish_seen_subcommand_from doctor' -l probe -x -a 'codex claude grok gemini kimi qwen opencode openrouter deepseek zai mistral groq cerebras' -d 'run one opt-in live probe'
50
+ complete -c omnilane -n '__fish_seen_subcommand_from doctor' -l probe-timeout -x -d 'probe timeout in seconds'
51
+ complete -c omnilane -n '__fish_seen_subcommand_from benchmark' -l json -d 'JSON output'
52
+ complete -c omnilane -n '__fish_seen_subcommand_from benchmark' -l run -d 'invoke providers in advise mode'
53
+ complete -c omnilane -n '__fish_seen_subcommand_from benchmark' -l vendor -x -a 'codex claude grok gemini kimi qwen opencode openrouter deepseek zai mistral groq cerebras' -d 'vendor to compare; repeatable'
54
+ complete -c omnilane -n '__fish_seen_subcommand_from benchmark' -l timeout -x -d 'per-workload timeout'
55
+ complete -c omnilane -n '__fish_seen_subcommand_from benchmark' -l workloads -r -d 'TSV workload file'
56
+ complete -c omnilane -n '__fish_seen_subcommand_from benchmark' -l cost-per-call -x -d 'user-supplied VENDOR=USD estimate'
45
57
  complete -c omnilane -n '__fish_seen_subcommand_from release-audit' -l target -x -d 'target version'
46
58
  complete -c omnilane -n '__fish_seen_subcommand_from release-audit' -l allow-dirty -d 'permit a dirty tree'
47
59
  complete -c omnilane -n '__fish_seen_subcommand_from release-audit' -l require-tag -d 'require an annotated tag'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnilane",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "One routing table, every harness — classify subtasks into lanes and dispatch each lane to the best vendor's agentic CLI (Codex, Claude, Gemini, Grok) using your existing subscription logins.",
5
5
  "bin": {
6
6
  "omnilane": "bin/omnilane"
@@ -11,6 +11,7 @@
11
11
  "!scripts/__pycache__",
12
12
  "!**/*.pyc",
13
13
  "completions/",
14
+ "benchmarks/",
14
15
  "ui/",
15
16
  "routing.yaml",
16
17
  "routing.local.yaml.example",