@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 +19 -0
- package/README.md +39 -7
- package/canon/manifest.yaml +2 -0
- package/canon/skills/authoring-prd/SKILL.md +8 -0
- package/canon/skills/performance/SKILL.md +48 -0
- package/dist/loop/loop.js +35 -24
- package/dist/loop/run-command.js +12 -1
- package/dist/loop/runner.js +7 -3
- package/dist/retrofit/config.js +3 -0
- package/package.json +1 -1
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
|
+
[](https://www.npmjs.com/package/@hecer/yoke)
|
|
10
|
+
[](https://www.npmjs.com/package/@hecer/yoke)
|
|
9
11
|
[](https://github.com/HECer/yoke/actions/workflows/ci.yml)
|
|
10
12
|
[](#-license)
|
|
11
13
|

|
|
12
14
|

|
|
13
|
-

|
|
14
16
|

|
|
15
17
|

|
|
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…
|
|
50
|
-
|
|
51
|
-
▶ STORY-
|
|
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 —
|
|
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 —
|
|
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 (
|
|
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
|
package/canon/manifest.yaml
CHANGED
|
@@ -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
|
|
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
|
|
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)`;
|
package/dist/loop/run-command.js
CHANGED
|
@@ -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, {
|
|
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,
|
package/dist/loop/runner.js
CHANGED
|
@@ -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.'
|
|
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;
|
package/dist/retrofit/config.js
CHANGED
|
@@ -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.
|
|
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": {
|