@hecer/yoke 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.0 — 2026-07-22
4
+
5
+ ### Added
6
+ - **Performance budget gate** (`perf.command` in `.yoke/config.yaml`, optional `perf.retries`).
7
+ A benchmark command with the same contract as verify (exit 0 = within budget) runs **after
8
+ verify** on every story — new loop phase `perf`, `YOKE_STORY` exposed, worktree-aware in
9
+ `--isolate` mode. A red benchmark blocks the story
10
+ (`story S6 exceeded its performance budget: …`) no matter how clean the diff is. When the
11
+ gate is configured, the implementer prompt names the budget command so agents keep hot
12
+ paths efficient and never "simplify away" an optimization without re-running the benchmark.
13
+ - **`performance` canon skill** (28 skills now): efficiency as a measured requirement —
14
+ clean-by-default with the decision ladder (minimal-code → measurable acceptance criterion →
15
+ project perf gate), profile-first, optimize leaves not boundaries, benchmarks committed as
16
+ tests, the *why* of every optimization versioned so future agents don't clean fast code
17
+ back to slow.
18
+ - **`authoring-prd` guidance**: performance requirements belong in acceptance criteria as
19
+ numbers ("imports 1M rows in < 2s"), and every clarifying question belongs in the planning
20
+ round — a criterion still needing a decision is not loop-ready.
21
+
3
22
  ## 0.8.0 — 2026-07-20
4
23
 
5
24
  ### Added
package/README.md CHANGED
@@ -6,14 +6,18 @@
6
6
 
7
7
  **Yoke** installs one curated canon of skills, **mechanical safety gates**, and tool wiring into any project — natively for **Claude Code, OpenAI Codex CLI, and Gemini CLI**. Then, when you want it, an opt-in autonomous loop ships your spec story-by-story: tested, cross-model-reviewed, committed — **with a screenshot to prove every story and a video for every failure**.
8
8
 
