@hecer/yoke 0.2.0 → 0.8.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.
Files changed (84) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/README.md +583 -494
  3. package/bench/.runs/claude-2026-07-09T22-34-01/.yoke/config.yaml +6 -0
  4. package/bench/.runs/claude-2026-07-09T22-34-01/.yoke/context/DECISIONS.md +9 -0
  5. package/bench/.runs/claude-2026-07-09T22-34-01/.yoke/prd.yaml +38 -0
  6. package/bench/.runs/claude-2026-07-09T22-34-01/bench-verify.mjs +15 -0
  7. package/bench/.runs/claude-2026-07-09T22-34-01/package.json +9 -0
  8. package/bench/.runs/claude-2026-07-09T22-34-01/src/index.mjs +48 -0
  9. package/bench/.runs/claude-2026-07-09T22-34-01/tests/STORY-1.test.mjs +24 -0
  10. package/bench/.runs/claude-2026-07-09T22-34-01/tests/STORY-2.test.mjs +28 -0
  11. package/bench/.runs/claude-2026-07-09T22-34-01/tests/STORY-3.test.mjs +25 -0
  12. package/bench/.runs/gemini-2026-07-09T22-34-02/.yoke/config.yaml +6 -0
  13. package/bench/.runs/gemini-2026-07-09T22-34-02/.yoke/prd.yaml +32 -0
  14. package/bench/.runs/gemini-2026-07-09T22-34-02/bench-verify.mjs +15 -0
  15. package/bench/.runs/gemini-2026-07-09T22-34-02/package.json +9 -0
  16. package/bench/.runs/gemini-2026-07-09T22-34-02/src/index.mjs +3 -0
  17. package/bench/.runs/gemini-2026-07-09T22-34-02/tests/STORY-1.test.mjs +24 -0
  18. package/bench/.runs/gemini-2026-07-09T22-34-02/tests/STORY-2.test.mjs +28 -0
  19. package/bench/.runs/gemini-2026-07-09T22-34-02/tests/STORY-3.test.mjs +25 -0
  20. package/bench/README.md +42 -0
  21. package/bench/RESULTS.md +36 -0
  22. package/bench/fixtures/string-kit/.yoke/config.yaml +6 -0
  23. package/bench/fixtures/string-kit/.yoke/prd.yaml +32 -0
  24. package/bench/fixtures/string-kit/bench-verify.mjs +15 -0
  25. package/bench/fixtures/string-kit/package.json +9 -0
  26. package/bench/fixtures/string-kit/src/index.mjs +3 -0
  27. package/bench/fixtures/string-kit/tests/STORY-1.test.mjs +24 -0
  28. package/bench/fixtures/string-kit/tests/STORY-2.test.mjs +28 -0
  29. package/bench/fixtures/string-kit/tests/STORY-3.test.mjs +25 -0
  30. package/bench/results/claude-2026-07-09T22-34-01.json +40 -0
  31. package/bench/run.mjs +115 -0
  32. package/canon/loop/loop-spec.md +5 -1
  33. package/canon/manifest.yaml +3 -0
  34. package/canon/skills/yoke-retrofit/SKILL.md +1 -0
  35. package/canon/tools/claude-mem.md +15 -0
  36. package/canon/tools/ui-ux-pro-max.md +15 -0
  37. package/dist/cli.js +37 -6
  38. package/dist/loop/cleanup.js +44 -3
  39. package/dist/loop/loop.js +67 -2
  40. package/dist/loop/reporter.js +88 -4
  41. package/dist/loop/run-command.js +25 -8
  42. package/dist/loop/runner.js +133 -14
  43. package/dist/loop/watchdog.js +67 -17
  44. package/dist/retrofit/apply.js +10 -3
  45. package/dist/retrofit/config.js +9 -1
  46. package/dist/retrofit/gitignore.js +4 -0
  47. package/dist/retrofit/planners/claude.js +4 -1
  48. package/dist/retrofit/planners/gemini.js +2 -1
  49. package/dist/retrofit/preserve.js +51 -0
  50. package/dist/update/check.js +86 -0
  51. package/dist/update/refresh.js +28 -0
  52. package/dist/update/upgrade.js +30 -0
  53. package/docs/PUBLISHING.md +41 -0
  54. package/docs/superpowers/plans/2026-06-27-forge-a-canon.md +815 -0
  55. package/docs/superpowers/plans/2026-06-27-forge-b1-retrofit-claude.md +842 -0
  56. package/docs/superpowers/plans/2026-06-27-forge-b2-codex-gemini-tools.md +884 -0
  57. package/docs/superpowers/plans/2026-06-27-forge-c1-loop-engine.md +891 -0
  58. package/docs/superpowers/plans/2026-06-28-baustein-e-context-layer.md +981 -0
  59. package/docs/superpowers/plans/2026-06-28-forge-b3-settings-merge.md +364 -0
  60. package/docs/superpowers/plans/2026-06-28-forge-c2-loop-verify.md +502 -0
  61. package/docs/superpowers/plans/2026-06-28-forge-c3-multi-agent-runners.md +377 -0
  62. package/docs/superpowers/plans/2026-06-28-forge-c4-worktree-isolation.md +413 -0
  63. package/docs/superpowers/plans/2026-06-28-forge-c5-review-iteration.md +419 -0
  64. package/docs/superpowers/plans/2026-06-28-forge-cleanup-dep0190.md +233 -0
  65. package/docs/superpowers/plans/2026-06-28-forge-d-codegraph-choice-minimal-code.md +523 -0
  66. package/docs/superpowers/plans/2026-06-29-baustein-f-routing.md +258 -0
  67. package/docs/superpowers/plans/2026-06-29-baustein-g-loop-observability.md +1006 -0
  68. package/docs/superpowers/plans/2026-06-29-baustein-h-loop-robustness.md +374 -0
  69. package/docs/superpowers/plans/2026-06-30-baustein-i-visual-design-verification.md +450 -0
  70. package/docs/superpowers/plans/2026-07-02-baustein-j-cross-model-review-gstack-compose.md +645 -0
  71. package/docs/superpowers/plans/2026-07-02-baustein-k-zero-to-100-bootstrap.md +1024 -0
  72. package/docs/superpowers/plans/2026-07-02-baustein-m-flow-smoke-proofs.md +574 -0
  73. package/docs/superpowers/specs/2026-06-27-forge-cross-agent-harness-design.md +144 -0
  74. package/docs/superpowers/specs/2026-06-28-baustein-e-context-layer-design.md +146 -0
  75. package/docs/superpowers/specs/2026-06-29-baustein-f-routing-design.md +106 -0
  76. package/docs/superpowers/specs/2026-06-29-baustein-g-loop-observability-design.md +186 -0
  77. package/docs/superpowers/specs/2026-06-29-baustein-h-loop-robustness-design.md +113 -0
  78. package/docs/superpowers/specs/2026-06-30-baustein-i-visual-design-verification-design.md +98 -0
  79. package/docs/superpowers/specs/2026-07-02-baustein-j-cross-model-review-gstack-compose-design.md +137 -0
  80. package/docs/superpowers/specs/2026-07-02-baustein-k-zero-to-100-bootstrap-design.md +200 -0
  81. package/docs/superpowers/specs/2026-07-02-baustein-m-flow-smoke-proofs-design.md +155 -0
  82. package/docs/superpowers/specs/2026-07-10-companion-tools-decision.md +33 -0
  83. package/docs/superpowers/specs/2026-07-10-multi-agent-parallel-loop-design.md +99 -0
  84. package/package.json +8 -2
