@hecer/yoke 1.10.0 → 1.12.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.
@@ -2,12 +2,12 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "yoke",
4
4
  "displayName": "Yoke",
5
- "version": "1.10.0",
5
+ "version": "1.12.0",
6
6
  "description": "Cross-agent coding harness: one curated skill canon (TDD, brainstorming, plans, reviews, shipping, design verification) plus mechanical safety gates and an autonomous loop via the yoke CLI.",
7
7
  "author": { "name": "HECer", "url": "https://github.com/HECer" },
8
8
  "homepage": "https://github.com/HECer/yoke#readme",
9
9
  "repository": "https://github.com/HECer/yoke",
10
10
  "license": "MIT",
11
- "keywords": ["harness", "cross-agent", "tdd", "code-review", "autonomous-loop", "codex", "gemini-cli"],
11
+ "keywords": ["harness", "cross-agent", "tdd", "code-review", "autonomous-loop", "codex", "gemini-cli", "qwen-code"],
12
12
  "skills": "./canon/skills/"
13
13
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yoke",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "description": "Cross-agent coding discipline, mechanical gates, and release workflows",
5
5
  "skills": "./canon/skills/",
6
6
  "hooks": "./hooks/hooks.json"
package/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.12.0 — 2026-09-08
4
+
5
+ ### Fixed
6
+ - Parse Qwen Code's native assistant/result/structured-result output and cumulative native model statistics, ignoring nested subagent results and refusing terminal-error verdicts.
7
+ - Use Qwen's native headless approval flags, retain the sandbox in safe mode and explicitly permit shell tools there; exclude native delegation tools when Yoke owns concurrency.
8
+ - Detect native Qwen session/project markers and include Qwen in automatic loop review selection and CLI review validation.
9
+ - Install manual Qwen skills with native invocation restrictions and a supported RTK PreToolUse retry guard; back up settings and remove only Yoke's obsolete BeforeTool hook on retrofit.
10
+ - Accept up to 32 routing profiles so an all-agent setup remains valid.
11
+
12
+ ### Added
13
+ - Opt-in `setup --model-provider=deepseek,kimi` with DeepSeek V4 Flash/Pro and Kimi K2.6/K2.7 Code/K3 API configurations, environment key references, preserved custom settings and routing profiles.
14
+ - Explicit Qwen `PROTOCOL::MODEL` selectors mapped to native authentication/model arguments without changing global login settings.
15
+
16
+ ### Migration and validation limits
17
+ - Fresh Qwen setups now use one standard profile with the user's configured model. Existing workers remain unchanged unless reset with `--routing-preset`; configure stronger profiles for stronger assessments.
18
+ - API presets require separately configured credentials and are not model-quality or cost benchmarks. Read [Qwen model support](docs/QWEN-MODEL-SUPPORT.md) for exact behavior and migration.
19
+ - Tested with regression fixtures and real Qwen Code 0.23.0 against a synthetic local tool-calling server; no authenticated provider benchmarks or production sandbox validation. This release targets npm package 1.12.0; publication is triggered by the published GitHub release and verified separately.
20
+
21
+ ## 1.11.0 — 2026-09-07
22
+
23
+ ### Added
24
+ - Add Qwen Code (Alibaba) as fourth supported provider alongside Claude, Codex and Gemini.
25
+ - Detect Qwen host environment via `QWEN_CLI` and `QWEN_CLI_HOME` environment variables.
26
+ - Add Qwen routing workers with four capability tiers: `qwen-turbo-latest` (light), `qwen3-coder-plus` (standard/strong), `qwen3-235b-a22b` (frontier).
27
+ - Parse Qwen streaming telemetry for token usage and model reporting.
28
+ - Support Qwen in all CLI commands: `setup`, `loop`, `review`, `prd draft`, `prd assess`, `goal run`.
29
+
30
+ ### Changed
31
+ - Update project description from "three agents" to "four agents" to reflect Qwen support.
32
+ - Extend review resolution order to include Qwen for cross-model reviews.
33
+ - Update setup prompts to offer Qwen as agent and runner option.
34
+
35
+ ### Migration and validation limits
36
+ - Existing configurations remain compatible. New setups can select Qwen as agent/runner.
37
+ - Qwen CLI uses Gemini-style arguments (`--approval-mode`, `--output-format stream-json`). `bare`, `reasoningEffort` and `nativeMultiAgent` selections are not supported (like Gemini).
38
+ - Qwen routing profiles are configurable hypotheses, not authenticated benchmarks. Update installed packages and restart the dashboard/runner.
39
+
3
40
  ## 1.10.0 — 2026-09-06
4
41
 
5
42
  ### Added
package/README.md CHANGED
@@ -1,15 +1,15 @@
1
1
  <div align="center">
2
2
 