9
+ [![npm](https://img.shields.io/npm/v/%40hecer%2Fyoke?logo=npm&color=CB3837)](https://www.npmjs.com/package/@hecer/yoke)
10
+ [![npm downloads](https://img.shields.io/npm/dm/%40hecer%2Fyoke?logo=npm)](https://www.npmjs.com/package/@hecer/yoke)
9
11
  [![CI](https://github.com/HECer/yoke/actions/workflows/ci.yml/badge.svg)](https://github.com/HECer/yoke/actions/workflows/ci.yml)
10
12
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](#-license)
11
13
  ![Node](https://img.shields.io/badge/node-%E2%89%A520-339933?logo=node.js&logoColor=white)
12
14
  ![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white)
13
- ![Tests](https://img.shields.io/badge/tests-322%20passing-brightgreen.svg)
15
+ ![Tests](https://img.shields.io/badge/tests-439%20passing-brightgreen.svg)
14
16
  ![Agents](https://img.shields.io/badge/agents-Claude%20%7C%20Codex%20%7C%20Gemini-8A2BE2)
15
17
  ![Built with TDD](https://img.shields.io/badge/built%20with-TDD%20%2B%20review-ff69b4.svg)
16
18
 
19
+ **Install:** [`npm i -g @hecer/yoke`](https://www.npmjs.com/package/@hecer/yoke)
20
+
17
21
  </div>
18
22
 
19
23
  > **TL;DR** — `yoke new my-app --idea="..."` scaffolds a git repo, installs the harness for all three agents, and drafts a story backlog from your idea. `yoke loop run my-app --isolate --review` then implements it story by story behind hard gates: **clean tree → acceptance criteria → your real tests green → an independent model approves → commit**. If any gate is red, nothing is committed. When a story is done, there's a photo of it in `.yoke/proof/<story>/`.
@@ -46,16 +50,18 @@ $ yoke prd check reading-app
46
50
 
47
51
  $ yoke loop on reading-app
48
52
  $ yoke loop run reading-app --isolate --review --max=10
49
- ▶ STORY-1 (0/8) — implementing… · verifying… · reviewing… committed → 1/8
50
- STORY-2 (1/8) — implementing… · verifying… ✔ committed → 2/8
51
- ▶ STORY-3 (2/8) — implementing… · verifying… blocked: story did not verify (tests red)
53
+ ▶ STORY-1 (0/8 · 0%) — implementing… · verifying… · reviewing… · committing…
54
+ STORY-1 done in 3m12s — 1/8 (13%) · ~22m left
55
+ ▶ STORY-2 (1/8 · 13%) — implementing… · ~22m left 3m12s/story)
56
+ ✓ STORY-2 done in 2m48s — 2/8 (25%) · ~18m left
57
+ ▶ STORY-3 (2/8 · 25%) — implementing… ✘ blocked: story did not verify (tests red)
52
58
  # nothing was committed. fix, then re-run.
53
59
 
54
60
  $ ls reading-app/.yoke/proof/STORY-2/
55
61
  home.png list.png # photographic evidence, labelled per story
56
62
  ```
57
63
 
58
- Every claim in that transcript is enforced by code paths with tests behind them — 322 of them, and this repo was built by its own loop and gates ([how it was built](#-why--how-it-was-built)).
64
+ Every claim in that transcript is enforced by code paths with tests behind them — 439 of them, and this repo was built by its own loop and gates ([how it was built](#-why--how-it-was-built)).
59
65
 
60
66
  ## 🚀 Quickstart
61
67
 
@@ -197,7 +203,7 @@ Three layers — **Canon** (`yoke validate`) → **Retrofit** (`yoke retrofit`)
197
203
  > instructions (tech stack, workflow, `@`-includes) inside it. Works in any yoke-written file;
198
204
  > content *outside* the markers is still replaced (and backed up under `.yoke/backup/`).
199
205
 
200
- ## 🧰 What's in the canon — 27 skills
206
+ ## 🧰 What's in the canon — 28 skills
201
207
 
202
208
  `yoke retrofit` installs all of these into each agent natively. Provenance is credited in [`canon/skills/ATTRIBUTION.md`](canon/skills/ATTRIBUTION.md).
203
209
 
@@ -233,13 +239,14 @@ To stop overlapping skills from auto-invoking against each other, `canon/AGENTS.
233
239
  | `retro` | Engineering retrospective from commit history |
234
240
  | `document-release` | Post-ship documentation sync (README / CHANGELOG / …) |
235
241
 
236
- **Yoke-native** — *authored or adapted for this harness (7)*
242
+ **Yoke-native** — *authored or adapted for this harness (8)*
237
243
 
238
244
  | Skill | What it does |
239
245
  |---|---|
240
246
  | `yoke-retrofit` | Set up the Yoke harness in a project (detect → plan → apply) |
241
247
  | `authoring-prd` | Slice a product idea into loop-ready stories with testable acceptance criteria |
242
248
  | `minimal-code` | Write the least code that solves the task (YAGNI; ponytail-derived) |
249
+ | `performance` | Efficiency as a measured requirement: benchmarks as tests, budgets as gates, optimizations local + documented |
243
250
  | `maintaining-context` | Keep `.yoke/context/` the durable source of truth (the Context layer) |
244
251
  | `workflow` | The default order of operations, from idea to deploy |
245
252
  | `unslop-ui` | Detect & remove AI-slop design tells (purple gradients, neon glow, emoji-icons…) |
@@ -302,6 +309,7 @@ yoke loop run . \
302
309
  --runner=codex \ # implement with Codex…
303
310
  --reviewer=claude \ # …review with Claude (role separation)
304
311
  --isolate \ # each story in a throwaway git worktree
312
+ --on-ambiguity=abort \ # strict: stop on undecidable criteria instead of guessing
305
313
  --max=20
306
314
  yoke loop off . # disable
307
315
  ```
@@ -381,6 +389,30 @@ Enable strict mode per run with `yoke loop run . --on-ambiguity=abort` or per pr
381
389
  put every clarifying question into the PRD **before** the loop starts (`yoke prd draft`
382
390
  criteria must be testable and decision-free).
383
391
 
392
+ ### Performance budgets: efficiency as a gate, not a style
393
+
394
+ Clean code is the default (the `minimal-code` skill) — but when efficiency matters, "should
395
+ be fast" is a vibe the loop cannot enforce. Yoke makes it mechanical, at two levels:
396
+
397
+ - **Per story:** write the requirement as a **measurable acceptance criterion**
398
+ ("imports 1M rows in < 2s, asserted by the bench test") and let your verify tests measure
399
+ it — no new machinery needed.
400
+ - **Per project:** wire a benchmark as a standing **perf gate** in `.yoke/config.yaml`:
401
+
402
+ ```yaml
403
+ perf:
404
+ command: node bench/check-budget.mjs # exit 0 = within budget
405
+ retries: 1 # benchmarks are noisy; same retry logic as verify
406
+ ```
407
+
408
+ The loop runs it **after verify** on every story (phase `perf`, with `YOKE_STORY` set); a
409
+ red benchmark blocks the story — `story S6 exceeded its performance budget: p95 62ms > budget 50ms` —
410
+ no matter how clean the diff was. The implementer prompt names the budget command, so the
411
+ agent knows not to trade hot-path efficiency for style and never "simplifies away" an
412
+ optimization without re-running the benchmark. The `performance` canon skill carries the
413
+ method: profile first, optimize leaves not boundaries, commit benchmarks as tests, version
414
+ the *why* of every optimization in `context/DECISIONS.md`.
415
+
384
416
  The loop trusts **verify**, not the agent's exit code: a story whose tests are green is
385
417
  committed even if the agent process exited non-zero (a common Windows `.cmd`-wrapper ghost).
386
418
  A failing verify is retried up to `verify.retries` times (default 1) so a transient flake
@@ -34,6 +34,8 @@ skills:
34
34
  - { id: visual-verification, path: skills/visual-verification, kind: methodology }
35
35
  # zero-to-100 bootstrap (kind: methodology)
36
36
  - { id: authoring-prd, path: skills/authoring-prd, kind: methodology }
37
+ # measured efficiency (kind: methodology)
38
+ - { id: performance, path: skills/performance, kind: methodology }
37
39
  policy:
38
40
  - { path: policy/gates.md }
39
41
  - { path: policy/roles.md }
@@ -21,6 +21,14 @@ good stories (small, testable, ordered) let it run overnight.
21
21
  5. **Greenfield: STORY-1 scaffolds.** Project skeleton + runnable test suite + a criterion
22
22
  that the verify command (`verify.command` in `.yoke/config.yaml`) exits 0. Every later
23
23
  story stands on a green pipeline.
24
+ 6. **Performance requirements are acceptance criteria — with numbers.** "Should be fast" is
25
+ a vibe the loop cannot gate; "imports 1M rows in < 2s (asserted by the bench test)" is a
26
+ criterion. If the whole project has a budget, wire `perf.command` in `.yoke/config.yaml`
27
+ (see the `performance` skill) instead of repeating it per story.
28
+ 7. **Ask everything now.** Clarifying questions belong in this planning round — a loop run
29
+ has nobody to ask. A criterion that still needs a decision ("TBD", "choose a provider")
30
+ is not loop-ready; resolve it here or the agent will either guess (default) or block
31
+ (`--on-ambiguity=abort`).
24
32
 
25
33
  ## Format (`.yoke/prd.yaml`)
26
34
 
@@ -0,0 +1,48 @@
1
+ ---
2
+ name: performance
3
+ description: Use when a task has efficiency requirements or touches a hot path — make performance a measured requirement (benchmarks as tests, budgets as gates), keep interfaces clean and optimizations local, and version the WHY so future agents don't "clean up" fast code back to slow.
4
+ ---
5
+
6
+ # Performance (measured, not vibed)
7
+
8
+ "Efficient" is a requirement, not a code style. Untested performance claims rot exactly like
9
+ untested behavior claims. This skill makes efficiency mechanical — the same move Yoke makes
10
+ for everything else.
11
+
12
+ ## The decision ladder
13
+
14
+ 1. **Default: clean + minimal.** For ~90% of code, the `minimal-code` rules ARE the
15
+ performance strategy — less code, fewer layers, no speculative abstraction. Do not
16
+ micro-optimize code that no measurement flagged (premature optimization).
17
+ 2. **Performance requirement? Make it an acceptance criterion.** A number, not an adjective:
18
+ - Good: "imports 1M rows in < 2s", "p95 request latency < 50ms in the bench test",
19
+ "no allocation inside the render loop (verified by the bench assertion)"
20
+ - Bad: "should be fast", "optimize the importer"
21
+ 3. **Whole-project budget? Use the perf gate.** Set `perf.command` in `.yoke/config.yaml`
22
+ (a benchmark script; exit 0 = within budget). The loop runs it after verify — a story
23
+ that breaks the budget is blocked, no matter how clean its diff is.
24
+
25
+ ## Writing efficient code that agents can maintain
26
+
27
+ - **Clean at the boundaries, aggressive in the leaves.** Interfaces, data flow, and names
28
+ stay simple and obvious. Optimization lives inside a few clearly-bounded leaf functions
29
+ whose contracts are pinned by tests. An ugly-fast function is maintainable; an
30
+ ugly-fast architecture is not.
31
+ - **Profile before optimizing.** Find the actual hot 5% (a profiler, a timing harness, the
32
+ bench script) — never optimize from intuition. Record the measurement in the PR/commit.
33
+ - **Benchmarks are tests. Commit them.** An optimization without a committed benchmark is
34
+ one refactor away from silently disappearing. The bench script doubles as `perf.command`.
35
+ - **Version the WHY.** Every non-obvious optimization gets a one-line comment
36
+ (`perf: avoids N+1 — see bench/import.mjs`) and, if it shaped a design, a line in
37
+ `context/DECISIONS.md`. The most common AI maintenance accident is a later agent
38
+ "simplifying" fast code back to slow because nothing said why it was shaped that way.
39
+ - **Know the classics before reaching for cleverness:** right data structure (map vs list
40
+ scan), batching over per-item round trips (N+1), streaming over buffering, avoiding
41
+ repeated work in loops, caching only with a measured hit rate and an invalidation story.
42
+
43
+ ## Red flags
44
+
45
+ - Optimizing without a measurement or a budget → stop, measure first.
46
+ - A "refactor" or "cleanup" story touching code with `perf:` comments → re-run the bench
47
+ before AND after; keep the numbers in the story outcome.
48
+ - Hand-rolled cleverness where the stdlib is already O(right) → `minimal-code` wins.
package/dist/loop/loop.js CHANGED
@@ -26,6 +26,21 @@ export function pauseFilePath(targetDir) {
26
26
  export function ambiguityFilePath(dir) {
27
27
  return join(dir, '.yoke', 'ambiguity.md');
28
28
  }
29
+ // Run a gate command with the story id exposed via YOKE_STORY (restored after),
30
+ // so cumulative fixtures and story-aware benchmarks know which story is on trial.
31
+ function runGate(gate, dir, storyId) {
32
+ const prev = process.env.YOKE_STORY;
33
+ process.env.YOKE_STORY = storyId;
34
+ try {
35
+ return gate(dir);
36
+ }
37
+ finally {
38
+ if (prev === undefined)
39
+ delete process.env.YOKE_STORY;
40
+ else
41
+ process.env.YOKE_STORY = prev;
42
+ }
43
+ }
29
44
  function consumeAmbiguity(dir) {
30
45
  const file = ambiguityFilePath(dir);
31
46
  if (!existsSync(file))
@@ -106,18 +121,7 @@ export function runLoop(opts) {
106
121
  // Verify is the source of truth — NOT the runner's exit code. A spurious non-zero
107
122
  // exit (e.g. a Windows .cmd wrapper ghost) must not block a story whose tests are green.
108
123
  reporter.phase('verifying');
109
- const prevStory = process.env.YOKE_STORY;
110
- process.env.YOKE_STORY = story.id;
111
- let verdict;
112
- try {
113
- verdict = opts.verify(wt);
114
- }
115
- finally {
116
- if (prevStory === undefined)
117
- delete process.env.YOKE_STORY;
118
- else
119
- process.env.YOKE_STORY = prevStory;
120
- }
124
+ const verdict = runGate(opts.verify, wt, story.id);
121
125
  if (!verdict.passed) {
122
126
  const base = result.success
123
127
  ? `story ${story.id} did not verify: ${verdict.summary}`
@@ -126,6 +130,15 @@ export function runLoop(opts) {
126
130
  reporter.blocked(reason);
127
131
  return { status: 'blocked', iterations, reason, finalProgress: progress(stories) };
128
132
  }
133
+ if (opts.perf) {
134
+ reporter.phase('perf');
135
+ const perfVerdict = runGate(opts.perf, wt, story.id);
136
+ if (!perfVerdict.passed) {
137
+ const reason = blockReason(`story ${story.id} exceeded its performance budget: ${perfVerdict.summary}`, opts.targetDir, opts.git);
138
+ reporter.blocked(reason);
139
+ return { status: 'blocked', iterations, reason, finalProgress: progress(stories) };
140
+ }
141
+ }
129
142
  const summary = result.success
130
143
  ? result.summary
131
144
  : `${result.summary} (runner exited non-zero but verify is green)`;
@@ -181,18 +194,7 @@ export function runLoop(opts) {
181
194
  // Verify is the source of truth — NOT the runner's exit code. A spurious non-zero
182
195
  // exit (e.g. a Windows .cmd wrapper ghost) must not block a story whose tests are green.
183
196
  reporter.phase('verifying');
184
- const prevStory = process.env.YOKE_STORY;
185
- process.env.YOKE_STORY = story.id;
186
- let verdict;
187
- try {
188
- verdict = opts.verify(opts.targetDir);
189
- }
190
- finally {
191
- if (prevStory === undefined)
192
- delete process.env.YOKE_STORY;
193
- else
194
- process.env.YOKE_STORY = prevStory;
195
- }
197
+ const verdict = runGate(opts.verify, opts.targetDir, story.id);
196
198
  if (!verdict.passed) {
197
199
  const base = result.success
198
200
  ? `story ${story.id} did not verify: ${verdict.summary}`
@@ -206,6 +208,15 @@ export function runLoop(opts) {
206
208
  finalProgress: progress(stories),
207
209
  };
208
210
  }
211
+ if (opts.perf) {
212
+ reporter.phase('perf');
213
+ const perfVerdict = runGate(opts.perf, opts.targetDir, story.id);
214
+ if (!perfVerdict.passed) {
215
+ const reason = blockReason(`story ${story.id} exceeded its performance budget: ${perfVerdict.summary}`, opts.targetDir, opts.git);
216
+ reporter.blocked(reason);
217
+ return { status: 'blocked', iterations, reason, finalProgress: progress(stories) };
218
+ }
219
+ }
209
220
  const summary = result.success
210
221
  ? result.summary
211
222
  : `${result.summary} (runner exited non-zero but verify is green)`;
@@ -85,6 +85,12 @@ export function runLoopCommand(targetDir, opts) {
85
85
  }
86
86
  verify = retryingVerifier(commandVerifier(command), config.verify?.retries ?? 1);
87
87
  }
88
+ // Optional performance budget gate: same contract as verify (exit 0 = within
89
+ // budget), same flake tolerance (benchmarks are noisy).
90
+ let perf = opts.perf;
91
+ if (!perf && config.perf?.command) {
92
+ perf = retryingVerifier(commandVerifier(config.perf.command), config.perf.retries ?? 1);
93
+ }
88
94
  // Opt-in self-update, loop START only — this run keeps executing the version
89
95
  // it started with; a fetched upgrade applies from the next invocation.
90
96
  maybeAutoUpgrade(config.update?.auto);
@@ -99,7 +105,11 @@ export function runLoopCommand(targetDir, opts) {
99
105
  }
100
106
  // Token reporting is part of the machine interface: in --json mode a claude
101
107
  // runner switches to stream-json so cumulative usage rides on every status.
102
- runner = makeRunner(runnerAgent, idleMs, { tokenReport: opts.json === true, onAmbiguity: opts.onAmbiguity ?? config.loop.onAmbiguity });
108
+ runner = makeRunner(runnerAgent, idleMs, {
109
+ tokenReport: opts.json === true,
110
+ onAmbiguity: opts.onAmbiguity ?? config.loop.onAmbiguity,
111
+ perfCommand: config.perf?.command,
112
+ });
103
113
  }
104
114
  let review = opts.reviewRunner;
105
115
  if (!review && (opts.review || opts.reviewer)) {
@@ -125,6 +135,7 @@ export function runLoopCommand(targetDir, opts) {
125
135
  runner,
126
136
  git: opts.git ?? realGitOps,
127
137
  verify,
138
+ perf,
128
139
  maxIterations: opts.maxIterations,
129
140
  isolate: opts.isolate ?? false,
130
141
  review,
@@ -6,7 +6,7 @@ import { loadContext, formatForPrompt, contextDir } from '../context/context.js'
6
6
  export function contextBlockFor(targetDir) {
7
7
  return formatForPrompt(loadContext(contextDir(targetDir)));
8
8
  }
9
- export function buildClaudePrompt(story, context, onAmbiguity = 'resolve') {
9
+ export function buildClaudePrompt(story, context, onAmbiguity = 'resolve', perfCommand) {
10
10
  const criteria = story.acceptance.map(a => `- ${a}`).join('\n');
11
11
  const lines = [
12
12
  'You are an autonomous coding agent running inside the Yoke loop.',
@@ -16,7 +16,11 @@ export function buildClaudePrompt(story, context, onAmbiguity = 'resolve') {
16
16
  lines.push('', context);
17
17
  lines.push('', `Story ${story.id}: ${story.title}`, 'Acceptance criteria (Definition of Done):', criteria, '', "When done, ensure the project's full test suite passes.", 'Do NOT commit — the loop commits on your behalf after verifying.', '', 'Working rules:', '- Add nothing beyond what the story requires: no extra features, abstractions, comments, or defensive code for cases that cannot happen.', '- Do not create summary, plan, or analysis documents — only files the story itself needs.', '- If a check fails, fix the root cause; never bypass it (e.g. --no-verify) or pass by weakening tests.', '- Report the outcome faithfully: if a criterion is unmet or tests fail, say so plainly instead of claiming success.', '- Never ask questions or wait for input — you run unattended and nobody can answer.', onAmbiguity === 'abort'
18
18
  ? '- If an acceptance criterion is genuinely undecidable, do NOT guess: write the open question(s) to .yoke/ambiguity.md, change nothing else, and stop.'
19
- : '- If an acceptance criterion is ambiguous, resolve it yourself in the way most consistent with the other criteria and the existing code, and state your interpretation in your final message.', '- Keep your final message to a few short sentences: what changed and what you verified.');
19
+ : '- If an acceptance criterion is ambiguous, resolve it yourself in the way most consistent with the other criteria and the existing code, and state your interpretation in your final message.');
20
+ if (perfCommand) {
21
+ lines.push(`- This project enforces a performance budget: \`${perfCommand}\` must exit 0 or the story is blocked. Keep hot paths efficient, and never simplify away an existing optimization without re-running that benchmark.`);
22
+ }
23
+ lines.push('- Keep your final message to a few short sentences: what changed and what you verified.');
20
24
  return lines.join('\n');
21
25
  }
22
26
  export function buildReviewPrompt(story, context) {
@@ -228,7 +232,7 @@ export function makeRunner(agent, idleTimeoutMs = 0, opts = {}) {
228
232
  // redundant for claude and meaningless elsewhere; kept for caller compatibility.
229
233
  const captureTokens = agent === 'claude';
230
234
  return (ctx) => {
231
- const base = runnerInvocation(agent, buildClaudePrompt(ctx.story, contextBlockFor(ctx.targetDir), opts.onAmbiguity), ctx.targetDir, captureTokens);
235
+ const base = runnerInvocation(agent, buildClaudePrompt(ctx.story, contextBlockFor(ctx.targetDir), opts.onAmbiguity, opts.perfCommand), ctx.targetDir, captureTokens);
232
236
  const inv = buildWatchdogInvocation(base, idleTimeoutMs);
233
237
  if (captureTokens) {
234
238
  const capture = opts.execCapture ?? runCliCapture;
@@ -17,6 +17,9 @@ export const YokeConfigSchema = z.object({
17
17
  onAmbiguity: z.enum(['resolve', 'abort']).optional(),
18
18
  }),
19
19
  verify: z.object({ command: z.string().min(1), retries: z.number().int().nonnegative().optional() }).optional(),
20
+ // Optional performance budget gate: a benchmark command that must exit 0 for a
21
+ // story to land (runs after verify). Benchmarks are noisy → retried like verify.
22
+ perf: z.object({ command: z.string().min(1), retries: z.number().int().nonnegative().optional() }).optional(),
20
23
  codeGraph: CodeGraphSchema.optional(),
21
24
  smoke: SmokeSchema.optional(),
22
25
  // Opt-in: upgrade yoke at loop START when a newer version is cached (never mid-run).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hecer/yoke",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "One harness, three agents, zero trust in \"done\" — cross-agent coding harness for Claude Code, Codex CLI, and Gemini CLI: one skill canon, mechanical safety gates, an autonomous loop with screenshot/video proofs.",
5
5
  "type": "module",
6
6
  "bin": {