@hecer/yoke 1.15.1 → 1.16.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 +3 -3
- package/.codex-plugin/plugin.json +2 -2
- package/CHANGELOG.md +22 -0
- package/README.md +24 -21
- package/canon/manifest.yaml +2 -2
- package/canon/skills/yoke-retrofit/SKILL.md +1 -1
- package/dist/agents/contracts.js +1 -1
- package/dist/agents/host.js +4 -0
- package/dist/agents/providers.js +26 -5
- package/dist/agents/supervision.js +1 -1
- package/dist/agents/telemetry.js +17 -4
- package/dist/change/inbox.js +1 -1
- package/dist/prd/command.js +2 -2
- package/dist/retrofit/detect.js +2 -0
- package/dist/retrofit/plan.js +2 -0
- package/dist/retrofit/planners/hermes.js +32 -0
- package/dist/retrofit/skill-actions.js +2 -1
- package/dist/review/command.js +1 -1
- package/dist/review/verdict.js +1 -1
- package/dist/routing/capability.js +1 -1
- package/dist/routing/router.js +1 -1
- package/dist/setup/command.js +3 -0
- package/docs/HARNESSES.md +29 -14
- package/gemini-extension.json +2 -2
- package/package.json +3 -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.
|
|
6
|
-
"description": "Cross-agent coding harness for Claude, Codex, Gemini, Qwen, OpenCode, Kilo and
|
|
5
|
+
"version": "1.16.0",
|
|
6
|
+
"description": "Cross-agent coding harness for Claude, Codex, Gemini, Qwen, OpenCode, Kilo, Pi and Hermes: one curated skill canon 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", "qwen-code", "opencode", "kilo", "pi"],
|
|
11
|
+
"keywords": ["harness", "cross-agent", "tdd", "code-review", "autonomous-loop", "codex", "gemini-cli", "qwen-code", "opencode", "kilo", "pi", "hermes"],
|
|
12
12
|
"skills": "./canon/skills/"
|
|
13
13
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yoke",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Cross-agent coding discipline, mechanical gates, and release workflows for
|
|
3
|
+
"version": "1.16.0",
|
|
4
|
+
"description": "Cross-agent coding discipline, mechanical gates, and release workflows for eight supported harnesses",
|
|
5
5
|
"skills": "./canon/skills/",
|
|
6
6
|
"hooks": "./hooks/hooks.json"
|
|
7
7
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.16.0 — 2026-09-17
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Add first-class adapter support for Nous Research's Hermes Agent (`hermes` CLI) as the eighth supported harness.
|
|
7
|
+
- Add non-interactive execution for Hermes via `hermes chat --format stream-json --query-file -`.
|
|
8
|
+
- Map permission profiles to Hermes toolset flags: `safe` to `--toolsets file,terminal`, `read-only` to `--toolsets file`, and `unsafe` to `--yolo`.
|
|
9
|
+
- Add telemetry extraction for Hermes `stream-json` streams: assistant text deltas, token usage (`input`, `output`, `cache_read`, `cache_write`), total cost USD, and active model identities.
|
|
10
|
+
- Recognize successful Hermes `tool_result` events as watchdog progress activity.
|
|
11
|
+
- Add Hermes project marker detection (`.hermes`, `HERMES.md`, `hermes.yaml`, `hermes.json`) and host environment marker detection (`HERMES_SESSION_ID`, `HERMES_CONFIG`, `HERMES_HOME`).
|
|
12
|
+
- Add Hermes retrofit planner generating complete skill packages under `.hermes/skills/`, shared `AGENTS.md` instructions, and a read-only reviewer agent (`.hermes/agents/yoke-reviewer.md`).
|
|
13
|
+
- Add `hermes` to setup CLI options, routing worker presets (`hermes-standard`), fallback review providers, and runner affinity validation.
|
|
14
|
+
- Add Hermes integration documentation in `docs/HARNESSES.md`.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- Update README, package metadata, and manifests to reflect eight supported harnesses across invocation, routing, and retrofit architecture.
|
|
18
|
+
- Synchronize Claude plugin, Codex plugin, Gemini extension, Canon, and npm package versions to 1.16.0.
|
|
19
|
+
|
|
20
|
+
### Migration and validation limits
|
|
21
|
+
- Run `yoke retrofit . --agent=hermes` or `yoke retrofit . --agent=all` to install native Hermes artifacts. External `hermes` CLI must be installed and configured separately; Yoke does not bundle runtimes or credentials.
|
|
22
|
+
- Hermes runner does not support bare startup mode or native multi-agent delegation; Yoke enforces managed worker and single-flight execution boundaries.
|
|
23
|
+
- Local verification: 1,285 tests passed, two platform-specific tests skipped, 141 test files passed; TypeScript lint/build, Canon validation, documentation metadata check, package dry run, and dependency audit passed.
|
|
24
|
+
|
|
3
25
|
## 1.15.1 — 2026-09-16
|
|
4
26
|
|
|
5
27
|
### Fixed
|
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.16.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.16.0<!-- yoke:version:end -->
|
|
6
|
+
<!-- yoke:tests:start -->1287<!-- yoke:tests:end -->
|
|
7
7
|
<!-- yoke:skills:start -->34<!-- yoke:skills:end -->
|
|
8
|
-
<!-- yoke:agents:start -->Claude | Codex | Gemini | Qwen | OpenCode | Kilo | Pi<!-- yoke:agents:end -->
|
|
8
|
+
<!-- yoke:agents:start -->Claude | Codex | Gemini | Qwen | OpenCode | Kilo | Pi | Hermes<!-- yoke:agents:end -->
|
|
9
9
|
|
|
10
|
-
### One harness,
|
|
10
|
+
### One harness, eight 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, Gemini CLI, Qwen Code, OpenCode, Kilo,
|
|
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, Qwen Code, OpenCode, Kilo, Pi coding agent, and Hermes Agent**. 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)
|
|
@@ -104,7 +104,7 @@ Agentic coding in 2026 fails in four well-documented ways. Yoke answers each one
|
|
|
104
104
|
| 🌀 **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. |
|
|
105
105
|
| 😵 **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. |
|
|
106
106
|
|
|
107
|
-
**Who it's for:** anyone driving Claude Code, Codex CLI, Gemini CLI, Qwen Code, OpenCode, Kilo or
|
|
107
|
+
**Who it's for:** anyone driving Claude Code, Codex CLI, Gemini CLI, Qwen Code, OpenCode, Kilo, Pi or Hermes 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.
|
|
108
108
|
|
|
109
109
|
**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.
|
|
110
110
|
|
|
@@ -159,7 +159,7 @@ The canon is also packaged as a Claude Code plugin — the repo is its own marke
|
|
|
159
159
|
/plugin install yoke@yoke
|
|
160
160
|
```
|
|
161
161
|
|
|
162
|
-
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/OpenCode/Kilo/Pi) still comes from `npm i -g @hecer/yoke`. Gemini CLI users can likewise `gemini extensions install https://github.com/HECer/yoke`.
|
|
162
|
+
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/OpenCode/Kilo/Pi/Hermes) still comes from `npm i -g @hecer/yoke`. Gemini CLI users can likewise `gemini extensions install https://github.com/HECer/yoke`.
|
|
163
163
|
|
|
164
164
|
For Codex, no preinstalled skill is required: run `npx @hecer/yoke setup .` in a terminal, or
|
|
165
165
|
ask Codex to run the six-question Yoke setup flow. The retrofit writes native skills to
|
|
@@ -186,7 +186,7 @@ Auto-upgrade is deliberately **not** the default: a gate harness shouldn't chang
|
|
|
186
186
|
|
|
187
187
|
## 🤖 Driving it through an agent
|
|
188
188
|
|
|
189
|
-
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, Qwen Code, OpenCode, Kilo, and
|
|
189
|
+
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, Qwen Code, OpenCode, Kilo, Pi, and Hermes):
|
|
190
190
|
|
|
191
191
|
> **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."*
|
|
192
192
|
|
|
@@ -210,11 +210,11 @@ Yoke's CLI is deterministic and chainable by design: an agent (or a shell `&&`)
|
|
|
210
210
|
| `yoke projects add\|list\|remove` | Register a project, list registrations or remove a reference by ID | `0` · `2` invalid/unavailable |
|
|
211
211
|
| `yoke check [dir] [--json] [--requirement=] [--protect [--refresh]]` | Execute acceptance checks or explicitly pin their infrastructure | `0` passed/pinned · `1` failed · `2` unverified/unavailable |
|
|
212
212
|
| `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 |
|
|
213
|
-
| `yoke setup [dir] [--yes] [--host=] [--agent=] [--runner=] [--code-graph=] [--code-intelligence=off\|shadow\|active] [--decision-policy=] [--loop\|--no-loop] [--routing\|--no-routing] [--model-provider=deepseek,kimi]` | Shared setup for all
|
|
213
|
+
| `yoke setup [dir] [--yes] [--host=] [--agent=] [--runner=] [--code-graph=] [--code-intelligence=off\|shadow\|active] [--decision-policy=] [--loop\|--no-loop] [--routing\|--no-routing] [--model-provider=deepseek,kimi]` | Shared setup for all eight harnesses; optional federated code intelligence and DeepSeek/Kimi API profiles | `0` · `1` invalid setup |
|
|
214
214
|
| `yoke code-intelligence-server [--workspace=] [--mode=off\|shadow\|active]` | Serve the single Yoke-controlled MCP facade for federated code intelligence | `0` · `1` invalid/unavailable |
|
|
215
215
|
| `yoke validate [canonDir]` | Validate the canon (schema, frontmatter, templates) | `0` valid · `1` errors |
|
|
216
216
|
| `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 |
|
|
217
|
-
| `yoke retrofit [dir] [--agent=claude,codex,gemini,qwen,opencode,kilo,pi\|all] [--code-graph=graphify\|serena] [--code-intelligence=off\|shadow\|active] [--loop]` | Install/update the harness for the selected agents, non-destructively | `0` |
|
|
217
|
+
| `yoke retrofit [dir] [--agent=claude,codex,gemini,qwen,opencode,kilo,pi,hermes\|all] [--code-graph=graphify\|serena] [--code-intelligence=off\|shadow\|active] [--loop]` | Install/update the harness for the selected agents, non-destructively | `0` |
|
|
218
218
|
| `yoke prd draft [dir] --idea= [--runner=] [--force]` | Idea → 5–12 stories with testable acceptance criteria | `0` · `1` invalid/guarded · `2` agent unavailable |
|
|
219
219
|
| `yoke prd check [dir]` | PRD lint gate (schema, dependencies, cycles, duplicate ids, acceptance) | `0` valid · `1` violations |
|
|
220
220
|
| `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 |
|
|
@@ -234,7 +234,7 @@ Three excellent projects, three different jobs. Honest version:
|
|
|
234
234
|
| | [superpowers](https://github.com/obra/superpowers) (obra) | [gstack](https://github.com/garrytan/gstack) (Garry Tan) | **Yoke** |
|
|
235
235
|
|---|---|---|---|
|
|
236
236
|
| **What it is** | The canonical *skills methodology*: brainstorm → plan → TDD → review as composable skills | A *software factory* for Claude Code: ~40 role skills (QA, CSO, ship…) + a real Chromium browser layer | A *cross-agent harness*: one canon → native installs, plus a gated autonomous loop |
|
|
237
|
-
| **Agents** | Claude Code first | Claude Code + hosts like Codex/Cursor/Kiro — **no Gemini CLI** | **Claude Code, Codex CLI, Gemini CLI, Qwen Code, OpenCode, Kilo, Pi** from one source of truth |
|
|
237
|
+
| **Agents** | Claude Code first | Claude Code + hosts like Codex/Cursor/Kiro — **no Gemini CLI** | **Claude Code, Codex CLI, Gemini CLI, Qwen Code, OpenCode, Kilo, Pi, Hermes** from one source of truth |
|
|
238
238
|
| **Enforcement** | Advisory — skills *describe* the discipline; following them is up to the agent | Skill-driven; browser QA is genuinely real | **Mechanical** — gates live in code: clean tree, acceptance criteria, green tests, review verdict, commit integrity |
|
|
239
239
|
| **Autonomy** | Interactive sessions | Interactive slash-commands (`/qa`, `/ship`, …) | Opt-in **Ralph loop** with watchdog, worktree isolation, single-flight lock, per-story proofs |
|
|
240
240
|
| **Visual QA** | — | **Best-in-class**: live browser daemon (Chromium/CDP) with deep interactive QA | Built-in `flow-smoke` gate: screenshots always, video on failure, labelled per story — lighter, but *enforced* and cross-agent |
|
|
@@ -242,7 +242,7 @@ Three excellent projects, three different jobs. Honest version:
|
|
|
242
242
|
| **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** |
|
|
243
243
|
| **License** | MIT | MIT | MIT |
|
|
244
244
|
|
|
245
|
-
**They compose — use all three where they're strongest.** Yoke's canon *ships* the superpowers methodology natively for all
|
|
245
|
+
**They compose — use all three where they're strongest.** Yoke's canon *ships* the superpowers methodology natively for all eight 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 non-Claude artifacts stay uniform.
|
|
246
246
|
|
|
247
247
|
**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.
|
|
248
248
|
|
|
@@ -262,6 +262,7 @@ flowchart TD
|
|
|
262
262
|
Skill --> OpenCode["OpenCode<br/>AGENTS.md · opencode.json · .opencode/skills"]
|
|
263
263
|
Skill --> Kilo["Kilo<br/>AGENTS.md · kilo.jsonc · .kilo/skills"]
|
|
264
264
|
Skill --> Pi["Pi<br/>AGENTS.md · .pi/settings.json · .pi/skills"]
|
|
265
|
+
Skill --> Hermes["Hermes<br/>AGENTS.md · .hermes/skills · yoke-reviewer.md"]
|
|
265
266
|
Loop["🤖 yoke loop — autonomous Ralph loop<br/>gates · verify · review · isolation · proofs"]
|
|
266
267
|
Claude -. drives .-> Loop
|
|
267
268
|
Codex -. drives .-> Loop
|
|
@@ -270,6 +271,7 @@ flowchart TD
|
|
|
270
271
|
OpenCode -. drives .-> Loop
|
|
271
272
|
Kilo -. drives .-> Loop
|
|
272
273
|
Pi -. drives .-> Loop
|
|
274
|
+
Hermes -. drives .-> Loop
|
|
273
275
|
```
|
|
274
276
|
|
|
275
277
|
Three layers — **Canon** (`yoke validate`) → **Retrofit** (`yoke retrofit`) → **Loop** (`yoke loop`) — on top of a durable **Context layer** (`yoke context`).
|
|
@@ -285,6 +287,7 @@ Three layers — **Canon** (`yoke validate`) → **Retrofit** (`yoke retrofit`)
|
|
|
285
287
|
| **OpenCode** | Complete skill packages under `.opencode/skills/`, shared `AGENTS.md`, merged `opencode.json` (instructions + local MCP), and `.opencode/agents/yoke-reviewer.md` |
|
|
286
288
|
| **Kilo** | Complete skill packages under `.kilo/skills/`, shared `AGENTS.md`, merged `kilo.jsonc` (instructions + local MCP), and `.kilo/agents/yoke-reviewer.md` |
|
|
287
289
|
| **Pi** | Complete skill packages under `.pi/skills/`, shared `AGENTS.md`, and merged `.pi/settings.json`; Pi's tool allowlists are applied at invocation time |
|
|
290
|
+
| **Hermes** | Complete skill packages under `.hermes/skills/`, shared `AGENTS.md`, and `.hermes/agents/yoke-reviewer.md` |
|
|
288
291
|
|
|
289
292
|
> **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.
|
|
290
293
|
|
|
@@ -301,11 +304,11 @@ Three layers — **Canon** (`yoke validate`) → **Retrofit** (`yoke retrofit`)
|
|
|
301
304
|
|
|
302
305
|
`yoke retrofit` installs all of these into each selected agent natively. Provenance is credited in [`canon/skills/ATTRIBUTION.md`](canon/skills/ATTRIBUTION.md).
|
|
303
306
|
|
|
304
|
-
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
|
|
307
|
+
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 eight agents.
|
|
305
308
|
|
|
306
309
|
Each manifest entry also declares `invocation: auto|manual`. Retrofit translates that intent into
|
|
307
310
|
the provider's native controls: Claude and Qwen disable model invocation for manual skills, Codex writes
|
|
308
|
-
`agents/openai.yaml`, Gemini lists only automatic skills in its generated index, and OpenCode/Kilo/Pi
|
|
311
|
+
`agents/openai.yaml`, Gemini lists only automatic skills in its generated index, and OpenCode/Kilo/Pi/Hermes
|
|
309
312
|
receive complete project-local skill packages. Validation
|
|
310
313
|
rejects conflicting package metadata and broken local Markdown links before anything is installed.
|
|
311
314
|
|
|
@@ -608,8 +611,8 @@ Loop runners disable native delegation in Codex, Claude, Gemini, Qwen, OpenCode
|
|
|
608
611
|
the Yoke worker budget. Integration retains its execution slot until the candidate lands.
|
|
609
612
|
|
|
610
613
|
**Provider support:** adaptive routing uses Yoke's shared provider adapter and works with Claude
|
|
611
|
-
Code, Codex CLI, Gemini CLI, Qwen Code, OpenCode, Kilo and
|
|
612
|
-
Internal contract tests cover invocation and routing behavior for all
|
|
614
|
+
Code, Codex CLI, Gemini CLI, Qwen Code, OpenCode, Kilo, Pi, and Hermes, including mixed-provider worker lists.
|
|
615
|
+
Internal contract tests cover invocation and routing behavior for all eight providers. The measured
|
|
613
616
|
performance evidence below is intentionally **Codex-only**; it does not claim equivalent savings
|
|
614
617
|
until authenticated, repeated in-the-wild runs exist for each provider.
|
|
615
618
|
|
|
@@ -910,7 +913,7 @@ src/
|
|
|
910
913
|
canon/ # manifest schema + validator (yoke validate)
|
|
911
914
|
code-intelligence/ # federated MCP facade, adapters, snapshots and guarded edits
|
|
912
915
|
change/ # append-only change inbox · planning · independent coverage review
|
|
913
|
-
retrofit/ # detect · plan · apply · planners (all
|
|
916
|
+
retrofit/ # detect · plan · apply · planners (all eight harnesses) · tools
|
|
914
917
|
loop/ # prd · gates · runner · verify · git/worktree · loop · run-command · lock · cleanup
|
|
915
918
|
quality/ # reference collection · blind critic · bounded repair · candidate comparison
|
|
916
919
|
new/ # yoke new — greenfield bootstrap
|
|
@@ -931,7 +934,7 @@ release provenance.
|
|
|
931
934
|
## 🧪 Development
|
|
932
935
|
|
|
933
936
|
```bash
|
|
934
|
-
npm test # vitest (
|
|
937
|
+
npm test # vitest (1287 tests)
|
|
935
938
|
npm run build # tsc, no emit errors
|
|
936
939
|
npm run yoke -- validate canon
|
|
937
940
|
```
|
package/canon/manifest.yaml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: yoke-canon
|
|
2
|
-
version: 1.
|
|
3
|
-
agents: [claude, codex, gemini, qwen, opencode, kilo, pi]
|
|
2
|
+
version: 1.16.0
|
|
3
|
+
agents: [claude, codex, gemini, qwen, opencode, kilo, pi, hermes]
|
|
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 }
|
|
@@ -7,7 +7,7 @@ description: Use when asked to "retrofit", "yoke this project", or set up the Yo
|
|
|
7
7
|
|
|
8
8
|
Set up or update Yoke through the shared `yoke setup` contract.
|
|
9
9
|
|
|
10
|
-
1. Inspect the project and identify the current host (`claude`, `codex`, `gemini`, `qwen`, `opencode`, `kilo`, or `
|
|
10
|
+
1. Inspect the project and identify the current host (`claude`, `codex`, `gemini`, `qwen`, `opencode`, `kilo`, `pi`, or `hermes`).
|
|
11
11
|
2. Ask these setup questions one at a time and give a direct recommendation:
|
|
12
12
|
- target agents (recommend the current host; use `all` for deliberately cross-agent projects),
|
|
13
13
|
- code-graph tool,
|
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', 'qwen', 'opencode', 'kilo', 'pi']);
|
|
2
|
+
export const AgentSchema = z.enum(['claude', 'codex', 'gemini', 'qwen', 'opencode', 'kilo', 'pi', 'hermes']);
|
|
3
3
|
export const PermissionProfileSchema = z.enum(['safe', 'unsafe', 'read-only']);
|
|
4
4
|
export const ModelSelectionSchema = z.object({
|
|
5
5
|
provider: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/).optional(),
|
package/dist/agents/host.js
CHANGED
|
@@ -13,6 +13,8 @@ export function detectHostAgent(env = process.env) {
|
|
|
13
13
|
return 'opencode';
|
|
14
14
|
if (env.KILO_CLIENT || env.KILO_CONFIG || env.KILO_CONFIG_DIR)
|
|
15
15
|
return 'kilo';
|
|
16
|
+
if (env.HERMES_SESSION_ID || env.HERMES_CONFIG)
|
|
17
|
+
return 'hermes';
|
|
16
18
|
if (env.CODEX_HOME)
|
|
17
19
|
return 'codex';
|
|
18
20
|
if (env.CLAUDE_CONFIG_DIR)
|
|
@@ -21,6 +23,8 @@ export function detectHostAgent(env = process.env) {
|
|
|
21
23
|
return 'gemini';
|
|
22
24
|
if (env.QWEN_CLI_HOME)
|
|
23
25
|
return 'qwen';
|
|
26
|
+
if (env.HERMES_HOME)
|
|
27
|
+
return 'hermes';
|
|
24
28
|
return undefined;
|
|
25
29
|
}
|
|
26
30
|
export function resolveRunnerAgent(config, explicit, host) {
|
package/dist/agents/providers.js
CHANGED
|
@@ -38,6 +38,13 @@ const argsFor = (agent, permissions) => {
|
|
|
38
38
|
args.push('--tools', permissions === 'read-only' ? 'read,grep,find,ls' : 'read,bash,edit,write');
|
|
39
39
|
return args;
|
|
40
40
|
}
|
|
41
|
+
if (agent === 'hermes') {
|
|
42
|
+
const args = ['chat', '--format', 'stream-json', '--query-file', '-'];
|
|
43
|
+
if (permissions === 'unsafe')
|
|
44
|
+
return [...args, '--yolo'];
|
|
45
|
+
const toolsets = permissions === 'read-only' ? 'file' : 'file,terminal';
|
|
46
|
+
return [...args, '--toolsets', toolsets];
|
|
47
|
+
}
|
|
41
48
|
if (permissions === 'unsafe')
|
|
42
49
|
return ['--yolo', '--output-format', 'stream-json'];
|
|
43
50
|
const approval = permissions === 'read-only' ? 'plan' : 'auto_edit';
|
|
@@ -45,7 +52,7 @@ const argsFor = (agent, permissions) => {
|
|
|
45
52
|
};
|
|
46
53
|
export function buildProviderInvocation(agent, prompt, cwd, permissions = 'safe', selection = {}, output = {}) {
|
|
47
54
|
const parsedSelection = ModelSelectionSchema.parse(selection);
|
|
48
|
-
if (['opencode', 'kilo', 'pi'].includes(agent) && parsedSelection.reasoningEffort && parsedSelection.variant && parsedSelection.reasoningEffort !== parsedSelection.variant) {
|
|
55
|
+
if (['opencode', 'kilo', 'pi', 'hermes'].includes(agent) && parsedSelection.reasoningEffort && parsedSelection.variant && parsedSelection.reasoningEffort !== parsedSelection.variant) {
|
|
49
56
|
throw new Error(`${agent} reasoningEffort and variant selections must match`);
|
|
50
57
|
}
|
|
51
58
|
if (agent === 'gemini' && parsedSelection.bare)
|
|
@@ -60,9 +67,13 @@ export function buildProviderInvocation(agent, prompt, cwd, permissions = 'safe'
|
|
|
60
67
|
throw new Error('Qwen does not support the reasoningEffort selection');
|
|
61
68
|
if (agent === 'qwen' && parsedSelection.nativeMultiAgent === true)
|
|
62
69
|
throw new Error('Qwen does not support enabling the nativeMultiAgent selection');
|
|
63
|
-
if (
|
|
70
|
+
if (agent === 'hermes' && parsedSelection.bare)
|
|
71
|
+
throw new Error('Hermes does not support the bare startup selection');
|
|
72
|
+
if (agent === 'hermes' && parsedSelection.nativeMultiAgent === true)
|
|
73
|
+
throw new Error('Hermes does not support enabling the nativeMultiAgent selection');
|
|
74
|
+
if (parsedSelection.provider && !['opencode', 'kilo', 'pi', 'hermes'].includes(agent))
|
|
64
75
|
throw new Error(`${agent} does not support an explicit provider selection`);
|
|
65
|
-
if (parsedSelection.variant && !['opencode', 'kilo', 'pi'].includes(agent))
|
|
76
|
+
if (parsedSelection.variant && !['opencode', 'kilo', 'pi', 'hermes'].includes(agent))
|
|
66
77
|
throw new Error(`${agent} does not support a model variant selection`);
|
|
67
78
|
const args = argsFor(agent, permissions);
|
|
68
79
|
if (output.schemaFile !== undefined || output.jsonSchema !== undefined) {
|
|
@@ -77,10 +88,10 @@ export function buildProviderInvocation(agent, prompt, cwd, permissions = 'safe'
|
|
|
77
88
|
throw new Error('Inline structured output is unsupported by the Windows provider shell shim');
|
|
78
89
|
args.push('--json-schema', schema);
|
|
79
90
|
}
|
|
80
|
-
else if (!['opencode', 'kilo', 'pi'].includes(agent))
|
|
91
|
+
else if (!['opencode', 'kilo', 'pi', 'hermes'].includes(agent))
|
|
81
92
|
throw new Error(`${agent} structured output schema requires ${agent === 'codex' ? 'schemaFile' : agent === 'claude' ? 'jsonSchema' : 'a supported native schema option (unavailable)'}`);
|
|
82
93
|
}
|
|
83
|
-
if (parsedSelection.provider && agent === 'pi')
|
|
94
|
+
if (parsedSelection.provider && (agent === 'pi' || agent === 'hermes'))
|
|
84
95
|
args.push('--provider', parsedSelection.provider);
|
|
85
96
|
if (parsedSelection.model) {
|
|
86
97
|
const qualified = agent === 'qwen' && parsedSelection.model.includes('::')
|
|
@@ -107,6 +118,8 @@ export function buildProviderInvocation(agent, prompt, cwd, permissions = 'safe'
|
|
|
107
118
|
args.push('--variant', parsedSelection.reasoningEffort);
|
|
108
119
|
else if (agent === 'pi')
|
|
109
120
|
args.push('--thinking', parsedSelection.reasoningEffort);
|
|
121
|
+
else if (agent === 'hermes')
|
|
122
|
+
args.push('--reasoning-effort', parsedSelection.reasoningEffort);
|
|
110
123
|
}
|
|
111
124
|
if (parsedSelection.variant) {
|
|
112
125
|
if ((agent === 'opencode' || agent === 'kilo') && !parsedSelection.reasoningEffort)
|
|
@@ -117,6 +130,12 @@ export function buildProviderInvocation(agent, prompt, cwd, permissions = 'safe'
|
|
|
117
130
|
if (!parsedSelection.reasoningEffort)
|
|
118
131
|
args.push('--thinking', parsedSelection.variant);
|
|
119
132
|
}
|
|
133
|
+
else if (agent === 'hermes') {
|
|
134
|
+
if (parsedSelection.reasoningEffort && parsedSelection.reasoningEffort !== parsedSelection.variant)
|
|
135
|
+
throw new Error('Hermes reasoningEffort and variant selections must match');
|
|
136
|
+
if (!parsedSelection.reasoningEffort)
|
|
137
|
+
args.push('--reasoning-effort', parsedSelection.variant);
|
|
138
|
+
}
|
|
120
139
|
}
|
|
121
140
|
if (agent === 'codex' && parsedSelection.nativeMultiAgent === false)
|
|
122
141
|
args.push('--disable', 'multi_agent');
|
|
@@ -134,6 +153,8 @@ export function buildProviderInvocation(agent, prompt, cwd, permissions = 'safe'
|
|
|
134
153
|
args.push('--pure');
|
|
135
154
|
else if (agent === 'pi')
|
|
136
155
|
throw new Error('Pi does not support the bare startup selection');
|
|
156
|
+
else if (agent === 'hermes')
|
|
157
|
+
throw new Error('Hermes does not support the bare startup selection');
|
|
137
158
|
}
|
|
138
159
|
if (agent === 'gemini' && parsedSelection.nativeMultiAgent === false) {
|
|
139
160
|
return { command: process.execPath, args: [fileURLToPath(new URL('../../hooks/bounded-gemini.mjs', import.meta.url)), ...args], input: prompt, cwd };
|
|
@@ -38,7 +38,7 @@ export function inspectProviderEvent(line) {
|
|
|
38
38
|
return { progress: (event.type === 'item.completed' && ((item?.type === 'command_execution' && item.exit_code === 0) || (item?.type === 'file_change' && item.status === 'completed')))
|
|
39
39
|
|| (event.type === 'tool_execution_end' && event.isError === false)
|
|
40
40
|
|| (event.type === 'tool_use' && event.part?.type === 'tool' && event.part.state?.status === 'completed')
|
|
41
|
-
|| (event.type === 'tool_result' && event.status === 'success')
|
|
41
|
+
|| (event.type === 'tool_result' && (event.status === 'success' || event.is_error === false))
|
|
42
42
|
|| (event.type === 'user' && Array.isArray(event.message?.content) && event.message.content.some((part) => part.type === 'tool_result' && part.is_error === false)) };
|
|
43
43
|
}
|
|
44
44
|
export function inspectProviderDiagnostic(line) {
|
package/dist/agents/telemetry.js
CHANGED
|
@@ -42,7 +42,7 @@ export function parseProviderResult(agent, output) {
|
|
|
42
42
|
if (agent === 'claude' && event.parent_tool_use_id != null)
|
|
43
43
|
continue;
|
|
44
44
|
if (event.type === 'error' || event.type === 'turn.failed' ||
|
|
45
|
-
(event.type === 'result' && (event.is_error === true || event.status === 'error')))
|
|
45
|
+
(event.type === 'result' && (event.is_error === true || event.status === 'error' || event.error != null)))
|
|
46
46
|
return null;
|
|
47
47
|
switch (agent) {
|
|
48
48
|
case 'claude':
|
|
@@ -77,6 +77,17 @@ export function parseProviderResult(agent, output) {
|
|
|
77
77
|
fragments.push(text);
|
|
78
78
|
}
|
|
79
79
|
break;
|
|
80
|
+
case 'hermes':
|
|
81
|
+
if (event.type === 'result' && directMachineResult(event.structured_output ?? event.structured_result) !== undefined) {
|
|
82
|
+
structuredResult = event.structured_output ?? event.structured_result;
|
|
83
|
+
}
|
|
84
|
+
if (event.type === 'text' && typeof event.text === 'string')
|
|
85
|
+
fragments.push(event.text);
|
|
86
|
+
if (event.type === 'result' && typeof event.text === 'string')
|
|
87
|
+
fragments.push(event.text);
|
|
88
|
+
if (event.type === 'result' && typeof event.result === 'string')
|
|
89
|
+
fragments.push(event.result);
|
|
90
|
+
break;
|
|
80
91
|
}
|
|
81
92
|
}
|
|
82
93
|
if (structuredResult !== undefined)
|
|
@@ -202,7 +213,9 @@ export function parseProviderTelemetry(agent, lines) {
|
|
|
202
213
|
? message.usage
|
|
203
214
|
: stats?.usage && typeof stats.usage === 'object'
|
|
204
215
|
? stats.usage
|
|
205
|
-
:
|
|
216
|
+
: event.tokens && typeof event.tokens === 'object'
|
|
217
|
+
? event.tokens
|
|
218
|
+
: stats);
|
|
206
219
|
const models = isRecord(stats?.models) ? stats.models : undefined;
|
|
207
220
|
const modelEntries = models ? Object.entries(models) : [];
|
|
208
221
|
const firstModel = modelEntries.length === 1 ? modelEntries[0] : undefined;
|
|
@@ -242,8 +255,8 @@ export function parseProviderTelemetry(agent, lines) {
|
|
|
242
255
|
source.cached_input_tokens = aggregateCached;
|
|
243
256
|
}
|
|
244
257
|
const inValue = finite(source?.input_tokens ?? source?.inputTokens ?? source?.prompt_tokens ?? source?.promptTokenCount ?? source?.input);
|
|
245
|
-
const cachedValue = finite(source?.cached_input_tokens ?? source?.cache_read_input_tokens ?? source?.cachedInputTokens ?? source?.cacheRead ?? source?.cachedContentTokenCount ?? source?.cached);
|
|
246
|
-
const cacheWriteValue = finite(source?.cache_write_input_tokens ?? source?.cache_creation_input_tokens ?? source?.cacheWriteInputTokens ?? source?.cacheWrite);
|
|
258
|
+
const cachedValue = finite(source?.cached_input_tokens ?? source?.cache_read_input_tokens ?? source?.cachedInputTokens ?? source?.cacheRead ?? source?.cachedContentTokenCount ?? source?.cached ?? source?.cache_read);
|
|
259
|
+
const cacheWriteValue = finite(source?.cache_write_input_tokens ?? source?.cache_creation_input_tokens ?? source?.cacheWriteInputTokens ?? source?.cacheWrite ?? source?.cache_write);
|
|
247
260
|
const outValue = finite(source?.output_tokens ?? source?.outputTokens ?? source?.completion_tokens ?? source?.candidatesTokenCount ?? source?.output);
|
|
248
261
|
const reasoningValue = finite(source?.reasoning_output_tokens ?? source?.reasoningOutputTokens ?? source?.thoughtsTokenCount ?? source?.thoughts);
|
|
249
262
|
if (inValue !== undefined)
|
package/dist/change/inbox.js
CHANGED
|
@@ -104,7 +104,7 @@ export function buildChangePrompt(request, proposalPath, stories, brief = '') {
|
|
|
104
104
|
'Every proposed story must have passes: false and 2-5 structured acceptance criteria.',
|
|
105
105
|
'Every criterion must have a stable id, behavioral text, and one or more executable verify commands.',
|
|
106
106
|
'Each criterion id must appear in every verify command; each entry must be one approved test command without shell control operators.',
|
|
107
|
-
'Use only a configured Yoke harness if an optional agent affinity is useful (claude, codex, gemini, qwen, opencode, kilo, or
|
|
107
|
+
'Use only a configured Yoke harness if an optional agent affinity is useful (claude, codex, gemini, qwen, opencode, kilo, pi, or hermes).',
|
|
108
108
|
'',
|
|
109
109
|
`Write ONLY a YAML array of the NEW stories to this exact file: ${proposalPath}`,
|
|
110
110
|
'Do not edit .yoke/prd.yaml or any source file. Do not commit.',
|
package/dist/prd/command.js
CHANGED
|
@@ -39,7 +39,7 @@ export function buildPrdDraftPrompt(idea, planningBrief) {
|
|
|
39
39
|
if (planningBrief?.trim()) {
|
|
40
40
|
lines.push('', '## Approved planning brief (treat these decisions as settled)', planningBrief.trim(), '', 'Do not reopen settled choices or invent alternatives that contradict this brief.');
|
|
41
41
|
}
|
|
42
|
-
lines.push('', 'Break the idea into 5-12 small, independently shippable stories; each must fit one loop iteration.', 'Each story needs:', '- id: STORY-1, STORY-2, ... (unique)', '- title: one imperative sentence', '- priority: dense integers from 1 (lower = built first)', '- needs: optional list of story IDs that must pass first; the graph must be acyclic', '- area: optional collision domain for safe parallel scheduling', '- agent: optional harness affinity (claude, codex, gemini, qwen, opencode, kilo, or
|
|
42
|
+
lines.push('', 'Break the idea into 5-12 small, independently shippable stories; each must fit one loop iteration.', 'Each story needs:', '- id: STORY-1, STORY-2, ... (unique)', '- title: one imperative sentence', '- priority: dense integers from 1 (lower = built first)', '- needs: optional list of story IDs that must pass first; the graph must be acyclic', '- area: optional collision domain for safe parallel scheduling', '- agent: optional harness affinity (claude, codex, gemini, qwen, opencode, kilo, pi, or hermes)', '- acceptance: 2-5 testable, behavioral criteria (observable outcomes, never implementation steps)', ' Each criterion is an object with a stable id, behavioral text, and verify: [one or more approved test commands].', ' Every criterion id must appear in every verify command; use one test command without shell control operators.', '- passes: false', '- writes: explicit relative write scopes for safe scheduling', assessmentInstructions, 'Include a complete assessment on every story in this same planning pass. Do not choose worker model names; the scheduler does that.', '', 'If the project has no source code yet, STORY-1 must scaffold the project skeleton with a runnable', 'test suite, and its acceptance must include that the verify command (verify.command in', '.yoke/config.yaml) exits 0.', '', 'Write ONLY the file .yoke/prd.yaml as a YAML array of stories in exactly that shape.', 'Do not modify any other file. Do not commit.');
|
|
43
43
|
return lines.join('\n');
|
|
44
44
|
}
|
|
45
45
|
export function prdFile(targetDir) {
|
|
@@ -71,7 +71,7 @@ export function runPrdDraft(targetDir, opts) {
|
|
|
71
71
|
const planner = resolvePlanner(config, resolveRunnerAgent(config, undefined, detectHostAgent()), config?.runner, opts.runner);
|
|
72
72
|
const agent = planner.agent;
|
|
73
73
|
if (!available(agent)) {
|
|
74
|
-
console.error(`Agent CLI "${agent}" was not found on PATH. Install it, or pick another with --runner=<claude|codex|gemini|qwen|opencode|kilo|pi>.`);
|
|
74
|
+
console.error(`Agent CLI "${agent}" was not found on PATH. Install it, or pick another with --runner=<claude|codex|gemini|qwen|opencode|kilo|pi|hermes>.`);
|
|
75
75
|
return 2;
|
|
76
76
|
}
|
|
77
77
|
const idleMs = resolveIdleMs(opts.timeoutMinutes, undefined);
|
package/dist/retrofit/detect.js
CHANGED
|
@@ -18,6 +18,8 @@ export function detectProject(targetDir) {
|
|
|
18
18
|
agents.push('kilo');
|
|
19
19
|
if (has('.pi') || has('PI.md'))
|
|
20
20
|
agents.push('pi');
|
|
21
|
+
if (has('.hermes') || has('HERMES.md') || has('hermes.yaml') || has('hermes.json'))
|
|
22
|
+
agents.push('hermes');
|
|
21
23
|
return {
|
|
22
24
|
agents,
|
|
23
25
|
hasAgentsMd: has('AGENTS.md'),
|
package/dist/retrofit/plan.js
CHANGED
|
@@ -5,6 +5,7 @@ import { planQwen } from './planners/qwen.js';
|
|
|
5
5
|
import { planOpenCode } from './planners/opencode.js';
|
|
6
6
|
import { planKilo } from './planners/kilo.js';
|
|
7
7
|
import { planPi } from './planners/pi.js';
|
|
8
|
+
import { planHermes } from './planners/hermes.js';
|
|
8
9
|
import { baseContextActions } from './context-actions.js';
|
|
9
10
|
export function planClaudeRetrofit(canonDir, targetDir) {
|
|
10
11
|
return planClaude(canonDir, targetDir);
|
|
@@ -17,6 +18,7 @@ export const PLANNERS = {
|
|
|
17
18
|
opencode: planOpenCode,
|
|
18
19
|
kilo: planKilo,
|
|
19
20
|
pi: planPi,
|
|
21
|
+
hermes: planHermes,
|
|
20
22
|
};
|
|
21
23
|
export function planRetrofit(canonDir, targetDir, agents, codeGraph = 'graphify', codeIntelligence = 'off') {
|
|
22
24
|
const seen = new Set();
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { loadManifest } from '../../canon/manifest.js';
|
|
4
|
+
import { rtkInstruction } from '../tools.js';
|
|
5
|
+
import { PRESERVE_SCAFFOLD } from '../preserve.js';
|
|
6
|
+
import { skillPackageActions } from '../skill-actions.js';
|
|
7
|
+
const reviewerAgent = `---
|
|
8
|
+
description: Read-only Yoke reviewer for correctness and acceptance criteria
|
|
9
|
+
mode: primary
|
|
10
|
+
toolsets:
|
|
11
|
+
- file
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
Review the observed diff and test evidence. Do not modify files. Return only actionable findings grounded in evidence.
|
|
15
|
+
`;
|
|
16
|
+
export function planHermes(canonDir, _targetDir, _codeGraph = 'graphify', _codeIntelligence = 'off') {
|
|
17
|
+
const manifest = loadManifest(join(canonDir, 'manifest.yaml'));
|
|
18
|
+
const baseline = readFileSync(join(canonDir, 'AGENTS.md'), 'utf8');
|
|
19
|
+
const actions = manifest.skills.flatMap(skill => skillPackageActions(canonDir, skill, 'hermes'));
|
|
20
|
+
actions.push({
|
|
21
|
+
kind: 'write',
|
|
22
|
+
target: 'AGENTS.md',
|
|
23
|
+
content: `${baseline.trimEnd()}\n\n${rtkInstruction()}\n\n## Hermes integration\n\nHermes loads project skills from .hermes/skills and project context from AGENTS.md.\n\n${PRESERVE_SCAFFOLD}\n`,
|
|
24
|
+
reason: 'baseline instructions (Hermes reads AGENTS.md natively)',
|
|
25
|
+
}, {
|
|
26
|
+
kind: 'write',
|
|
27
|
+
target: '.hermes/agents/yoke-reviewer.md',
|
|
28
|
+
content: reviewerAgent,
|
|
29
|
+
reason: 'Hermes read-only reviewer agent',
|
|
30
|
+
});
|
|
31
|
+
return actions;
|
|
32
|
+
}
|
|
@@ -8,6 +8,7 @@ const roots = {
|
|
|
8
8
|
opencode: '.opencode/skills',
|
|
9
9
|
kilo: '.kilo/skills',
|
|
10
10
|
pi: '.pi/skills',
|
|
11
|
+
hermes: '.hermes/skills',
|
|
11
12
|
};
|
|
12
13
|
function manualClaudeSkill(content, skill) {
|
|
13
14
|
const source = content.toString('utf8');
|
|
@@ -52,7 +53,7 @@ export function skillPackageActions(canonDir, skill, provider) {
|
|
|
52
53
|
.map(file => ({
|
|
53
54
|
kind: 'write',
|
|
54
55
|
target: `${roots[provider]}/${skill.id}/${file.relativePath}`,
|
|
55
|
-
content: (provider === 'claude' || provider === 'qwen' || provider === 'pi') && skill.invocation === 'manual' && file.relativePath === 'SKILL.md'
|
|
56
|
+
content: (provider === 'claude' || provider === 'qwen' || provider === 'pi' || provider === 'hermes') && skill.invocation === 'manual' && file.relativePath === 'SKILL.md'
|
|
56
57
|
? manualClaudeSkill(file.content, skill)
|
|
57
58
|
: portableContent(file),
|
|
58
59
|
executable: file.executable,
|
package/dist/review/command.js
CHANGED
|
@@ -6,7 +6,7 @@ import { loadConfig } from '../retrofit/config.js';
|
|
|
6
6
|
import { parseReviewVerdict } from './verdict.js';
|
|
7
7
|
// Resolve to the first available agent, preferring a *second* model so the review
|
|
8
8
|
// is genuinely cross-model. claude last => a Claude-only box degrades to self-review.
|
|
9
|
-
const RESOLUTION_ORDER = ['codex', 'gemini', 'qwen', 'claude', 'opencode', 'kilo', 'pi'];
|
|
9
|
+
const RESOLUTION_ORDER = ['codex', 'gemini', 'qwen', 'claude', 'opencode', 'kilo', 'pi', 'hermes'];
|
|
10
10
|
export function runReview(targetDir, opts = {}) {
|
|
11
11
|
const available = opts.isAvailable ?? isAgentAvailable;
|
|
12
12
|
const implementer = opts.implementer ?? loadConfig(targetDir)?.agents[0] ?? 'claude';
|
package/dist/review/verdict.js
CHANGED
|
@@ -70,7 +70,7 @@ export function formatReviewContract(path, provider) {
|
|
|
70
70
|
return [
|
|
71
71
|
`Write your final verdict to this absolute path: ${path}`,
|
|
72
72
|
'The file must contain exactly one JSON object with this contract:',
|
|
73
|
-
`{"schemaVersion":1,"approved":boolean,"summary":"non-empty string","findings":[{"id":"optional id","severity":"blocking|warning|info","message":"non-empty string","file":"optional path","line":1,"actionable":true,"suggestedFix":"optional repair","evidence":["optional evidence reference"]}],"provenance":{"provider":"${provider ?? 'claude|codex|gemini|qwen|opencode|kilo|pi'}","model":"provider-reported model","role":"review","promptVersion":1,"permissions":"safe"}}`,
|
|
73
|
+
`{"schemaVersion":1,"approved":boolean,"summary":"non-empty string","findings":[{"id":"optional id","severity":"blocking|warning|info","message":"non-empty string","file":"optional path","line":1,"actionable":true,"suggestedFix":"optional repair","evidence":["optional evidence reference"]}],"provenance":{"provider":"${provider ?? 'claude|codex|gemini|qwen|opencode|kilo|pi|hermes'}","model":"provider-reported model","role":"review","promptVersion":1,"permissions":"safe"}}`,
|
|
74
74
|
'Set approved=false when any blocking finding exists. Create the file even when the process also exits non-zero.',
|
|
75
75
|
].join('\n');
|
|
76
76
|
}
|
|
@@ -68,7 +68,7 @@ export function chooseCapability(input) {
|
|
|
68
68
|
const worker = reliable[0];
|
|
69
69
|
const blocked = !worker && (input.fallback === 'block' || input.maxTier !== undefined);
|
|
70
70
|
const provider = worker?.agent ?? input.story.agent ?? input.parent;
|
|
71
|
-
const selection = worker ? { provider: worker.provider, model: worker.model, reasoningEffort: worker.reasoningEffort, variant: worker.variant, nativeMultiAgent: false, ...(provider !== 'gemini' && provider !== 'qwen' && provider !== 'pi' && input.parentSelection?.bare !== undefined ? { bare: input.parentSelection.bare } : {}) }
|
|
71
|
+
const selection = worker ? { provider: worker.provider, model: worker.model, reasoningEffort: worker.reasoningEffort, variant: worker.variant, nativeMultiAgent: false, ...(provider !== 'gemini' && provider !== 'qwen' && provider !== 'pi' && provider !== 'hermes' && input.parentSelection?.bare !== undefined ? { bare: input.parentSelection.bare } : {}) }
|
|
72
72
|
: { ...(provider === input.parent ? input.parentSelection : {}), nativeMultiAgent: false };
|
|
73
73
|
const reason = `${role}: ${tiers[level]}; ${input.assessment.reason}${failures.length ? `; ${failures.length} verified failure(s), ${failures.length === 1 ? 'one targeted repair' : 'escalated'}` : ''}${worker ? '' : '; no eligible profile, parent/provider fallback'}`;
|
|
74
74
|
return { worker, provider, selection, reason: blocked ? `${role}: no eligible profile within routing limits; execution blocked` : reason, blocked, requiredTier: baseTier, selectedTier: tiers[level], failures: failures.length, exhausted, next: input.maxTier && level >= tiers.indexOf(input.maxTier) ? 'stop at configured tier limit' : level < 3 ? tiers[level + 1] : 'stop after bounded attempts' };
|
package/dist/routing/router.js
CHANGED
|
@@ -249,7 +249,7 @@ function routingSteps(options) {
|
|
|
249
249
|
return blocked('Selected routing profile exceeds configured limits; execution blocked');
|
|
250
250
|
const provider = worker?.agent ?? options.parent;
|
|
251
251
|
const selection = worker
|
|
252
|
-
? { provider: worker.provider, model: worker.model, reasoningEffort: worker.reasoningEffort, variant: worker.variant, nativeMultiAgent: false, ...(provider !== 'gemini' && provider !== 'qwen' && provider !== 'pi' ? { bare: options.parentSelection?.bare } : {}) }
|
|
252
|
+
? { provider: worker.provider, model: worker.model, reasoningEffort: worker.reasoningEffort, variant: worker.variant, nativeMultiAgent: false, ...(provider !== 'gemini' && provider !== 'qwen' && provider !== 'pi' && provider !== 'hermes' ? { bare: options.parentSelection?.bare } : {}) }
|
|
253
253
|
: { ...(options.parentSelection ?? {}), nativeMultiAgent: false };
|
|
254
254
|
const workerStarted = now();
|
|
255
255
|
const result = yield () => makeWorker(provider, selection)(ctx);
|
package/dist/setup/command.js
CHANGED
|
@@ -42,6 +42,9 @@ export function defaultRoutingWorkers(agents) {
|
|
|
42
42
|
pi: [
|
|
43
43
|
{ id: 'pi-standard', agent: 'pi', tier: 'standard', costTier: 'medium', capabilities: ['implementation'] },
|
|
44
44
|
],
|
|
45
|
+
hermes: [
|
|
46
|
+
{ id: 'hermes-standard', agent: 'hermes', tier: 'standard', costTier: 'medium', capabilities: ['implementation'] },
|
|
47
|
+
],
|
|
45
48
|
};
|
|
46
49
|
return agents.flatMap(agent => workers[agent]);
|
|
47
50
|
}
|
package/docs/HARNESSES.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# OpenCode, Kilo and
|
|
1
|
+
# OpenCode, Kilo, Pi and Hermes
|
|
2
2
|
|
|
3
|
-
Yoke
|
|
3
|
+
Yoke adds first-class adapters for OpenCode, Kilo, Pi coding agent, and Hermes Agent. The adapters share Yoke's invocation, routing, review, quality, telemetry and retrofit contracts, while preserving the controls each harness actually provides.
|
|
4
4
|
|
|
5
5
|
This is a CLI integration, not an authentication bundle. Install the selected harness, log in or configure its API provider, and verify it independently before starting a Yoke loop.
|
|
6
6
|
|
|
@@ -10,12 +10,13 @@ Pi project trust is separate from Yoke's permission profile. Current Pi ignores
|
|
|
10
10
|
|
|
11
11
|
## Setup and retrofit
|
|
12
12
|
|
|
13
|
-
Select one of the
|
|
13
|
+
Select one of the harnesses explicitly:
|
|
14
14
|
|
|
15
15
|
```sh
|
|
16
16
|
yoke setup . --yes --agent=opencode --runner=opencode
|
|
17
17
|
yoke setup . --yes --agent=kilo --runner=kilo
|
|
18
18
|
yoke setup . --yes --agent=pi --runner=pi
|
|
19
|
+
yoke setup . --yes --agent=hermes --runner=hermes
|
|
19
20
|
```
|
|
20
21
|
|
|
21
22
|
To add one to an existing project without changing the other generated artifacts:
|
|
@@ -24,9 +25,10 @@ To add one to an existing project without changing the other generated artifacts
|
|
|
24
25
|
yoke retrofit . --agent=opencode
|
|
25
26
|
yoke retrofit . --agent=kilo
|
|
26
27
|
yoke retrofit . --agent=pi
|
|
28
|
+
yoke retrofit . --agent=hermes
|
|
27
29
|
```
|
|
28
30
|
|
|
29
|
-
`--agent=all`
|
|
31
|
+
`--agent=all` includes all eight supported harnesses. Retrofit is merge-aware for the native config files and backs up Yoke-managed overwrites under `.yoke/backup/`.
|
|
30
32
|
|
|
31
33
|
## Native artifacts
|
|
32
34
|
|
|
@@ -35,8 +37,9 @@ yoke retrofit . --agent=pi
|
|
|
35
37
|
| OpenCode | `AGENTS.md`, `.opencode/skills/`, `opencode.json`, `.opencode/agents/yoke-reviewer.md` | `run --format json`, provider/model selection, variants, plan agent, local MCP servers |
|
|
36
38
|
| Kilo | `AGENTS.md`, `.kilo/skills/`, `kilo.jsonc`, `.kilo/agents/yoke-reviewer.md` | OpenCode-compatible `run --format json`, provider/model selection, variants, plan agent, local MCP servers |
|
|
37
39
|
| Pi | `AGENTS.md`, `.pi/skills/`, `.pi/settings.json` | JSONL mode, provider/model selection, thinking level, explicit tool allowlists |
|
|
40
|
+
| Hermes | `AGENTS.md`, `.hermes/skills/`, `.hermes/agents/yoke-reviewer.md` | `chat --format stream-json`, `--query-file -`, provider/model selection, reasoning effort, toolsets |
|
|
38
41
|
|
|
39
|
-
All
|
|
42
|
+
All consume the shared `AGENTS.md` and `.yoke/context/*.md` context. OpenCode and Kilo also receive the configured local MCP servers from the Yoke code-graph choice. Pi and Hermes integrations use portable skills and Yoke's loop boundaries.
|
|
40
43
|
|
|
41
44
|
## Provider, model and variant selection
|
|
42
45
|
|
|
@@ -64,24 +67,36 @@ This becomes `--provider openai --model gpt-5.6 --thinking high`. Pi calls `vari
|
|
|
64
67
|
|
|
65
68
|
OpenCode and Kilo likewise map these two fields to a single `--variant`: matching values are emitted once and conflicting values are rejected before spawning a process.
|
|
66
69
|
|
|
70
|
+
For Hermes the equivalent is:
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
runner:
|
|
74
|
+
agent: hermes
|
|
75
|
+
provider: openrouter
|
|
76
|
+
model: anthropic/claude-sonnet-4
|
|
77
|
+
reasoningEffort: high
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This becomes `--provider openrouter --model anthropic/claude-sonnet-4 --reasoning-effort high`. Hermes accepts `--reasoning-effort`; configuring conflicting `variant` and `reasoningEffort` values is rejected.
|
|
81
|
+
|
|
67
82
|
The same fields are available on routing workers and quality critic/repair roles. Routing evidence is keyed by harness, provider, model, reasoning effort and variant, so a model profile does not inherit another profile's success history.
|
|
68
83
|
|
|
69
84
|
## Permission profiles and honest limits
|
|
70
85
|
|
|
71
|
-
| Yoke profile | OpenCode / Kilo | Pi |
|
|
72
|
-
|
|
73
|
-
| `safe` | Headless `--auto` run; Yoke still verifies the resulting tree and gates the commit | `read,bash,edit,write` tool allowlist so the implementer can test and modify the project |
|
|
74
|
-
| `read-only` | `--agent plan` plus JSON output | `read,grep,find,ls` only |
|
|
75
|
-
| `unsafe` | Explicit `--dangerously-skip-permissions` | Harness default tool set; no Yoke-added allowlist |
|
|
86
|
+
| Yoke profile | OpenCode / Kilo | Pi | Hermes |
|
|
87
|
+
|---|---|---|---|
|
|
88
|
+
| `safe` | Headless `--auto` run; Yoke still verifies the resulting tree and gates the commit | `read,bash,edit,write` tool allowlist so the implementer can test and modify the project | `--toolsets file,terminal` restricts tools to local filesystem and shell execution |
|
|
89
|
+
| `read-only` | `--agent plan` plus JSON output | `read,grep,find,ls` only | `--toolsets file` allows inspection without terminal or write execution |
|
|
90
|
+
| `unsafe` | Explicit `--dangerously-skip-permissions` | Harness default tool set; no Yoke-added allowlist | `--yolo` skips dangerous command approvals |
|
|
76
91
|
|
|
77
|
-
OpenCode, Kilo and
|
|
92
|
+
OpenCode, Kilo, Pi and Hermes do not provide the same OS-level sandbox boundary as Codex or Gemini. The Yoke `safe` label therefore describes the selected harness controls and Yoke's mechanical gates, not a universal filesystem sandbox. Use `read-only` for review and do not use `unsafe` unless the project owner accepts the boundary.
|
|
78
93
|
|
|
79
|
-
The Yoke loop disables native delegation where the harness exposes it, so Yoke's worker budget remains the authority. OpenCode and Kilo native agents are installed as a read-only reviewer artifact; Yoke's review runner still validates the structured verdict itself. Pi has no native sub-agent or plan mode by design.
|
|
94
|
+
The Yoke loop disables native delegation where the harness exposes it, so Yoke's worker budget remains the authority. OpenCode and Kilo native agents are installed as a read-only reviewer artifact; Hermes uses a read-only agent configuration with `toolsets: [file]`; Yoke's review runner still validates the structured verdict itself. Pi has no native sub-agent or plan mode by design.
|
|
80
95
|
|
|
81
96
|
## Telemetry and validation limits
|
|
82
97
|
|
|
83
|
-
Yoke parses OpenCode/Kilo JSON text events and `step_finish` token/cost events,
|
|
98
|
+
Yoke parses OpenCode/Kilo JSON text events and `step_finish` token/cost events, Pi `message_end` plus `message_update.usage` events, and Hermes `result` (tokens, cost, model) and `text` streaming events. Missing provider events produce partial or unknown measurements; they are never reported as zero-cost success. Provider-reported model and usage remain provider claims.
|
|
84
99
|
|
|
85
100
|
The release test suite covers argument construction, provider/variant propagation, routing and quality configuration, retrofit plans, host detection, JSON result parsing, and representative telemetry fixtures. It does not authenticate against every provider or claim equal model quality. Run a small project-specific smoke task after installing a harness and configuring credentials.
|
|
86
101
|
|
|
87
|
-
Official CLI references: [OpenCode CLI](https://dev.opencode.ai/docs/cli), [OpenCode configuration](https://opencode.ai/docs/config), [OpenCode skills](https://opencode.ai/docs/skills), [Kilo CLI reference](https://github.com/Kilo-Org/kilocode/blob/main/packages/kilo-docs/pages/code-with-ai/platforms/cli-reference.md),
|
|
102
|
+
Official CLI references: [OpenCode CLI](https://dev.opencode.ai/docs/cli), [OpenCode configuration](https://opencode.ai/docs/config), [OpenCode skills](https://opencode.ai/docs/skills), [Kilo CLI reference](https://github.com/Kilo-Org/kilocode/blob/main/packages/kilo-docs/pages/code-with-ai/platforms/cli-reference.md), [Pi coding agent](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/README.md), and [Hermes Agent](https://github.com/NousResearch/hermes-agent).
|
package/gemini-extension.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yoke",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Cross-agent coding harness for
|
|
3
|
+
"version": "1.16.0",
|
|
4
|
+
"description": "Cross-agent coding harness for eight supported CLIs: curated skill canon, mechanical safety gates, autonomous loop with proof artifacts. CLI: npm i -g @hecer/yoke",
|
|
5
5
|
"contextFileName": "GEMINI-EXTENSION.md"
|
|
6
6
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hecer/yoke",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "One harness,
|
|
3
|
+
"version": "1.16.0",
|
|
4
|
+
"description": "One harness, eight agents, zero trust in \"done\" — cross-agent coding harness for Claude Code, Codex CLI, Gemini CLI, Qwen Code, OpenCode, Kilo, Pi and Hermes: one skill canon, mechanical safety gates, an autonomous loop with screenshot/video proofs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"yoke": "dist/cli.js"
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"opencode",
|
|
50
50
|
"kilo",
|
|
51
51
|
"pi-coding-agent",
|
|
52
|
+
"hermes-agent",
|
|
52
53
|
"agents",
|
|
53
54
|
"agentic-coding",
|
|
54
55
|
"harness",
|