3
- <h1><img src="https://raw.githubusercontent.com/HECer/yoke/v1.10.0/docs/assets/yoke-logo.png" alt="Yoke" width="100" height="63"></h1>
3
+ <h1><img src="https://raw.githubusercontent.com/HECer/yoke/v1.12.0/docs/assets/yoke-logo.png" alt="Yoke" width="100" height="63"></h1>
4
4
 
5
- <!-- yoke:version:start -->1.10.0<!-- yoke:version:end -->
6
- <!-- yoke:tests:start -->1173<!-- yoke:tests:end -->
5
+ <!-- yoke:version:start -->1.12.0<!-- yoke:version:end -->
6
+ <!-- yoke:tests:start -->1213<!-- yoke:tests:end -->
7
7
  <!-- yoke:skills:start -->34<!-- yoke:skills:end -->
8
- <!-- yoke:agents:start -->Claude | Codex | Gemini<!-- yoke:agents:end -->
8
+ <!-- yoke:agents:start -->Claude | Codex | Gemini | Qwen<!-- yoke:agents:end -->
9
9
 
10
- ### One harness, three agents — and zero trust in "done."
10
+ ### One harness, four agents — and zero trust in "done."
11
11
 
12
- **Yoke** installs one curated canon of skills, **mechanical safety gates**, and tool wiring into any project — natively for **Claude Code, OpenAI Codex CLI, and Gemini CLI**. Its opt-in loop implements and verifies stories before committing. Independent review and browser proofs run when configured; screenshots and videos require the browser smoke gate.
12
+ **Yoke** installs one curated canon of skills, **mechanical safety gates**, and tool wiring into any project — natively for **Claude Code, OpenAI Codex CLI, Gemini CLI, and Qwen Code**. Its opt-in loop implements and verifies stories before committing. Independent review and browser proofs run when configured; screenshots and videos require the browser smoke gate.
13
13
 
