@hecer/yoke 1.5.1 → 1.6.1
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 +13 -13
- package/.codex-plugin/plugin.json +7 -7
- package/CHANGELOG.md +280 -259
- package/README.md +855 -834
- package/TODOS.md +5 -5
- package/agents/docs.toml +6 -6
- package/agents/implementer.toml +6 -6
- package/agents/reviewer.toml +6 -6
- package/agents/security.toml +6 -6
- package/bench/README.md +86 -86
- package/bench/RESULTS.md +35 -35
- package/bench/output-compaction.mjs +65 -65
- package/bench/result-schema.mjs +12 -12
- package/bench/results/claude-2026-07-27T18-03-26.json +50 -50
- package/bench/results/codex-unavailable-1785175418318.json +15 -15
- package/bench/results/gemini-2026-07-27T18-03-44.json +46 -46
- package/bench/run-matrix.mjs +26 -26
- package/bench/run.mjs +106 -106
- package/canon/AGENTS.md +30 -30
- package/canon/context/DECISIONS.md +4 -4
- package/canon/context/GLOSSARY.md +11 -0
- package/canon/context/KNOWLEDGE.md +4 -4
- package/canon/context/PROJECT.md +15 -15
- package/canon/loop/loop-spec.md +65 -65
- package/canon/loop/prd.schema.md +43 -43
- package/canon/manifest.yaml +59 -53
- package/canon/policy/gates.md +7 -7
- package/canon/policy/roles.md +9 -9
- package/canon/skills/ATTRIBUTION.md +99 -71
- package/canon/skills/authoring-prd/SKILL.md +58 -58
- package/canon/skills/brainstorming/SKILL.md +164 -164
- package/canon/skills/codebase-design/DEEPENING.md +15 -0
- package/canon/skills/codebase-design/DESIGN-IT-TWICE.md +12 -0
- package/canon/skills/codebase-design/SKILL.md +39 -0
- package/canon/skills/dispatching-parallel-agents/SKILL.md +182 -182
- package/canon/skills/document-release/SKILL.md +302 -297
- package/canon/skills/domain-modeling/ADR-FORMAT.md +19 -0
- package/canon/skills/domain-modeling/CONTEXT-FORMAT.md +39 -0
- package/canon/skills/domain-modeling/SKILL.md +35 -0
- package/canon/skills/executing-plans/SKILL.md +70 -70
- package/canon/skills/finishing-a-development-branch/SKILL.md +200 -200
- package/canon/skills/health/SKILL.md +177 -177
- package/canon/skills/maintaining-context/SKILL.md +34 -34
- package/canon/skills/minimal-code/SKILL.md +21 -21
- package/canon/skills/no-ai-slop/SKILL.md +103 -0
- package/canon/skills/no-ai-slop/eval.md +43 -0
- package/canon/skills/plan-ceo-review/SKILL.md +541 -541
- package/canon/skills/plan-eng-review/SKILL.md +362 -362
- package/canon/skills/receiving-code-review/SKILL.md +213 -213
- package/canon/skills/requesting-code-review/SKILL.md +105 -105
- package/canon/skills/resolving-merge-conflicts/SKILL.md +18 -0
- package/canon/skills/retro/SKILL.md +397 -397
- package/canon/skills/review/SKILL.md +246 -246
- package/canon/skills/ship/SKILL.md +691 -691
- package/canon/skills/subagent-driven-development/SKILL.md +277 -277
- package/canon/skills/systematic-debugging/SKILL.md +296 -296
- package/canon/skills/tdd/SKILL.md +371 -371
- package/canon/skills/unslop-ui/SKILL.md +34 -34
- package/canon/skills/using-git-worktrees/SKILL.md +218 -218
- package/canon/skills/verification-before-completion/SKILL.md +139 -139
- package/canon/skills/visual-verification/SKILL.md +54 -54
- package/canon/skills/workflow/SKILL.md +22 -22
- package/canon/skills/writing-for-agents/SKILL-MECHANICS.md +27 -0
- package/canon/skills/writing-for-agents/SKILL.md +42 -0
- package/canon/skills/writing-plans/SKILL.md +152 -152
- package/canon/skills/writing-skills/SKILL.md +655 -655
- package/canon/skills/yoke-retrofit/SKILL.md +26 -26
- package/canon/skills/yoke-workflow/SKILL.md +20 -20
- package/canon/tools/codex-rtk-hook.mjs +35 -35
- package/canon/tools/graphify.md +3 -3
- package/canon/tools/playwright-mcp.md +3 -3
- package/canon/tools/rtk.md +7 -7
- package/canon/tools/serena.md +6 -6
- package/dist/agents/process.js +3 -0
- package/dist/canon/manifest.js +2 -0
- package/dist/canon/skill-package.js +113 -0
- package/dist/canon/validate.js +16 -1
- package/dist/context/command.js +4 -1
- package/dist/context/context.js +6 -0
- package/dist/loop/dispatcher.js +1 -1
- package/dist/loop/loop.js +26 -0
- package/dist/loop/parallel-command.js +3 -0
- package/dist/loop/run-command.js +11 -0
- package/dist/loop/watchdog.js +28 -11
- package/dist/loop/worker.js +11 -0
- package/dist/prd/command.js +17 -17
- package/dist/retrofit/apply.js +22 -7
- package/dist/retrofit/command.js +4 -1
- package/dist/retrofit/config.js +4 -0
- package/dist/retrofit/context-actions.js +1 -1
- package/dist/retrofit/detect.js +2 -0
- package/dist/retrofit/planners/claude.js +16 -20
- package/dist/retrofit/planners/codex.js +3 -7
- package/dist/retrofit/planners/gemini.js +11 -1
- package/dist/retrofit/preserve.js +2 -2
- package/dist/retrofit/report.js +5 -0
- package/dist/retrofit/skill-actions.js +66 -0
- package/dist/retrofit/ui-detect.js +83 -0
- package/dist/scan/gate.js +36 -0
- package/docs/MIGRATING-TO-1.0.md +33 -33
- package/docs/MIGRATING-TO-1.1.md +27 -27
- package/docs/MIGRATING-TO-1.4.md +70 -70
- package/docs/PUBLISHING.md +91 -91
- package/docs/superpowers/plans/2026-06-28-baustein-e-context-layer.md +981 -981
- package/docs/superpowers/plans/2026-06-29-baustein-f-routing.md +258 -258
- package/docs/superpowers/plans/2026-06-29-baustein-g-loop-observability.md +1006 -1006
- package/docs/superpowers/plans/2026-06-29-baustein-h-loop-robustness.md +374 -374
- package/docs/superpowers/plans/2026-06-30-baustein-i-visual-design-verification.md +450 -450
- package/docs/superpowers/plans/2026-07-02-baustein-k-zero-to-100-bootstrap.md +1024 -1024
- package/docs/superpowers/plans/2026-07-02-baustein-m-flow-smoke-proofs.md +574 -574
- package/docs/superpowers/plans/2026-08-13-gauntlet-quality-loop.md +537 -537
- package/docs/superpowers/plans/2026-08-16-artifact-backed-output-compaction.md +329 -329
- package/docs/superpowers/plans/2026-08-20-automatic-ui-design-gate.md +59 -0
- package/docs/superpowers/plans/2026-08-20-capability-skills-and-context.md +51 -0
- package/docs/superpowers/plans/2026-08-20-complete-skill-packages-and-invocation.md +59 -0
- package/docs/superpowers/plans/2026-08-20-windows-reliability-and-release.md +67 -0
- package/docs/superpowers/specs/2026-06-28-baustein-e-context-layer-design.md +146 -146
- package/docs/superpowers/specs/2026-06-29-baustein-f-routing-design.md +106 -106
- package/docs/superpowers/specs/2026-06-29-baustein-g-loop-observability-design.md +186 -186
- package/docs/superpowers/specs/2026-06-29-baustein-h-loop-robustness-design.md +113 -113
- package/docs/superpowers/specs/2026-06-30-baustein-i-visual-design-verification-design.md +98 -98
- package/docs/superpowers/specs/2026-07-02-baustein-k-zero-to-100-bootstrap-design.md +200 -200
- package/docs/superpowers/specs/2026-07-02-baustein-m-flow-smoke-proofs-design.md +155 -155
- package/docs/superpowers/specs/2026-08-13-gauntlet-quality-loop-design.md +422 -422
- package/docs/superpowers/specs/2026-08-16-artifact-backed-output-compaction-design.md +166 -166
- package/docs/superpowers/specs/2026-08-20-skill-capabilities-and-reliability-design.md +391 -0
- package/gemini-extension.json +6 -6
- package/hooks/hooks.json +19 -19
- package/package.json +84 -84
|
@@ -1,200 +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.
|
|
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.
|