@@ -0,0 +1,113 @@
1
+ # Baustein H — Loop Robustness (verify-as-truth + flaky-tolerant verify)
2
+
3
+ **Status:** Design approved 2026-06-29
4
+ **Component:** Yoke (🐂)
5
+ **Relates to:** [[harness-build-progress]], [[harness-loop-technique]]
6
+
7
+ ## Problem & Goal
8
+
9
+ Evidence from a real autonomous run (NewMarket, 52/57 stories): the loop blocked and needed
10
+ human intervention on a handful of stories. Two of those block causes are **harness defects**,
11
+ not project defects, and both are cheap to fix:
12
+
13
+ 1. **Runner exit-code is treated as ground truth (SE5).** The story was implemented and the full
14
+ suite was green (769/769), but the `claude` `.cmd` wrapper on Windows exited **127**. The loop
15
+ checks `result.success` *before* running verify, so it **blocked a successful story** on a
16
+ spurious exit code and didn't auto-commit. This contradicts the loop's own stated philosophy
17
+ (Baustein C2): *"independent verification is the source of truth, not the agent's exit code."*
18
+
19
+ 2. **No flaky-test tolerance (T4).** Under autonomous load, a heavy background task caused an
20
+ async test to time out — a **false** red, not a real defect. The single-shot verify gate
21
+ blocked the story. The user had to manually add `retry: 2` to the project's `vitest.config`.
22
+ That resilience belongs in the harness.
23
+
24
+ **Goal:** make the loop trust **verify**, not the agent's exit code, and tolerate transient
25
+ flakes — closing two real block causes without weakening the gate.
26
+
27
+ **Out of scope (deferred):** cross-story regression repair (S5/S6 — a later story breaking an
28
+ earlier story's tests). That needs its own design; this spec does not address it.
29
+
30
+ ## Key Decisions (locked)
31
+
32
+ | Decision | Choice |
33
+ |---|---|
34
+ | Verify authority | The **implementer** runner's `success` flag is advisory; **verify decides**. Run verify regardless of runner exit, block only on verify failure. |
35
+ | Runner-fail + verify-pass | Proceed (commit) — the exit code was a ghost. Note it via the reporter / summary. |
36
+ | Runner-fail + verify-fail | Block, with a reason naming both signals. |
37
+ | Reviewer runner | **Unchanged** — its non-zero exit *is* the reject verdict (there is no separate verify for the review). |
38
+ | Flaky tolerance | `retryingVerifier(inner, retries)` re-runs a failing verify up to `retries` times; first pass wins. |
39
+ | Retry default | `config.verify.retries` default **1** (one retry). `0` = strict/no-retry. A real failure still fails (twice). |
40
+ | Out of scope | Cross-story regression repair; changing the reviewer's exit-code semantics. |
41
+
42
+ ## Architecture
43
+
44
+ ### 1. `src/loop/verify.ts` — `retryingVerifier`
45
+ ```ts
46
+ export function retryingVerifier(inner: Verifier, retries: number): Verifier {
47
+ return (targetDir) => {
48
+ let last = inner(targetDir)
49
+ let attempt = 0
50
+ while (!last.passed && attempt < retries) {
51
+ attempt++
52
+ last = inner(targetDir)
53
+ }
54
+ if (last.passed && attempt > 0) {
55
+ return { passed: true, summary: `${last.summary} (passed on retry ${attempt})` }
56
+ }
57
+ return attempt > 0 && !last.passed
58
+ ? { passed: false, summary: `${last.summary} (still failing after ${attempt} retr${attempt === 1 ? 'y' : 'ies'})` }
59
+ : last
60
+ }
61
+ }
62
+ ```
63
+ Pure, injectable `inner` for tests (no real command needed).
64
+
65
+ ### 2. `src/loop/loop.ts` — verify is the gate
66
+ In **both** the isolate and non-isolate paths, restructure the implementer step so verify always
67
+ runs:
68
+
69
+ - Run the implementer runner (keep `result` for its summary).
70
+ - `reporter.phase('verifying')`; run `opts.verify(dir)`.
71
+ - If verify **fails**: block. Reason = verify summary, and if the runner *also* reported failure,
72
+ prepend that (`runner reported failure (<summary>); verify also red: <verify summary>`).
73
+ - If verify **passes**: proceed to review/commit even if `result.success` was false. When the
74
+ runner had reported failure, the committed decision/summary notes
75
+ `(runner exited non-zero but verify is green)` so the ghost is auditable.
76
+
77
+ The reviewer step is untouched: `if (!reviewResult.success) → block` stays (exit = verdict).
78
+
79
+ The Baustein-E invariant (no `passes:true` without a commit; decision rollback on commit failure)
80
+ and the leftover-hint are preserved exactly — only the implementer-failure gate moves from
81
+ "before verify" to "verify decides".
82
+
83
+ ### 3. `src/retrofit/config.ts` — `verify.retries`
84
+ Extend the `verify` config object: `verify: { command: string; retries?: number }`.
85
+ Backwards-compatible (optional).
86
+
87
+ ### 4. `src/loop/run-command.ts` — wire the retry
88
+ When building the verifier from config, wrap it:
89
+ `verify = retryingVerifier(commandVerifier(command), config.verify?.retries ?? 1)`.
90
+ The injected-verifier test path (`opts.verify`) is unchanged.
91
+
92
+ ### 5. README + loop-spec
93
+ Document: verify is the source of truth (a spurious agent exit code can't block a green story),
94
+ and `verify.retries` (default 1) for flaky suites. Update `canon/loop/loop-spec.md` step 5.
95
+
96
+ ## Testing (subagent-driven TDD)
97
+ - **verify.ts:** `retryingVerifier` passes immediately when inner passes (no retry); passes on
98
+ retry N when inner fails then passes; fails after exhausting retries; `retries: 0` = single
99
+ shot; summary notes the retry/exhaustion. Inner is a stub Verifier.
100
+ - **loop.ts:** runner-fail + verify-pass → story is committed + `passes:true` (the SE5 case);
101
+ runner-fail + verify-fail → blocked with a combined reason; runner-pass + verify-fail →
102
+ blocked (unchanged); the reviewer still blocks on its own failure; existing invariant tests
103
+ (commit-failure revert, leftover hint) stay green; applies to both isolate and non-isolate.
104
+ - **config.ts:** `verify.retries` accepted + optional.
105
+ - **run-command.ts:** the built verifier is wrapped with the resolved retry count (assert via a
106
+ small seam or by config round-trip).
107
+ - Full suite green; `tsc` clean.
108
+
109
+ ## What this would have done for the run
110
+ - **SE5:** runner exits 127 but suite is 769/769 green → verify passes → story auto-commits. No
111
+ manual finalize.
112
+ - **T4:** the load-flake fails once, the retry passes → story proceeds. No manual `vitest.config`
113
+ patch, no false block.
@@ -0,0 +1,98 @@
1
+ # Baustein I — Visual & Design Verification
2
+
3
+ **Status:** Design approved 2026-06-30 (autonomous)
4
+ **Component:** Yoke (🐂)
5
+ **Relates to:** [[harness-build-progress]], [[readme-always-update]]
6
+ **Inspired by:** [vibecoded-design-tells](https://github.com/JCarterJohnson/vibecoded-design-tells) (MIT © 2026 Carter Johnson) — a data-ranked study of the visual "tells" of AI-generated UIs. Yoke implements the *idea* natively in TypeScript and credits the research; no code or data is copied.
7
+
8
+ ## Problem & Goal
9
+
10
+ Yoke's verify gate is **code-only** (`tsc` + unit/component tests). It does not check that the UI is **visually sound** (free of generic AI-slop design) or that **user flows actually work end-to-end**. Evidence: in the real NewMarket run, integration/visual bugs (unwired auth pages, a seed id-collision, an old "purple #6c5ce7 dark theme" — itself a classic AI-slop tell) slipped past the unit-test gate and were only caught by a later manual QA sweep.
11
+
12
+ **Goal:** add a **visual & design verification layer** that plugs into the existing verify model (so it's gated, not advisory) and is honest about cost:
13
+ 1. **Mechanical:** a static **design-slop scanner** (`yoke design-scan`) that flags the high-signal AI-slop tells and gates on exit code.
14
+ 2. **Methodology:** two canon skills — `unslop-ui` (the design rubric) and `visual-verification` (compose a verify pipeline: types + unit + design-scan + a Playwright flow-smoke; capture video only on failure).
15
+
16
+ Both integrate through one idea: **the project's `verify.command` becomes a pipeline**, and Baustein-H's verify-as-truth makes those gates authoritative.
17
+
18
+ ## Key Decisions (locked)
19
+
20
+ | Decision | Choice |
21
+ |---|---|
22
+ | Design-slop detection | A **TS-native** scanner built into the `yoke` CLI (`yoke design-scan`), not a port of the upstream Python; high-precision static heuristics |
23
+ | Gate model | Exit non-zero when the weighted tell-score exceeds `--max` (default **4**); `--report` lists without failing |
24
+ | Flow / video | **Methodology, not CLI** — the agent drives the wired Playwright MCP per the `visual-verification` skill. Yoke gates + guides; it does not embed a browser. Video capture is **opt-in, on failure only** (token-aware). |
25
+ | Skills | `unslop-ui` (rubric) + `visual-verification` (pipeline + flow-smoke + video-on-failure) → all 3 agents |
26
+ | Attribution | Credit vibecoded-design-tells (MIT) in `ATTRIBUTION.md` + README + the skill |
27
+ | Canon count | 24 → **26** skills |
28
+ | Out of scope (YAGNI) | Embedding Playwright/a browser in the Yoke CLI; structural-layout detection ("centered hero + 3 cards") — left to the rubric + agent eye; auto-fixing slop (the agent fixes, guided by the skill) |
29
+
30
+ ## Architecture
31
+
32
+ ### 1. `src/scan/design.ts` (new) — the static scanner
33
+ Pure + unit-testable. Walks the project's source and scores AI-slop tells.
34
+
35
+ ```ts
36
+ export interface Tell { name: string; weight: number; test: (line: string) => boolean; hint: string }
37
+ export interface Finding { file: string; line: number; tell: string; hint: string; text: string }
38
+ export interface ScanResult { findings: Finding[]; score: number }
39
+
40
+ export const TELLS: Tell[] // the curated tell set (below)
41
+ export function scanText(text: string, tells?: Tell[]): { line: number; tell: Tell; text: string }[]
42
+ export function scanDir(dir: string, tells?: Tell[]): ScanResult // walks files, aggregates
43
+ ```
44
+
45
+ **Curated high-precision tells** (each match adds `weight` to the score):
46
+
47
+ | Tell | weight | matches (case-insensitive) | hint |
48
+ |---|---|---|---|
49
+ | `ai-purple` | 2 | hex `#6c5ce7\|#7c3aed\|#8b5cf6\|#a855f7\|#9333ea`, or Tailwind `(from\|via\|to)-(purple\|violet\|fuchsia)-(4\|5\|6\|7)00` | AI-purple is the #1 vibecoded tell — choose a real brand color |
50
+ | `gradient-clip-text` | 2 | a line containing both `bg-clip-text` and `text-transparent`, or CSS `-webkit-background-clip:\s*text` near a gradient | gradient hero text reads as AI-slop — solid color + weight instead |
51
+ | `neon-glow` | 2 | Tailwind `(shadow\|drop-shadow)-\[0_0_`, or CSS `box-shadow:[^;]*0\s+0\s+\d{2,}px` with a color | neon glow is a tell — use subtle, neutral elevation |
52
+ | `gradient-overload` | 1 | `bg-gradient-to-` or CSS `linear-gradient(` | gradients everywhere flatten hierarchy — use them sparingly |
53
+ | `emoji-icon` | 1 | an emoji (unicode pictographic) inside a `.tsx/.jsx` line that also contains `<button`, `<a `, `aria-hidden`, or a JSX `>…<` icon slot | emoji-as-icons is a tell — use a real icon set |
54
+
55
+ File walk: extensions `.css .scss .tsx .jsx .ts .js .html .vue .svelte .astro`; skip `node_modules`, `dist`, `.next`, `build`, `.yoke`, `coverage`, `.git`. The tell set is the default but injectable (for tests). Heuristic by design — documented as high-signal, not exhaustive.
56
+
57
+ ### 2. `src/cli.ts` — `yoke design-scan [dir] [--max=N] [--report]`
58
+ - Runs `scanDir(dir)`, prints findings grouped by tell as `file:line <tell> — <hint>`.
59
+ - `--report`: print + summary, **always exit 0** (advisory).
60
+ - default (gate): print + summary; **exit 1 if `score > max`** (default `max=4`), else 0. So a couple incidental matches pass; pervasive slop fails. Designed to sit in a verify pipeline.
61
+ - A small body extracted to `runDesignScan(dir, { max, report }): number` for testability.
62
+
63
+ ### 3. `canon/skills/unslop-ui/SKILL.md` (new) — the rubric
64
+ Agent-facing. Lists the ranked tells (AI-purple gradients, gradient hero text, neon glow, emoji-as-icons, shadcn defaults left unchanged, "centered hero + three cards", homogeneous spacing) and how to fix each. Instructs: before finishing UI work, run `yoke design-scan .` and resolve findings; also apply the structural items the scanner can't see. Credits the research.
65
+
66
+ ### 4. `canon/skills/visual-verification/SKILL.md` (new) — ties it together
67
+ Methodology for UI projects:
68
+ - **Compose the verify pipeline** so the loop gate covers more than units: `verify.command` chains types → unit tests → `yoke design-scan .` → a Playwright flow-smoke. (Baustein-H makes these authoritative.)
69
+ - **Flow-smoke via the wired Playwright MCP:** load the key routes against the dev server, assert they render and the console has **no errors**, screenshot each. This catches the "unwired page / runtime crash" bugs unit tests miss.
70
+ - **Video only when necessary:** capture a video of a flow **only on failure** (or when explicitly debugging a UX issue), then analyse it — keeps tokens down. Never record every run.
71
+
72
+ ### 5. Wiring
73
+ - `canon/manifest.yaml`: add `unslop-ui` + `visual-verification` (kind: methodology).
74
+ - `canon/skills/ATTRIBUTION.md`: credit vibecoded-design-tells (MIT © Carter Johnson).
75
+ - `README.md` (**mandatory**): a "Visual & design verification" section (the scanner + the two skills + the pipeline idea), the catalog updated (24 → 26, methodology group +2), and the test-count badge synced.
76
+
77
+ ## Data flow (gate)
78
+ ```
79
+ verify.command = tsc --noEmit && vitest run && yoke design-scan . && <playwright flow-smoke>
80
+ │ exit 1 if slop-score > max
81
+ loop verify (Baustein H: verify is the source of truth) ──► block on any red step
82
+ ```
83
+
84
+ ## Testing (subagent-driven TDD)
85
+ - **design.ts:** `scanText` flags each tell with correct line + weight; clean text → no findings; `scanDir` walks + skips ignored dirs + aggregates score; injected tell-set works; emoji/purple/clip/glow/gradient cases each covered; a known-clean snippet scores 0.
86
+ - **cli:** `runDesignScan` exits 1 when score > max, 0 when ≤ max, 0 always in `--report`; `--max` parsed; bad `--max` rejected.
87
+ - **canon:** `unslop-ui` + `visual-verification` registered; `validateCanon` stays zero-error; real-canon asserts both present.
88
+ - Full suite green; `tsc` clean.
89
+
90
+ ## What this would have caught
91
+ - NewMarket's old **purple `#6c5ce7` dark theme** → `ai-purple` tell, scored, flagged before it shipped.
92
+ - **Unwired auth pages / runtime crashes** → the flow-smoke (render + no console errors) gate, not the unit tests.
93
+
94
+ ## Non-goals (YAGNI)
95
+ - No browser embedded in the Yoke CLI (Playwright MCP is the agent's tool).
96
+ - No always-on video (opt-in, on failure).
97
+ - No auto-rewrite of slop (the agent fixes via the rubric).
98
+ - No structural-layout static detection (rubric + agent eye).
@@ -0,0 +1,137 @@
1
+ # Baustein J — Cross-Model Review + gstack Compose
2
+
3
+ **Date:** 2026-07-02
4
+ **Status:** Approved (design), pending implementation plan
5
+
6
+ ## Motivation
7
+
8
+ A comparison of Yoke against gstack (garrytan/gstack) and superpowers (obra/superpowers)
9
+ surfaced two capabilities worth adopting, each in a way that preserves Yoke's identity
10
+ (cross-agent portability + reproducible retrofit + safe autonomy):
11
+
12
+ 1. **On-demand cross-model review.** gstack's `/codex` lets a *second* model review the
13
+ current diff interactively with a pass/fail gate. Yoke already does cross-model review
14
+ *inside the loop* (`yoke loop run --review --reviewer=codex`, see `src/loop/runner.ts`
15
+ `makeReviewRunner` + `src/loop/loop.ts`), but there is **no standalone command** for a
16
+ human working interactively to get a second-model review before committing/pushing.
17
+
18
+ 2. **Composing with gstack instead of bundling it.** gstack is Claude-Code- and Bun-bound,
19
+ auto-updates hourly, and owns its own skill namespace. Bundling it would break Yoke's
20
+ cross-agent uniformity (Codex/Gemini users would get nothing) and create two masters over
21
+ `~/.claude/skills`. The sound integration is **detect-and-compose**: if gstack is present,
22
+ route Claude to it for capabilities Yoke deliberately does not ship — without depending on
23
+ it, and without ever touching Codex/Gemini artifacts.
24
+
25
+ Explicitly rejected: bundling gstack at install time; copying its browser/iOS/design-shotgun
26
+ breadth (that is gstack's Claude-Code + Bun identity, and copying it would dilute Yoke's
27
+ cross-agent promise).
28
+
29
+ ## Part 1 — `yoke review` (standalone cross-model gate)
30
+
31
+ ### Command
32
+
33
+ ```
34
+ yoke review [targetDir] [--reviewer=<claude|codex|gemini>] [--base=<ref>] [--focus="..."]
35
+ ```
36
+
37
+ ### Behavior
38
+
39
+ - **Diff scope:**
40
+ - Default: the **uncommitted working-tree changes** (working tree + staged). Matches the
41
+ loop's per-story review semantics and the common "review before I commit/push" case.
42
+ - `--base=<ref>`: review the committed range `<ref>..HEAD` (pre-PR review of a whole branch).
43
+ - **Reviewer resolution:**
44
+ - `--reviewer=<agent>` overrides (validated against `claude|codex|gemini`).
45
+ - Without the flag, resolve to the first **available** (CLI on PATH) agent in the order
46
+ **`[codex, gemini, claude]`** — preferring a *second* model so the review is genuinely
47
+ cross-model.
48
+ - If resolution lands on `claude` (only Claude on PATH), proceed but print a note that this
49
+ is a **self-review, not cross-model**.
50
+ - If no agent CLI is on PATH, exit non-zero with an install hint (same diagnostics style as
51
+ `runLoopCommand`).
52
+ - **Prompt:** a new story-less builder `buildStandaloneReviewPrompt(scope, focus?)` added to
53
+ `src/loop/runner.ts` next to the existing `buildReviewPrompt`. It instructs the reviewer to inspect
54
+ the diff (it runs `git diff` itself — it has repo access) for correctness, unmet intent,
55
+ missing tests, and obvious bug/security risks. `--focus` injects an emphasis line.
56
+ Approve by exiting 0; reject (any blocking issue) by exiting non-zero. Do not modify files.
57
+ - **Gate semantics:** the reviewer's stdout inherits to the console (like the loop). Approve →
58
+ `yoke review` exits **0**; findings → exits **1**. This makes it chainable as a gate:
59
+ `... && yoke review`, inside `verify.command`, or as a pre-push hook. Consistent with the
60
+ Baustein-H gate model.
61
+ - **Watchdog:** wrap the invocation with the existing `buildWatchdogInvocation` using the
62
+ default idle timeout (reuse `resolveIdleMs` / `DEFAULT_IDLE_MINUTES`), overridable via a
63
+ `--timeout=<minutes>` flag, so a silent hang is killed. Minor but keeps parity with the loop.
64
+
65
+ ### Module shape
66
+
67
+ - Export a thin `runAgent(inv: Invocation): AgentResult` from `src/loop/runner.ts` (wraps the
68
+ existing private `runCli`) so a standalone caller can reuse invocation + watchdog plumbing
69
+ without duplicating the win32/exec logic.
70
+ - Add `src/review/command.ts` with `runReview(targetDir, opts): number` (mirrors
71
+ `src/context/command.ts`). It resolves the reviewer, builds the scope description + prompt,
72
+ invokes via `agentInvocation` + `buildWatchdogInvocation` + `runAgent`, and maps the result
73
+ to an exit code.
74
+ - Wire a `case 'review':` into `src/cli.ts` `main()` with flag parsing mirroring the existing
75
+ `loop run` parsing (validate `--reviewer`, `--timeout`; accept `--base`, `--focus`).
76
+
77
+ ### Out of scope (YAGNI)
78
+
79
+ - No loop wiring (the loop already has `--review`/`--reviewer`).
80
+ - No multi-reviewer quorum.
81
+ - No diff parsing in Node — the agent reads the diff itself.
82
+
83
+ ## Part 2 — gstack compose (interop bridge)
84
+
85
+ ### Detection
86
+
87
+ `retrofit` checks, best-effort and non-fatal, for a gstack install:
88
+
89
+ - repo-local: `<targetDir>/.claude/skills/gstack`
90
+ - global: `<os.homedir()>/.claude/skills/gstack`
91
+
92
+ Detection returns a boolean; a filesystem error is treated as "not detected".
93
+
94
+ ### Effect (Claude artifact only)
95
+
96
+ When gstack is detected **and** `claude` is a target agent, `planClaude` appends a short
97
+ **"Composed tools"** section to the generated `CLAUDE.md` (`src/retrofit/planners/claude.ts`,
98
+ `claudeMd(...)`). The section routes Claude to gstack for the capabilities Yoke deliberately
99
+ does not ship:
100
+
101
+ - Live-browser QA → gstack `/qa`
102
+ - Security audit → gstack `/cso`
103
+ - Ship / deploy → gstack `/ship`, `/land-and-deploy`
104
+
105
+ The note is **never** added to Codex or Gemini artifacts (gstack is Claude-only). If gstack is
106
+ absent, nothing is written — the feature degrades cleanly to nothing, preserving cross-agent
107
+ uniformity.
108
+
109
+ ### Non-goals
110
+
111
+ - No bundling, no install of gstack, no dependency, no interaction with gstack's auto-update.
112
+ - Yoke does not own gstack; it composes with it only when the user has installed it.
113
+
114
+ ## Part 3 — Tests, docs, attribution
115
+
116
+ - **Tests:**
117
+ - `yoke review`: diff-scope resolution (default vs `--base`), reviewer resolution order
118
+ (codex→gemini→claude) including the self-review fallback note and the no-agent error,
119
+ exit-code gate (approve=0 / reject=1), using an injected fake runner (no real CLI call,
120
+ mirroring the loop tests' seam).
121
+ - gstack detection: repo-local present, global present, absent; and that `planClaude` adds
122
+ the "Composed tools" section to CLAUDE.md only when detected + claude targeted, and never
123
+ to Codex/Gemini artifacts.
124
+ - **Docs:** README gets a `yoke review` section + a "composes with gstack" note; the canon
125
+ `review` skill mentions `yoke review` as the interactive counterpart to the loop's
126
+ `--review`. Bump the test-count badge.
127
+ - **Attribution:** credit the `/codex` idea to gstack (MIT © Garry Tan) in
128
+ `canon/skills/ATTRIBUTION.md` — natively re-implemented, cross-agent, no code copied.
129
+
130
+ ## Testability & isolation
131
+
132
+ - `runReview` takes an injectable runner (default = real `runAgent`) and an injectable
133
+ `isAvailable` probe, exactly like `runLoopCommand`, so tests never spawn a real agent CLI.
134
+ - gstack detection takes the two candidate paths (or a `homedir` injection) so tests can point
135
+ at a temp dir.
136
+ - Each unit has one purpose: prompt building, reviewer resolution, detection, and CLI wiring
137
+ are separable and independently testable.
@@ -0,0 +1,200 @@
1
+ # Baustein K — Zero-to-100 Bootstrap: `yoke new`, PRD draft/check, loop cleanup, loop lock
2
+
3
+ Date: 2026-07-02
4
+ Status: approved (design delegated by user; scope approved in conversation: "ok setze es um wie du es geplant hast, gleich nach K")
5
+
6
+ ## Problem
7
+
8
+ Yoke's core claim is "zero to 100% autonomous development", but today the zero side is missing:
9
+ the loop requires a hand-written `.yoke/prd.yaml` in an already-existing git repo. Greenfield
10
+ start is undocumented agent work. Two robustness gaps compound this: a crashed loop leaves
11
+ orphaned worktrees behind (manual `git worktree remove`), and two concurrent `yoke loop run`
12
+ invocations race on the PRD and status files.
13
+
14
+ ## Goal
15
+
16
+ One command from idea to loop-ready project, plus loop robustness:
17
+
18
+ ```
19
+ yoke new my-app --idea="CLI tool that ..." # scaffold + retrofit + context + drafted PRD, committed
20
+ yoke loop on my-app && yoke loop run my-app --isolate
21
+ ```
22
+
23
+ ## Part 1: `yoke new <dir> [--idea="..."] [--agent=...] [--runner=<agent>] [--loop]`
24
+
25
+ Module: `src/new/command.ts`, export `runNew(dir: string, opts: RunNewOptions): number`.
26
+
27
+ Behavior, in order:
28
+
29
+ 1. `<dir>` is required (usage + exit 1 if missing). If the directory exists **and is non-empty**,
30
+ refuse with exit 1 (`yoke new` is greenfield-only; retrofit exists for brownfield). An existing
31
+ empty directory is fine.
32
+ 2. Create the directory (recursive) and `git init` it.
33
+ 3. Minimal scaffold (language-agnostic — the PRD's first story scaffolds the real project):
34
+ - `README.md`: `# <basename>` plus the idea text as a paragraph when `--idea` is given.
35
+ - `.gitignore`: `node_modules/`, `dist/`, `.env` (one per line).
36
+ 4. Run the existing retrofit (`runRetrofit(dir, { loop: opts.loop, agents })`). Agents resolve like
37
+ the `retrofit` CLI case: `--agent=` list or default; in an empty dir detection finds nothing, so
38
+ the default is `['claude']`.
39
+ 5. Run `runContextInit(dir)`. When `--idea` is given, append `\n## Idea\n\n<idea>\n` to
40
+ `.yoke/context/PROJECT.md` so every loop iteration sees the north star.
41
+ 6. Write the PRD **template** to `.yoke/prd.yaml` (see Part 2a below): an empty story array `[]`
42
+ preceded by comment lines showing a fully-formed example story. Comments survive because we
43
+ write the file verbatim; `loadPrd` still parses it (empty array is schema-valid).
44
+ 7. Initial commit: `git add -A` + commit `chore: bootstrap <basename> with yoke`
45
+ (`-c commit.gpgsign=false`, same as `realGitOps.commitAll`). This makes `--isolate` work from
46
+ iteration 1 (worktrees check out committed HEAD).
47
+ 8. When `--idea` is given: run the PRD draft (Part 2) with `--runner` resolution, then commit the
48
+ drafted PRD as a second commit `docs: draft PRD from idea`. If the draft fails (agent error or
49
+ invalid YAML), keep the template, print
50
+ `PRD draft failed (<reason>). Project is ready; retry with: yoke prd draft <dir> --idea="..."`
51
+ and return **1** (the scaffold succeeded, but the user's idea→PRD ask did not — signal it).
52
+ 9. Print next steps: edit/inspect `.yoke/prd.yaml`, set `verify.command` in `.yoke/config.yaml`,
53
+ `yoke loop on <dir>`, `yoke loop run <dir> --isolate`.
54
+
55
+ Exit codes: 0 success; 1 usage / non-empty dir / draft failure; 2 requested draft agent unavailable.
56
+
57
+ Injectable seams for tests: `git?: (args: string[], cwd: string) => void` (default execFileSync
58
+ wrapper) and the Part-2 seams passed through (`isAvailable`, `run`).
59
+
60
+ ## Part 2: `yoke prd draft [dir] --idea="..." [--runner=<agent>] [--force] [--timeout=<minutes>]`
61
+
62
+ Module: `src/prd/command.ts`, export `runPrdDraft(targetDir: string, opts: PrdDraftOptions): number`.
63
+
64
+ - `--idea` is required (exit 1 with usage if missing/empty).
65
+ - Overwrite guard: if `.yoke/prd.yaml` exists and parses to **> 0 stories**, refuse with exit 1
66
+ (`PRD already has N stories — use --force to overwrite`) unless `--force`. The Part-1 template
67
+ (0 stories) never triggers the guard.
68
+ - Agent resolution mirrors the loop: `--runner` ?? `config.agents[0]` ?? `'claude'`; must pass
69
+ `isAgentAvailable`, else exit 2 with install hint. (No cross-model preference here — drafting is
70
+ not adversarial review.)
71
+ - Prompt builder `buildPrdDraftPrompt(idea: string): string` in `src/prd/command.ts`:
72
+ - You are drafting a PRD for the Yoke loop.
73
+ - Break the idea into 5–12 small, independently shippable stories; each must fit one loop
74
+ iteration.
75
+ - Each story: `id` (STORY-1…), `title` (imperative), `priority` (dense from 1, lower = first),
76
+ `acceptance` (2–5 testable, behavioral criteria — outcomes, not implementation), `passes: false`.
77
+ - If the project has no source code yet, STORY-1 must scaffold the project skeleton including a
78
+ runnable test suite, and its acceptance must include that the verify command
79
+ (`.yoke/config.yaml` → `verify.command`) runs green.
80
+ - Write ONLY the file `.yoke/prd.yaml` as a YAML array matching this schema (schema inlined).
81
+ Do not modify other files. Do not commit.
82
+ - Execution reuses the Baustein-J plumbing: `agentInvocation` → default runner
83
+ `runAgent(buildWatchdogInvocation(inv, idleMs))` with `resolveIdleMs(opts.timeoutMinutes, undefined)`;
84
+ injectable `isAvailable` / `run` seams exactly like `src/review/command.ts`.
85
+ - Post-validation: `loadPrd(prdPath)` — on parse/schema failure exit 1 with the zod message; on
86
+ success print `Drafted N stories → .yoke/prd.yaml` and exit 0. 0 drafted stories is a failure
87
+ (exit 1, `agent produced an empty PRD`).
88
+
89
+ ### Part 2a: PRD template
90
+
91
+ Exported const `PRD_TEMPLATE` (in `src/prd/command.ts`), written by `yoke new`:
92
+
93
+ ```yaml
94
+ # Yoke PRD — the loop picks the lowest-priority open story each iteration.
95
+ # Story format (see canon/loop/prd.schema.md):
96
+ # - id: STORY-1
97
+ # title: scaffold the project with a runnable test suite
98
+ # priority: 1
99
+ # acceptance:
100
+ # - "the verify command exits 0"
101
+ # - "a placeholder test exists and passes"
102
+ # passes: false
103
+ []
104
+ ```
105
+
106
+ ## Part 3: `yoke prd check [dir]`
107
+
108
+ Same module, export `runPrdCheck(targetDir: string): number`.
109
+
110
+ - Missing file → exit 1 (`No PRD at .yoke/prd.yaml — run yoke prd draft or yoke new`).
111
+ - Schema violation (zod) → print message, exit 1.
112
+ - Lints beyond the schema, each an error (exit 1): duplicate story ids; any story with an **empty
113
+ `acceptance` array** (the schema allows `[]`, but the loop's stop-the-line gate will block it —
114
+ fail fast here); zero stories (`PRD has no stories`).
115
+ - Success: print `✓ PRD valid — N stories, M pass` and exit 0. Chainable pre-loop gate.
116
+
117
+ ## Part 4: `yoke loop cleanup [dir]`
118
+
119
+ Module: `src/loop/cleanup.ts`, export `runLoopCleanup(targetDir: string, opts?): number`.
120
+
121
+ - Scans `.yoke/worktrees/` only (yoke-created paths; never touches user worktrees). Missing/empty
122
+ → `Nothing to clean.`, exit 0.
123
+ - For each entry: `git worktree remove --force <path>` from the repo root; collect failures and
124
+ fall through. Afterwards run `git worktree prune`.
125
+ - Also removes a **stale** `.yoke/loop.lock` (holder pid not alive — see Part 5). A live lock is
126
+ reported and left alone.
127
+ - Report `Removed N worktree(s).` (+ failures). Exit 0 when everything cleaned, 1 if any removal
128
+ failed.
129
+ - Injectable seam: `git?: (args: string[], cwd: string) => void`.
130
+ - Registered under the existing `loop` CLI case as sub-command `cleanup`.
131
+
132
+ ## Part 5: Loop lock (single-flight guard)
133
+
134
+ Module: `src/loop/lock.ts`:
135
+
136
+ - `lockPath(targetDir)` → `.yoke/loop.lock`; contents JSON `{ "pid": number, "startedAt": ISO }`.
137
+ - `isPidAlive(pid: number): boolean` — `process.kill(pid, 0)` in try/catch (works on Windows);
138
+ `EPERM` counts as alive.
139
+ - `acquireLock(targetDir, pid?): { acquired: boolean; holderPid?: number }` — no file or unreadable/
140
+ corrupt file → take it (mkdir `.yoke` if needed); holder alive → `{ acquired: false, holderPid }`;
141
+ holder dead → warn-and-take (caller prints the warning; the function returns
142
+ `{ acquired: true, stalePid }` — include `stalePid?: number` in the result).
143
+ - `releaseLock(targetDir)` — best-effort unlink, never throws.
144
+
145
+ Wiring in `runLoopCommand` (src/loop/run-command.ts): after the existing pre-checks (loop enabled,
146
+ PRD exists, verify resolved, agent available) and before `runLoop`, acquire the lock; on
147
+ `acquired: false` print
148
+ `Another loop is already running (pid <holderPid>). If that is wrong, run: yoke loop cleanup` and
149
+ return 2. On stale takeover print a warning. Release in `finally`.
150
+
151
+ Gitignore: add `.yoke/loop.lock` to `YOKE_IGNORE_LINES` (src/retrofit/gitignore.ts) so the
152
+ pre-dispatch clean-tree gate is not broken by the lock file itself. Note: `ensureGitignore` is
153
+ idempotent and appends only missing lines, so existing retrofitted projects pick the new line up
154
+ on their next retrofit.
155
+
156
+ ## Part 6: Canon skill `authoring-prd`
157
+
158
+ `canon/skills/authoring-prd/SKILL.md` (kind: methodology), registered in `canon/manifest.yaml`.
159
+ Content: how to slice a product idea into loop-ready stories — small and independently shippable
160
+ (one loop iteration each); acceptance criteria are testable behavioral outcomes, never
161
+ implementation steps; dense priorities; greenfield STORY-1 scaffolds project + test runner and
162
+ wires `verify.command`; full `prd.yaml` example. This gives interactive sessions (all three
163
+ agents, via retrofit) the same discipline `yoke prd draft` encodes.
164
+
165
+ Canon count moves 26 → 27; the real-canon test that asserts the skill count must be updated.
166
+
167
+ ## CLI usage line
168
+
169
+ `yoke new <dir> [--idea="..."] [--agent=...] [--runner=<agent>] [--loop] | prd <draft|check> [dir] [--idea="..."] [--runner=<agent>] [--force] | loop <on|off|status|run|cleanup> | ...`
170
+
171
+ ## Testing
172
+
173
+ - `tests/prd/command.test.ts`: draft — runner receives resolved agent invocation (seam), `--runner`
174
+ honored, unavailable → 2, overwrite guard (>0 stories blocks, `--force` passes, template `[]`
175
+ passes), post-validation failure → 1, empty result → 1, success prints count; prompt builder —
176
+ contains idea, schema, story-count band, STORY-1 scaffold rule, "Write ONLY"; check — valid PRD
177
+ 0, duplicate ids 1, empty acceptance 1, no stories 1, missing file 1.
178
+ - `tests/new/command.test.ts`: non-empty dir refused; scaffold files + git init + initial commit
179
+ (seam-recorded git calls); retrofit artifacts present (real canon); PROJECT.md gets idea section;
180
+ template PRD written and schema-parses to `[]`; `--idea` triggers draft via injected run seam and
181
+ second commit; draft failure → exit 1 + template intact.
182
+ - `tests/loop/cleanup.test.ts`: removes listed worktrees via git seam + prune called; nothing to
183
+ clean; failure → exit 1; stale lock removed, live lock kept.
184
+ - `tests/loop/lock.test.ts`: acquire on empty; blocked by live pid (use `process.pid`); stale
185
+ takeover (dead pid, e.g. a just-exited child or an absurd pid); corrupt file → take; release
186
+ best-effort; `runLoopCommand` returns 2 when locked (existing run-command tests gain one case,
187
+ using the real lock with `process.pid`).
188
+ - `tests/retrofit/gitignore.test.ts`: extend for `.yoke/loop.lock`.
189
+ - Real-canon tests: 27 skills, `authoring-prd` frontmatter valid.
190
+
191
+ ## Non-goals
192
+
193
+ - No language/framework project templates (STORY-1 scaffolds; keeps `yoke new` universal).
194
+ - No parallel loop, no CI triggers, no PRD estimation/dependencies.
195
+ - No cross-model preference for drafting (that's review's job).
196
+
197
+ ## Attribution
198
+
199
+ PRD-driven Ralph loop: Geoffrey Huntley's Ralph technique; story-slicing discipline informed by
200
+ superpowers `writing-plans`. No external code.