14
14
  [![npm](https://img.shields.io/npm/v/%40hecer%2Fyoke?logo=npm&color=CB3837)](https://www.npmjs.com/package/@hecer/yoke)
15
15
  [![npm downloads](https://img.shields.io/npm/dm/%40hecer%2Fyoke?logo=npm)](https://www.npmjs.com/package/@hecer/yoke)
@@ -17,8 +17,8 @@
17
17
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](#-license)
18
18
  ![Node](https://img.shields.io/badge/node-%E2%89%A520-339933?logo=node.js&logoColor=white)
19
19
  ![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white)
20
- ![Tests](https://img.shields.io/badge/tests-1173%20defined-blue.svg)
21
- ![Agents](https://img.shields.io/badge/agents-Claude%20%7C%20Codex%20%7C%20Gemini-8A2BE2)
20
+ ![Tests](https://img.shields.io/badge/tests-1213%20defined-blue.svg)
21
+ ![Agents](https://img.shields.io/badge/agents-Claude%20%7C%20Codex%20%7C%20Gemini%20%7C%20Qwen-8A2BE2)
22
22
  ![Built with TDD](https://img.shields.io/badge/built%20with-TDD%20%2B%20review-ff69b4.svg)
23
23
 
24
24
  **Install:** [`npm i -g @hecer/yoke`](https://www.npmjs.com/package/@hecer/yoke)
@@ -27,7 +27,7 @@
27
27
 
28
28
  > **TL;DR** — `yoke setup .` asks six questions and installs the native harness for your agent. `yoke new my-app --idea="..."` bootstraps a project and drafts its story backlog. `yoke loop run my-app --isolate --review` then implements it behind hard gates: **clean tree → acceptance criteria → your real tests green → an independent model approves → commit**. Add `--parallel=N` for dependency-aware workers, or declare a reference and add `--quality` for a bounded critic/repair gauntlet. If any blocking gate is red, nothing is committed. Proof lives in `.yoke/proof/<story>/`.
29
29
 
30
- **New in 1.10.0:** [dashboard search, filters and period comparisons](docs/DASHBOARD-EVOLUTION.md), [batch task assessments with separate planning models](docs/CAPABILITY-ROUTING.md), and [Windows sandbox preflight and process supervision](docs/WINDOWS-RUNNER-VALIDATION.md). Existing routing settings remain authoritative. See the [changelog](CHANGELOG.md) for migration and validation limits.
30
+ **New in 1.12.0:** [Qwen Code hardening and explicit DeepSeek/Kimi API model profiles](docs/QWEN-MODEL-SUPPORT.md). Since 1.10.0, Yoke also includes [dashboard search, filters and period comparisons](docs/DASHBOARD-EVOLUTION.md), [batch task assessments with separate planning models](docs/CAPABILITY-ROUTING.md), and [Windows sandbox preflight and process supervision](docs/WINDOWS-RUNNER-VALIDATION.md). Existing routing settings remain authoritative. See the [changelog](CHANGELOG.md) for migration and validation limits.
31
31
 
32
32
  ### One dashboard, multiple projects
33
33
 
@@ -76,6 +76,19 @@ See [the 1.1 migration guide](docs/MIGRATING-TO-1.1.md) for setup/decision parit
76
76
 
77
77
  ---
78
78
 
79
+ ## Qwen Code, DeepSeek and Kimi
80
+
81
+ Qwen Code supports Yoke's setup, routing, planning and review workflows. New Qwen
82
+ setups use your configured model. To add DeepSeek and Kimi API model profiles:
83
+
84
+ ```sh
85
+ yoke setup . --yes --agent=qwen --runner=qwen --model-provider=deepseek,kimi
86
+ ```
87
+
88
+ Set `DEEPSEEK_API_KEY` and `MOONSHOT_API_KEY` in the environment. Presets include
89
+ DeepSeek V4 Flash/Pro and Kimi K2.6/K2.7 Code/K3, executed through Qwen Code.
90
+ See [setup, permissions, reasoning configuration and validation limits](docs/QWEN-MODEL-SUPPORT.md).
91
+
79
92
  ## Why Yoke exists
80
93
 
81
94
  Agentic coding in 2026 fails in four well-documented ways. Yoke answers each one **mechanically** — in code, not in a prompt the agent can ignore:
@@ -83,11 +96,11 @@ Agentic coding in 2026 fails in four well-documented ways. Yoke answers each one
83
96
  | The pain | What actually happens | What Yoke does about it |
84
97
  |---|---|---|
85
98
  | 🎭 **The verification gap** — *"agent says done, but it isn't"* | A success message can omit untested acceptance criteria. | The loop executes acceptance and project checks. Enabled review and browser gates must pass before a story lands. `yoke check` exposes unmapped outcomes as unverified. |
86
- | 🔀 **Three agents, three configs** | Teams hand-maintain `CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, skills, and MCP wiring separately — copy-paste drift everywhere | **One canon → `yoke retrofit`** generates the idiomatic native artifacts for each agent. Change the canon once, re-retrofit everywhere. |
99
+ | 🔀 **Four agents, four configs** | Teams hand-maintain `CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, skills, and MCP wiring separately — copy-paste drift everywhere | **One canon → `yoke retrofit`** generates the idiomatic native artifacts for each agent. Change the canon once, re-retrofit everywhere. |
87
100
  | 🌀 **Overnight loops going off the rails** | Raw Ralph-loop users "wake up to broken codebases that don't compile" | Yoke is **"Ralph, but with gates"**: clean-worktree gate, acceptance-criteria gate, green-tests gate, review gate, per-story worktree isolation, idle-timeout watchdog, single-flight lock, commit integrity. |
88
101
  | 😵 **Review fatigue** | AI adoption nearly doubles PR volume and review time; humans start skimming | **`yoke review`**: a second model writes a schema-validated pass/fail verdict — chainable into verify, pre-push, or CI. Cross-model review catches what self-review misses. |
89
102
 
90
- **Who it's for:** anyone driving Claude Code, Codex CLI, or Gemini CLI on real projects — especially if you use more than one, want autonomous runs you can trust, or are tired of "done" meaning "probably". Greenfield (`yoke new`) and brownfield (`yoke retrofit`) both work.
103
+ **Who it's for:** anyone driving Claude Code, Codex CLI, Gemini CLI, or Qwen Code on real projects — especially if you use more than one, want autonomous runs you can trust, or are tired of "done" meaning "probably". Greenfield (`yoke new`) and brownfield (`yoke retrofit`) both work.
91
104
 
92
105
  **Who it's not for:** if you want a chat pair-programmer with no process, you don't need a harness. Yoke is for shipping with discipline.
93
106
 
@@ -142,7 +155,7 @@ The canon is also packaged as a Claude Code plugin — the repo is its own marke
142
155
  /plugin install yoke@yoke
143
156
  ```
144
157
 
145
- That gives you all canon skills under the `yoke:` namespace (e.g. `yoke:tdd`, `yoke:review`) inside Claude Code — no retrofit needed. The `yoke` CLI (loop, gates, retrofit for Codex/Gemini) still comes from `npm i -g @hecer/yoke`. Gemini CLI users can likewise `gemini extensions install https://github.com/HECer/yoke`.
158
+ That gives you all canon skills under the `yoke:` namespace (e.g. `yoke:tdd`, `yoke:review`) inside Claude Code — no retrofit needed. The `yoke` CLI (loop, gates, retrofit for Codex/Gemini/Qwen) still comes from `npm i -g @hecer/yoke`. Gemini CLI users can likewise `gemini extensions install https://github.com/HECer/yoke`.
146
159
 
147
160
  For Codex, no preinstalled skill is required: run `npx @hecer/yoke setup .` in a terminal, or
148
161
  ask Codex to run the six-question Yoke setup flow. The retrofit writes native skills to
@@ -169,7 +182,7 @@ Auto-upgrade is deliberately **not** the default: a gate harness shouldn't chang
169
182
 
170
183
  ## 🤖 Driving it through an agent
171
184
 
172
- Yoke is meant to be operated *by* your coding agent — after a retrofit, the agent has the skills, the safety policy, and the routing, so it knows the methodology. Copy-paste prompts (identical wording works for Claude Code, Codex CLI, and Gemini CLI):
185
+ Yoke is meant to be operated *by* your coding agent — after a retrofit, the agent has the skills, the safety policy, and the routing, so it knows the methodology. Copy-paste prompts (identical wording works for Claude Code, Codex CLI, Gemini CLI, and Qwen Code):
173
186
 
174
187
  > **Set it up** — *"Set up Yoke in this project. Ask me the Yoke setup questions one at a time with your recommendation, then run `yoke setup . --yes` with the selected host, agents, code graph, loop, runner, and decision policy. Commit in my configured identity."*
175
188
 
@@ -193,10 +206,10 @@ Yoke's CLI is deterministic and chainable by design: an agent (or a shell `&&`)
193
206
  | `yoke projects add\|list\|remove` | Register a project, list registrations or remove a reference by ID | `0` · `2` invalid/unavailable |
194
207
  | `yoke check [dir] [--json] [--requirement=] [--protect [--refresh]]` | Execute acceptance checks or explicitly pin their infrastructure | `0` passed/pinned · `1` failed · `2` unverified/unavailable |
195
208
  | `yoke goal set\|run\|resume\|pause\|status\|handoff\|budget [dir]` | Durable objectives, provider handoff, protected checks and checkpoint budgets | run/resume: `0` complete · `1` unfinished · `2` unavailable |
196
- | `yoke setup [dir] [--yes] [--host=] [--agent=] [--runner=] [--code-graph=] [--decision-policy=] [--loop\|--no-loop] [--routing\|--no-routing]` | Shared six-question setup for Claude, Codex, and Gemini; routing defaults on for new setups and preserves explicit opt-outs | `0` · `1` invalid setup |
209
+ | `yoke setup [dir] [--yes] [--host=] [--agent=] [--runner=] [--code-graph=] [--decision-policy=] [--loop\|--no-loop] [--routing\|--no-routing] [--model-provider=deepseek,kimi]` | Shared setup for Claude, Codex, Gemini and Qwen; optional DeepSeek/Kimi API profiles run through Qwen | `0` · `1` invalid setup |
197
210
  | `yoke validate [canonDir]` | Validate the canon (schema, frontmatter, templates) | `0` valid · `1` errors |
198
211
  | `yoke new <dir> [--idea=] [--agent=] [--runner=] [--loop]` | Greenfield bootstrap: git init → scaffold → retrofit → context → PRD (drafted from `--idea`) → committed | `0` · `1` usage / non-empty dir / draft failed (scaffold survives) · `2` draft agent unavailable |
199
- | `yoke retrofit [dir] [--agent=claude,codex,gemini\|all] [--code-graph=graphify\|serena] [--loop]` | Install/update the harness, non-destructively | `0` |
212
+ | `yoke retrofit [dir] [--agent=claude,codex,gemini,qwen\|all] [--code-graph=graphify\|serena] [--loop]` | Install/update the harness for the selected agents, non-destructively | `0` |
200
213
  | `yoke prd draft [dir] --idea= [--runner=] [--force]` | Idea → 5–12 stories with testable acceptance criteria | `0` · `1` invalid/guarded · `2` agent unavailable |
201
214
  | `yoke prd check [dir]` | PRD lint gate (schema, dependencies, cycles, duplicate ids, acceptance) | `0` valid · `1` violations |
202
215
  | `yoke change add\|status [dir] [--idea=]` | Queue a change at any time; the loop turns it into append-only stories at the next safe boundary | `0` · `1` invalid inbox/request |
@@ -224,7 +237,7 @@ Three excellent projects, three different jobs. Honest version:
224
237
  | **Footprint** | Markdown skills (plugin) | ~230 MB with browser runtime; hourly auto-update | Node CLI + markdown canon; Playwright only if you use flow-smoke, resolved **from your project** |
225
238
  | **License** | MIT | MIT | MIT |
226
239
 
227
- **They compose — use all three where they're strongest.** Yoke's canon *ships* the superpowers methodology natively for all three agents (13 skills, [attributed](canon/skills/ATTRIBUTION.md)). And if gstack is installed, `yoke retrofit` detects it and adds a routing note to `CLAUDE.md` telling Claude to prefer gstack's live-browser `/qa`, `/cso`, and ship pipeline for what Yoke deliberately doesn't bundle — no dependency, no conflict, and Codex/Gemini artifacts stay uniform.
240
+ **They compose — use all three where they're strongest.** Yoke's canon *ships* the superpowers methodology natively for all four agents (13 skills, [attributed](canon/skills/ATTRIBUTION.md)). And if gstack is installed, `yoke retrofit` detects it and adds a routing note to `CLAUDE.md` telling Claude to prefer gstack's live-browser `/qa`, `/cso`, and ship pipeline for what Yoke deliberately doesn't bundle — no dependency, no conflict, and Codex/Gemini artifacts stay uniform.
228
241
 
229
242
  **Choose Yoke when** you run more than one agent, want autonomy you can audit (gates + proofs + logs), or want one place to maintain your team's methodology. **Choose gstack when** you live 100% in Claude Code and want the deepest interactive browser QA. **Choose superpowers when** you want the methodology alone, interactively, in Claude Code — or just use it *through* Yoke.
230
243
 
@@ -240,10 +253,12 @@ flowchart TD
240
253
  Skill --> Claude["Claude Code<br/>.claude/skills · .mcp.json · hook"]
241
254
  Skill --> Codex["Codex CLI<br/>AGENTS.md · config.toml · RTK.md"]
242
255
  Skill --> Gemini["Gemini CLI<br/>GEMINI.md · commands · settings.json"]
256
+ Skill --> Qwen["Qwen Code<br/>QWEN.md · skills · settings.json"]
243
257
  Loop["🤖 yoke loop — autonomous Ralph loop<br/>gates · verify · review · isolation · proofs"]
244
258
  Claude -. drives .-> Loop
245
259
  Codex -. drives .-> Loop
246
260
  Gemini -. drives .-> Loop
261
+ Qwen -. drives .-> Loop
247
262
  ```
248
263
 
249
264
  Three layers — **Canon** (`yoke validate`) → **Retrofit** (`yoke retrofit`) → **Loop** (`yoke loop`) — on top of a durable **Context layer** (`yoke context`).
@@ -255,6 +270,7 @@ Three layers — **Canon** (`yoke validate`) → **Retrofit** (`yoke retrofit`)
255
270
  | **Claude** | Complete skill packages under `.claude/skills/` (including referenced resources), `AGENTS.md`, `CLAUDE.md`, `.mcp.json` (code-graph + Playwright), and an rtk `PreToolUse` hook when WSL is available |
256
271
  | **Codex** | Complete skill packages under `.agents/skills/`, per-skill implicit-invocation policy, `AGENTS.md`, `RTK.md`, `.codex/config.toml`, native hooks, reusable `.codex/agents/*.toml`, and package plugin metadata |
257
272
  | **Gemini** | Complete skill packages under `.gemini/skills/`, an auto-invocation index, `GEMINI.md`, `.gemini/commands/*.toml`, and `.gemini/settings.json` (MCP + `AGENTS.md` context) |
273
+ | **Qwen** | Complete skill packages under `.qwen/skills/`, `QWEN.md`, `.qwen/settings.json`, native invocation restrictions, and an RTK PreToolUse retry guard |
258
274
 
259
275
  > **rtk integration:** Claude receives its PreToolUse hook; Codex receives a native hook adapter around `rtk hook check`; Gemini retains instruction-mode fallback where its CLI has no equivalent command-rewrite lifecycle.
260
276
 
@@ -271,10 +287,10 @@ Three layers — **Canon** (`yoke validate`) → **Retrofit** (`yoke retrofit`)
271
287
 
272
288
  `yoke retrofit` installs all of these into each agent natively. Provenance is credited in [`canon/skills/ATTRIBUTION.md`](canon/skills/ATTRIBUTION.md).
273
289
 
274
- To stop overlapping skills from auto-invoking against each other, `canon/AGENTS.md` carries a **skill routing & precedence** block (methodology before role; one canonical entrypoint per concern — e.g. pre-merge code review is always `review`), emitted into all three agents.
290
+ To stop overlapping skills from auto-invoking against each other, `canon/AGENTS.md` carries a **skill routing & precedence** block (methodology before role; one canonical entrypoint per concern — e.g. pre-merge code review is always `review`), emitted into all four agents.
275
291
 
276
292
  Each manifest entry also declares `invocation: auto|manual`. Retrofit translates that intent into
277
- the provider's native controls: Claude disables model invocation for manual skills, Codex writes
293
+ the provider's native controls: Claude and Qwen disable model invocation for manual skills, Codex writes
278
294
  `agents/openai.yaml`, and Gemini lists only automatic skills in its generated index. Validation
279
295
  rejects conflicting package metadata and broken local Markdown links before anything is installed.
280
296
 
@@ -573,13 +589,13 @@ parent remains the strong planner/controller. Before each bounded story it recei
573
589
  story, acceptance criteria, and at most three eligible worker profiles, then returns one
574
590
  machine-readable choice. The worker can be a cheaper/faster Claude, Codex, or Gemini profile;
575
591
  `SELF` keeps difficult work on the parent. Explicit project rules skip the controller.
576
- Loop runners disable native delegation in Codex, Claude and Gemini so it cannot multiply
592
+ Loop runners disable native delegation in Codex, Claude, Gemini and Qwen so it cannot multiply
577
593
  the Yoke worker budget. Integration retains its execution slot until the candidate lands.
578
594
 
579
595
  **Provider support:** adaptive routing uses Yoke's shared provider adapter and works with Claude
580
- Code, Codex CLI, and Gemini CLI, including mixed-provider worker lists. Internal contract tests
581
- cover invocation and routing behavior for all three providers. The measured performance evidence
582
- below is intentionally **Codex-only**; it does not claim equivalent Claude or Gemini savings
596
+ Code, Codex CLI, Gemini CLI, and Qwen Code, including mixed-provider worker lists. Internal contract tests
597
+ cover invocation and routing behavior for all four providers. The measured performance evidence
598
+ below is intentionally **Codex-only**; it does not claim equivalent Claude, Gemini or Qwen savings
583
599
  until authenticated, repeated in-the-wild runs exist for those providers.
584
600
 
585
601
  ```yaml
@@ -673,7 +689,7 @@ for example:
673
689
  An agent can read that ordinary file when the preview is insufficient; nothing is injected into
674
690
  later stories automatically. Repeated identical failures reuse the same content-addressed path.
675
691
  Successful gate output is discarded as before. This affects only commands executed by Yoke's own
676
- gates. It does **not** intercept tool output generated internally by Claude Code, Codex, or Gemini,
692
+ gates. It does **not** intercept tool output generated internally by Claude Code, Codex, Gemini, or Qwen,
677
693
  so benchmark ratios for this feature are not provider-token or billing claims.
678
694
 
679
695
  Command capture is capped at 16 MiB per stdout/stderr stream. Exceeding that quota fails the gate
@@ -897,7 +913,7 @@ release provenance.
897
913
  ## 🧪 Development
898
914
 
899
915
  ```bash
900
- npm test # vitest (1173 tests)
916
+ npm test # vitest (1213 tests)
901
917
  npm run build # tsc, no emit errors
902
918
  npm run yoke -- validate canon
903
919
  ```
@@ -1,6 +1,6 @@
1
1
  name: yoke-canon
2
- version: 1.7.0
3
- agents: [claude, codex, gemini]
2
+ version: 1.12.0
3
+ agents: [claude, codex, gemini, qwen]
4
4
  skills:
5
5
  - { id: tdd, path: skills/tdd, kind: methodology, invocation: auto }
6
6
  - { id: yoke-retrofit, path: skills/yoke-retrofit, kind: methodology, invocation: auto }
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ // PreToolUse guard requesting a retry through RTK. Never evaluates or executes tool commands.
3
+ import { readFileSync } from 'node:fs'
4
+
5
+ const record = value => value !== null && typeof value === 'object' && !Array.isArray(value)
6
+ try {
7
+ const event = JSON.parse(readFileSync(0, 'utf8'))
8
+ if (!record(event) || typeof event.tool_name !== 'string' ||
9
+ (event.hook_event_name !== undefined && event.hook_event_name !== 'PreToolUse')) throw new Error('event')
10
+ let response = {}
11
+ if (event.tool_name === 'run_shell_command') {
12
+ if (!record(event.tool_input) || typeof event.tool_input.command !== 'string' || !event.tool_input.command.trim() || event.tool_input.command.includes('\0')) throw new Error('command')
13
+ const command = event.tool_input.command
14
+ // Restrict rewriting to simple supported invocations. Leave shell syntax,
15
+ // quoted executables, assignments and existing RTK wrappers untouched.
16
+ if (!/[;&|<>`$\r\n()]/u.test(command) && /^\s*(?:git|rg|npm|npx|cargo|pytest|go|docker|kubectl)\s/u.test(command)) {
17
+ const rewritten = command.trimStart().replace(/^rg\s/u, 'grep ')
18
+ response = { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: `Retry this command through RTK: rtk ${rewritten}` } }
19
+ }
20
+ }
21
+ process.stdout.write(JSON.stringify(response) + '\n')
22
+ } catch {
23
+ process.stderr.write('Invalid Qwen PreToolUse hook input\n')
24
+ process.exitCode = 2
25
+ }
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- export const AgentSchema = z.enum(['claude', 'codex', 'gemini']);
2
+ export const AgentSchema = z.enum(['claude', 'codex', 'gemini', 'qwen']);
3
3
  export const PermissionProfileSchema = z.enum(['safe', 'unsafe', 'read-only']);
4
4
  export const ModelSelectionSchema = z.object({
5
5
  model: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/).optional(),
@@ -7,12 +7,16 @@ export function detectHostAgent(env = process.env) {
7
7
  return 'claude';
8
8
  if (env.GEMINI_CLI)
9
9
  return 'gemini';
10
+ if (env.QWEN_CODE || env.QWEN_CODE_SESSION_ID || env.QWEN_CLI)
11
+ return 'qwen';
10
12
  if (env.CODEX_HOME)
11
13
  return 'codex';
12
14
  if (env.CLAUDE_CONFIG_DIR)
13
15
  return 'claude';
14
16
  if (env.GEMINI_CLI_HOME)
15
17
  return 'gemini';
18
+ if (env.QWEN_CLI_HOME)
19
+ return 'qwen';
16
20
  return undefined;
17
21
  }
18
22
  export function resolveRunnerAgent(config, explicit, host) {
@@ -17,6 +17,13 @@ const argsFor = (agent, permissions) => {
17
17
  // Automatic review already selects workspace-write and conflicts with --sandbox.
18
18
  return ['exec', '--approve-for-me', '--json'];
19
19
  }
20
+ // Qwen Code uses its own approval modes and native tool exclusions.
21
+ if (agent === 'qwen') {
22
+ if (permissions === 'unsafe')
23
+ return ['--yolo', '--output-format', 'stream-json'];
24
+ const approval = permissions === 'read-only' ? 'plan' : 'auto-edit';
25
+ return ['--approval-mode', approval, '--sandbox', '--output-format', 'stream-json', ...(permissions === 'safe' ? ['--allowed-tools', 'run_shell_command'] : [])];
26
+ }
20
27
  if (permissions === 'unsafe')
21
28
  return ['--yolo', '--output-format', 'stream-json'];
22
29
  const approval = permissions === 'read-only' ? 'plan' : 'auto_edit';
@@ -30,6 +37,12 @@ export function buildProviderInvocation(agent, prompt, cwd, permissions = 'safe'
30
37
  throw new Error('Gemini does not support the reasoningEffort selection');
31
38
  if (agent === 'gemini' && parsedSelection.nativeMultiAgent === true)
32
39
  throw new Error('Gemini does not support enabling the nativeMultiAgent selection');
40
+ if (agent === 'qwen' && parsedSelection.bare)
41
+ throw new Error('Qwen does not support the bare startup selection');
42
+ if (agent === 'qwen' && parsedSelection.reasoningEffort)
43
+ throw new Error('Qwen does not support the reasoningEffort selection');
44
+ if (agent === 'qwen' && parsedSelection.nativeMultiAgent === true)
45
+ throw new Error('Qwen does not support enabling the nativeMultiAgent selection');
33
46
  const args = argsFor(agent, permissions);
34
47
  if (output.schemaFile !== undefined || output.jsonSchema !== undefined) {
35
48
  if (agent === 'codex' && output.schemaFile && output.jsonSchema === undefined) {
@@ -46,8 +59,18 @@ export function buildProviderInvocation(agent, prompt, cwd, permissions = 'safe'
46
59
  else
47
60
  throw new Error(`${agent} structured output schema requires ${agent === 'codex' ? 'schemaFile' : agent === 'claude' ? 'jsonSchema' : 'a supported native schema option (unavailable)'}`);
48
61
  }
49
- if (parsedSelection.model)
50
- args.push('--model', parsedSelection.model);
62
+ if (parsedSelection.model) {
63
+ const qualified = agent === 'qwen' && parsedSelection.model.includes('::')
64
+ ? parsedSelection.model.match(/^(openai|anthropic|gemini|vertex-ai|qwen-oauth)::(.+)$/u) : undefined;
65
+ if (agent === 'qwen' && parsedSelection.model.includes('::') && !qualified)
66
+ throw Error('Invalid Qwen auth/model selector');
67
+ if (qualified) {
68
+ const model = ModelSelectionSchema.shape.model.parse(qualified[2]);
69
+ args.push('--auth-type', qualified[1], '--model', model);
70
+ }
71
+ else
72
+ args.push('--model', parsedSelection.model);
73
+ }
51
74
  if (parsedSelection.reasoningEffort) {
52
75
  if (agent === 'claude')
53
76
  args.push('--effort', parsedSelection.reasoningEffort);
@@ -58,6 +81,9 @@ export function buildProviderInvocation(agent, prompt, cwd, permissions = 'safe'
58
81
  args.push('--disable', 'multi_agent');
59
82
  if (agent === 'claude' && parsedSelection.nativeMultiAgent === false)
60
83
  args.push('--disallowedTools', 'Agent', 'Task', 'TeamCreate', 'SendMessage');
84
+ if (agent === 'qwen' && parsedSelection.nativeMultiAgent === false) {
85
+ args.push('--exclude-tools', 'agent', 'task', 'create_sub_session', 'team_create', 'send_message');
86
+ }
61
87
  if (parsedSelection.bare) {
62
88
  if (agent === 'codex')
63
89
  args.push('--ignore-user-config');
@@ -22,6 +22,8 @@ export function parseProviderResult(agent, output) {
22
22
  if (direct !== undefined)
23
23
  return direct;
24
24
  }
25
+ if (agent === 'qwen')
26
+ return parseQwenResult(output);
25
27
  const fragments = [];
26
28
  for (const line of output.split(/\r?\n/u)) {
27
29
  const parsed = parseJson(line);
@@ -63,6 +65,34 @@ export function parseProviderResult(agent, output) {
63
65
  }
64
66
  return null;
65
67
  }
68
+ /** Qwen uses assistant content blocks and result envelopes, not Gemini messages. */
69
+ function parseQwenResult(output) {
70
+ let candidate = null;
71
+ for (const line of output.split(/\r?\n/u)) {
72
+ const parsed = parseJson(line);
73
+ if (!parsed.ok || !isRecord(parsed.value))
74
+ continue;
75
+ const event = parsed.value;
76
+ if (event.parent_tool_use_id != null)
77
+ continue;
78
+ if (event.type === 'result') {
79
+ if (event.is_error === true) {
80
+ candidate = null;
81
+ continue;
82
+ }
83
+ const structured = directMachineResult(event.structured_result);
84
+ const result = typeof event.result === 'string' ? parseJson(event.result) : { ok: false };
85
+ candidate = structured ?? (result.ok ? directMachineResult(result.value) : undefined) ?? null;
86
+ }
87
+ else if (event.type === 'assistant' && isRecord(event.message) && Array.isArray(event.message.content)) {
88
+ const text = event.message.content.filter(isRecord).filter(part => part.type === 'text' && typeof part.text === 'string').map(part => part.text).join('');
89
+ const result = parseJson(text);
90
+ if (result.ok)
91
+ candidate = directMachineResult(result.value) ?? candidate;
92
+ }
93
+ }
94
+ return candidate;
95
+ }
66
96
  export function parseProviderTelemetry(agent, lines) {
67
97
  let inputTokens;
68
98
  let cachedInputTokens;
@@ -83,6 +113,8 @@ export function parseProviderTelemetry(agent, lines) {
83
113
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
84
114
  continue;
85
115
  const event = parsed;
116
+ if (agent === 'qwen' && event.parent_tool_use_id != null)
117
+ continue;
86
118
  const message = event.message && typeof event.message === 'object' ? event.message : undefined;
87
119
  const stats = event.stats && typeof event.stats === 'object' ? event.stats : undefined;
88
120
  const usage = (event.usage && typeof event.usage === 'object'
@@ -101,12 +133,12 @@ export function parseProviderTelemetry(agent, lines) {
101
133
  // Older JSON stats only provide model-local token objects. Sum a field
102
134
  // only when every model measured it; a missing measurement is not zero.
103
135
  let source = usage ?? nestedModelTokens ?? modelUsage;
104
- if (agent === 'gemini' && modelEntries.length > 0) {
136
+ if ((agent === 'gemini' || agent === 'qwen') && modelEntries.length > 0) {
105
137
  reportedModels = modelEntries.map(([name]) => name);
106
138
  model = firstModel?.[0];
107
139
  const fields = {
108
- input_tokens: ['input_tokens', 'inputTokens', 'promptTokenCount', 'input'],
109
- output_tokens: ['output_tokens', 'outputTokens', 'candidatesTokenCount', 'output'],
140
+ input_tokens: ['input_tokens', 'inputTokens', 'promptTokenCount', 'input', 'prompt'],
141
+ output_tokens: ['output_tokens', 'outputTokens', 'candidatesTokenCount', 'output', 'candidates'],
110
142
  cached_input_tokens: ['cached_input_tokens', 'cachedInputTokens', 'cachedContentTokenCount', 'cached'],
111
143
  reasoning_output_tokens: ['reasoning_output_tokens', 'reasoningOutputTokens', 'thoughtsTokenCount', 'thoughts'],
112
144
  };
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { parse } from 'yaml';
3
3
  import { readFileSync } from 'node:fs';
4
- export const AgentSchema = z.enum(['claude', 'codex', 'gemini']);
4
+ export const AgentSchema = z.enum(['claude', 'codex', 'gemini', 'qwen']);
5
5
  export const InvocationSchema = z.enum(['auto', 'manual']);
6
6
  export const SkillEntrySchema = z.object({
7
7
  id: z.string().min(1),