@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,144 @@
1
+ # Forge — Cross-Agent Coding Harness + Retrofit-Skill + Loop
2
+
3
+ **Status:** Design approved 2026-06-27
4
+ **Working name:** Forge (changeable)
5
+
6
+ ## Problem & Goal
7
+
8
+ Build two deliverables on one shared foundation:
9
+
10
+ 1. **Harness** — a cross-agent coding harness runnable across **Claude Code + OpenAI Codex CLI + Gemini CLI**.
11
+ 2. **Retrofit-Skill** — a user-invocable skill that, run inside any existing project, rebuilds that project's harness for more **efficiency**, better **quality**, and more **autonomous** agent action.
12
+
13
+ Both need an autonomous **Loop** variant (Ralph-loop pattern, per video `xBygL_fIK78` "Claude Code and Codex Don't Need You Anymore", Hunter Sneed).
14
+
15
+ **Build principle:** one source-of-truth → idiomatic native artifacts per harness (wshobson/agents-style), NOT lowest-common-denominator translation. No drift.
16
+
17
+ ## Key Decisions (locked)
18
+
19
+ | Decision | Choice |
20
+ |---|---|
21
+ | Target environment | Cross-agent: Claude Code + Codex CLI + Gemini CLI |
22
+ | Artifact model | One source-of-truth (Canon); the skill is the installer |
23
+ | Packaging | Ansatz 1: Monorepo Canon + runtime-generating skill |
24
+ | Build sequence | A (Canon) complete → B (Retrofit-Skill) → C (Loop) |
25
+ | Generation | At runtime inside the skill (no Make; Windows-native) |
26
+ | Loop autonomy | Aggressive-autonomous with hard mechanical gates |
27
+ | Loop driver | Spec/PRD file with `passes:true` stories (Ralph standard) |
28
+ | **Loop opt-in** | **Loop (C) is OPTIONAL & configurable.** Retrofit asks whether to enable it; toggle on/off any time via one simple command/flag. Default: OFF until explicitly enabled. |
29
+
30
+ ## Stack Decisions (researched, adversarially verified 2026-06-27)
31
+
32
+ | Category | Tool | Verdict |
33
+ |---|---|---|
34
+ | Methodology spine | **superpowers** | KEEP — only one with native install paths for all 3 agents (Gemini-parity anchor) |
35
+ | Role-team overlay | **gstack** | KEEP — Claude/Codex first-class, Gemini benchmark-only (issue #619) |
36
+ | Token efficiency | **rtk** | KEEP — Windows caveat: auto-rewrite needs WSL else instruction-mode |
37
+ | Code-graph | **graphify** | KEEP (user's informed choice) — MIT, multimodal; heuristic + static index tradeoff accepted |
38
+ | — | jcodemunch | OUT — proprietary/paid license = showstopper for bundling/retrofitting |
39
+ | Loop core | **gsd-pi** | KEEP as loop reference — milestones→slices→tasks, mechanical HITL blockers |
40
+ | Safety/policy | **safe-agentic-workflow** | KEEP as policy layer — Stop-the-Line gates + role separation |
41
+ | Browser/dogfood | **Playwright MCP** | ADD — only one native-MCP across all 3 agents; cleanest Windows story |
42
+ | — | dogfood/agent-browser skill | OUT — Claude-only (MCP layer optional for token-tight cases) |
43
+ | Instruction baseline | **AGENTS.md** | ADD — portable canon across 28+ agents |
44
+ | Generator architecture | **wshobson/agents** pattern | ADOPT — source-of-truth → idiomatic per-harness |
45
+
46
+ **Deferred (later swap notes):** Serena (LSP-accurate code-graph) if graphify's heuristic/staleness becomes limiting; sub-agents-skills (cross-model delegation); native plugin packaging (Ansatz 3).
47
+
48
+ ## Architecture — 3 layers
49
+
50
+ ```
51
+ A: CANON (this repo, harness-agnostic source-of-truth)
52
+ → read by →
53
+ B: RETROFIT-SKILL (runtime generator): detect → plan → generate → wire → report
54
+ → emits idiomatic artifacts into →
55
+ Claude Code | Codex CLI | Gemini CLI
56
+ C: LOOP-ENGINE (autonomous Ralph mode, PRD-driven, hard gates) — usable by A and B
57
+ ```
58
+
59
+ ### Baustein A — The Canon
60
+
61
+ ```
62
+ canon/
63
+ AGENTS.md # portable baseline instructions (the spine)
64
+ skills/ # harness-agnostic SKILL.md (superpowers spine + gstack roles)
65
+ brainstorming/ test-driven-development/ investigate/ ...
66
+ ceo-review/ eng-review/ qa/ security/ ship/ ...
67
+ policy/
68
+ gates.md # Stop-the-Line: DoD/Acceptance-Criteria required before implementation
69
+ roles.md # role separation: implementer != merger != reviewer != security
70
+ loop/
71
+ loop-spec.md # Ralph+GSD algorithm + stop condition
72
+ prd.schema.md # PRD format (stories with passes:true)
73
+ tools/
74
+ rtk.md graphify.md playwright-mcp.md # per-agent wiring manifests
75
+ manifest.yaml # contents + versions + which artifacts emit per agent
76
+ ```
77
+
78
+ The Canon contains NO agent-specific paths — those are produced only in B.
79
+
80
+ ### Baustein B — Retrofit-Skill (5 phases)
81
+
82
+ 1. **detect** — scan target project: which agents (`.claude/`, `.codex/`, `.gemini/`, `AGENTS.md`/`CLAUDE.md`/`GEMINI.md`), existing skills, hooks, MCP configs. Also detect rtk WSL availability on Windows. Read existing `.forge/config.yaml` if present (preserve prior loop choice).
83
+ 2. **plan** — propose an **additive, non-destructive** retrofit (never overwrite without backup + consent); show diff preview. **Ask whether to enable the autonomous Loop (C)** — default OFF; the answer is recorded in `.forge/config.yaml`. The Canon's skills/policy install regardless; loop artifacts are only wired when enabled.
84
+ 3. **generate** — emit idiomatic per detected agent from the Canon:
85
+ - **Claude**: `.claude/skills/`, `settings.json` hooks (rtk PreToolUse), MCP servers, `CLAUDE.md`→`AGENTS.md` symlink
86
+ - **Codex**: `AGENTS.md`, `config.toml` (MCP), `RTK.md`, skills dir
87
+ - **Gemini**: `.gemini/commands/*.toml`, `GEMINI.md` (+ `AGENTS.md` via `context.fileName`), `settings.json` `mcpServers`, optional `gemini-extension.json`
88
+ 4. **wire** — connect cross-agent tools with caveats handled (see Cross-Agent section).
89
+ 5. **report** — summary + diff; everything reversible (backup directory).
90
+
91
+ ### Baustein C — Loop-Engine (aggressive-autonomous, hard gates) — OPTIONAL
92
+
93
+ **Opt-in & toggle-able.** The loop is a separable layer: the harness (Canon skills/policy/tools) is fully usable without it. Enablement is a single config flag plus a one-word command.
94
+
95
+ - **Config:** `.forge/config.yaml` → `loop.enabled: true|false` (default `false`). Set by the retrofit `plan` prompt, or any time later.
96
+ - **Toggle command:** `forge loop on` / `forge loop off` (also exposed as a slash-command per agent, e.g. `/forge-loop on|off`). `on` wires the loop artifacts (driver, PRD scaffolding, gate hooks) for every detected agent; `off` unwires them and disables the driver — both non-destructive and reversible (no Canon/skill changes, only loop wiring).
97
+ - **`forge loop status`** reports enabled/disabled + current PRD progress.
98
+ - When disabled, no loop artifacts are active and no autonomous runs can start; re-enabling restores them without re-running a full retrofit.
99
+
100
+ When enabled, the thin driver starts **fresh context per iteration**:
101
+
102
+ ```
103
+ while not (all PRD stories passes:true):
104
+ pre-dispatch gates → missing tools / dirty worktree / git conflict → "blocked"
105
+ pick highest-priority unfinished story
106
+ Stop-the-Line gate → DoD/AC present? else blocked
107
+ spawn fresh agent (claude -p | codex exec | gemini) in a git worktree
108
+ implement ONE story → tests green? → review iteration (different role)
109
+ update PRD (passes:true) + atomic commit
110
+ iteration-cap reached? → stop
111
+ ```
112
+
113
+ - **State outside context**: PRD file + git. **Stop**: all stories `passes:true`.
114
+ - **Gates mechanically enforced** (not voluntary), **worktree-isolated** → always rollback-able.
115
+ - **Cross-agent**: same loop invokes the chosen agent CLI non-interactively.
116
+
117
+ ## Cross-Agent Specifics & the Gemini Asymmetry
118
+
119
+ | Tool | Claude | Codex | Gemini |
120
+ |---|---|---|---|
121
+ | **rtk** | PreToolUse hook (Windows: needs WSL, else instruction-mode) | `AGENTS.md`/`RTK.md` | **no hook** → MCP tool OR `GEMINI.md` instruction |
122
+ | **graphify** | MCP server | MCP server | MCP server (`mcpServers`) |
123
+ | **Playwright** | MCP server | MCP server | MCP server |
124
+ | **Instructions** | `CLAUDE.md`→`AGENTS.md` | `AGENTS.md` native | `GEMINI.md` + `AGENTS.md` via `context.fileName` |
125
+ | **Slash-commands** | `.claude/` | TOML | `.gemini/commands/*.toml` |
126
+
127
+ **Central asymmetry:** Gemini has **no hook system** → rtk's transparent rewriting is not 1:1 reproducible; generator falls back to MCP tool or GEMINI.md instruction. Codex-only flags (`model_reasoning_effort`, `resume`) are abstracted/emulated by the generator.
128
+
129
+ ## Testing
130
+
131
+ - **TDD** (superpowers spine).
132
+ - **B** tested against **fixture target projects** (various agent setups) → asserts correct artifacts emitted, **idempotent**, **non-destructive**.
133
+ - **C** tested with a tiny PRD fixture completing in 1–2 iterations against a mock agent.
134
+ - **Windows-native**: no Make; generator logic in the skill (or Node/Python). rtk WSL-fallback detected in `detect`, applied in `wire`.
135
+
136
+ ## Out of Scope (YAGNI, first cut)
137
+
138
+ Native plugin packages (Ansatz 3); sub-agents-skills cross-model delegation; Serena swap; benchmark-models. All retrofittable later.
139
+
140
+ ## Open Risks
141
+
142
+ - **Gemini parity** is the largest technical risk (no hooks; no session-continuity equivalent to Codex `resume`).
143
+ - rtk's 60–90% savings is self-reported; real savings vary per command and per agent.
144
+ - graphify is young (v0.8.x), heuristic edges, static index can go stale.
@@ -0,0 +1,146 @@
1
+ # Baustein E — Context Layer (durable cross-session context)
2
+
3
+ **Status:** Design approved 2026-06-28
4
+ **Component:** Yoke (🐂)
5
+ **Relates to:** [[harness-project-goal]], [[harness-stack-decisions]], [[harness-loop-technique]]
6
+
7
+ ## Problem & Goal
8
+
9
+ The dev.to article ("A Claude Code Skills Stack") frames a three-layer division of labor:
10
+ **gstack decides → GSD stabilizes context → Superpowers executes.** Yoke today has the
11
+ Decision layer (ported gstack roles) and a strong Execution layer (superpowers methodology +
12
+ the Ralph loop), but it never built the **Context layer** — GSD's actual contribution:
13
+ durable, cross-session artifacts that prevent specification drift.
14
+
15
+ Concretely, the loop's [`buildClaudePrompt`](../../../src/loop/runner.ts) injects **only the
16
+ current story + its acceptance criteria**. Every fresh-context iteration starts blind to the
17
+ project's overall goal, the decisions already made, and the gotchas already learned. Over many
18
+ iterations this is exactly where drift leaks in. The user's own auto-memory does this job by
19
+ hand; the harness should give its users the same thing.
20
+
21
+ **Goal:** a durable Context layer — three markdown files under `.yoke/context/` that the loop
22
+ **reads before each iteration** and **writes decisions back to**, plus a skill so interactive
23
+ (non-loop) sessions honor the same files. This closes the spec-drift hole and completes the
24
+ third leg of the article's model.
25
+
26
+ ## Key Decisions (locked)
27
+
28
+ | Decision | Choice |
29
+ |---|---|
30
+ | Scope | Loop **and** interactive sessions (retrofit scaffolds for all 3 agents) |
31
+ | Write-back | **Hybrid**: loop deterministically auto-logs decisions; agents enrich `DECISIONS`/`KNOWLEDGE` via the skill |
32
+ | Files location | `.yoke/context/` (agent-agnostic shared state, like `.yoke/prd.yaml`) |
33
+ | Config | None new — injection auto-on when files present; prompt bound is a constant |
34
+ | Backwards-compat | No `.yoke/context/` → loop prompt is byte-identical to today |
35
+ | Out of scope | Routing/priority fix (separate Baustein F), structured decision schema, cross-file linking |
36
+
37
+ ## The three files — `.yoke/context/`
38
+
39
+ | File | Role | Writer |
40
+ |------|------|--------|
41
+ | `PROJECT.md` | North star: goal, constraints, **non-goals**, success criteria | Human/brainstorm authored; retrofit scaffolds a template. Read-only input. |
42
+ | `DECISIONS.md` | Append-only ADR ledger | Loop auto-appends per completed+verified story; agents append in interactive work. |
43
+ | `KNOWLEDGE.md` | Gotchas, conventions, reusable learnings | Agent/human maintained via the skill. |
44
+
45
+ The files are plain markdown — no schema, no required structure beyond `DECISIONS.md`'s
46
+ append format (so the loop can append unambiguously). Missing or partial files are valid:
47
+ the layer degrades gracefully (an absent file contributes nothing to the prompt).
48
+
49
+ ## Architecture
50
+
51
+ ### New module — `src/context/context.ts`
52
+ Pure and unit-testable, structured like `src/loop/prd.ts`:
53
+
54
+ - `loadContext(dir): ProjectContext` — read the three files if present; missing → empty strings. Never throws on absence.
55
+ - `formatForPrompt(ctx, maxChars): string` — render a "Project context" block, **tail-bounding** each file to `maxChars` (constant, ~2 KB) so a large ledger can't blow up the prompt. Returns `''` when all three are empty.
56
+ - `appendDecision(dir, entry): { rollback: () => void }` — append a `DECISIONS.md` entry and return a rollback that restores the prior file content (captured before the write). Creates the file if absent.
57
+
58
+ `ProjectContext = { project: string; decisions: string; knowledge: string }`.
59
+
60
+ A decision entry is formatted as:
61
+ ```
62
+ ## <YYYY-MM-DD> — <story-id>: <title>
63
+ <one-line summary>
64
+ ```
65
+ The date comes from the Node runtime at loop time (the loop is normal Node, not a Workflow
66
+ script — `Date` is available).
67
+
68
+ ### Loop read — `src/loop/runner.ts`
69
+ `buildClaudePrompt(story, context?)` and `buildReviewPrompt(story, context?)` gain an optional
70
+ pre-formatted `context` string. When present, a "Project context" section is inserted **ahead
71
+ of** the story block. The reviewer gets the north star too (so it reviews against goals, not
72
+ just acceptance criteria). When `context` is undefined/empty, the prompts are unchanged.
73
+
74
+ The loop loads + formats context once per iteration (`.yoke/context/` resolved relative to
75
+ `targetDir`) and threads it through the runner/review call.
76
+
77
+ ### Loop write-back — `src/loop/loop.ts`
78
+ After verify (and optional review) passes, **before the commit**:
79
+
80
+ 1. `appendDecision(contextDir, { storyId, title, summary })` → keep the returned `rollback`.
81
+ 2. `savePrd(passes:true)`.
82
+ 3. `commitAll(...)` — now also stages `DECISIONS.md`, so the decision and the `passes:true`
83
+ flip land in the **same atomic commit**.
84
+
85
+ If the commit throws, revert **both**: `savePrd(prior stories)` *and* `rollback()` for the
86
+ decision file. This preserves the existing invariant — *`passes:true` never persists without a
87
+ commit* — and extends it to the decision ledger (no orphan decision without a commit).
88
+
89
+ In `--isolate` mode the append happens inside the worktree before the worktree commit, so
90
+ `integrate` fast-forwards the decision back into the main tree along with the code.
91
+
92
+ ### Retrofit scaffolding — `src/retrofit/`
93
+ A retrofit action writes `.yoke/context/{PROJECT,DECISIONS,KNOWLEDGE}.md` from
94
+ `canon/context/*.md` templates **only if absent** (non-destructive + idempotent, the same rule
95
+ as every other artifact). Agent-agnostic — one set under `.yoke/` serves claude/codex/gemini,
96
+ so it is emitted once regardless of `--agent`. The report lists the scaffolded files.
97
+
98
+ ### Skill — `canon/skills/maintaining-context/SKILL.md`
99
+ Agent-facing, flows to all three agents via the existing planners + `manifest.yaml`:
100
+ > Before substantial work, read `.yoke/context/PROJECT.md` for the north star and
101
+ > `KNOWLEDGE.md` for known gotchas. When you make a non-obvious decision, append it to
102
+ > `DECISIONS.md`. When you learn a reusable fact or gotcha, append it to `KNOWLEDGE.md`.
103
+
104
+ This is what extends drift-protection from the loop to interactive sessions.
105
+
106
+ ### CLI — `src/cli.ts`
107
+ - `yoke context init` — scaffold the three files standalone (idempotent, non-destructive).
108
+ - `yoke context status` — show presence, byte sizes, and the last decision heading.
109
+
110
+ ## Data flow (loop iteration)
111
+
112
+ ```
113
+ load PRD ─► pick story ─► load+format .yoke/context ─► runner(prompt + context)
114
+ ─► verify ─► [review] ─► appendDecision() ─► savePrd(passes:true) ─► commitAll(+DECISIONS.md)
115
+ └── on commit failure: rollback() + savePrd(prior) ──► blocked
116
+ ```
117
+
118
+ ## Error handling
119
+
120
+ - Missing/partial context files: treated as empty; no error, prompt simply omits that part.
121
+ - Oversized files: tail-bounded to a constant per file; never unbounded.
122
+ - `appendDecision` before commit + rollback on commit failure: no orphan decisions.
123
+ - Isolate mode: decision written in the worktree, carried back only on successful integrate.
124
+ - `yoke context init` over existing files: skips them (reports "exists"), never overwrites.
125
+
126
+ ## Testing (subagent-driven TDD, like A–D)
127
+
128
+ **context.ts units:** load with all/none/partial files present; `formatForPrompt` bounding +
129
+ empty-returns-`''`; `appendDecision` format correctness + rollback restores prior content +
130
+ creates file when absent.
131
+
132
+ **loop:** prompt includes the context block when `.yoke/context/` present; prompt unchanged
133
+ when absent; decision appended on success; **not** appended on a blocked story; both PRD and
134
+ decision reverted on commit failure; isolate path carries the decision back via integrate.
135
+
136
+ **retrofit:** scaffolds the three files; idempotent on re-run; non-destructive over existing
137
+ files; emitted once for `--agent=all`.
138
+
139
+ **canon:** `maintaining-context` present in `manifest.yaml`; `yoke validate canon` stays green.
140
+
141
+ ## Non-goals (YAGNI)
142
+
143
+ - No new config keys (injection is automatic; bound is a constant).
144
+ - No structured/parsed decision schema — markdown append only.
145
+ - No cross-file linking or decision superseding.
146
+ - Routing/priority arbitration is **Baustein F**, not this spec.
@@ -0,0 +1,106 @@
1
+ # Baustein F — Skill Routing/Precedence + Review-Family Consolidation
2
+
3
+ **Status:** Design approved 2026-06-29
4
+ **Component:** Yoke (🐂)
5
+ **Relates to:** [[harness-build-progress]], [[readme-always-update]]
6
+
7
+ ## Problem & Goal
8
+
9
+ The dev.to skills-stack article's #1 warning is **auto-invocation chaos**: when superpowers
10
+ (aggressive "1% chance → you MUST invoke") and gstack roles ("proactively suggest") are both
11
+ installed, overlapping skills compete on the same triggers, causing random/redundant selection.
12
+ Yoke's `canon/manifest.yaml` distinguishes `kind: methodology|role` but emits **no precedence or
13
+ routing** — nothing tells an agent which skill is canonical when several match.
14
+
15
+ The collision is concentrated in the review family. From the actual descriptions:
16
+
17
+ | Skill | Phase / angle | Status |
18
+ |---|---|---|
19
+ | `plan-eng-review` | plan-time, architecture | distinct ✓ |
20
+ | `plan-ceo-review` | plan-time, product/scope | distinct ✓ |
21
+ | `review` | **pre-merge, diff safety/structure** | collides ⚠ |
22
+ | `eng-review` (14-line stub) | **pre-merge, architecture/edge-cases/tests** | collides ⚠ |
23
+ | `requesting-code-review` | protocol: request a review | distinct ✓ |
24
+ | `receiving-code-review` | protocol: handle feedback | distinct ✓ |
25
+
26
+ The one real overlap is `eng-review` ↔ `review` — both "review the change before merge."
27
+
28
+ **Goal:** emit an explicit routing/precedence into every agent so auto-invocation resolves
29
+ deterministically, and collapse the `eng-review`/`review` overlap into one canonical pre-merge
30
+ review skill.
31
+
32
+ ## Key Decisions (locked)
33
+
34
+ | Decision | Choice |
35
+ |---|---|
36
+ | Routing location | Authored prose section in `canon/AGENTS.md` (injected verbatim into all 3 agents) — NOT a new manifest field or generator |
37
+ | Precedence rule | Methodology (HOW) before role (perspective); process before implementation |
38
+ | Review collision | Fold `eng-review`'s checklist into `review`; **remove `eng-review`**; `review` becomes the single canonical pre-merge code review |
39
+ | Description hygiene | Sharpen the remaining review-family descriptions so triggers are phase-distinct, non-overlapping |
40
+ | Skill count | Canon 25 → **24** |
41
+ | README | Update the skills catalog (Roles 8→7, total 25→24) — **mandatory deliverable** |
42
+ | Out of scope (YAGNI) | No NLP trigger-collision validator (too fragile); no `priority` manifest field + generator (the `kind` distinction + authored prose suffices) |
43
+
44
+ ## Architecture
45
+
46
+ ### 1. Routing section in `canon/AGENTS.md`
47
+ A new "Skill routing & precedence" section appended to the baseline (which already covers
48
+ quality-first/stop-the-line/role-separation). It states three things:
49
+
50
+ 1. **Precedence** — methodology skills decide *how* to work and take precedence over role skills
51
+ (which add a perspective). Process before implementation.
52
+ 2. **Canonical entrypoint per concern** — a small map, especially the review family:
53
+ - Plan-time architecture review → `plan-eng-review`
54
+ - Plan-time product/scope review → `plan-ceo-review`
55
+ - **Pre-merge code review → `review`** (the single canonical one)
56
+ - Requesting a review (dispatch a reviewer) → `requesting-code-review`
57
+ - Handling review feedback → `receiving-code-review`
58
+ - Build flow / order of operations → `workflow`
59
+ 3. **Arbitration rule** — "These skills declare their own triggers aggressively; when several
60
+ match the same task, this precedence + the most-specific-entrypoint rule decides. Don't run
61
+ two skills that serve the same concern."
62
+
63
+ `canon/AGENTS.md` is copied verbatim by the Claude/Codex/Gemini planners and imported by the
64
+ generated `CLAUDE.md`/`GEMINI.md`, so this reaches all three agents with no generator change.
65
+
66
+ ### 2. Review-family consolidation
67
+ - **`review`** absorbs `eng-review`'s checklist: in addition to diff safety (SQL, trust
68
+ boundaries, side effects), it now also covers architecture fit, edge cases, test coverage, and
69
+ performance — the engineering-manager angle. One canonical pre-merge review.
70
+ - **`eng-review` removed**: delete `canon/skills/eng-review/` and its `manifest.yaml` entry.
71
+ - **Descriptions sharpened**: each review-family skill's description names its phase explicitly
72
+ so triggers don't overlap (plan-time vs pre-merge vs protocol).
73
+
74
+ ### 3. Manifest + validator
75
+ - Remove the `eng-review` entry from `canon/manifest.yaml`.
76
+ - `validateCanon` must stay green (it validates that each listed skill has a dir + frontmatter;
77
+ removing the entry + dir keeps it consistent). No validator code change required unless a test
78
+ hard-codes the skill list.
79
+
80
+ ### 4. README (mandatory)
81
+ Update the skills catalog: remove the `eng-review` row, move its purpose into the `review` row,
82
+ change the Roles count 8→7 and the total 25→24. Per the standing rule, the README must reflect
83
+ what shipped.
84
+
85
+ ## Data flow (how routing reaches an agent)
86
+ ```
87
+ canon/AGENTS.md (routing section)
88
+ └─ planClaude → AGENTS.md + CLAUDE.md (@AGENTS.md import) → Claude reads precedence
89
+ └─ planCodex → AGENTS.md (Codex reads it natively) → Codex reads precedence
90
+ └─ planGemini → GEMINI.md + .gemini/settings (AGENTS.md ctx) → Gemini reads precedence
91
+ ```
92
+
93
+ ## Testing
94
+ - `tests/canon/real-canon.test.ts`: assert `eng-review` is NOT in the manifest; assert
95
+ `canon/AGENTS.md` contains the routing section (e.g. a stable heading + the "Pre-merge code
96
+ review → `review`" line); `validateCanon('canon')` stays zero-error.
97
+ - `tests/retrofit/plan-dispatch.test.ts` (or planners tests): the emitted skill set no longer
98
+ includes `eng-review`; total skill count reflects 24. Adjust any hard-coded count.
99
+ - `review` skill still validates (frontmatter intact after the checklist merge).
100
+ - Full suite green; `tsc` clean.
101
+
102
+ ## Non-goals (YAGNI)
103
+ - No automated trigger-collision detection.
104
+ - No new manifest `priority` field or routing generator.
105
+ - No change to plan-ceo-review / plan-eng-review / requesting- / receiving-code-review bodies
106
+ beyond description sharpening.
@@ -0,0 +1,186 @@
1
+ # Baustein G — Loop Observability (heartbeat, timeout, live feedback)
2
+
3
+ **Status:** Design approved 2026-06-29
4
+ **Component:** Yoke (🐂)
5
+ **Relates to:** [[harness-loop-technique]], [[harness-build-progress]]
6
+
7
+ ## Problem & Goal
8
+
9
+ A real incident: the autonomous loop ran on a downstream project (NewMarket) and sat
10
+ **blocked for ~5 hours, completely invisibly**. A story failed its verify gate, the loop
11
+ correctly returned `blocked` and stopped — but that outcome was printed to the stdout of a
12
+ detached background command and never surfaced. The user saw "running forever," couldn't tell
13
+ progress from a hang, and `yoke loop status` only reported the PRD count (`18/45`), not *why*
14
+ nothing was advancing.
15
+
16
+ Two gaps caused this:
17
+ 1. **No durable, queryable run state.** `blocked` (and its reason) lived only in transient
18
+ stdout. There was no artifact to glance at.
19
+ 2. **No agent-run timeout.** A genuinely hung nested agent (`claude -p`) would wedge the loop
20
+ indefinitely — only `verify` had a timeout, not the implementation run.
21
+
22
+ **Timeout must not punish slow-but-working agents.** A fixed *total-runtime* timeout cannot tell
23
+ "hung" from "working, just slow" and would kill a legitimately long story. So G uses an
24
+ **inactivity (idle) timeout**, not a wall-clock one: it measures time since the agent's *last
25
+ sign of life* (its last output byte), not total duration. An agent that keeps emitting output
26
+ runs for an unbounded time; only true silence is treated as a hang. The same output stream that
27
+ gives the user live feedback IS the liveness signal — feedback and hang-detection are one.
28
+
29
+ **Goal:** make the loop observable and self-limiting. The loop continuously reports where it
30
+ is (which story, which phase, why blocked) through **token-free, harness-side channels**, and
31
+ a per-iteration timeout breaks true hangs.
32
+
33
+ **Design principle — maximize free feedback.** All feedback added here is produced by the loop
34
+ driver (Node: console + local files), never by an agent. It costs **zero agent tokens**. The
35
+ only token cost in the loop remains the nested agent runs themselves, which G does not change.
36
+ So we deliberately make local feedback dense and frequent.
37
+
38
+ ## Key Decisions (locked)
39
+
40
+ | Decision | Choice |
41
+ |---|---|
42
+ | Scope | Heartbeat status file + `yoke loop status` upgrade + agent-run timeout + blocked-leftover reporting + live console narration + append log |
43
+ | Timeout kind | **Idle (no-output) timeout**, NOT total runtime. Resets on every output byte; only true silence counts. Total runtime is unbounded while the agent keeps producing output. |
44
+ | Timeout on expiry | Kill the child → treated as a normal failure → `blocked` → loop stops (consistent with runner-fail / verify-fail / review-reject). No silent skip. |
45
+ | Timeout default | 20 minutes of **silence**; `--timeout=<minutes>` flag > `config.loop.timeoutMinutes` > default. `0` disables. |
46
+ | Timeout mechanism | A small watchdog wrapper (`src/loop/watchdog.ts`) runs the agent as a child, forwards stdio (prompt in, output live out), and kills on idle expiry. Keeps `runLoop`/runner synchronous — no async ripple. |
47
+ | Feedback channels | Console narration + `.yoke/loop-status.json` (current state) + `.yoke/loop.log` (append-only timeline). All Node-side, 0 tokens. |
48
+ | Backwards-compat | No status file → `yoke loop status` behaves exactly as today. Reporter is injectable and defaults to a real impl; existing `runLoop` tests pass a no-op. |
49
+ | Out of scope (YAGNI) | No token metering of nested agents (technically not exposable), no log streaming server, no web dashboard. |
50
+
51
+ ## Architecture
52
+
53
+ All new surface lives in the `loop/` subsystem.
54
+
55
+ ### 1. `src/loop/reporter.ts` (new) — status types + reporter + reader
56
+ ```ts
57
+ type LoopState = 'running' | 'blocked' | 'complete' | 'cap-reached'
58
+ type LoopPhase = 'implementing' | 'verifying' | 'reviewing' | 'committing'
59
+
60
+ interface LoopStatus {
61
+ state: LoopState
62
+ phase?: LoopPhase // only meaningful while state === 'running'
63
+ story?: string
64
+ storyTitle?: string
65
+ reason?: string // populated on 'blocked'
66
+ iteration: number
67
+ progress: { passed: number; total: number }
68
+ startedAt: string // ISO — when the current story/iteration began
69
+ updatedAt: string // ISO — last heartbeat write
70
+ }
71
+
72
+ interface LoopReporter {
73
+ storyStart(story, iteration, progress): void // → state:running, phase:implementing
74
+ phase(phase: LoopPhase): void // → updates phase + updatedAt
75
+ blocked(reason: string): void // → state:blocked
76
+ complete(progress): void // → state:complete
77
+ capReached(progress): void // → state:cap-reached
78
+ }
79
+
80
+ makeReporter(dir, opts?: { quiet?: boolean }, now?: () => Date): LoopReporter
81
+ readStatus(dir): LoopStatus | null
82
+ ```
83
+ The default `makeReporter` writes three places on every event:
84
+ - **`.yoke/loop-status.json`** — the single current `LoopStatus` (overwritten atomically: temp file + rename).
85
+ - **`.yoke/loop.log`** — appends one line: `<ISO> <state/phase> <story> <detail>`.
86
+ - **console** — a human line (suppressed when `quiet`): e.g.
87
+ - `▶ S6-seed-rich (19/45) — implementing…`
88
+ - ` ✓ verified` / ` ✓ reviewed` / ` ✓ committed → 20/45`
89
+ - `■ blocked on S6-seed-rich: verify failed (working tree left dirty — clean before restart)`
90
+
91
+ `now` is injectable for deterministic tests. Timestamps use the Node runtime (`Date`),
92
+ which is available (the loop is not a Workflow script).
93
+
94
+ ### 2. `src/loop/loop.ts` — drive the reporter
95
+ `runLoop` takes an optional `reporter: LoopReporter` in `LoopOptions` (default: a no-op so the
96
+ existing tests are untouched; `run-command` injects the real one). At each boundary it calls:
97
+ `storyStart` before dispatch → `phase('verifying')` before verify → `phase('reviewing')` before
98
+ review → `phase('committing')` before commit → `complete`/`capReached`/`blocked` at terminal
99
+ returns. The **blocked** call's reason is enriched: if the working tree is dirty
100
+ (`opts.git.isClean(targetDir) === false`) after a block, append
101
+ `" (working tree has uncommitted changes from the blocked story — review/clean before re-running)"`.
102
+ This is the S5 leftover case made explicit.
103
+
104
+ ### 3. `src/loop/watchdog.ts` (new) — idle-timeout wrapper
105
+ A tiny standalone CLI: `node watchdog.js --idle-ms=<N> -- <command> [args...]`.
106
+ - Spawns `<command>` with piped stdio. Forwards its own stdin to the child (so the prompt still
107
+ reaches `claude -p`) and the child's stdout/stderr to its own (so live output still reaches
108
+ the user — the feedback channel).
109
+ - Maintains an idle timer reset on **every** stdout/stderr chunk. On expiry it kills the child
110
+ (SIGTERM, then SIGKILL after a short grace; on win32 `taskkill /pid /t /f` for the tree) and
111
+ exits **124** (timeout convention). Otherwise it exits with the child's own exit code.
112
+ - `--idle-ms=0` means no watchdog (spawn-through with no timer).
113
+ All the async/stream complexity lives here, isolated and unit-testable. Nothing else in the
114
+ loop becomes async.
115
+
116
+ ### 4. `src/loop/runner.ts` — run the agent through the watchdog
117
+ `Invocation` gains an optional `idleTimeoutMs?: number`. When set (>0), `runCli` runs the agent
118
+ **through the watchdog**: `node <dist>/loop/watchdog.js --idle-ms=<N> -- <command> <args...>`,
119
+ with the prompt still piped via stdin. `execSync` blocks on the watchdog (runner stays
120
+ synchronous). A watchdog exit of 124 (or any non-zero) is caught as today →
121
+ `{ success: false, summary: 'no output for Nm — treated as hung' }` → the loop blocks. When
122
+ `idleTimeoutMs` is unset/0, the agent runs directly as before. `makeRunner`/`makeReviewRunner`
123
+ accept and thread `idleTimeoutMs`; the reviewer run uses the same budget.
124
+
125
+ ### 5. `src/loop/run-command.ts` — wire + upgrade `loop status`
126
+ - `runLoopCommand` resolves the idle timeout (`--timeout` flag > `config.loop.timeoutMinutes` >
127
+ default 20 minutes; `0` disables), builds the real `makeReporter(targetDir)`, builds runners
128
+ with the resolved `idleTimeoutMs`, and passes the reporter into `runLoop`.
129
+ - `loopStatus(targetDir)` reads `readStatus(targetDir)`. If present, it renders state + phase +
130
+ story + title + reason + iteration + a relative `updatedAt`, and — when `state==='running'`
131
+ and `updatedAt` is older than the timeout — a `possibly stuck (no update in Nm)` hint. If
132
+ absent, it falls back to today's `enabled + PRD progress` output verbatim.
133
+
134
+ ### 6. `src/retrofit/config.ts` — optional config field
135
+ `loop.timeoutMinutes?: number` (idle minutes) added to the schema (optional, backwards-compatible).
136
+
137
+ ### 7. `src/cli.ts` — flag + gitignore note
138
+ Parse `--timeout=<minutes>` on `yoke loop run`. `.yoke/loop-status.json` and `.yoke/loop.log`
139
+ are runtime artifacts — ensure the retrofit gitignore covers them (or document that they are
140
+ local-only). They must NOT block the clean-tree gate: the loop writes them under `.yoke/`,
141
+ which retrofit already gitignores for `worktrees`/`backup`; extend that to these two files.
142
+
143
+ ## Data flow (one iteration)
144
+ ```
145
+ storyStart(S6) ─► [console ▶ / status running:implementing / log]
146
+ runner(idle-watchdog) ─► phase(verifying) ─► verify ─► phase(reviewing) ─► [review]
147
+ ─► phase(committing) ─► appendDecision ─► savePrd(passes) ─► commitAll
148
+ ─► [console ✓ committed → 20/45]
149
+ on any failure ─► blocked(reason + leftover-hint) ─► [console ■ / status blocked / log] ─► return
150
+ ```
151
+
152
+ ## Error handling
153
+ - Status writes are best-effort and atomic (temp + rename); a write failure never aborts the
154
+ loop (wrapped, logged to console, swallowed) — observability must not break execution.
155
+ - Timeout kills the child and surfaces as `blocked`; the clean-tree gate + leftover hint guard
156
+ the restart.
157
+ - `readStatus` on a missing/corrupt file returns `null` → `loop status` falls back gracefully.
158
+
159
+ ## Testing (subagent-driven TDD, like A–F)
160
+ - **reporter.ts:** status round-trip (write→read); missing file → null; each event sets the
161
+ right state/phase; `loop.log` appends one line per event; `quiet` suppresses console;
162
+ injected `now` makes timestamps deterministic; atomic write leaves no temp file behind.
163
+ - **loop.ts:** reporter receives `storyStart`/`phase`/`complete` in order on success;
164
+ `blocked` with reason on a failed verify; leftover hint appended when `git.isClean` is false
165
+ on block; existing loop tests still pass with the default no-op reporter.
166
+ - **watchdog.ts:** wrapping a child that keeps emitting output past the idle window is **not**
167
+ killed (proves slow-but-working survives); wrapping a silent child that outputs nothing is
168
+ killed after the idle window and exits 124; a fast child passes its exit code through; stdin
169
+ is forwarded to the child; `--idle-ms=0` never kills. (Use tiny `node -e` children with short
170
+ idle windows for deterministic, fast tests.)
171
+ - **runner.ts:** when `idleTimeoutMs>0` the invocation runs through the watchdog wrapper (assert
172
+ the built command); when unset/0 it runs the agent directly; resolution order flag>config>20.
173
+ - **run-command.ts:** `loopStatus` renders state+phase+reason when a status file exists; falls
174
+ back to PRD-only when absent; `--timeout` parsed and forwarded.
175
+ - **config.ts:** `timeoutMinutes` accepted and optional.
176
+
177
+ ## What this would have done for the incident
178
+ `yoke loop status .` →
179
+ ```
180
+ Loop: BLOCKED on S5-segment-schemas "All 9 segment attribute schemas…"
181
+ phase: verifying · iteration 19 · 18/45 · last update 5h ago
182
+ reason: story did not verify (working tree has uncommitted changes — clean before restart)
183
+ ```
184
+ …instead of `18/45`. And a true hang (no agent output at all) would self-terminate after 20
185
+ minutes of silence as `blocked` — while a genuinely slow story that keeps streaming progress
186
+ runs as long as it needs.