@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.
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +37 -0
- package/README.md +40 -24
- package/canon/manifest.yaml +2 -2
- package/canon/tools/qwen-rtk-hook.mjs +25 -0
- package/dist/agents/contracts.js +1 -1
- package/dist/agents/host.js +4 -0
- package/dist/agents/providers.js +28 -2
- package/dist/agents/telemetry.js +35 -3
- package/dist/canon/manifest.js +1 -1
- package/dist/cli.js +27 -19
- package/dist/goals/command.js +2 -2
- package/dist/loop/claims.js +1 -1
- package/dist/loop/decision.js +2 -2
- package/dist/loop/prd.js +1 -1
- package/dist/loop/run-command.js +3 -3
- package/dist/quality/types.js +1 -1
- package/dist/retrofit/apply.js +8 -1
- package/dist/retrofit/config.js +1 -1
- package/dist/retrofit/detect.js +2 -0
- package/dist/retrofit/plan.js +2 -0
- package/dist/retrofit/planners/qwen.js +73 -0
- package/dist/retrofit/qwen-settings.js +17 -0
- package/dist/retrofit/skill-actions.js +2 -1
- package/dist/review/command.js +1 -1
- package/dist/routing/capability.js +1 -1
- package/dist/routing/router.js +1 -1
- package/dist/setup/command.js +27 -7
- package/dist/setup/model-presets.js +48 -0
- package/docs/QWEN-MODEL-SUPPORT.md +142 -0
- package/gemini-extension.json +1 -1
- package/package.json +2 -2
|
@@ -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.
|
|
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
|
}
|
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.
|
|
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.
|
|
6
|
-
<!-- yoke:tests:start -->
|
|
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,
|
|
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
|
|
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
|
[](https://www.npmjs.com/package/@hecer/yoke)
|
|
15
15
|
[](https://www.npmjs.com/package/@hecer/yoke)
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
[](#-license)
|
|
18
18
|

|
|
19
19
|

|
|
20
|
-

|
|
20
|
+

|
|
21
|
+

|
|
22
22
|

|
|
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.
|
|
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
|
-
| 🔀 **
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
581
|
-
cover invocation and routing behavior for all
|
|
582
|
-
below is intentionally **Codex-only**; it does not claim equivalent Claude or
|
|
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
|
|
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 (
|
|
916
|
+
npm test # vitest (1213 tests)
|
|
901
917
|
npm run build # tsc, no emit errors
|
|
902
918
|
npm run yoke -- validate canon
|
|
903
919
|
```
|
package/canon/manifest.yaml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: yoke-canon
|
|
2
|
-
version: 1.
|
|
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
|
+
}
|
package/dist/agents/contracts.js
CHANGED
|
@@ -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(),
|
package/dist/agents/host.js
CHANGED
|
@@ -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) {
|
package/dist/agents/providers.js
CHANGED
|
@@ -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
|
-
|
|
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');
|
package/dist/agents/telemetry.js
CHANGED
|
@@ -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
|
};
|
package/dist/canon/manifest.js
CHANGED
|
@@ -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),
|