diff --git a/README.md b/README.md index 0cdacab..73e4806 100644 --- a/README.md +++ b/README.md @@ -55,20 +55,21 @@ define the knowledge once, and it flows automatically through every phase: (3/3 = very high, 2/3 = high) turning agreement into a confidence signal. Impactful findings are applied in place. 3. **Implementation** ([`/implement-mainspec`](./skills/sdd/implement-mainspec/SKILL.md)) — - slices implemented in dependency order, sequential or auto-parallelized across - git worktrees, each gated by **Signal** (the runtime feedback loop, below). + slices implemented in dependency order, one at a time on the feature branch. + Each slice is verified by its unit tests, then a **Reflect** step (below) feeds + what the implementer learned back into the expert. **Composable, not hardcoded.** Multiple experts activate for one feature — a React expert and a DynamoDB expert both contribute on a full-stack change. Add or remove experts without touching any existing skill; organizations layer in private experts for internal libraries the same way. -![Signal](./signal.png) - -**Signal is half of what an expert is.** Where the expert curates context -*before* implementation, Signal validates behavior *during* it: run the tests, -hit the endpoint, check the build, iterate until it passes. Unit tests are the -default; experts can define richer ones. +**Reflection is the other half of what an expert is.** Where the expert curates +context *before* implementation, Reflection updates it *after*: once a slice's code +and unit tests are green, the implementer judges whether what it learned — a new +pattern, or a spec that contradicted the codebase — should change the project's +long-term memory, and writes it back to the expert. The bar is high; most slices +change nothing. → [Chapter 2 — Spec-Driven Development](./docs/2-spec-driven-development.md) @@ -102,7 +103,7 @@ state**. The complete state of every feature is observable from the files on dis and the branches in git. A small, deterministic dispatcher — **no LLM in the loop** — reads that state each tick and shells out to a fresh `claude -p` process per step, so no step inherits another's polluted window and crash recovery is free. -Four layers of verification (pre-commit, slice signals, `local-checks`, and a +Four layers of verification (pre-commit, slice unit tests, `local-checks`, and a runnable `run-prd-test.sh` definition of done) can't be talked past — and when a step genuinely can't make progress, the harness stops and hands you a **diagnosis-first** report rather than faking success. @@ -266,7 +267,7 @@ Run from your target project directory. Copies agent definitions (e.g. | **1 · SDD** | `/expert-sdd-creator` | Create a domain expert from your docs | | | `/spec-planning` | Idea → mainspec + temporal slices | | | `/spec-validate` | Multi-agent consensus + expert review | -| | `/implement-slice`, `/implement-mainspec` | Implement with Signal feedback, sequential or parallel | +| | `/implement-slice`, `/implement-mainspec` | Implement slices sequentially with unit tests + Reflect | | **2 · Harness** | `/harness-init` | Guided setup of the local harness | | | `/fix-local-checks` | Honest fixes for a failing pre-PR gate | | | `/address-feedback` | Triage and answer reviewer findings | diff --git a/docs/2-spec-driven-development.md b/docs/2-spec-driven-development.md index 6a0be42..b0ce60c 100644 --- a/docs/2-spec-driven-development.md +++ b/docs/2-spec-driven-development.md @@ -42,7 +42,7 @@ it generates a complete, progressively-disclosed knowledge module: ``` expert-{name}/ -├── SKILL.md # high-level pointer (Expert Mode + Signal Mode) +├── SKILL.md # high-level pointer, read first ├── references/ # dense knowledge, read only when relevant │ ├── {topic}.md │ └── signal-workflow.md @@ -62,10 +62,12 @@ Two properties make Experts more than a docs folder: feature, each curating its slice of the context. Add or remove Experts without touching any existing skill; organizations layer in private Experts for internal libraries the same way. -- **Two modes — and Signal is half of what an Expert *is*.** Expert Mode curates - context *before* you write code. Signal Mode validates behavior *during* - implementation. The same module that knows your patterns also knows how to tell - whether the code honoring them actually works. +- **It curates before, and sharpens after.** An Expert curates context *before* + you write code; then the **Reflect** step at the end of each slice feeds new + lessons — a fresh pattern, a spec that fought the codebase — back into it, so the + Expert gets a little better every time the project builds. (An Expert can also + ship an optional runtime signal script, but a slice's unit tests are the default + proof it works.) ## Specs — planning lifted out of the window @@ -107,10 +109,9 @@ for future ones. That ordering does two jobs at once. For the implementer, it's progressive disclosure made structural: the agent is fed **only the current slice**, never the whole feature, so its window stays small and focused. For the orchestrator, -the dependencies form a DAG — and a DAG can be parallelized. Every mainspec -carries a **Slice Dependency Map** (a table plus a Mermaid graph), and -[`compute_tiers.py`](../skills/sdd/implement-mainspec/scripts/compute_tiers.py) -topologically sorts it to find which slices can run concurrently. +the dependencies define the execution order. Every mainspec carries a **Slice +Dependency Map** (a table plus a Mermaid graph), which the orchestrator reads to +implement slices in dependency order, one at a time. ## Validation — consensus before code @@ -136,38 +137,34 @@ A single reviewer has blind spots; independent reviewers have *different* blind spots, and consensus scoring turns that into a confidence signal instead of a coin flip. -## Implementation — a feedback loop, not a single pass +## Implementation — write, verify, reflect -![Signal](../signal.png) - -[`/implement-slice`](../skills/sdd/implement-slice/SKILL.md) implements -one slice as a tight loop: implement the code, then run the slice's **Signal** to -check it behaves, iterating until the signal validates. Signal is the runtime -counterpart to the Expert's up-front curation — where the Expert shapes context -*before*, Signal feeds focused feedback *during*. Unit tests are the default -signal, but an Expert can define richer ones: hit an endpoint and check the -response, run browser automation and screenshot it, deploy to a lower -environment and read the logs. +[`/implement-slice`](../skills/sdd/implement-slice/SKILL.md) implements one slice +in three beats: write the code, verify it with unit tests, then **Reflect**. The +first two are the obvious ones. Reflect is the one that compounds: once the code +is green, the agent loads the Expert, compares what it just learned against what +the project already knows, and — only when there's a real lesson — writes it back +as a new or corrected reference. Where the Expert shapes context *before* +implementation, Reflect updates it *after*, so the curation and the learning are +two halves of the same module. The bar is deliberately high: most slices reflect +nothing. [`/implement-mainspec`](../skills/sdd/implement-mainspec/SKILL.md) -orchestrates the whole feature and auto-detects how to run it: - -- **Sequential** (≤3 slices) — one slice at a time, committed directly to the - branch. Simple and linear. -- **Parallel** (>3 slices) — the DAG from `compute_tiers.py` drives tiered - execution: Tier 0 (foundation) first, then later tiers fan out across git - worktrees with a focused `slice-implementer` subagent per slice (up to 7 - concurrent), gating each tier before the next. +orchestrates the whole feature: it reads the Slice Dependency Map and delegates +each slice, in dependency order, to a focused `slice-implementer` subagent — +committing each to the feature branch as it lands. One slice at a time keeps the +orchestrator's window lean and every commit reviewable. -Either way, the feedback loop runs **per slice**, not just at the end — the agent -knows whether it's on track as it goes, not after. +The feedback loop runs **per slice**, not just at the end — the agent knows +whether it's on track as it goes, not after. ## What you have at the end of Layer 1 A repeatable way to take one feature from idea to working code while keeping the agent's window full of exactly the right context and nothing else: knowledge defined once and pulled on demand, planning externalized and disclosed -progressively, validation by consensus, implementation gated by signal. All of +progressively, validation by consensus, implementation verified by unit tests +and fed back through Reflect. All of it framework-agnostic, all of it usable on its own. --- diff --git a/docs/3-the-agent-harness.md b/docs/3-the-agent-harness.md index d6cfd38..d69f829 100644 --- a/docs/3-the-agent-harness.md +++ b/docs/3-the-agent-harness.md @@ -108,7 +108,7 @@ Autonomy is only safe if "done" can't be faked. So the harness leans on checks that run **regardless of what the agent decides**, fastest to slowest: 1. **Pre-commit hooks** — linters, formatters, type checks on changed files. -2. **Slice signals** — each slice's Signal section, run during implementation. +2. **Slice unit tests** — each slice's tests, run during implementation. 3. **`local-checks.sh`** — the cheapest gate before a PR: lint, typecheck, the fast unit suite, skip-detection, and any custom project lints. It proves **correctness, not coverage** — it blocks on real defects and merely warns on diff --git a/docs/4-continuous-improvement.md b/docs/4-continuous-improvement.md index 45f282c..30e96cf 100644 --- a/docs/4-continuous-improvement.md +++ b/docs/4-continuous-improvement.md @@ -15,9 +15,9 @@ own long-term memory.** The next feature starts knowing what the last one taught ```mermaid flowchart LR Merge([Merge to main]) --> Learn["/learn\nreads the merged diff"] - Learn --> Vote{Consensus:\nanything worth\nremembering?} - Vote -->|no — common| Noop[No-op. Most merges\nteach nothing durable.] - Vote -->|yes| Route[Route each fact to\nexactly one destination] + Learn --> Reconcile{Reconcile against\ncurrent memory:\nanything to add,\nedit, or delete?} + Reconcile -->|no — common| Noop[Nothing to learn.\nMost merges teach\nnothing durable.] + Reconcile -->|yes| Route[Route each change to\nexactly one destination] Route --> PR["learn/ PR\nyou review and merge"] ``` @@ -74,14 +74,15 @@ keeping routes to **exactly one** place: |---|---|---| | **A lint** (`scripts/lints/*`) | The rule is mechanically checkable — pass/fail needs no judgment | Enforced on every PR forever; the error message doubles as a fix prompt | | **Eager prose** (`AGENTS.md`) | It must be known *before* an agent would think to consult the Expert, and clears a strict five-part bar | Paid every session — the high bar | -| **Lazy prose** (an Expert shard) | It's useful when *deliberately reasoning* about an area | Paid only when consulted — the usual home | +| **Lazy prose** (an Expert reference file) | It's useful when *deliberately reasoning* about an area | Paid only when consulted — the usual home | | **Nowhere** | It's inferable from the code, taste-only, or transient | — | -Most facts go to the last two. A 0/3 consensus — "this merge teaches nothing -durable" — is a **common and correct** outcome, not a failure: vuln fixes, -refactors that don't change shape, and routine bug fixes usually change no memory -at all. The system improves by accumulating signal, not noise, and *preferring -nothing over noise* is what keeps the Expert worth reading. +Most facts go to the last two. A **nothing-to-learn** outcome — "this merge +teaches nothing durable" — is a **common and correct** result, not a failure: +vuln fixes, refactors that don't change shape, and routine bug fixes usually +change no memory at all, so `/learn` prints `nothing to learn` and opens no PR. +The system improves by accumulating signal, not noise, and *preferring nothing +over noise* is what keeps the Expert worth reading. ## Lints — the memory an agent cannot ship past @@ -105,15 +106,22 @@ lint only on recurrence, and a drafted lint **must pass against the just-merged code** before it's wired in (the inverse of `/intent`'s right-reason check — a lint that reddens the merge it was born from is wrong). -## Consensus gates the write - -Before anything changes, `/learn` spawns a few cheap reviewers that read the -merged diff against current memory and vote, per surface, on whether anything -*must* change. Only changes that clear the threshold survive. It's the same -consensus pattern `/spec-validate` uses in Chapter 2, pointed at a different -question — and it's cheap insurance against corrupting long-term memory on every -commit. Everything that does survive lands on a reviewable, revertible PR. You -stay the final gate. +## Reconcile, don't accumulate + +Memory is a *current model of `main`*, not an append-only log — so `/learn` doesn't +just add. On every merge it runs a three-pass reconcile against the merged diff, +inline in the main agent (no subagent voting): it **deletes** reference files whose +anchor code is gone, resolves **contradictions** between files that now disagree, +and **edits** claims the diff has invalidated. Adds, edits, and deletes all ride +on the same `learn/` PR, each with a one-line justification citing the diff +hunk that motivated it — if it can't be justified from the diff, it's dropped. +That inline justification is the bar that keeps memory honest, and everything that +survives lands on a reviewable, revertible PR. You stay the final gate. + +Inside the Expert, the files are kept small and topic-focused, cross-linked with +`[[wikilinks]]` so an agent reads the one-line index, opens only what's relevant, +and follows links to the rest — progressive disclosure, so consulting memory +never means loading all of it. ## What you have at the end of Layer 2 diff --git a/docs/5-the-human-loop.md b/docs/5-the-human-loop.md index bc1e217..a772030 100644 --- a/docs/5-the-human-loop.md +++ b/docs/5-the-human-loop.md @@ -140,7 +140,7 @@ What you find turns into two durable outcomes — the **flywheel**: regression test over the harness's **own skills and context** (the analog of testing a prompt), distinct from the PRD runner, which tests the product. - **Context fixes** — when a piece of context misled an agent, fix the Expert - shard, the `AGENTS.md` pointer, or the skill. + reference file, the `AGENTS.md` pointer, or the skill. This is the moment the framing of the whole book becomes literal: every PR the harness builds is a graded trial of your project's context, and every eval you diff --git a/docs/README.md b/docs/README.md index 1364be5..bfacac4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -42,7 +42,7 @@ as you climb — but you can stop on any rung. over. 2. **[Spec-Driven Development](./2-spec-driven-development.md)** *(Layer 1)* — how Context Specs applies context engineering to a single feature: experts, specs, - temporal slicing, signal, consensus validation. The foundation, usable on its + temporal slicing, reflection, consensus validation. The foundation, usable on its own. 3. **[The agent harness](./3-the-agent-harness.md)** *(Layer 2)* — file a PRD, walk away, come back to a finished PR. The autonomous loop, and why you can diff --git a/signal.png b/signal.png deleted file mode 100644 index 7b4185a..0000000 Binary files a/signal.png and /dev/null differ diff --git a/skills/harness/harness-init/SKILL.md b/skills/harness/harness-init/SKILL.md index 531e018..0c16241 100644 --- a/skills/harness/harness-init/SKILL.md +++ b/skills/harness/harness-init/SKILL.md @@ -1,59 +1,73 @@ --- name: harness-init -description: One-time, guided setup of the local agent-first coding harness — the polling loop, the deterministic dispatcher, its config, the AGENTS.md contract, and the worktree provisioning that makes per-feature worktrees runnable. Use when a developer wants to set up, bootstrap, install, or initialize the coding harness for the first time in a project. +description: One-time setup of the local agent-first coding harness — the polling loop, the deterministic dispatcher, its config, the AGENTS.md contract, and the worktree provisioning that makes per-feature worktrees runnable. Runs as an autonomous installer: writes the artifacts, then opens a feature/harness-init PR for the human to review and merge. Use when a developer wants to set up, bootstrap, install, or initialize the coding harness for the first time in a project. --- # harness-init -Stand up the **local** coding harness for a project, step by step, as a guided -expert session. The developer should finish understanding exactly what was -created and why, with every artifact reviewed and tweakable. +Stand up the **local** coding harness for a project. You are an autonomous +installer: you write the artifacts end-to-end, explain the *significance* of each +as you go, and gather everything onto a `feature/harness-init` PR the human +reviews and merges. The developer should finish understanding what the harness +*is* and why each piece exists — not because you paused for approval at every +line, but because the narration and the PR together make it legible. > Scope: this sets up the **local** harness (Claude Code `/loop` outer runtime). > Server / Claude Agent SDK / OpenSWE modes are future iterations — the > artifacts here are built so they don't have to be torn out to move there. -## How to run this skill +## Operating mode -You are a guide, not a script runner. For **every** step that writes to disk or -runs a git operation: **explain what you're about to do and why → confirm with -the user → do it → show the result → take feedback and adjust.** You have rich -context in `references/` so you never have to guess — read the relevant -reference before each step and narrate from it in plain language. +Run Steps 0–8 end-to-end without stopping for per-step approval. Do not ask the +user to confirm each file write or present diffs for sign-off — **the user +reviews everything in the `feature/harness-init` PR (Step 8).** Your job while +installing is to explain *significance*: for each artifact, say in a sentence or +two what it is, why it exists, and what would break without it. Lead with the +why, then write the file. + +Read first, before you start writing: +- `references/mental-model.md` — so you can explain what the harness *is*. +- `references/invariants-to-preserve.md` — the rules nothing you generate may break. + +Then load other references as each step needs them. Two kinds of artifact: - **Canonical** (drop in verbatim from `assets/`): the dispatcher, the shim - skill, `REVIEW.md`, the env template, the AGENTS.md template skeleton, the - review workflow. Show them, explain them, invite tweaks — but they're the same - everywhere. + skills, `REVIEW.md`, the env template, the AGENTS.md template skeleton, the + review workflow. Same everywhere — install and explain, don't reinvent. - **Software 3.0** (generated by reading the repo): `AGENTS.md`'s filled-in parts, `scripts/local-checks.sh`, and `scripts/bootstrap-worktree.sh`. Scan, - propose, let the user correct. **Never invent a command you didn't see + generate, cite the evidence. **Never invent a command you didn't see evidence for** — ask instead. -Read first, before talking to the user: -- `references/mental-model.md` — so you can explain what the harness *is*. -- `references/invariants-to-preserve.md` — the rules nothing you generate may break. +**Stop-and-confirm points — the only times you interrupt the autonomous install:** -Then load other references as each step needs them. +1. **Step 6 secret-copying** — before writing any `cp` line that touches a + `.env` or credentials file, name the exact files, confirm copy direction + (source → worktree, never reverse, never delete), and get explicit consent. +2. **Step 7 reviewer choice** — recommend the self-hosted default, but confirm + which reviewer path (and, for self-hosted, which auth) the user wants *before* + configuring it. This wires CI and billing, so it's the user's call. +3. **Step 8 the PR merge** — after the PR opens, you stop and wait for the human + to merge before the host worktree and loops can start. + +Everything else runs without interruption. A non-blocking `[warn]` (e.g. an +existing artifact) is noted and handled (re-run mode), not a stop. ## Preconditions The skill runs in the **human's checkout** (their normal working copy), on `main`, with a clean tree and an `origin` remote. The harness will operate in -sibling worktrees, never here (Invariant 6) — but setup writes/commits happen -here because the human is present and consenting. +sibling worktrees, never here (Invariant 6) — but setup writes happen here +because the human is present, and they land on `main` only via the Step 8 PR. --- -## The guided flow +## The install chain ### Step 0 — Preflight -Run `scripts/preflight.sh` from the repo root and walk the user through the -report together. It checks the environment, detects the toolchain, inventories -which inner skills are installed, and flags any harness artifacts that already -exist (re-run detection). +Run `scripts/preflight.sh` from the repo root and interpret the report: - `[MISS]` on environment (no git repo, no `gh`) → resolve before continuing. - `[MISS]` on inner skills → see "Inner skill dependency" below; note them, don't @@ -61,15 +75,15 @@ exist (re-run detection). - `[warn]` on existing artifacts → this is a re-run; diff against what's there rather than clobbering. -Summarize what you found in plain language and confirm the project is ready. +Summarize what you found in plain language and continue. ### Step 1 — Config (`.harness/env`) Read `references/config-options.md`. Derive the author-slug from -`git config user.email`. Explain `MAX_WORKTREES` (default 1) and `WATCH_PATTERN` -(per-dev default) and their tradeoffs; recommend the defaults and only change on -a concrete reason. Write `.harness/env` from `assets/harness-env.template` with -the chosen values. Add the runtime counter files to `.gitignore`: +`git config user.email`. `MAX_WORKTREES` (default 1) and `WATCH_PATTERN` +(per-dev default) carry tradeoffs — use the defaults unless there's a concrete +reason not to. Write `.harness/env` from `assets/harness-env.template` with the +chosen values. Add the runtime counter files to `.gitignore`: ``` .harness/feedback-rounds-* @@ -86,7 +100,7 @@ the chosen values. Add the runtime counter files to `.gitignore`: (The memory loop keeps no local watermark file — the `refs/harness/last-learned` ref on the remote is the watermark — so there is no `last-main-sha` to ignore.) -(Commit `.harness/env` itself; ignore only the counters.) +`.harness/env` itself is committed; only the counters are ignored. ### Step 2 — The dispatcher, the shared lib, and the two tick wrappers @@ -100,33 +114,31 @@ and `chmod +x` the shell scripts: it's sourced, never executed. - `learn-tick.sh` — the **memory-loop** wrapper (the post-merge `/learn` driver). -Then **walk the user through them** — the load-bearing properties of the dispatcher -(including the HUMAN_REVIEW convergence exit — the reviewer's `REVIEW_CLEAN_MARKER` -comment hands the PR to the human for `/evaluate-pr`), the section map, the -**bootstrap hook** (the project-owned worktree provisioning; explain why it's there), -and the **`harness-tick.sh` wrapper**: the build loop targets the wrapper, which -force-syncs the host worktree to a clean `origin/main` and *then* `exec`s the -(HEAD-agnostic) dispatcher. Explain why the sync lives in the wrapper, not the -dispatcher: the dispatcher must never reset its own running file, and this is how -loop-infrastructure updates merged to main (dispatcher, `.harness/env`) reach the -loop — feature *pipeline* skills ride the PRD branch and need no sync. +Explain the load-bearing properties as you install them — the dispatcher's +HUMAN_REVIEW convergence exit (the reviewer's `REVIEW_CLEAN_MARKER` comment hands +the PR to the human for `/evaluate-pr`), the **bootstrap hook** (the project-owned +worktree provisioning), and the **`harness-tick.sh` wrapper**: the build loop +targets the wrapper, which force-syncs the host worktree to a clean `origin/main` +and *then* `exec`s the (HEAD-agnostic) dispatcher. The sync lives in the wrapper, +not the dispatcher, because the dispatcher must never reset its own running file — +and this is how loop-infrastructure updates merged to main (dispatcher, +`.harness/env`) reach the loop; feature *pipeline* skills ride the PRD branch and +need no sync. Then explain **the memory loop is a separate loop** (`references/dispatcher-explained.md` → "The memory loop"): `/learn` is NOT a dispatcher step. `learn-tick.sh` runs it under its own `/loop … /learn-loop`, in its own dedicated `../-harness-learn` worktree, gated by the `refs/harness/last-learned` watermark ref and paused while a `learn/` -PR is open. The two loops never block each other and coordinate only through git. Offer -the common tweaks (PRD-runner stuck cap, feedback round cap, extra pipeline steps, the -memory-loop interval). Do not let them break the "what NOT to do" list. +PR is open. The two loops never block each other and coordinate only through git. ### Step 3 — The shim skills Copy **both** shims: `assets/poll-and-dispatch-SKILL.md` → `.claude/skills/poll-and-dispatch/SKILL.md` and `assets/learn-loop-SKILL.md` → -`.claude/skills/learn-loop/SKILL.md`. Explain the `/loop` gotcha: an outer session must -never do real work — each shim only calls its tick script (`harness-tick.sh` / -`learn-tick.sh`), and all real work happens in fresh `claude -p` subprocesses. One -paragraph; then move on. +`.claude/skills/learn-loop/SKILL.md`. The significance: an outer `/loop` session +must never do real work — each shim only calls its tick script (`harness-tick.sh` +/ `learn-tick.sh`), and all real work happens in fresh `claude -p` subprocesses, +so no step inherits another's polluted window. ### Step 4 — `AGENTS.md` (Software 3.0) @@ -134,18 +146,17 @@ Read `references/agents-md-guidance.md` and `references/project-discovery.md`. Scan the repo, fill `assets/AGENTS.md.template`'s bracketed parts (project name, the verification-layer tooling), keep the rest verbatim. AGENTS.md is the neutral, eagerly-loaded contract — a map that points into the Expert, not an encyclopedia; -keep it tight (the freshness lint caps the root at ~150 lines). Present it as a -diff against the template, citing the scan finding behind each filled-in line. Let -the user edit before committing. Tell them the Expert section is intentional even -though the Expert is empty until their first merge. +keep it tight (the freshness lint caps the root at ~150 lines). Cite the scan +finding behind each filled-in line as you write it. The Expert section is +intentional even though the Expert is empty until the first `/learn`. ### Step 5 — `scripts/local-checks.sh` (optional, Software 3.0) The deterministic gate the dispatcher runs before opening a PR (two-strike retry: `local-checks.sh fix` → `/fix-local-checks` → STUCK). It's the cheapest place to catch correctness issues — left of the reviewer and CI. **Read -`references/local-checks-design.md` first** and narrate the *why* to the user; they -own and tune this script. Detect commands via `references/project-discovery.md`. +`references/local-checks-design.md` first** and narrate the *why*; the user owns +and tunes this script. Detect commands via `references/project-discovery.md`. It has two responsibilities (the reference details both): @@ -155,17 +166,18 @@ It has two responsibilities (the reference details both): project already defines; call existing pre-commit hooks rather than duplicating. - **Propose custom correctness lints** from the codebase as-is (snapshot discovery) — observed existing invariants + surface-scoped best-practice lints, behind the - five guards in the reference. **Propose; the user disposes.** + five guards in the reference. The gate proves **correctness, not coverage** — block only correctness/structural, *warn* on legibility/observability, and don't add checks for volume (mutation testing is a sensor, not here). The **skip rule** is load-bearing: an agent may never add a test-skip marker; a legitimate skip is the human's call at STUCK. -If the user doesn't want this gate, skip it — the dispatcher treats the script as -absent and works fine. **If you do generate it,** mind the Step 6 dependency: -because it runs typecheck and tests, the worktree must be fully runnable, so -`bootstrap-worktree.sh` has to be solid or these legs fail for the wrong reason. +If the project clearly has no deterministic checks to wire, skip this — the +dispatcher treats the script as absent and works fine. **If you do generate it,** +mind the Step 6 dependency: because it runs typecheck and tests, the worktree must +be fully runnable, so `bootstrap-worktree.sh` has to be solid or these legs fail +for the wrong reason. ### Step 6 — `scripts/bootstrap-worktree.sh` (Software 3.0) @@ -175,18 +187,23 @@ install dependencies, run codegen. Discover the setup from README/CI/manifests (project-discovery.md); if discovery is thin, **ask the user how they set the project up on a fresh machine**. -**Flag the secret-copying loudly.** This is the one place the harness touches -sensitive files: name exactly which files get copied and from where, confirm the -destination is a same-machine `*-harness*` worktree, confirm copy direction is -always source→worktree and nothing is deleted, and get explicit consent. If the -project has no gitignored secrets, skip that part and say so. Generate an -idempotent, non-interactive, well-commented script. - -### Step 7 — Reviewer (optional) - -Read `references/reviewer-options.md`. Present the three paths (self-hosted via -`claude-code-action` = recommended default; managed Code Review; none). Explain -the tradeoffs and that switching later is cheap (one workflow file). Then: +**Flag the secret-copying loudly — stop-and-confirm point 1.** This is the one place the +harness touches sensitive files: name exactly which files get copied and from +where, confirm the destination is a same-machine `*-harness*` worktree, confirm +copy direction is always source→worktree and nothing is deleted, and get explicit +consent before writing those lines. If the project has no gitignored secrets, skip +that part and say so. Generate an idempotent, non-interactive, well-commented +script. + +### Step 7 — Reviewer (stop-and-confirm point 2) + +Read `references/reviewer-options.md`. There are three paths (self-hosted via +`claude-code-action` = recommended default; managed Code Review; none); switching +later is cheap (one workflow file). **This step is not autonomous: present the +three paths, recommend self-hosted, and confirm with the user which path they +want — and, for self-hosted, which auth — before configuring anything.** It wires +CI workflow files and a billing choice, so it's the user's decision. Once they +choose, install that path: - **Self-hosted:** copy `assets/REVIEW.md` to repo root + `assets/workflows/claude-review.yml` to `.github/workflows/`, and: 1. **Pick auth with the user.** The template ships with `claude_code_oauth_token` @@ -215,31 +232,63 @@ the tradeoffs and that switching later is cheap (one workflow file). Then: **Convergence marker (both reviewer paths).** `REVIEW.md` instructs the reviewer to post a PR comment containing `HARNESS_REVIEW_CLEAN` once it has no Important findings left. That marker is how the dispatcher knows the review loop converged and hands the -PR to the human for `/evaluate-pr` — it's a single deterministic signal, -the same for managed and self-hosted. The token is configurable via -`REVIEW_CLEAN_MARKER` in `.harness/env` (must match what `REVIEW.md` tells the -reviewer to post). If the reviewer never posts it, the feedback loop reaches -`FEEDBACK_CAP` and STUCKs instead — a clean PR the human merges. - -### Step 8 — Host worktree + provision - -Explain the three-checkout model (see `references/mental-model.md`): the -human's checkout stays on `main`; the -harness **host** worktree is a sibling that stays **detached at `origin/main`** -(re-synced every tick by `harness-tick.sh`); per-feature work happens in -*ephemeral* `../-harness-` worktrees the dispatcher creates and -tears down. Create the host worktree **detached** — git won't let two worktrees -check out `main` at once, and the human's checkout already holds it: +PR to the human for `/evaluate-pr` — a single deterministic signal, the same for +managed and self-hosted. The token is configurable via `REVIEW_CLEAN_MARKER` in +`.harness/env` (must match what `REVIEW.md` tells the reviewer to post). If the +reviewer never posts it, the feedback loop reaches `FEEDBACK_CAP` and STUCKs +instead — a clean PR the human merges. + +### Step 8 — Commit on `feature/harness-init`, push, open PR, wait for merge + +**Hard rule: never commit harness setup artifacts directly to `main`.** Everything +from Steps 1–7 lands on `feature/harness-init` and reaches `main` only via a merged +PR. This is the review checkpoint — the whole install in one diff. + +Create the branch, stage the artifacts, commit, push, and open the PR: + +``` +git checkout -b feature/harness-init +git add .harness/env .gitignore scripts/ AGENTS.md REVIEW.md .github/ +git commit -m "harness: initial setup via harness-init" +git push -u origin feature/harness-init +gh pr create --base main --head feature/harness-init \ + --title "harness: initial setup" --fill +``` + +Walk the user through the PR — what's in it, why each piece is there — then **stop +and ask them to merge it before continuing (stop-and-confirm point 3).** + +**Why merge first?** Two load-bearing reasons: +- Step 9 creates the host worktree with `git worktree add --detach … origin/main`. + Until the merge lands, `origin/main` has none of the harness scripts, so the + worktree would be empty of `harness-tick.sh` and `poll-and-dispatch.sh`. +- `harness-tick.sh` force-resyncs to `origin/main` on every tick. Even if you + bootstrapped from the feature branch directly, the first tick would wipe it back + to a `main` that has no scripts. + +Once the user confirms the PR is merged and their local `main` is current +(`git fetch && git pull`), continue to Step 9. + +### Step 9 — Host worktree + provision + +Explain the three-checkout model (see `references/mental-model.md`): the human's +checkout stays on `main`; the harness **host** worktree is a sibling that stays +**detached at `origin/main`** (re-synced every tick by `harness-tick.sh`); +per-feature work happens in *ephemeral* `../-harness-` worktrees the +dispatcher creates and tears down. Create the host worktree **detached** — git +won't let two worktrees check out `main` at once, and the human's checkout already +holds it: ``` git worktree add --detach ../-harness origin/main ``` -Then run `scripts/bootstrap-worktree.sh ../-harness` to provision it. This +Then provision it with `scripts/bootstrap-worktree.sh ../-harness`. This **doubles as validation of the bootstrap script** — if the host worktree is -runnable afterward, the script works. Fix and re-run if not. +runnable afterward, the script works. Verify runnability before continuing; fix +and re-run if not. -### Step 9 — Dry-run the tick +### Step 10 — Dry-run the tick From the **harness host worktree** (`../-harness`), run `./scripts/harness-tick.sh` once. It force-syncs the worktree to `origin/main` @@ -251,15 +300,13 @@ Then run `./scripts/learn-tick.sh` once. On a fresh repo (Expert not yet built, merges since the watermark) it creates and bootstraps the `../-harness-learn` worktree, finds no new range, and exits — proving the memory-loop wiring. Walk through both outputs with the user. If anything errors, debug before starting the -loops. (Note: with no `origin` yet, the fetch fails fast — that's expected until the -remote exists.) +loops. -### Step 10 — Commit, then start the loop +### Step 11 — Start the loops -Commit the setup artifacts on `main` (or on a setup branch for the user to merge -— ask). Then explain how to start the harness. It runs as **two independent loops**, -each in its own long-lived Claude Code session pinned to the harness worktree — the -build loop (features) and the memory loop (`/learn`). They never block each other: +Explain how to start the harness. It runs as **two independent loops**, each in its +own long-lived Claude Code session pinned to the harness worktree — the build loop +(features) and the memory loop (`/learn`). They never block each other: ``` cd ../-harness @@ -304,15 +351,18 @@ both **outer** loops; it does not author these. Preflight reports which are installed. If some are missing, set up the harness anyway and tell the user clearly which skills must be installed from the catalog before the chain runs end to end — the harness will dispatch to them the moment they exist. -(`/learn` is the memory skill under `.claude/skills/memory/`; the project's -Expert is born from the first `/learn`.) +(The project's Expert is born from the first `/learn`, written directly under +`.claude/skills/expert/`.) ## Re-running Safe to re-run. Preflight flags existing artifacts. For canonical files, diff and offer to update. For generated files (`AGENTS.md`, `local-checks.sh`, `bootstrap-worktree.sh`), re-scan and show a diff rather than overwriting blind. -Never delete the human's host worktree or secrets on a re-run. +Never delete the human's host worktree or secrets on a re-run. If +`feature/harness-init` already exists from a prior run, detect it, diff the +existing branch against the current state, and update that branch rather than +creating a fresh one. ## Hard rules (from invariants-to-preserve.md) @@ -322,3 +372,6 @@ Never delete the human's host worktree or secrets on a re-run. - Never generate a "go backward" / sentinel-deleting step (Invariant 9). - `bootstrap-worktree.sh` copies secrets source→worktree only, never deletes, never the reverse (Invariant 6). +- Never commit harness setup artifacts directly to `main`. All harness-init + artifacts land on `feature/harness-init` and reach `main` only via a merged + PR. The host worktree and the loops cannot start until that merge (Step 8). diff --git a/skills/harness/harness-init/assets/AGENTS.md.template b/skills/harness/harness-init/assets/AGENTS.md.template index 6a1b3da..ceb4cdc 100644 --- a/skills/harness/harness-init/assets/AGENTS.md.template +++ b/skills/harness/harness-init/assets/AGENTS.md.template @@ -76,7 +76,7 @@ leaves the next invocation a clean re-derivation from disk. Four layers gate "done." All four must pass; none can be talked past. 1. **Pre-commit hooks** — linters, formatters, type checks ([PROJECT CONFIG]). -2. **Slice signals** — each slice's `Signal` section runs during `/implement-slice`. +2. **Slice unit tests** — each slice's tests run during `/implement-slice`. 3. **CI** — full test suite, full type check, full lint (incl. custom invariant lints in `scripts/lints/`). 4. **PRD runner** — `./prds//run-prd-test.sh` must exit 0 before merge. This is the definition of done. diff --git a/skills/harness/harness-init/assets/learn-tick.sh b/skills/harness/harness-init/assets/learn-tick.sh index a2c72bb..4726a59 100644 --- a/skills/harness/harness-init/assets/learn-tick.sh +++ b/skills/harness/harness-init/assets/learn-tick.sh @@ -54,10 +54,10 @@ signal_learn_review() { { echo "## /learn session — why memory changed" echo - echo "These memory edits (Expert shards, invariants, AGENTS.md pointers," + echo "These memory edits (Expert reference files, invariants, AGENTS.md pointers," echo "candidate lints) were written by a headless \`/learn\` over the merged" echo "diff. If a routing call here looks wrong — a fact in the wrong surface," - echo "an overfit lint, an Expert shard that says too much — open the trace" + echo "an overfit lint, an Expert reference file that says too much — open the trace" echo "below to see exactly what \`/learn\` read and how it decided, before you" echo "accept or revise it." echo diff --git a/skills/harness/harness-init/assets/poll-and-dispatch.sh b/skills/harness/harness-init/assets/poll-and-dispatch.sh index 9f713cd..aec3612 100755 --- a/skills/harness/harness-init/assets/poll-and-dispatch.sh +++ b/skills/harness/harness-init/assets/poll-and-dispatch.sh @@ -129,7 +129,7 @@ signal_stuck() { echo "## Diagnosis-first" echo echo "Your **first** job is not the code — it is to identify which piece of" - echo "context (\`AGENTS.md\`, an Expert shard, a spec, or the PRD) misled the" + echo "context (\`AGENTS.md\`, an Expert reference file, a spec, or the PRD) misled the" echo "agent or was missing. Correct it on this branch *before* the code fix;" echo "the merge carries both into main and \`/learn\` picks up the context fix." echo diff --git a/skills/harness/learn/SKILL.md b/skills/harness/learn/SKILL.md index 88da022..18fc6cf 100644 --- a/skills/harness/learn/SKILL.md +++ b/skills/harness/learn/SKILL.md @@ -1,6 +1,6 @@ --- name: learn -description: Update the project's long-term memory after a merge to main. Reads the merged diff and the current memory, then writes Expert shards, discovered invariants, candidate lints, and AGENTS.md pointers — all on a reviewable learn/ PR. Use post-merge (the harness invokes it automatically) or with --rebuild to regenerate memory from scratch. Triggers - learn, expert-update, update memory, update expert, post-merge memory, self-improve (project) +description: Update the project's long-term memory after a merge to main. Reads the merged diff and reconciles the current memory — adding, editing, and deleting Expert reference files — then drafts candidate lints and AGENTS.md pointers. Opens a reviewable learn/ PR, or prints "nothing to learn" and exits if the merge produced no material change. Use post-merge (the harness invokes it automatically) or with --rebuild to regenerate memory from scratch. Triggers - learn, expert-update, update memory, update expert, post-merge memory, self-improve (project) --- # learn @@ -42,28 +42,42 @@ into main with the feature merge, and **you observe them in the diff you read**. the bar for putting something in AGENTS.md is **far higher** than for the Expert. - **P3 — The four destinations.** Every fact worth remembering routes to exactly one place: a **lint** (if mechanically checkable), **eager prose** (AGENTS.md, if - it clears the high bar), **lazy prose** (an Expert shard), or **nowhere**. Most - things go nowhere or to the Expert. See `references/routing-rules.md`. + it clears the high bar), **lazy prose** (an Expert reference file — one of + `how-to-*` / `concept-*` / `pattern-*` / `invariant-*` / `example-*`), or + **nowhere**. Most things go nowhere or to the Expert. See + `references/routing-rules.md` and `references/expert-structure.md`. - **P4 — Map, not encyclopedia.** AGENTS.md is the table of contents that *points into* the Expert; it never duplicates it. A monolithic AGENTS.md rots, crowds out the task, and turns "everything important" into "nothing important." Keep it a map. See `references/agents-md-guidance.md`. -- **P5 — Consensus gates a write.** A few cheap reviewers vote per surface before - anything changes. Routine merges (vuln fixes, refactors that don't change shape) - typically produce no change — that's correct, not a failure. See - `references/consensus.md`. +- **P5 — Progressive disclosure inside the Expert.** Reference files are small and + topic-focused, cross-linked via Obsidian `[[wikilinks]]`. SKILL.md is an index — + one line per file. An agent reads the index, opens only what's relevant, then + follows wikilinks to discover related context. See + `references/expert-structure.md` and `references/wikilink-convention.md`. - **P6 — Invariants are discovered, then promoted.** You may notice architectural - rules the codebase upholds. Record them as prose first; flag the mechanically - checkable ones as candidate **lints** (the highest-value memory, because a lint - is a rule the agent *cannot* ship past). See `references/invariant-discovery.md`. + rules the codebase upholds. Record each as its own `invariant-.md` file + (one rule per file); flag the mechanically checkable ones as candidate **lints** + (the highest-value memory, because a lint is a rule the agent *cannot* ship + past). See `references/invariant-discovery.md`. - **P7 — Human-authored memory edits are authoritative.** When the merged diff - *already* touches AGENTS.md, an Expert shard, or a spec, treat those changes as - **ground truth, not as a proposal to second-guess.** They came from a human - resolving a STUCK or making a deliberate correction. Your job there is to - *extend* (what else, given this correction, now needs to change?) — not to - vote on whether to apply it. -- **P8 — Reviewable, revertible, human-merged.** Everything lands on `learn/` - with a changelog entry citing the merge. Never auto-merge. + *already* touches AGENTS.md, an Expert reference file, or a spec, treat those + changes as **ground truth, not as a proposal to second-guess.** They came from a + human resolving a STUCK or making a deliberate correction. Your job there is to + *extend* (what else, given this correction, now needs to change?) — not to vote + on whether to apply it. +- **P8 — Reviewable, revertible, human-merged.** Everything lands on a `learn/` + PR. Never auto-merge. +- **P9 — Reconcile, don't accumulate.** Memory is a *current model of main*, not + an append-only log. Every run must look for deleted concepts (the code is gone → + the reference file goes), inter-file contradictions (two files disagree → merge + or scope-qualify), and claims invalidated by the merged diff. **Adds, edits, and + deletes** all ride on the same PR. See `references/reconcile.md`. +- **P10 — Nothing to learn is a valid outcome.** Most merges produce nothing for + memory. When `reconcile` finds zero adds, edits, deletes, AGENTS.md pointer + changes, or candidate lints, print `learn: nothing to learn from , skipping + PR` to stdout, exit 0, and do not push a branch or open a PR. The outer loop + advances `refs/harness/last-learned` regardless. ## How to run this skill @@ -71,82 +85,125 @@ Read the seam references before acting; they are the hackable contract a project tunes to its taste: - `references/routing-rules.md` — the four destinations + the five-predicate test - for eager placement + the line-count caps. *(Primary hackable seam.)* -- `references/expert-shards.md` — the shard taxonomy and templates. + for eager placement + the five sub-destinations for the Expert + the line-count + caps. *(Primary hackable seam.)* +- `references/expert-structure.md` — the flat prefix-named layout, the five + prefixes, the SKILL.md index, the bootstrap seed list. +- `references/wikilink-convention.md` — the `[[basename]]` cross-link syntax used + inside `expert/references/` and the lint that enforces it. +- `references/reconcile.md` — the three-pass add/edit/delete logic plus the + nothing-to-learn early exit. - `references/agents-md-guidance.md` — map-not-manual; which folders earn a nested AGENTS.md; the freshness contract. - `references/invariant-discovery.md` — how to surface invariants and draft lints without overfitting. -- `references/consensus.md` — the voting threshold (2/3 default) and how to run it. ## The flow ### Step 0 — Mode + idempotency Determine mode: -- **Bootstrap** — no `.claude/skills/expert/` exists (or `--rebuild`): create the - Expert from scratch by scanning committed code, seed all shards + the root - `AGENTS.md`. This is also the recovery path (see `--rebuild`). -- **Incremental** — the Expert exists: reconcile against the merged diff. +- **Bootstrap** — `.claude/skills/expert/` is empty (no `SKILL.md` index inside), + or `--rebuild` was passed: create the Expert from scratch by scanning committed + code, seed the minimum-viable file set (see `references/expert-structure.md`) + + the root `AGENTS.md`. This is also the recovery path. Check for the presence + of `SKILL.md` inside the dir, not for the dir itself. +- **Incremental** — the Expert exists (contains a `SKILL.md`): reconcile against the merged diff. Idempotency: if `learn/` already exists on origin, another node handled this merge — exit cleanly. (The memory loop pre-checks this too, via `git ls-remote`.) ### Step 1 — Gather -Read: the diff for `--since ..--sha `; the current Expert shards -(`.claude/skills/expert/references/*.md`); the AGENTS.md files the diff touches -(root + any in changed folders); and the PRD(s)/spec(s) for the merged feature if -present (`prds//`, `specs//`) for the *why*. - -**Notice whether the diff itself touches memory files** (AGENTS.md, Expert shards, -spec sections). If so, you're looking at a human's context correction — see P7; -those changes are ground truth. - -### Step 2 — Consensus -Spawn 2–3 cheap parallel reviewers (`references/consensus.md`). Each reads the diff -+ current memory and votes, **per surface**, on whether anything must change: -shards, invariants, AGENTS.md. (No votes against human-authored memory edits — P7.) -Keep only changes that clear the threshold. A 0/3 on every surface is a valid, -common outcome — log it and no-op. +Read: the diff for `--since ..--sha `; the current Expert reference +files (`ls .claude/skills/expert/references/*.md` plus their contents); the +AGENTS.md files the diff touches (root + any in changed folders); and the +PRD(s)/spec(s) for the merged feature if present (`prds//`, `specs//`) for +the *why*. + +**Notice whether the diff itself touches memory files** (AGENTS.md, Expert +reference files, spec sections). If so, you're looking at a human's context +correction — see P7; those changes are ground truth. + +### Step 2 — Reconcile +Run the three-pass reconcile (`references/reconcile.md`) in the main agent — no +subagent fanout. Produce three candidate lists: `adds[]`, `edits[]`, `deletes[]`. +Every candidate carries a **one-line justification** citing the diff hunk +(file:line range) or the contradicting reference file. If you can't write the +justification, drop the candidate. + +The three passes: +1. **Deleted-concept** — references whose anchor code is gone → `delete`. +2. **Inter-file contradiction** — pairs that disagree → rewrite both to + scope-qualify, merge them, or drop the stale one. +3. **Diff-invalidates-file** — references contradicted by the merge → `edit`. ### Step 3 — Route -For each surviving candidate fact, apply `references/routing-rules.md`: lint / -eager AGENTS.md / lazy Expert shard / nowhere. When in doubt, prefer the Expert -(cheap) over AGENTS.md (eager), and prefer *nothing* over noise. +For each surviving candidate, apply `references/routing-rules.md`: lint / +eager AGENTS.md / lazy Expert reference (then pick the prefix: `how-to-*` / +`concept-*` / `pattern-*` / `invariant-*` / `example-*`) / nowhere. When in +doubt, prefer the Expert (cheap) over AGENTS.md (eager), and prefer *nothing* +over noise. + +### Step 3.5 — Nothing-to-learn check +If after Reconcile + Route the totals are empty: + +``` +len(adds) + len(edits) + len(deletes) == 0 +AND no AGENTS.md pointer change +AND no candidate lint +``` + +…print `learn: nothing to learn from , skipping PR` to stdout, exit 0. +Do **not** push a branch, do **not** open a PR, do **not** commit. The outer +loop advances `refs/harness/last-learned` regardless. ### Step 4 — Write -- **Expert shards** — apply diffs to the relevant shard(s); add to `core-files.md`, - `patterns.md`, etc. -- **invariants.md** — add any discovered hard rules. For mechanically checkable - ones, draft a candidate lint (code + remediation-message-as-prompt) under - `scripts/lints/` and wire it into `scripts/local-checks.sh`. **The drafted lint - MUST pass against the just-merged code before you include it** — run it; if it - fails on current main it's wrong (the inverse of `/intent`'s right-reason check). +- **Expert references** — apply adds/edits/deletes to + `.claude/skills/expert/references/`. New files use the prefix layout + (`how-to-.md`, `concept-.md`, etc.). Cross-link to related + files with `[[wikilinks]]` (no path, no extension). Update + `.claude/skills/expert/SKILL.md`'s one-line-per-file index to match the new set. +- **Invariants** — each discovered hard rule lands as its own + `invariant-.md` (one rule per file). For mechanically checkable ones, + draft a candidate lint (code + remediation-message-as-prompt) under + `scripts/lints/` and wire it into `scripts/local-checks.sh`. **The drafted + lint MUST pass against the just-merged code before you include it** — run it; + if it fails on current main it's wrong. - **AGENTS.md** — update the relevant map's pointer(s). Propose a *new* nested AGENTS.md only when the merge introduced a folder-local rule that clears the - five-predicate bar and no file exists. Stay under the caps. + five-predicate bar and no file exists. Stay under the caps. AGENTS.md uses + regular `[text](path)` markdown links — **not** wikilinks. ### Step 5 — Validate -Run `scripts/check-agents-md.sh` (referenced paths exist, cross-links resolve, -under caps). Re-run any drafted lint against current main (must pass). +Run `scripts/check-agents-md.sh` (AGENTS.md pointers exist, cross-links resolve, +under caps) and `scripts/check-expert-links.sh` (every `[[wikilink]]` resolves +to a file in `expert/references/`). Re-run any drafted lint against current +main (must pass). -### Step 6 — Changelog + PR -Append a `changelog.md` entry citing the merge sha and listing every surface -touched and the consensus vote. Open the `learn/` PR. Done. +### Step 6 — Open PR +Commit the changes and open the `learn/` PR. The PR body lists every +add/edit/delete with its one-line justification. **No changelog file is +appended** — the git history of `learn/` PRs is the changelog. ## Invocation & output contract - **Invoked by:** the memory loop, `claude -p "/learn --since --sha "`, inside the `../-harness-learn` worktree. The `--since` is the `refs/harness/last-learned` watermark (how far memory has already been digested); `--sha` is current `origin/main`. Also runnable by a human with `--rebuild` - (regenerate memory from main). -- **Writes:** under `.claude/skills/expert/` (shards incl. `invariants.md` and - `changelog.md`), `scripts/lints/*` + `scripts/local-checks.sh` wiring, and - `AGENTS.md` files across the repo. -- **Completion signal:** the pushed `learn/` branch + open PR. Idempotent via - `git ls-remote origin learn/`. After the PR opens, the memory loop advances - `refs/harness/last-learned` to `` (atomic CAS) and pauses new runs until you - merge or close the PR. -- **Session trail:** after the PR opens, the memory loop (`signal_learn_review`) + (regenerate memory from main) or `--dry-run` (print the candidate set plus + justifications; open no PR; useful for evaluation runs). +- **Writes:** under `.claude/skills/expert/` (prefix-named reference files plus + the `SKILL.md` index), `scripts/lints/*` + `scripts/local-checks.sh` wiring, + and `AGENTS.md` files across the repo. **No `changelog.md`** — the git history + of `learn/` PRs is the changelog. +- **Completion signal — two valid outcomes:** + - **PR opened.** The pushed `learn/` branch + open PR. Idempotent via + `git ls-remote origin learn/`. After the PR opens, the memory loop + advances `refs/harness/last-learned` to `` (atomic CAS) and pauses new + runs until you merge or close the PR. + - **Nothing to learn.** Stdout `learn: nothing to learn from , skipping + PR`; exit 0; no branch pushed. The memory loop advances the watermark + regardless, so the next merge is picked up cleanly. +- **Session trail:** when a PR opens, the memory loop (`signal_learn_review`) attaches this run's headless `claude -p` session as a PR comment, so the human evaluating the memory changes can open the trace and troubleshoot *why* `/learn` routed a fact as it did. No action needed in this skill — the loop posts it. @@ -158,8 +215,15 @@ touched and the consensus vote. Open the `learn/` PR. Done. the caps (P4). - **Never include a candidate lint you haven't run against current main.** A lint that reddens the merge it was born from is wrong. -- **Never frame an invariant as taste.** If it needs judgment, it's a pattern - (Expert), not an invariant (lint/hard rule). +- **Never frame an invariant as taste.** If it needs judgment, it's a `pattern-*` + file (Expert), not an `invariant-*` file (lint/hard rule). - **Never second-guess a memory edit that's already in the merged diff** (P7) — the human resolved a STUCK or made a deliberate correction; your job is to *extend* it, not vote on it. +- **Never accumulate without reconciling** (P9). A concept deleted from code is a + reference file deleted in this PR. Inbound wikilinks get rewritten in the same + commit. +- **Never write a wikilink that does not resolve** to a file in + `expert/references/`. `check-expert-links.sh` will fail the PR. +- **Never open a PR for nothing-to-learn** (P10). Print the stdout line, exit 0, + push nothing. diff --git a/skills/harness/learn/references/agents-md-guidance.md b/skills/harness/learn/references/agents-md-guidance.md index 8771782..a951d45 100644 --- a/skills/harness/learn/references/agents-md-guidance.md +++ b/skills/harness/learn/references/agents-md-guidance.md @@ -36,6 +36,11 @@ path it references exists, cross-links resolve, and it's under the line cap. Wir this into `local-checks.sh`/CI so a stale pointer fails the build. This is the same "enforce invariants mechanically" instinct turned on the memory files themselves. +> AGENTS.md uses regular `[text](path)` markdown links — **not** wikilinks. The +> Obsidian-style `[[wikilinks]]` convention is internal to `expert/references/` +> only; it has its own validator (`check-expert-links.sh`). Keep the two link +> styles separate so each validator stays simple. + ## Caps (tweakable, enforced by check-agents-md.sh) - Root: ≤ 150 lines. - Nested: ≤ 80 lines. diff --git a/skills/harness/learn/references/consensus.md b/skills/harness/learn/references/consensus.md deleted file mode 100644 index 70dd0be..0000000 --- a/skills/harness/learn/references/consensus.md +++ /dev/null @@ -1,35 +0,0 @@ -# Consensus - -**Hackable seam.** The voting threshold that gates a memory write. Mirrors the -multi-agent consensus `/spec-validate` already uses, so the project has one mental -model for "a panel decided," not two. - -## Why consensus here -Memory is long-lived and compounding. A wrong write poisons every future session -that reads it. The trade is cheap: a few extra LLM calls per merge in exchange for -not corrupting long-term memory on every commit. Most merges (vuln fixes, refactors -that don't change shape, routine bug fixes) produce **no change** — 0/3 across all -surfaces is the common, correct outcome. - -## How to run it -1. Spawn **2–3** parallel reviewers (cheap model is fine — this is judgment over a - bounded diff, not generation). -2. Give each: the merged diff, the current Expert shards, the touched AGENTS.md - files, and the PRD/spec for the *why*. -3. Each votes **per surface** (shards / invariants / AGENTS.md): does anything - need to change, and if so what? -4. Apply only changes that meet the **threshold (default 2/3)**. Discard 1/3. - -## Tuning -- Raise to **3/3** for a stricter bar (fewer, higher-confidence writes). -- Lower to a single reviewer only for low-stakes projects that want speed over - precision — not recommended for invariant/lint promotion, which should stay - consensus-gated *and* human-confirmed. -- **Human-authored memory edits already in the diff bypass voting** (see SKILL.md - P7) — they're ground truth. Vote on what to *extend*, not whether to apply them. - -## Drift signal -If consensus returns 0/3 for *many* consecutive merges, that's worth noticing: the -Expert may be too coarse to capture what's changing, or the project is genuinely -stable. Either way, it's an observability signal, not a bug — surface it, don't -force a write. diff --git a/skills/harness/learn/references/expert-shards.md b/skills/harness/learn/references/expert-shards.md deleted file mode 100644 index c1f9be1..0000000 --- a/skills/harness/learn/references/expert-shards.md +++ /dev/null @@ -1,44 +0,0 @@ -# Expert shard taxonomy - -**Hackable seam.** A project may add, rename, or split shards. These are the -defaults. The Expert is the project's *lazy* memory — pulled on demand by `/intent`, -`/spec-planning`, `/spec-validate`, and `/implement-*` via the catalog, or read -directly. It is shaped exactly like a framework expert (so it composes with them): - -``` -.claude/skills/expert/ -├── SKILL.md # Expert Mode entry: quick-reference table → references/ -├── references/ -│ ├── architecture.md # Decisions, layering, boundaries, the "why" of the shape -│ ├── verification.md # How features get tested/verified in THIS project -│ ├── patterns.md # Soft DO/DON'T, with examples (taste, judgment) -│ ├── procedural.md # "How to add a new feature here" — the steps -│ ├── core-files.md # Key files & abstractions, with paths -│ ├── invariants.md # HARD rules the codebase upholds (see below) -│ └── changelog.md # Provenance: what /learn changed, when, why, vote -└── scripts/ - └── (optional signal scripts, like any expert) -``` - -## The shard this design adds: invariants.md (hard rules, distinct from patterns.md) - -`patterns.md` is *soft* ("prefer composition here"); `invariants.md` is *hard* -("Repo layer never imports Service"). The distinguishing test: **can it be -mechanically checked?** If yes, it's an invariant and the highest-value ones become -lints (see `invariant-discovery.md`). If it needs judgment, it's a pattern. - -Each invariant entry: the rule, *why* it holds, and — once promoted — a pointer to -its lint (`scripts/lints/.sh`). - -## SKILL.md shape -Mirror the framework-expert shape (`expert-sdd-creator`'s `expert-structure.md`): a -short Expert-Mode entry with a quick-reference table mapping topics → reference -files. The body stays small; the references hold the density. Front-matter -`description` ends with `(project)` and lists triggers so the catalog can activate -it during the SDD phases. - -## Bootstrap / --rebuild -On a fresh project (no Expert) or `--rebuild`, seed every shard by scanning -committed code: derive `core-files.md` from the actual tree, `verification.md` from -the test/CI setup, `architecture.md` from the module layering, `invariants.md` from -rules the code visibly upholds (conservatively — see `invariant-discovery.md`). diff --git a/skills/harness/learn/references/expert-structure.md b/skills/harness/learn/references/expert-structure.md new file mode 100644 index 0000000..63cc8a6 --- /dev/null +++ b/skills/harness/learn/references/expert-structure.md @@ -0,0 +1,129 @@ +# Expert structure + +**Hackable seam.** A project may add, rename, or split files. These are the +defaults. The Expert is the project's *lazy* memory — pulled on demand by `/intent`, +`/spec-planning`, `/spec-validate`, and `/implement-*` via the catalog, or read +directly. + +The Expert is a **flat directory of many small, topic-focused files**, each named +by a prefix that tells the agent (and `/learn`) what kind of memory it is. Files +cross-link to each other with Obsidian-style `[[wikilinks]]` so an agent reading +one file can discover related context by following links. `SKILL.md` is the +**index**: one line per reference file plus a short note about the link convention. + +``` +.claude/skills/expert/ +├── SKILL.md # INDEX: one-line description per reference +└── references/ + ├── how-to-run-the-project.md # procedural SOP + ├── how-to-validate.md # procedural SOP + ├── how-to-e2e-test.md # procedural SOP (setup, exercise, teardown) + ├── concept-architecture.md # semantic + ├── concept-core-files.md # semantic + ├── pattern-.md # soft prefer/avoid (judgment) + ├── invariant-.md # one hard rule per file + └── example-.md # past trace, cited from a real sha +``` + +## The five prefixes + +Each fact `/learn` decides to remember lands in exactly one prefixed file. The +filename slug is the topic; the prefix is the *kind* of memory. + +- **`how-to-.md` — procedural.** A sequence of steps a contributor (or + agent) follows. Imperative voice. Answers "How do I X?" Examples: + `how-to-run-the-project.md`, `how-to-validate.md`, `how-to-e2e-test.md`, + `how-to-add-a-feature.md`. Setup/teardown belongs inside the relevant how-to. + +- **`concept-.md` — semantic (nouns).** The shape of the system, a model, + a definition. Declarative voice. Answers "What is X?" Examples: + `concept-architecture.md`, `concept-core-files.md`, `concept-routing.md`. + +- **`pattern-.md` — semantic (soft rules).** A DO/DON'T that requires + judgment. Has counterexamples. Cannot be mechanically checked. Example: + `pattern-error-handling.md` ("prefer typed errors at boundaries; raw exceptions + fine inside a module"). + +- **`invariant-.md` — semantic (hard rules).** A single rule the codebase + upholds, mechanically checkable in principle. **One rule per file.** Filename + is the rule. Example: `invariant-no-repo-imports-service.md`. The highest-value + invariants get drafted as lints (see `invariant-discovery.md`). + +- **`example-.md` — episodic.** A concrete past trace — a real PR or + merge, input → reasoning → output. Cited from a real sha. Never synthetic. + Acts as a few-shot demonstration. Example: + `example-added-search-endpoint-2024-q3.md`. + +The split exists so an agent reading the SKILL.md index can ask one question of +each file ("does this topic apply now?") and open only what's relevant — instead +of paging through a 500-line `patterns.md` for one fact. + +## Wikilinks between reference files + +Reference files cross-link to each other with **Obsidian-style `[[wikilinks]]`**: +two square brackets around the *basename of another file in `references/`*, no +extension, no path. + +```markdown +The e2e flow stands up the test DB; see [[how-to-run-the-project]] for the +non-test boot path, and [[invariant-no-repo-imports-service]] for why the +fixture loader lives in `test/` and not `repo/`. +``` + +Resolution: `[[name]]` → `references/name.md`. The agent already knows how to +traverse these — wikilinks are a near-universal convention. + +`scripts/check-expert-links.sh` mechanically validates every wikilink resolves +to a file in the same directory. Broken links fail the PR. See +`wikilink-convention.md` for the formal spec. + +> AGENTS.md does **not** use wikilinks. It uses regular `[text](path)` markdown +> links so `check-agents-md.sh` keeps working unchanged. Wikilinks are an +> internal Expert-only convention. + +## SKILL.md as the index + +`SKILL.md` is generated by `/learn` and is a thin **catalog**, not a knowledge +file. Body: + +1. A one-paragraph header naming the project and stating the wikilink convention. +2. A table: `file | one-line description`. One row per reference file in the + directory. + +That's it. An agent reads the index, decides which references look relevant to +the task at hand, opens those, then follows wikilinks from there. + +The front-matter `description` ends with `(project)` and lists triggers so the +catalog can activate the Expert during the SDD phases. + +## Bootstrap / `--rebuild` + +On a fresh project (no Expert) or when invoked with `--rebuild`, seed a small +**minimum viable Expert** by scanning committed code. Bias toward fewer, broader +files; the incremental post-merge path will split them later when topics get +specific. + +**Always-seed (5 files):** + +1. `how-to-run-the-project.md` — derived from `package.json` / `Makefile` / + `pyproject.toml` / equivalent entry-points. +2. `how-to-validate.md` — derived from `scripts/local-checks.sh` and CI config. +3. `how-to-add-a-feature.md` — the SDD skill chain order for this project. +4. `concept-architecture.md` — module layering from the directory scan. +5. `concept-core-files.md` — key files and abstractions with paths. + +**Conditionally-seed (only on clear visible signal):** + +- `how-to-e2e-test.md` — if end-to-end tests exist. +- `concept-verification.md` — if testing/CI is non-trivial enough to warrant a + separate file from `how-to-validate.md`. +- One `invariant-.md` per rule the code *visibly* upholds. Skip on + uncertainty; let post-merge discovery promote them. +- `pattern-*.md` — **default to zero on bootstrap.** Patterns emerge from + recurring evidence across merges, not from one snapshot. +- `example-*.md` — **zero on bootstrap.** Episodic memory needs real past + experiences, not synthetic ones. + +**Soft cap: ~15 files total** on bootstrap. If the scan suggests more, fold +related items into one `concept-*.md` and let `/learn` split it later when a +topic genuinely warrants its own file. diff --git a/skills/harness/learn/references/invariant-discovery.md b/skills/harness/learn/references/invariant-discovery.md index dbe59dd..bf2e438 100644 --- a/skills/harness/learn/references/invariant-discovery.md +++ b/skills/harness/learn/references/invariant-discovery.md @@ -72,23 +72,28 @@ check would reward). - **Structured logging only** — no `console.log(`/`print(` in `src/`. - **Naming/format** — migration filenames match `^\d{14}_`. - **File-size cap / public-API drift** — `wc -l` ceiling; exports match `openapi.yaml`. -- **Memory freshness** — `check-agents-md.sh` itself (referenced paths exist). +- **Memory freshness** — `check-agents-md.sh` (AGENTS.md pointers exist) and + `check-expert-links.sh` (Expert wikilinks resolve). ## What stays prose (NOT a lint) Anything needing judgment: "is this the right abstraction?", "prefer composition -here." Those are `patterns.md` (Expert), or — if pre-emptively load-bearing — -AGENTS.md. The test: **if pass/fail is unambiguous, lint it; if it needs taste, it -stays prose.** +here." Those live in `pattern-.md` files in the Expert, or — if pre-emptively +load-bearing — AGENTS.md. The test: **if pass/fail is unambiguous, lint it; if it +needs taste, it stays prose.** ## The discipline (avoid overfitting) A discovered "invariant" can be an incidental pattern, or a quirk of the current model's failures rather than a real codebase property. So: -1. **Consensus-gate it** like every `/learn` write. -2. **Prose first, lint on recurrence.** First sighting → record in `invariants.md` - and flag a candidate lint. Same invariant seen across merges → promote to an - enforced lint. One occurrence is a pattern; repetition is an invariant. +1. **Justify it.** In a single inline pass, write a one-line justification citing + the diff hunk (file + line range) that motivated this rule. If you can't, drop + it. This is the inline replacement for the old consensus gate — same bar, + applied by the main agent. +2. **Prose first, lint on recurrence.** First sighting → record as a new + `invariant-.md` (one rule per file) and flag a candidate lint. Same + invariant seen across merges → promote to an enforced lint. One occurrence is + a pattern; repetition is an invariant. 3. **Frame the *reason*.** "Repo→Service imports break the build because layers are compiled bottom-up" survives model upgrades; a bare prohibition doesn't. 4. **The drafted lint MUST pass current main.** Run it against the just-merged code diff --git a/skills/harness/learn/references/reconcile.md b/skills/harness/learn/references/reconcile.md new file mode 100644 index 0000000..4f77128 --- /dev/null +++ b/skills/harness/learn/references/reconcile.md @@ -0,0 +1,138 @@ +# Reconcile — the new write logic + +**Hackable seam.** How `/learn` decides what to **add, edit, and delete** in the +Expert. Replaces the old consensus pass: reasoning happens inline in the main +agent (no subagent fanout), but every proposed change carries a one-line +justification cited from the diff or the contradicting reference file. + +## The premise + +Memory must reflect what is true *on main right now* — not what was true at the +last merge plus everything observed since. So every `/learn` run does three +things, in order: + +1. **Add** facts that appear in the merged diff and aren't in the Expert yet. +2. **Edit** existing reference files whose claims are still relevant but + incomplete or outdated. +3. **Delete** reference files whose underlying concept no longer exists in code, + or that contradict a more recent file beyond rescue. + +## The three reconcile passes + +Run these in order, in the main agent (no subagents). Each produces entries on +one of three candidate lists: `adds[]`, `edits[]`, `deletes[]`. Every entry +carries a **one-line justification** citing either a specific diff hunk +(file:line range) or a specific contradicting reference file. If you can't write +the justification, drop the candidate + +### Pass 1 — Deleted-concept pass + +For each `concept-*.md` and `how-to-*.md` in the Expert: + +1. Extract the file's **key code references**: paths it mentions, function or + class names, module identifiers, command names. +2. For each, check whether it still exists post-merge: `git cat-file -e + :`, `git grep -E `, or equivalent. +3. Decide: + - **Wholly gone** (no references survive) → mark **delete**. + - **Partially gone** (some references survive, some don't) → mark **edit**; + prune the dead pointers and update the prose. + - **All present** → no action from this pass. + +Do the same for `invariant-*.md` and `pattern-*.md`: if the rule's anchor code +is gone (the layer, the module, the API surface), the rule is dead. + +### Pass 2 — Inter-file contradiction pass + +Build a list of **topic-related file pairs**: + +- Pairs that share a wikilink (one references `[[the-other]]`). +- Pairs whose slugs share two or more tokens (e.g. `pattern-error-handling.md` + and `how-to-handle-errors.md`). +- Pairs that mention the same code paths or identifiers. + +For each pair, read both files in full and ask: **do they assert opposing +facts?** Common patterns: + +- **Stale + Fresh.** One was written before a refactor; the other after. + Resolution: drop the stale file (`delete`); the fresh one already covers the + current truth. Justification cites the merge sha that made the older claim + false. + +- **Two facets of one rule.** Two files about the same underlying concept that + disagree because they were each scoped narrowly. Resolution: **merge them** + into one file (`add` the merged file, `delete` both originals). Update any + inbound wikilinks. Justification names both prior files. + +- **Both true at different scopes.** Each is correct in its own context. + Resolution: `edit` both to scope-qualify ("inside the repo layer, …"; "at the + service boundary, …"). Justification cites the scope distinction. + +**Default heuristic: rewrite both / scope-qualify.** Drop one only when one is +clearly stale (its anchor code is gone, it predates a refactor that already +landed, or a human-authored P7 edit in this diff supersedes it). Never silently +delete on contradiction — every `delete` shows up in the PR body with its +one-line justification. + +### Pass 3 — Diff-invalidates-file pass + +For each code path touched by the merged diff, scan all references that mention +that path. For each reference: + +- Does the merge **contradict** a claim in the file? → mark **edit**. +- Does the merge **extend** a claim (new module added, new step in a how-to)? + → mark **edit** to incorporate, or **add** a new sibling file if the new + topic deserves its own. +- Does the merge **replace** the concept the file is about? → see Pass 1 (may + become a `delete`). + +## Routing and writing + +After reconcile, each entry on `adds[]` / `edits[]` / `deletes[]` is routed +through `routing-rules.md` to pick its prefix (`how-to-*` / `concept-*` / +`pattern-*` / `invariant-*` / `example-*`) or — for an `add` — to be redirected +to AGENTS.md, a lint, or nowhere instead. + +Write logic: + +- **Add** → create the file, add it to `SKILL.md`'s index, populate wikilinks to + related references. +- **Edit** → rewrite the relevant section. If a wikilink target was renamed or + deleted, fix the inbound link. +- **Delete** → `rm` the file, remove its row from `SKILL.md`'s index, and + rewrite any inbound wikilinks (drop them or repoint). + +`check-expert-links.sh` will fail the PR if any inbound wikilink was missed. +That's the safety net. + +## Nothing to learn + +If after all three passes plus routing the candidate sets are empty: + +``` +len(adds) + len(edits) + len(deletes) == 0 +AND no candidate AGENTS.md pointer change +AND no candidate lint +``` + +…then `/learn` prints: + +``` +learn: nothing to learn from , skipping PR +``` + +…to stdout, exits 0, and does **not** push a branch or open a PR. This is a +valid, common outcome — most merges are routine and produce nothing for memory. +The harness's outer loop advances `refs/harness/last-learned` regardless of +whether a PR opened, so the watermark keeps moving forward. + +## Why no subagent consensus + +The old design ran 2–3 cheap reviewers and required a 2/3 vote. That gated +hallucinations, but at the cost of a fanout step and the parallel review +overhead. The inline justification rule does the same job more cheaply: the +single agent must cite the diff hunk or contradicting file for *every* +candidate. Anything without a citation is dropped. A `--dry-run` flag (for +human-driven evaluation) prints the candidate set with justifications and +opens no PR — useful when a project wants periodic spot-checks on how `/learn` +is reasoning. diff --git a/skills/harness/learn/references/routing-rules.md b/skills/harness/learn/references/routing-rules.md index ce6c03a..d09e779 100644 --- a/skills/harness/learn/references/routing-rules.md +++ b/skills/harness/learn/references/routing-rules.md @@ -10,9 +10,30 @@ to the last two rows. |---|---|---| | **A lint** (`scripts/lints/*` → `local-checks.sh`) | The rule is *mechanically checkable* — pass/fail needs no judgment (layer-dependency direction, "parse at boundaries", naming, no raw SQL interpolation, structured-logging-only, file-size caps) | Best: enforced on every PR forever, and the failure message doubles as a fix prompt. See `invariant-discovery.md`. | | **Eager prose** (`AGENTS.md`) | It clears **all five predicates** below | Paid in tokens on *every* session that touches the folder — strict bar. | -| **Lazy prose** (an Expert shard) | Useful when an agent is *deliberately reasoning* about this area, but not needed pre-emptively | Paid only when consulted — looser bar. The default home for real knowledge. | +| **Lazy prose** (an Expert reference file) | Useful when an agent is *deliberately reasoning* about this area, but not needed pre-emptively | Paid only when consulted — looser bar. The default home for real knowledge. Splits into five sub-destinations (see below). | | **Nowhere** | Inferable from the code, taste-only, or transient | — | +## Sub-routing for lazy prose: the five prefixes + +A fact destined for the Expert lands in exactly one prefixed file. See +`expert-structure.md` for the directory layout. + +| Sub-destination | Use when | +|---|---| +| `how-to-.md` | Procedural SOP — a sequence of steps. Imperative voice. "How do I X?" | +| `concept-.md` | Semantic — a noun, a model, the shape of the system. "What is X?" | +| `pattern-.md` | Soft DO/DON'T requiring judgment. Has counterexamples. Cannot be linted. | +| `invariant-.md` | A single hard rule, mechanically checkable in principle. One rule per file; filename = the rule. The highest-value ones get drafted as lints. | +| `example-.md` | Episodic / few-shot — a *past* concrete trace (PR, merge, debugging session) cited from a real sha. Never synthetic. | + +**Tiebreakers:** + +- A step list → `how-to`. A noun or definition → `concept`. A rule → `invariant` + if linable, else `pattern`. A past trace with a sha → `example`. +- If a fact is *both* procedural and load-bearing as a rule, write both: the + `how-to-*` mentions the rule and wikilinks `[[invariant-*]]`. +- One rule per `invariant-*.md` file — never bundle multiple invariants. + ## The five-predicate test for *eager* (AGENTS.md) placement A fact earns a line in AGENTS.md only if **all five** hold. If any fails, it goes @@ -47,3 +68,22 @@ prefer nothing over noise.** AGENTS.md **points into** the Expert; it never copies it. A pointer ("this folder does X; consult `expert/references/Y.md` before non-trivial work") stays correct as the Expert grows. A copied paragraph drifts. Reference, don't duplicate. + +## Deletes — routing the other direction + +Routing rules also decide when an existing reference file should be **removed**. +A `delete` candidate is justified when one of these holds: + +- **Deleted concept.** The code the file anchors to is gone from main — the + module, route, command, or identifier it cites no longer exists. (See pass 1 + in `reconcile.md`.) +- **Superseded by a fresher file.** Two reference files contradict, and one is + clearly stale (its anchor code is gone, or a more recent merge supersedes its + claim). The stale one is deleted. (See pass 2 in `reconcile.md`.) +- **Merged into another.** Two files covered overlapping topics and have been + combined; the originals are deleted in favor of the merged file. + +Every `delete` carries a one-line justification in the PR body. Inbound +wikilinks must be rewritten or removed in the same PR; +`scripts/check-expert-links.sh` fails the build if any inbound link still +points at a deleted target. diff --git a/skills/harness/learn/references/wikilink-convention.md b/skills/harness/learn/references/wikilink-convention.md new file mode 100644 index 0000000..1055dbc --- /dev/null +++ b/skills/harness/learn/references/wikilink-convention.md @@ -0,0 +1,68 @@ +# Wikilink convention + +**Hackable seam.** The link syntax `/learn` uses inside `expert/references/`, +and the lint that enforces it. AGENTS.md is **out of scope** — it uses regular +markdown links and has its own validator (`check-agents-md.sh`). + +## Why wikilinks + +Reference files cross-link each other (a `how-to-*` file points at a +`[[concept-*]]` it depends on, an `invariant-*` points at the `[[pattern-*]]` it +hardens, etc.). Two options: + +- Regular markdown links: `[text](concept-architecture.md)`. Verbose, ties text + to filename, and rots when files are renamed. +- Wikilinks: `[[concept-architecture]]`. Short, basename-only, near-universal in + Obsidian-style notes. **Agents already know how to traverse these** without + prompting. + +We pick wikilinks for the Expert's internal cross-references because the cost +of one extra link-resolution rule is bought back many times over in +readability and stable refactoring. + +## The spec + +A wikilink is the literal token `[[]]` where `` matches +`[a-z0-9-]+`. + +Resolution: `[[]]` refers to the file `.md` in the **same directory** +as the file that contains the link (i.e. `.claude/skills/expert/references/`). + +- No paths. No leading `./`. No subdirectories. +- No `.md` extension inside the brackets. +- No alias form (`[[slug|display text]]`) for now — keep the syntax minimal. +- The link target file must exist. + +## Valid + +```markdown +For setup, see [[how-to-run-the-project]]. +The repo layer must obey [[invariant-no-repo-imports-service]]. +This pattern complements [[concept-architecture]]. +``` + +## Invalid + +```markdown +See [[./how-to-run-the-project]]. ← no path +See [[how-to-run-the-project.md]]. ← no extension +See [[How-To-Run-The-Project]]. ← lowercase only +See [[how-to-run-the-project|setup guide]]. ← no alias form +See [[expert/how-to-run-the-project]]. ← no directory prefix +``` + +## Enforcement + +`scripts/check-expert-links.sh` (next to `check-agents-md.sh`) walks every +`expert/references/*.md`, extracts every `[[...]]` token, and asserts each +resolves to an existing `.md` file in the same directory. + +- **Broken link → exit 1.** The PR fails. Fix the link or restore the file. +- **Orphan files** (no inbound wikilink from any other reference) → warn only, + do not fail. Top-level concepts can legitimately be orphans. +- **Cycles** (A → B → A) → informational only. Small cycles between mutually + related concepts are fine. + +Wire the script into `scripts/local-checks.sh` next to `check-agents-md.sh` so +the same gate that flags a stale AGENTS.md pointer also flags a stale Expert +wikilink. diff --git a/skills/harness/learn/scripts/check-expert-links.sh b/skills/harness/learn/scripts/check-expert-links.sh new file mode 100755 index 0000000..afd6884 --- /dev/null +++ b/skills/harness/learn/scripts/check-expert-links.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +# check-expert-links.sh — mechanical link gate for the project's lazy memory. +# +# Reference files under .claude/skills/expert/references/ cross-link via Obsidian +# wikilinks ([[basename]] resolving to basename.md in the same directory). Renames, +# deletions, and typos break these silently — this lint makes those failures loud. +# Wire it into scripts/local-checks.sh (and CI) so a broken Expert link fails the +# build. +# +# Checks: +# 1. Every [[]] token in every references/*.md resolves to .md in +# the same directory. +# 2. Slugs match the convention [a-z0-9-]+ (lowercase, hyphenated). +# +# Warns (does NOT fail): +# - Orphan files (no inbound wikilink from any other reference) — top-level +# concepts can legitimately be orphans. +# +# Exit 0 = clean. Exit 1 = at least one broken or malformed link. +# +# The Expert root is tweakable via env; default is .claude/skills/expert. + +set -euo pipefail + +EXPERT_ROOT="${EXPERT_ROOT:-.claude/skills/expert}" +REF_DIR="${EXPERT_ROOT}/references" + +fail=0 +note() { echo "❌ $*" >&2; fail=1; } +warn() { echo "⚠️ $*" >&2; } + +if [[ ! -d "$REF_DIR" ]]; then + echo "no Expert at ${REF_DIR} (nothing to check)" + exit 0 +fi + +mapfile -t files < <(find "$REF_DIR" -maxdepth 1 -type f -name '*.md' 2>/dev/null | sort) + +if (( ${#files[@]} == 0 )); then + echo "no reference files in ${REF_DIR} (nothing to check)" + exit 0 +fi + +# Build the set of valid slugs (filenames without .md) for fast existence check. +declare -A valid_slug +for f in "${files[@]}"; do + base="$(basename "$f" .md)" + valid_slug["$base"]=1 +done + +# Track inbound link counts to surface orphan warnings. +declare -A inbound_count +for f in "${files[@]}"; do + base="$(basename "$f" .md)" + inbound_count["$base"]=0 +done + +slug_re='^[a-z0-9-]+$' + +for f in "${files[@]}"; do + # Extract every [[...]] token. + while IFS= read -r token; do + [[ -z "$token" ]] && continue + # Strip leading [[ and trailing ]] (escape the brackets so bash treats them + # as literals, not as bracket-expression delimiters in the pattern). + slug="${token##\[\[}" + slug="${slug%%\]\]}" + + # Convention check: lowercase, alphanumeric + hyphen, no path, no extension, + # no alias. + if ! [[ "$slug" =~ $slug_re ]]; then + note "$f: malformed wikilink '[[${slug}]]' — slugs must match [a-z0-9-]+ (no paths, no extensions, no aliases). See wikilink-convention.md." + continue + fi + + # Existence check: target file must exist in references/. + if [[ -z "${valid_slug[$slug]:-}" ]]; then + note "$f: wikilink '[[${slug}]]' does not resolve — no file ${REF_DIR}/${slug}.md. Fix the link or restore the file." + continue + fi + + # Don't count self-links as inbound. + src_base="$(basename "$f" .md)" + if [[ "$slug" != "$src_base" ]]; then + inbound_count["$slug"]=$((inbound_count["$slug"] + 1)) + fi + done < <(grep -oE '\[\[[^]]+\]\]' "$f" || true) +done + +# Orphan warnings (informational only — SKILL.md / top-level concepts are +# expected to be orphans). +for slug in "${!inbound_count[@]}"; do + if (( inbound_count[$slug] == 0 )); then + warn "${REF_DIR}/${slug}.md has no inbound wikilinks (orphan — informational only)." + fi +done + +if (( fail )); then + echo "check-expert-links: FAIL" >&2 + exit 1 +fi +echo "check-expert-links: OK (${#files[@]} file(s))" diff --git a/skills/human-loop/evaluate-sessions/SKILL.md b/skills/human-loop/evaluate-sessions/SKILL.md index 428b766..84fae49 100644 --- a/skills/human-loop/evaluate-sessions/SKILL.md +++ b/skills/human-loop/evaluate-sessions/SKILL.md @@ -13,9 +13,9 @@ harness ran to produce it — into two durable outcomes: behaved well or badly given the context it had, freeze that as a runnable check under `evals/`. This is the flywheel Chase/LangSmith describe — *observe a trace → capture it as an eval → fix the prompt → it persists as a regression test* — except the "prompt" - here is the project's **context** (Expert shards, AGENTS.md, a skill's own text). + here is the project's **context** (Expert reference files, AGENTS.md, a skill's own text). - **Context fixes.** When a session reveals that a piece of context **misled** an agent or - was **missing**, fix it — the Expert shard, the AGENTS.md pointer, or the skill — *with* + was **missing**, fix it — the Expert reference file, the AGENTS.md pointer, or the skill — *with* the human, on a branch. `/evaluate-pr` evaluates **what was built** (the change); `/evaluate-sessions` evaluates @@ -86,7 +86,7 @@ not auditing a single PR, you're tuning the project's context so the *whole harn an agent would think to consult the Expert; non-inferable from the code; harmful if violated (breaks behavior/data, not style); stable; and local-or-truly-global. Eager memory is paid in tokens on every session, so the bar is high. - - **Lazy prose (an Expert shard)** — the default home for real knowledge: useful when an + - **Lazy prose (an Expert reference file)** — the default home for real knowledge: useful when an agent *deliberately reasons* about the area, paid only when consulted. - **Nowhere** — inferable from the code, taste-only, or transient. diff --git a/skills/human-loop/evaluate-sessions/references/evals.md b/skills/human-loop/evaluate-sessions/references/evals.md index 535face..006111f 100644 --- a/skills/human-loop/evaluate-sessions/references/evals.md +++ b/skills/human-loop/evaluate-sessions/references/evals.md @@ -48,7 +48,7 @@ If the project has no `evals/` dir yet, create it when you author the first eval # `evals/` — regression tests over this project's harness skills/context Authored by `/evaluate-sessions` when a build trail reveals a skill behaving well or badly -because of this project's context (an Expert shard, an AGENTS.md pointer, a skill's text, a +because of this project's context (an Expert reference file, an AGENTS.md pointer, a skill's text, a spec). Evals test the **harness** ("given this context, does the skill behave?"); `prds// run-prd-test.sh` tests the **product** ("does the feature work?"). Keep them separate. diff --git a/skills/human-loop/evaluate-sessions/references/outcomes.md b/skills/human-loop/evaluate-sessions/references/outcomes.md index ba3e983..f80b82f 100644 --- a/skills/human-loop/evaluate-sessions/references/outcomes.md +++ b/skills/human-loop/evaluate-sessions/references/outcomes.md @@ -26,14 +26,14 @@ Two reasons, both load-bearing: ## Where in the project a context fix is written -You route the fix to a destination in SKILL.md's S9 (lint / AGENTS.md / Expert shard / +You route the fix to a destination in SKILL.md's S9 (lint / AGENTS.md / Expert reference file / nowhere). To find the exact file, read the **project's own** artifacts — not any skill's internals: -- **Expert shard** — open `.claude/skills/expert/references/` in the project and pick the shard - whose subject matches (architecture, verification, patterns, procedural, core-files, - invariants). If none fits and the fact is real, the project's memory updater can place it on - its post-merge pass; your job is to write a clear, correctly-scoped note in the closest shard. +- **Expert reference file** — open `.claude/skills/expert/references/` in the project and pick the + file whose subject matches, by prefix (`how-to-*`, `concept-*`, `pattern-*`, `invariant-*`, + `example-*`). If none fits and the fact is real, the project's memory updater can place it on + its post-merge pass; your job is to write a clear, correctly-scoped note in the closest file. - **AGENTS.md** — the root `AGENTS.md`, or the nested one in the folder the rule is local to. Only if it clears the five-predicate bar (S9); otherwise prefer the Expert. - **A skill defect** — if a *skill's own text* steered the agent wrong, the fix is that skill's diff --git a/skills/human-loop/evaluate-sessions/references/trace-reading.md b/skills/human-loop/evaluate-sessions/references/trace-reading.md index 8dcd4e2..27def1a 100644 --- a/skills/human-loop/evaluate-sessions/references/trace-reading.md +++ b/skills/human-loop/evaluate-sessions/references/trace-reading.md @@ -111,7 +111,7 @@ first: become evals and/or context fixes. - **Inherent difficulty** — the task was just hard; no context change would have helped. Name it and move on. Calling difficulty a "defect" over-fits memory with noise — the - thing `/learn`'s consensus gate and this skill's S4 both guard against. + thing `/learn`'s reconcile justification bar and this skill's S4 both guard against. ## CI / remote degradation diff --git a/skills/sdd/implement-mainspec/SKILL.md b/skills/sdd/implement-mainspec/SKILL.md index 4bbedfa..7324648 100644 --- a/skills/sdd/implement-mainspec/SKILL.md +++ b/skills/sdd/implement-mainspec/SKILL.md @@ -1,53 +1,42 @@ --- name: implement-mainspec -description: Implements a mainspec end-to-end by auto-detecting mode. Sequential mode (≤3 slices) commits slices in order on the current `feature/` branch. Parallel mode (>3 slices) uses dependency-aware tiered execution with per-slice worktrees, branches, PRs, and auto-merge into the feature branch. Agent-first — invoked headless by the harness dispatcher with the feature slug as its single argument. No human-in-the-loop, no approval gates. +description: Implements a mainspec end-to-end by delegating each slice to a `slice-implementer` subagent in dependency order, committing to `feature/` directly. Agent-first — invoked headless by the harness dispatcher with the feature slug. No human-in-the-loop, no approval gates. --- # Implement Mainspec -Implements all slices from a mainspec in dependency order. Auto-detects **sequential** (≤3 slices) or **parallel** (>3 slices) mode based on tier computation. Agent-first: invoked headless by the harness dispatcher, runs end-to-end with no approval gates, exits. +Implements all slices from a mainspec in dependency order, sequentially. Agent-first: invoked headless by the harness dispatcher, runs end-to-end with no approval gates, exits. ## Invocation Contract -**Invoked by the dispatcher as:** `claude -p "/implement-mainspec "`, run from inside the feature worktree (the dispatcher `cd`s into it — there is no print-mode `--cwd` flag). +**Invoked by the dispatcher as:** `claude -p "/implement-mainspec "`, run from inside the feature worktree (the dispatcher `cd`s into it). **Single argument:** `` — kebab-case feature slug. The mainspec path is `specs//mainspec.md` relative to cwd. **Inputs read from disk (paths relative to cwd):** - `specs//mainspec.md` - `specs//slices/*.md` -- Current `feature/` branch state (worktree HEAD). +- Current `feature/` branch state. **Outputs to disk / remote:** -- Per-slice branches `slice/-` pushed to origin (**not deleted after merge** — they remain as archaeological evidence so the eventual `feature/ → main` PR review can trace which commits belong to which slice). -- Per-slice PRs merged into `feature/` with `gh pr merge --merge` (preserves slice commits + a merge commit). -- All implementation code committed and pushed on `feature/`. +- All implementation code committed and pushed on `feature/`, one commit per slice. -**No sentinel.** The dispatcher's success signal for this step is `./prds//run-prd-test.sh` exits 0, verified externally. The PRD test is encoded into a slice's Signal by spec-planning, so completing all slices implies the PRD test passes. We do not write a sentinel file — the *dispatcher* records the green result in `specs//.prd-passed` once the runner first exits 0 (so a possibly-LLM-judge runner isn't re-run every tick); this skill neither writes nor reads that file. +**No sentinel.** The dispatcher's success signal for this step is `./prds//run-prd-test.sh` exits 0, verified externally. The final slice's Success Criteria encodes this into a slice so completing all slices implies the PRD test passes. We do not write a sentinel file — the *dispatcher* records the green result in `specs//.prd-passed` once the runner first exits 0; this skill neither writes nor reads that file. **Idempotency:** -- On every invocation, inspect `feature/` git history. Determine which slices are already merged (look for merge commits like `Merge slice/-` or branches whose commits match slice scope). -- Compute the remaining slices via the DAG (`compute_tiers.py`) minus the already-merged set. +- On every invocation, inspect `feature/` git history. Determine which slices are already committed (look for commits like `Implement slice : `). +- The orchestrator reads the mainspec's Slice Dependency Map inline to plan its execution order. - Work only on remaining slices. -- If all slices appear merged, exit cleanly. The dispatcher will re-verify the PRD test externally. +- If all slices appear committed, exit cleanly. The dispatcher will re-verify the PRD test externally. -**No approval gates anywhere.** No `AskUserQuestion` for plan approval, default-branch warning, tier review, retry decisions, or branch-state ambiguity. Every decision is deterministic or deferred to the next dispatcher tick. +**No approval gates anywhere.** No `AskUserQuestion` for plan approval, default-branch warning, or any other decision. Every decision is deterministic or deferred to the next dispatcher tick. ## Workflow Overview ``` -Phase 0: Detection — Run compute_tiers.py, check total_slices to select mode -Phase 1: Parse & Resume — Read mainspec, parse DAG, inspect feature/ for already-merged slices - ---- SEQUENTIAL MODE (≤3 slices) --- -Phase 2: Implement — Delegate each remaining slice to slice-implementer subagent (sequential, foreground). Orchestrator handles git directly on feature/. +Phase 1: Parse & Resume — Read mainspec Slice Dependency Map, determine execution order, inspect feature/ for already-committed slices +Phase 2: Implement — Delegate each remaining slice to slice-implementer subagent (foreground). Orchestrator handles git directly on feature/. Phase 3: Log — Log final status of this invocation to stdout for observability - ---- PARALLEL MODE (>3 slices) --- -Phase 2: Tier 0 — Delegate foundation slices to slice-implementer subagents (sequential, foreground). Orchestrator handles git directly on feature/. -Phase 3: Parallel Tiers — For each subsequent tier: create worktrees, spawn slice-implementer subagents (background), orchestrator handles git after completion. -Phase 4: Auto-Merge — Create PRs and auto-merge into feature/ with `gh pr merge --merge`. Branches are NOT deleted on merge. -Phase 5: Log — Log final status of this invocation to stdout for observability ``` ## Pre-conditions @@ -57,106 +46,54 @@ The dispatcher guarantees a clean worktree on `feature/` before invokin 1. **Git repository** — `git rev-parse --git-dir` succeeds. If not, exit non-zero. 2. **Current branch matches `feature/`** — must match the argument exactly. If not, exit. (Never create the feature branch; the dispatcher's atomic rename from `prd//` did that.) 3. **Clean working tree** — `git status --porcelain` empty. If not, exit (the dispatcher's wipe should guarantee this; if it trips, something upstream is wrong). -4. **Remote configured** — `git remote get-url origin` succeeds. If not, exit. Parallel mode requires remote. -5. **Worktree gitignore** — verify `.claude/worktrees/` is in `.gitignore`. Auto-append if missing (idempotent; no prompt). - -## Phase 0: Mode Detection - -Run the tier computation script and select the execution mode deterministically: - -``` -1. Run: python3 .claude/skills/implement-mainspec/scripts/compute_tiers.py specs//mainspec.md -2. Parse the JSON output which includes: - - mainspec_name, feature_branch - - tiers: array of { tier: N, slices: [{ number, name, file }] } - - file: absolute path to the slice file (can be used directly by subagents) - - total_slices, max_parallel -3. Mode detection (no override, no prompt): - - total_slices ≤ 3 → SEQUENTIAL MODE - - total_slices > 3 → PARALLEL MODE -4. Log to stdout: "Detected slices → mode" for observability. -``` +4. **Remote configured** — `git remote get-url origin` succeeds. If not, exit. ## Phase 1: Parse & Resume -### DAG Parsing - -`compute_tiers.py` parses the mainspec's "Slice Dependency Map" table: +### Slice Ordering -| Field | Source | Example | -|-------|--------|---------| -| Slice number | First column, before ` — ` | `3.1` | -| Slice name | First column, after ` — ` | `SVG Path Animation Utilities` | -| Dependencies | "Depends On" column | `3.1` or `—` (none) | -| Blocks | "Blocks" column | `3.2, 3.3, 3.4, 3.5` or `—` (none) | -| Slice file path | From compute_tiers.py output | absolute path to slice file | +Read the mainspec's "Slice Dependency Map" table directly. For each slice, note its number, name, and `Depends On` list. Use that understanding to plan the execution order — slices whose dependencies are already committed (or have no dependencies) can run next; slices with outstanding dependencies wait. -The script handles topological sort and tier assignment via the algorithm: +Reference the slice file path convention: `specs//slices/-.md`. -``` -1. Start with all slices. remaining = all slices. -2. Tier 0 = slices with no dependencies (Depends On = "—" or "Nothing") -3. Remove Tier 0 from remaining -4. Tier N = slices in remaining whose ALL dependencies are in Tiers 0..N-1 -5. Remove Tier N from remaining -6. Repeat until remaining is empty -7. If remaining is not empty and no progress was made → circular dependency error -``` +### Resume Detection (Idempotency) -### Resume detection (idempotency) - -Before implementing anything, inspect what's already merged on `feature/`: +Before implementing anything, inspect what's already committed on `feature/`: ``` 1. git fetch origin -2. For each slice in the mainspec DAG: - - Check whether a merge commit referencing `slice/-` exists in - `git log feature/` (e.g., `git log --merges --grep "slice/-"`). - - If yes, mark slice as ALREADY-MERGED. Skip it. +2. For each slice in the mainspec: + - Check whether a commit referencing "Implement slice :" exists in + git log feature/ (e.g., git log --oneline --grep "Implement slice :"). + - If yes, mark slice as ALREADY-COMMITTED. Skip it. - Otherwise, mark slice as REMAINING. -3. If all slices are ALREADY-MERGED, log "all slices merged, nothing to do" and exit cleanly. -4. Otherwise, proceed with REMAINING slices in their original tier assignments. +3. If all slices are ALREADY-COMMITTED, log "all slices committed, nothing to do" and exit cleanly. +4. Otherwise, proceed with REMAINING slices in their dependency order. ``` -### Plan logging (no approval) - -Log the computed plan to stdout for observability — what mode, which tiers, which slices are remaining vs. already-merged. **No approval gate, no `AskUserQuestion`.** Proceed directly to Phase 2. +### Plan Logging (No Approval) -**Sequential mode log format:** +Log the computed plan to stdout for observability — which slices are remaining vs. already-committed. **No approval gate, no `AskUserQuestion`.** Proceed directly to Phase 2. ``` ## Execution Plan: -Mode: Sequential -Total slices: N (already-merged: M, remaining: K) +Total slices: N (already-committed: M, remaining: K) Remaining order: 1. Slice X.Y: 2. Slice X.Y: ``` -**Parallel mode log format:** - -``` -## Execution Plan: -Mode: Parallel -Feature branch: feature/ -Total slices: N (already-merged: M, remaining: K) -Total tiers: T (remaining tiers to process: T') -Tier 0 (Foundation): X.Y , X.Y -Tier 1 (Parallel — N subagents): X.Y , X.Y -Tier 2 (Parallel — N subagents): X.Y -``` +## Phase 2: Implementation -## Phase 2: Sequential Implementation +The preconditions already guaranteed we are on `feature/`, so we commit directly to it. No default-branch warning, no approval prompt. -**Applies only to SEQUENTIAL MODE (≤3 slices).** The preconditions section already guaranteed we are on `feature/`, so we commit directly to it. No default-branch warning, no approval prompt. +For each **REMAINING** slice in dependency order (skipping already-committed ones from Phase 1): -For each **REMAINING** slice in dependency order (skipping already-merged ones from Phase 1): - -1. Create TODO list — One item per slice with sub-items for: Implement, Signal Validation, Unit Tests. +1. Create TODO list — One item per slice. 2. Mark slice in_progress. 3. Delegate to a `slice-implementer` subagent (foreground, NOT background): - Use `subagent_type: "slice-implementer"` and `mode: "bypassPermissions"`. - - Use the prompt template from `references/subagent-prompt-template.md` (Sequential Mode section). The template passes `max_signal_iterations: 3` to bound the inner signal-fix loop. + - Use the prompt template from `references/subagent-prompt-template.md`. - Set working directory to the repo root. - Wait for subagent to complete. 4. After subagent completes successfully: @@ -166,345 +103,50 @@ For each **REMAINING** slice in dependency order (skipping already-merged ones f - Orchestrator runs `git push origin feature/`. 5. If subagent reports FAILURE: - Log the failure (slice number, error text) to stdout. - - Commit any safe partial work attributable to a completed earlier slice (we should never have partial state at this point since each slice's commits happen synchronously after subagent success — but if `git status` shows changes, they should not be committed). + - Do not commit any partial state. - Exit. The dispatcher will re-fire on the next tick; idempotency in Phase 1 will skip the already-completed slices and retry this one. 6. Mark slice complete. After all remaining slices: proceed to Phase 3 (Log). -## Phases 2–5: Parallel Implementation - -**Applies only to PARALLEL MODE (>3 slices).** See below for detailed instructions: - -- **Phase 2 (Tier 0)** — See "Tier 0: Foundation Slices" section. -- **Phase 3 (Parallel Tiers)** — See "Parallel Tier Execution" section. -- **Phase 4 (Auto-Merge)** — See "Tier Auto-Merge" section. -- **Phase 5 (Log)** — Stdout log only. - -## Feature Branch Verification & Git Strategy - -**Applies only to PARALLEL MODE.** The dispatcher's atomic rename already created `feature/` before this skill ran. We verify and proceed; we never create the feature branch. - -``` -1. Verify HEAD branch == feature/: - actual="$(git rev-parse --abbrev-ref HEAD)" - [[ "${actual}" == "feature/" ]] || exit 1 -2. Verify the branch tracks origin: - git rev-parse --abbrev-ref --symbolic-full-name @{u} >/dev/null 2>&1 || git push -u origin feature/ -``` - -**Naming conventions:** -- Feature branch: `feature/` (e.g., `feature/svg-and-charts`). -- Slice branch: `slice/-` (e.g., `slice/3.3-barchart`). -- Worktree path: `.claude/worktrees//slice--`. - -**Spec file access:** Spec files are referenced by absolute path (resolved from `compute_tiers.py` output). Subagents read specs from the original location, not from their worktree. - -**Git state recovery (deterministic, no prompts):** -- Feature branch exists locally and matches origin: proceed. -- Feature branch exists but is behind origin: `git pull --ff-only origin feature/` before any tier work. -- Slice branch already exists on origin: this means a prior `/implement-mainspec` tick was interrupted. Check whether the corresponding PR is merged via `gh pr view slice/- --json state -q .state`: - - State `MERGED` → slice is done; skip it. - - State `OPEN` → PR was created but not merged; auto-merge it as part of Phase 4. - - No PR found → branch has commits but no PR; create the PR in Phase 4 and proceed. - - Branch exists with no commits past `feature/` HEAD → unusable, but harmless; recreate the worktree on top. - -## Tier 0: Foundation Slices - -**Applies only to PARALLEL MODE.** Execute on `feature/` directly. - -Tier 0 slices are delegated to `slice-implementer` subagents sequentially (one at a time, foreground). The orchestrator handles all git operations. - -For each REMAINING Tier 0 slice (in dependency order): - -1. Spawn a `slice-implementer` subagent (foreground, NOT background): - - Use `subagent_type: "slice-implementer"` and `mode: "bypassPermissions"`. - - Use the prompt template from `references/subagent-prompt-template.md` (Tier 0 section). - - Set working directory to the repo root (subagent works on feature branch directly). - - Wait for subagent to complete. -2. After subagent completes successfully: - - Orchestrator runs `git status` to detect changed files. - - Orchestrator runs `git add ` (only files the subagent created/modified). - - Orchestrator runs `git commit -m "Implement slice : "`. - - Orchestrator runs `git push origin feature/`. -3. If subagent reports FAILURE: - - Log the failure. - - Do not commit any subagent partial state. - - Exit. Dispatcher will re-fire; idempotency will skip merged work and retry this slice. -4. Proceed to next Tier 0 slice (if multiple foundation slices). - -Tier 0 slices are committed directly to `feature/` — no PRs. This is the foundation that all subsequent tiers depend on. - -## Worktree Management - -**Applies only to PARALLEL MODE.** Create worktrees before spawning subagents for each tier. - -**Critical:** Do NOT use `isolation: "worktree"` from the Agent tool — it branches from the remote default branch and won't have Tier 0 code. - -**Preflight — bootstrap availability.** Before creating any worktrees, check for `scripts/bootstrap-worktree.sh`. If it is **not** an executable file, emit a loud warning to stdout — e.g. `WARN: no executable scripts/bootstrap-worktree.sh found; slice worktrees will lack deps/.env/codegen, so signals and unit tests may fail. Continuing anyway.` — then proceed. Do **not** halt: a missing bootstrap script is a warning, not a precondition. When the script IS present, each worktree is bootstrapped right after creation (see below). - -### Creating Worktrees - -For each REMAINING slice in the current tier (skip slices whose PR is already merged): - -```bash -# Derive names from compute_tiers.py output -worktree_name="slice--" -branch_name="slice/-" -worktree_path=".claude/worktrees//${worktree_name}" - -# Create worktree branching FROM the feature branch. -# If branch already exists locally or on origin, attach without -b. -if git show-ref --verify --quiet "refs/heads/${branch_name}" \ - || git show-ref --verify --quiet "refs/remotes/origin/${branch_name}"; then - git worktree add "${worktree_path}" "${branch_name}" -else - git worktree add "${worktree_path}" -b "${branch_name}" feature/ -fi - -# Record the absolute worktree path for the subagent prompt -abs_worktree_path="$(realpath ${worktree_path})" - -# Make the fresh worktree runnable: a bare worktree has no gitignored files -# (node_modules, .env, generated code), so slice signals and unit tests would fail -# for reasons unrelated to the feature. Mirrors the dispatcher's bootstrap hook. -# Idempotent + non-interactive; a failure here is logged but does not stop the slice. -if [[ -x scripts/bootstrap-worktree.sh ]]; then - ./scripts/bootstrap-worktree.sh "${abs_worktree_path}" || \ - echo "WARN: bootstrap-worktree.sh failed for ${abs_worktree_path} — signals/tests may fail" >&2 -fi -``` - -### Cleaning Up Worktrees - -After PRs for a tier have been merged (or on a controlled exit): - -``` -1. For each worktree: git worktree remove -2. If removal fails (uncommitted changes): log the failure and exit. - The dispatcher's next tick will wipe state and re-derive what to do. -3. After all tiers complete: git worktree prune -``` - -## Parallel Tier Execution - -**Applies only to PARALLEL MODE.** Execute for each REMAINING tier after Tier 0. - -### 3a. Create Worktrees - -Create worktrees for all REMAINING slices in this tier per the Worktree Management section above. - -### 3b. Spawn Subagents - -Read `references/subagent-prompt-template.md` for the full prompt template. - -Spawn one background subagent per slice: -- Use the Agent tool with `run_in_background: true`. -- Use `subagent_type: "slice-implementer"` and `mode: "bypassPermissions"`. -- Do NOT use `isolation: "worktree"` (worktree already created in 3a). -- Spawn all subagents for this tier in a SINGLE message (maximizes parallelism). -- Capture the agent_id / task_id for each subagent. -- The prompt template includes `max_signal_iterations: 3` to bound the inner signal-fix loop. - -**Batching:** If a tier has more than 7 slices, split into batches of up to 7. Spawn batch 1, wait for all to complete, then spawn batch 2. +## Phase 3: Stdout Log (Observability Only) -### 3c. Wait for Completion - -- Subagents will notify when done (automatic delivery). -- Alternatively, use `TaskOutput(task_id=, block=true)` per subagent. -- Collect results: success/failure per slice. - -### 3d. Git Operations (Orchestrator) - -After all subagents for the tier complete, the orchestrator handles git for each worktree of a successful slice: - -``` -For each successful slice in the tier: -1. cd -2. git status (detect changed files) -3. git add (only files the subagent created/modified, NOT git add -A) -4. git commit -m "Implement slice : " -5. git push -u origin -``` - -### 3e. Handle Failures - -- If a subagent fails, log the slice number and error details to stdout. -- Other subagents in the same tier are NOT affected — they finish their work. -- The failed slice will NOT get a PR in Phase 4. -- Read `references/error-handling.md` for detailed recovery procedures. - -## Tier Auto-Merge - -**Applies only to PARALLEL MODE.** Execute after each tier's subagents complete and orchestrator git operations finish. There is no review gate, no `AskUserQuestion`, no "Review & merge / Stop here" prompt. Slice PRs are created and merged automatically. - -### 4a. Create PRs - -For each successfully completed slice in the current tier whose PR does not already exist: - -```bash -gh pr create \ - --head slice/- \ - --base feature/ \ - --title "Slice : " \ - --body "Implements slice of . Part of Tier . - -Signal: " -``` - -Create all PRs for the tier in parallel. - -### 4b. Auto-Merge PRs - -For each PR (in slice-number order): - -```bash -gh pr merge --merge -``` - -**Key constraints:** -- Use `--merge` (NOT `--squash`) — preserves the slice's individual commits plus a merge commit. The merge commit clearly delineates which commits belong to which slice when reviewing the eventual `feature/ → main` PR. -- **Do NOT pass `--delete-branch`** — slice branches stay on origin as archaeological evidence. Reviewers of the feature→main PR can navigate to `slice/-` to see the focused, in-context diff for any slice. - -After each successful merge: -- `git pull origin feature/` in the orchestrator's checkout to bring the merged commits in. - -### 4c. Handle Merge Conflicts - -If `gh pr merge` fails for conflict reasons: -- Attempt automatic resolution for simple conflicts (barrel exports, registry additions, import lists). Same heuristic patterns as the historical code. -- If unresolvable: log which PR failed and which files conflicted, then exit. The dispatcher will re-fire `/implement-mainspec` on the next tick; that invocation's idempotency check will see the failed PR is still open and either retry the merge (if origin now has the prerequisite commits) or skip if the work has moved on. - -### 4d. Worktree Cleanup - -After all PRs for the tier are merged: -- `git worktree remove ` for each slice worktree in this tier. -- Continue to the next tier (subsequent-tier worktrees branch from the updated `feature/`). - -### 4e. Failed Slices Within a Tier - -If any slice in the tier failed in Phase 3 (slice-implementer reported FAILURE): -- Do not create a PR for it. -- Log the failure to stdout with slice number and error details. -- The other tier slices proceed normally through PR creation and auto-merge. -- After the tier completes auto-merge, exit. The dispatcher will re-fire on the next tick; idempotency in Phase 1 will identify the failed slice as REMAINING and retry it. Subsequent tiers must wait until this tier is fully merged, so the exit is the correct behavior. - -## Phase 3/5: Stdout Log (Observability Only) - -There is no human to report to. Write a concise stdout log of what this invocation did so the dispatcher's log is readable. The dispatcher does not parse this output; it only checks `./prds//run-prd-test.sh` exit code externally. - -**Sequential mode log:** +There is no human to report to. Write a concise stdout log of what this invocation did so the dispatcher's log is readable. ``` ## /implement-mainspec invocation summary: -Mode: Sequential -Skipped (already-merged): (or none) +Skipped (already-committed): (or none) Implemented this invocation: Failed this invocation: (or none) Branch: feature/ (pushed) ``` -**Parallel mode log:** - -``` -## /implement-mainspec invocation summary: -Mode: Parallel -Feature branch: feature/ -Skipped (already-merged): (or none) -Tier 0 implemented: -Tier 1 implemented (auto-merged): -Tier 2 implemented (auto-merged): -... -Failed this invocation: (or none) - -If failures: dispatcher will re-fire on next tick; idempotency will skip merged work. -If no failures: dispatcher will verify `./prds//run-prd-test.sh` exits 0 on next tick. -``` - -## Signal Processing - -Signal skills provide runtime feedback during implementation. They validate that code works correctly in real environments. - -### Reading the Signal Section - -Each slice includes a Signal section after the Objective: - -``` -## Signal - -**Signal Skill:** {signal-skill-name | None} - -**Expected Behavior:** -- what should succeed when correctly implemented -``` - -### Signal Workflow - -1. After implementing slice code, check the Signal section -2. If Signal Skill is specified: - - Invoke the signal: `skill: "{signal-name}"` - - Wait for signal output - - Follow the guidance from Signal -3. If signal indicates success: Continue to unit tests -4. If signal indicates failure: - - Review signal output to identify specific issue - - Fix the implementation - - Re-invoke signal - - Repeat until signal validates success -5. If Signal Skill is "None": Skip signal validation, proceed to unit tests - ## TODO Structure -**Sequential mode:** -``` -[ ] X.Y- - Implement -[ ] X.Y- - Signal Validation -[ ] X.Y- - Unit Tests -... -``` - -**Parallel mode:** ``` -[ ] Phase 0 - Mode Detection -[ ] Phase 1 - Parse DAG & resume detection -[ ] Phase 2 - Tier 0 foundation slices (sequential) -[ ] Tier 0: X.Y- - Delegate to slice-implementer -[ ] Tier 0: X.Y- - Git commit & push -[ ] Tier 1: Create worktrees -[ ] Tier 1: Spawn slice-implementer subagents (N slices) -[ ] Tier 1: Wait for completion -[ ] Tier 1: Git commit & push for each worktree -[ ] Tier 1: Create PRs -[ ] Tier 1: Auto-merge PRs (`gh pr merge --merge`) -[ ] Tier 1: Worktree cleanup +[ ] X.Y- +[ ] X.Y- ... -[ ] Phase 5 - Stdout log ``` ## References -Detailed reference material for parallel mode execution: - -- **Subagent prompt template**: Read `references/subagent-prompt-template.md` when spawning subagents in Phase 4 +- **Subagent prompt template**: Read `references/subagent-prompt-template.md` when spawning subagents in Phase 2 - **Error handling**: Read `references/error-handling.md` when any phase encounters errors -- **Release strategy**: Read `references/release-strategy.md` when presenting the summary report or when the user asks about releasing ## Guidelines -### Both Modes - **DO:** - Log the execution plan to stdout for observability (no approval gate; proceed directly). - Use absolute paths for all spec file references. - Delegate slice implementation to `slice-implementer` subagents — orchestrator stays lean. - Use `subagent_type: "slice-implementer"` and `mode: "bypassPermissions"` for all subagent spawns. -- Pass `max_signal_iterations: 3` to each subagent (via the prompt template) so the inner signal-fix loop is bounded. -- Handle all git operations (add, commit, push) in the orchestrator — subagents do NOT run git. +- Handle all git operations (add, commit, push) in the orchestrator — subagents do NOT run git. This includes any Expert-file changes a subagent's Reflect step wrote. - Use `git status` to detect changed files after subagent completion. - Read only the current slice file — never read all slices at once. - Treat the current branch as the feature branch (must match `feature/`); never create it. -- Inspect `feature/` git history at the start of every invocation to determine which slices are already merged; idempotency depends on this. -- On any failure (subagent FAILURE, merge conflict, etc.): log clearly, commit any safe partial state, exit. The dispatcher's next tick is the retry mechanism. +- Inspect `feature/` git history at the start of every invocation to determine which slices are already committed; idempotency depends on this. +- On any failure: log clearly, exit. The dispatcher's next tick is the retry mechanism. **DON'T:** - Use `AskUserQuestion` anywhere. The dispatcher provides no input, and the human is not in the loop. @@ -513,28 +155,5 @@ Detailed reference material for parallel mode execution: - Skip slices or implement out of order (within remaining work). - Proceed with failing tests. - Over-engineer beyond slice scope. -- Skip signal validation when a signal is specified. -- Ignore signal failure indicators. +- Create worktrees, per-slice branches, or PRs for individual slices. - Create a sentinel file for this skill. The dispatcher's success check is `./prds//run-prd-test.sh` exit code, not a sentinel. - -### Sequential Mode Only - -**DON'T:** -- Create PRs for individual slices. -- Create worktrees. - -### Parallel Mode Only - -**DO:** -- Bootstrap every slice worktree via `scripts/bootstrap-worktree.sh` right after `git worktree add`; if the script is absent, warn and continue (do not fail the run). -- Spawn all subagents for a tier in a single message. -- Auto-merge slice PRs with `gh pr merge --merge` (preserving slice commits + a merge commit). Never `--squash`. Never `--delete-branch` — slice branches stay on origin as evidence. -- Clean up worktrees after each tier's PRs are merged. -- Check for existing slice branches/PRs at the start of every invocation; reuse merged ones (skip), reuse open ones (continue auto-merge), recreate branchless ones from `feature/`. - -**DON'T:** -- Use `isolation: "worktree"` — it branches from default branch, missing Tier 0 code. -- Create PRs targeting `main` — always target `feature/`. -- Spawn more than 7 subagents simultaneously (batch if needed). -- Pause for human tier review — there is no human; auto-merge each tier and continue. -- Eagerly merge `main` into `feature/` — only when needed for conflicts during the eventual feature→main PR. \ No newline at end of file diff --git a/skills/sdd/implement-mainspec/references/error-handling.md b/skills/sdd/implement-mainspec/references/error-handling.md index 1b1bfa4..0fe461f 100644 --- a/skills/sdd/implement-mainspec/references/error-handling.md +++ b/skills/sdd/implement-mainspec/references/error-handling.md @@ -2,69 +2,24 @@ Reference for handling errors during mainspec implementation. Read this when any phase encounters errors. -Agent-first model: there is no human in the loop and no orchestrator session waiting on stdout. The retry mechanism is the harness dispatcher: each tick wipes uncommitted state in the worktree and re-invokes `/implement-mainspec`. Idempotency (Phase 1 resume detection) is what makes retries safe — the skill identifies which slices are already merged and works only on remaining ones. +Agent-first model: there is no human in the loop and no orchestrator session waiting on stdout. The retry mechanism is the harness dispatcher: each tick wipes uncommitted state and re-invokes `/implement-mainspec`. Idempotency (Phase 1 resume detection) is what makes retries safe — the skill identifies which slices are already committed and works only on remaining ones. ## 1. Subagent Fails Implementation - Log error details from subagent output to stdout for observability. -- Mark the slice as failed for this invocation; do not create a PR for it. -- Other slices in the same tier are NOT affected — let them finish and auto-merge. -- After the tier finishes auto-merge, exit. The dispatcher's next tick will re-fire `/implement-mainspec`; Phase 1's idempotency check will skip merged slices and retry this one. -- There is no skill-level retry cap. The dispatcher has no attempt counter for this skill (only for `local-checks`). If the same slice keeps failing across many ticks, that is an observability concern, not a skill-design concern. +- Mark the slice as failed for this invocation; do not commit any partial state. +- Exit. The dispatcher's next tick will re-fire `/implement-mainspec`; Phase 1's idempotency check will skip committed slices and retry this one. -## 2. Signal Validation Fails Inside Subagent -- The subagent's `implement-slice` workflow handles this via the bounded inner loop (`max_signal_iterations: 3` — fix code, re-invoke signal, repeat up to 3 times). -- If the cap is hit, the subagent returns FAILURE with `reason: signal_failure` and the last signal output. -- Treat this like §1 — mark slice failed, let tier finish, exit, dispatcher re-fires. - -## 3. PR Creation Fails - -- Usually means the slice branch wasn't pushed or origin is unavailable. -- Log error, continue creating PRs for other slices in the tier. -- The next dispatcher tick will re-enter Phase 4; idempotency will detect the missing PR and retry creation. - -## 4. Tier Auto-Merge — Out-of-Order or Missing Dependency - -- With auto-merge, the orchestrator processes PRs in slice-number order within each tier, and only moves to the next tier after the current tier is fully merged. Out-of-order should not happen. -- If a tier's PR somehow lacks a merged dependency (shouldn't, given Phase 1's DAG-ordered execution): exit. Dispatcher re-fires; Phase 1 will recompute and retry. - -## 5. Merge Conflicts During PR Auto-Merge - -- Attempt automatic resolution for simple, well-known conflict patterns: barrel exports (`index.ts` re-export lists), registry additions (append-only arrays/objects), import lists at file tops. -- If a conflict is outside these patterns or auto-resolution fails: - - Log affected files and the PR number to stdout. - - Commit any safe partial work (typically nothing — the auto-merge step doesn't leave the worktree dirty). - - Exit. The dispatcher's next tick re-fires; the worktree wipe clears any aborted merge state. The next invocation will retry the merge with origin's updated state. - -## 6. Worktree Issues - -- **Path already exists:** the worktree was created by a prior interrupted tick. The dispatcher's tick-start wipe operates on the **outer** worktree; nested per-slice worktrees survive. Check whether the existing slice worktree's branch matches what we want: - - If yes: attach to it via `git worktree add ` (without `-b`). - - If no: `git worktree remove --force ` then recreate. This is safe because per-slice worktrees only ever hold uncommitted slice-implementer output, which is recoverable. -- **Removal fails (uncommitted changes):** log and exit. The next tick will retry. -- **Stale references:** run `git worktree prune` at the end of a successful tier to clean up. - -## 7. Branch Already Exists - -- **Feature branch:** must exist (dispatcher's atomic rename created it). The precondition check verifies HEAD matches `feature/`; exit if not. -- **Slice branch already on origin:** auto-detect via `gh pr view --json state -q .state`: - - `MERGED` → slice is done; skip in this invocation. - - `OPEN` → PR was created but not yet merged; pick up at the Phase 4 auto-merge step. - - No PR found → branch has commits but no PR was created. Continue: Phase 4 creates the PR, then merges it. - - Branch with no commits past `feature/` HEAD → recreate the worktree on top. -- Never delete a slice branch automatically. Branches are evidence; preservation is intentional. - -## 8. Push Fails +## 2. Push Fails - Preconditions require a remote, so this means transient network / auth failure or remote rejection. - Log and exit. The dispatcher's next tick will retry. There is no fallback to "local-only mode" — the harness model assumes remote is available. -## 9. Resuming After Interruption +## 3. Resuming After Interruption - Every dispatcher tick wipes uncommitted state in the worktree (`git reset --hard HEAD && git clean -fd`) and re-invokes `/implement-mainspec`. - The skill re-derives all state from disk on every invocation: - - What's merged on `feature/` (Phase 1 idempotency check). - - Which slice branches exist on origin and the state of their PRs (Phase 4 prep). - - Which worktrees exist locally (Worktree Management section in SKILL.md). -- No checkpoint files or in-memory state to persist; the artifacts on disk ARE the state. + - What's committed on `feature/` (Phase 1 idempotency check via `git log --oneline --grep "Implement slice"`). +- No checkpoint files or in-memory state to persist; the git history IS the state. +- **Feature branch:** must exist (dispatcher's atomic rename created it). The precondition check verifies HEAD matches `feature/`; exit if not. diff --git a/skills/sdd/implement-mainspec/references/release-strategy.md b/skills/sdd/implement-mainspec/references/release-strategy.md deleted file mode 100644 index e568a32..0000000 --- a/skills/sdd/implement-mainspec/references/release-strategy.md +++ /dev/null @@ -1,49 +0,0 @@ -# Release Strategy (Parallel Mode) - -The feature branch IS the release candidate. PR `feature/` to `main` directly. No separate release branches. - -## When to Release - -The developer can release at any point during implementation: -- After Tier 0 (foundation only) -- After any tier's PRs are merged -- After all tiers are complete - -The natural release unit is "everything merged to feat so far." - -For partial release: leave unwanted slice PRs unmerged on feat, then PR feat to main with only the desired slices. - -## How to Release - -```bash -gh pr create --base main --head feature/ \ - --title "Release: " \ - --body "Includes slices: " -``` - -## Lazy Merge Policy - -After releasing (merging feat to main), do NOT immediately merge main back into feat. Only merge main into feat when: - -1. Next release PR has merge conflicts with main -2. The feat branch needs changes that landed on main after the release - -Git/GitHub handles the diff correctly — it knows what was already merged. - -### If Merging main into feat for Conflict Resolution - -```bash -git checkout feature/ -git merge main -# Resolve any conflicts -# Run tests to verify nothing broke -git push origin feature/ -# Then create the release PR -``` - -## Partial Release Constraints - -The dependency DAG determines valid release subsets: -- Can't ship a slice without its dependencies (e.g., can't ship 3.6 without 3.2-3.5) -- Only merge slice PRs whose dependencies are also merged -- The summary report (Phase 6) lists valid release subsets diff --git a/skills/sdd/implement-mainspec/references/subagent-prompt-template.md b/skills/sdd/implement-mainspec/references/subagent-prompt-template.md index 3228f60..de804cc 100644 --- a/skills/sdd/implement-mainspec/references/subagent-prompt-template.md +++ b/skills/sdd/implement-mainspec/references/subagent-prompt-template.md @@ -1,6 +1,6 @@ # Subagent Prompt Template -Use this template when spawning subagents for any tier (including Tier 0). Fill in the placeholder values for each slice. +Use this template when spawning the `slice-implementer` subagent. Fill in the placeholder values for each slice. ## Template @@ -15,23 +15,19 @@ Read the slice file at: (This is an absolute path — it may be outside your working directory) -Configuration: - max_signal_iterations: 3 - (Cap on the inner signal-fix-retry loop in implement-slice. If signal validation - has not passed after 3 fix attempts, return FAILURE with reason: signal_failure - and the last signal output. Do not loop indefinitely.) - -Follow the implement-slice workflow (preloaded in your context) to implement the slice. +Follow the implement-slice workflow (preloaded in your context) to implement the slice: +Implement → Unit Tests → Reflect Report your result: - - SUCCESS: all code implemented, tests pass. List key files created/modified. - - FAILURE: describe what went wrong and where you stopped. If signal-related, - include reason: signal_failure and the last signal output. + - SUCCESS: all code implemented, tests pass, Reflect outcome (no-op | add | edit | delete). + List key files created/modified (including any Expert reference files touched during Reflect). + - FAILURE: describe what went wrong and where you stopped. Do NOT use AskUserQuestion — no human is in the loop. Do NOT run any git commands (add, commit, push) — the orchestrator handles git. -Do NOT create a PR — the orchestrator handles PR creation. -Do NOT modify files outside your assigned slice scope. +Do NOT create a PR — the orchestrator handles all git operations. +Do NOT modify files outside your assigned slice scope (exception: `.claude/skills/expert/references/` + edits during the Reflect step are explicitly in-scope). Do NOT use git worktree commands — you are already in the correct directory. ``` @@ -39,16 +35,14 @@ Do NOT use git worktree commands — you are already in the correct directory. | Placeholder | Source | Example | |------------|--------|---------| -| `` | compute_tiers.py output → slice number | `3.3` | -| `` | compute_tiers.py output → slice name | `BarChart Component` | -| `` | compute_tiers.py output → mainspec_name | `svg-and-charts` | -| `` | For Tier 0: repo root. For Tier 1+: `realpath .claude/worktrees//` | `/home/user/repo/.claude/worktrees/svg-and-charts/slice-3.3-barchart` | -| `` | compute_tiers.py output → slice file | `/home/user/specs/svg-and-charts/slices/3.3-barchart-component.md` | +| `` | Slice Dependency Map → slice number | `3.3` | +| `` | Slice Dependency Map → slice name | `BarChart Component` | +| `` | mainspec.md → feature slug | `svg-and-charts` | +| `` | Repo root | `/home/user/repo` | +| `` | `specs//slices/-.md` (resolved to absolute path) | `/home/user/repo/specs/svg-and-charts/slices/3.3-barchart-component.md` | ## Usage -### Tier 0 (Sequential — foreground) - Spawn one subagent at a time. Wait for completion before spawning the next. ```python @@ -61,48 +55,5 @@ Agent( ) # Wait for completion # Orchestrator runs: git add, git commit, git push -# Then spawn next Tier 0 slice -``` - -### Tier 1+ (Parallel — background) - -Spawn all subagents for a tier in a **single message** for maximum parallelism: - -```python -Agent( - subagent_type="slice-implementer", - run_in_background=True, - mode="bypassPermissions", - name="slice-3.2", - description="Implement slice 3.2", - prompt="" -) -Agent( - subagent_type="slice-implementer", - run_in_background=True, - mode="bypassPermissions", - name="slice-3.3", - description="Implement slice 3.3", - prompt="" -) -# ... one per slice in the tier -# Wait for all to complete -# Orchestrator handles git for each worktree -``` - -### Sequential Mode - -Same as Tier 0 — spawn one at a time, foreground, orchestrator handles git after each. - -```python -Agent( - subagent_type="slice-implementer", - mode="bypassPermissions", - name="slice-1.1", - description="Implement slice 1.1", - prompt="" -) -# Wait for completion -# Orchestrator runs: git add, git commit # Then spawn next slice ``` diff --git a/skills/sdd/implement-mainspec/scripts/compute_tiers.py b/skills/sdd/implement-mainspec/scripts/compute_tiers.py deleted file mode 100644 index 06e5d99..0000000 --- a/skills/sdd/implement-mainspec/scripts/compute_tiers.py +++ /dev/null @@ -1,253 +0,0 @@ -#!/usr/bin/env python3 -"""Compute parallelizable tiers from a mainspec's Slice Dependency Map. - -Parses the markdown dependency table, builds a DAG, runs topological sort -to group slices into tiers, and outputs JSON with tier assignments and -naming conventions. - -Usage: - python compute_tiers.py -""" - -import json -import re -import sys -from dataclasses import dataclass, field -from pathlib import Path - - -class CircularDependencyError(Exception): - """Raised when the dependency graph contains a cycle.""" - - pass - - -@dataclass -class SliceInfo: - number: str - name: str - depends_on: list[str] = field(default_factory=list) - blocks: list[str] = field(default_factory=list) - file: str = "" - - -def _parse_refs(col: str) -> list[str]: - """Parse a comma-separated list of slice references. - - Returns empty list for '—', '–', '-', 'Nothing', or empty string. - """ - col = col.strip() - if col in ("—", "–", "-", "Nothing", ""): - return [] - return [ref.strip() for ref in col.split(",") if ref.strip()] - - -def parse_dependency_table(content: str) -> list[SliceInfo]: - """Parse the Slice Dependency Map table from mainspec content. - - Expects a section headed '## Slice Dependency Map' with a markdown table - having at minimum Slice and Depends On columns. Blocks column is optional. - """ - lines = content.split("\n") - - # Find the "## Slice Dependency Map" section - in_section = False - table_lines = [] - for line in lines: - if re.match(r"^##\s+Slice Dependency Map", line): - in_section = True - continue - if in_section: - if line.startswith("## "): - break - if line.startswith("|"): - table_lines.append(line) - - if len(table_lines) < 3: - raise ValueError( - "No valid dependency table found in 'Slice Dependency Map' section" - ) - - # Determine column indices from header - header_cols = [c.strip().lower() for c in table_lines[0].split("|")] - # Filter empty strings from leading/trailing pipes - col_map = {} - for i, col in enumerate(header_cols): - if "slice" in col and "slice" not in col_map: - col_map["slice"] = i - elif "depends" in col: - col_map["depends_on"] = i - elif "blocks" in col: - col_map["blocks"] = i - - if "slice" not in col_map or "depends_on" not in col_map: - raise ValueError( - f"Table must have 'Slice' and 'Depends On' columns. Found: {header_cols}" - ) - - has_blocks = "blocks" in col_map - - # Parse data rows (skip header + separator) - slices = [] - for row in table_lines[2:]: - cols = [c.strip() for c in row.split("|")] - if len(cols) <= col_map["slice"]: - continue - - slice_col = cols[col_map["slice"]].strip() - depends_col = cols[col_map["depends_on"]].strip() - blocks_col = cols[col_map["blocks"]].strip() if has_blocks and len(cols) > col_map["blocks"] else "" - - # Parse slice column: "X.Y — Name" or "X.Y - Name" - match = re.match(r"^([\d.]+)\s*[—–\-]\s*(.+)$", slice_col) - if not match: - continue - - number = match.group(1) - name = match.group(2).strip() - - slices.append( - SliceInfo( - number=number, - name=name, - depends_on=_parse_refs(depends_col), - blocks=_parse_refs(blocks_col), - file="", - ) - ) - - return slices - - -def compute_tiers(slices: list[SliceInfo]) -> list[list[SliceInfo]]: - """Compute parallelizable tiers via topological sort. - - Tier 0 = slices with no dependencies. - Tier N = slices whose ALL dependencies are in tiers 0..N-1. - - Raises CircularDependencyError if the graph has cycles. - """ - if not slices: - return [] - - remaining = {s.number: s for s in slices} - assigned: set[str] = set() - tiers: list[list[SliceInfo]] = [] - - while remaining: - current_tier = [ - s - for s in remaining.values() - if all(dep in assigned for dep in s.depends_on) - ] - - if not current_tier: - raise CircularDependencyError( - f"Circular dependency detected. Remaining slices: {list(remaining.keys())}" - ) - - # Sort by slice number for deterministic output - current_tier.sort(key=lambda s: [int(x) for x in s.number.split(".")]) - tiers.append(current_tier) - - for s in current_tier: - assigned.add(s.number) - del remaining[s.number] - - return tiers - - -def to_kebab(name: str) -> str: - """Convert a name to kebab-case. - - Handles CamelCase, spaces, and special characters. - """ - # Insert hyphens before uppercase letters (CamelCase → camel-case) - s = re.sub(r"([a-z0-9])([A-Z])", r"\1-\2", name) - # Replace non-alphanumeric with hyphens - s = re.sub(r"[^a-z0-9]+", "-", s.lower()) - # Collapse multiple hyphens, strip leading/trailing - s = re.sub(r"-+", "-", s).strip("-") - return s - - -def feature_branch_name(mainspec_name: str) -> str: - """Compute feature branch name from mainspec directory name.""" - return f"feature/{mainspec_name}" - - -def slice_branch_name(number: str, name: str) -> str: - """Compute slice branch name.""" - return f"slice/{number}-{to_kebab(name)}" - - -def worktree_path(mainspec_name: str, number: str, name: str) -> str: - """Compute worktree path for a slice, namespaced under the mainspec.""" - return f".claude/worktrees/{mainspec_name}/slice-{number}-{to_kebab(name)}" - - -def batch_slices(slices: list, max_agents: int = 7) -> list[list]: - """Split a tier's slices into batches of max_agents.""" - if not slices: - return [] - return [slices[i : i + max_agents] for i in range(0, len(slices), max_agents)] - - -def resolve_slice_files(slices: list[SliceInfo], mainspec_path: str) -> None: - """Resolve slice file paths to absolute paths from the slices/ directory. - - Mutates each SliceInfo.file in place. Paths are absolute so subagents - in worktrees can read them regardless of working directory. - """ - spec_dir = Path(mainspec_path).resolve().parent - slices_dir = spec_dir / "slices" - - if not slices_dir.exists(): - return - - files = list(slices_dir.glob("*.md")) - for s in slices: - for f in files: - if f.name.startswith(f"{s.number}-"): - s.file = str(f.resolve()) - break - - -def analyze_mainspec(mainspec_path: str) -> dict: - """Full analysis: parse, compute tiers, resolve files, return JSON-ready dict.""" - path = Path(mainspec_path) - content = path.read_text() - - mainspec_name = path.parent.name - - slices = parse_dependency_table(content) - resolve_slice_files(slices, mainspec_path) - tiers = compute_tiers(slices) - - max_parallel = max(len(t) for t in tiers) if tiers else 0 - - return { - "mainspec_name": mainspec_name, - "feature_branch": feature_branch_name(mainspec_name), - "tiers": [ - { - "tier": i, - "slices": [ - {"number": s.number, "name": s.name, "file": s.file} - for s in tier - ], - } - for i, tier in enumerate(tiers) - ], - "total_slices": sum(len(t) for t in tiers), - "max_parallel": max_parallel, - } - - -if __name__ == "__main__": - if len(sys.argv) != 2: - print("Usage: compute_tiers.py ", file=sys.stderr) - sys.exit(1) - - result = analyze_mainspec(sys.argv[1]) - print(json.dumps(result, indent=2)) diff --git a/skills/sdd/implement-mainspec/scripts/tests/test_tier_computation.py b/skills/sdd/implement-mainspec/scripts/tests/test_tier_computation.py deleted file mode 100644 index dc5893c..0000000 --- a/skills/sdd/implement-mainspec/scripts/tests/test_tier_computation.py +++ /dev/null @@ -1,332 +0,0 @@ -"""Tests for compute_tiers.py — tier computation from mainspec dependency tables. - -Validates the algorithm against real mainspecs and synthetic DAG patterns. -""" - -import sys -from pathlib import Path - -import pytest - -# Add scripts directory to path so we can import compute_tiers -SCRIPTS_DIR = Path(__file__).resolve().parent.parent -sys.path.insert(0, str(SCRIPTS_DIR)) - -from compute_tiers import ( - CircularDependencyError, - SliceInfo, - analyze_mainspec, - batch_slices, - compute_tiers, - feature_branch_name, - parse_dependency_table, - slice_branch_name, - to_kebab, - worktree_path, -) - -PROJECT_ROOT = Path(__file__).resolve().parents[5] -SPECS_DIR = PROJECT_ROOT / "specs" - - -# --------------------------------------------------------------------------- -# Helper: build SliceInfo list from a compact dict -# --------------------------------------------------------------------------- - - -def _make_slices(deps: dict[str, list[str]]) -> list[SliceInfo]: - """Create SliceInfo list from {number: [dep_numbers]} dict.""" - return [ - SliceInfo(number=num, name=f"Slice {num}", depends_on=dep_list) - for num, dep_list in deps.items() - ] - - -def _tier_numbers(tiers: list[list[SliceInfo]]) -> list[list[str]]: - """Extract just the slice numbers from tier results.""" - return [sorted([s.number for s in tier]) for tier in tiers] - - -# =========================================================================== -# Real mainspec tests -# =========================================================================== - - -class TestSvgChartsTiers: - """specs/svg-and-charts/mainspec.md → 3 tiers.""" - - @pytest.fixture() - def result(self): - path = SPECS_DIR / "svg-and-charts" / "mainspec.md" - return analyze_mainspec(str(path)) - - def test_tier_count(self, result): - assert len(result["tiers"]) == 3 - - def test_tier_0(self, result): - numbers = [s["number"] for s in result["tiers"][0]["slices"]] - assert numbers == ["3.1"] - - def test_tier_1(self, result): - numbers = sorted([s["number"] for s in result["tiers"][1]["slices"]]) - assert numbers == ["3.2", "3.3", "3.4", "3.5"] - - def test_tier_2(self, result): - numbers = [s["number"] for s in result["tiers"][2]["slices"]] - assert numbers == ["3.6"] - - def test_total_slices(self, result): - assert result["total_slices"] == 6 - - def test_max_parallel(self, result): - assert result["max_parallel"] == 4 - - def test_feature_branch(self, result): - assert result["feature_branch"] == "feature/svg-and-charts" - - def test_slice_files_resolved(self, result): - for tier in result["tiers"]: - for s in tier["slices"]: - assert s["file"], f"Slice {s['number']} has no resolved file" - assert Path(s["file"]).is_absolute(), f"Slice {s['number']} file is not absolute: {s['file']}" - assert Path(s["file"]).exists(), f"Slice {s['number']} file does not exist: {s['file']}" - - -class TestSceneTransitionsTiers: - """specs/scene-transitions/mainspec.md → 4 tiers.""" - - @pytest.fixture() - def result(self): - path = SPECS_DIR / "scene-transitions" / "mainspec.md" - return analyze_mainspec(str(path)) - - def test_tier_count(self, result): - assert len(result["tiers"]) == 4 - - def test_tier_0(self, result): - numbers = [s["number"] for s in result["tiers"][0]["slices"]] - assert numbers == ["2.1"] - - def test_tier_1(self, result): - numbers = [s["number"] for s in result["tiers"][1]["slices"]] - assert numbers == ["2.2"] - - def test_tier_2(self, result): - numbers = sorted([s["number"] for s in result["tiers"][2]["slices"]]) - assert numbers == ["2.3", "2.5"] - - def test_tier_3(self, result): - numbers = [s["number"] for s in result["tiers"][3]["slices"]] - assert numbers == ["2.4"] - - -class TestImplementParallelTiers: - """specs/implement-mainspec-parallel/mainspec.md → 3 tiers (linear chain).""" - - @pytest.fixture() - def result(self): - path = SPECS_DIR / "implement-mainspec-parallel" / "mainspec.md" - return analyze_mainspec(str(path)) - - def test_tier_count(self, result): - assert len(result["tiers"]) == 3 - - def test_tier_0(self, result): - numbers = [s["number"] for s in result["tiers"][0]["slices"]] - assert numbers == ["1.1"] - - def test_tier_1(self, result): - numbers = [s["number"] for s in result["tiers"][1]["slices"]] - assert numbers == ["1.2"] - - def test_tier_2(self, result): - numbers = [s["number"] for s in result["tiers"][2]["slices"]] - assert numbers == ["1.3"] - - -# =========================================================================== -# Synthetic DAG pattern tests -# =========================================================================== - - -class TestLinearChain: - """A → B → C → D → 4 tiers, 1 slice each.""" - - def test_tiers(self): - slices = _make_slices({ - "1.1": [], - "1.2": ["1.1"], - "1.3": ["1.2"], - "1.4": ["1.3"], - }) - tiers = compute_tiers(slices) - assert len(tiers) == 4 - for tier in tiers: - assert len(tier) == 1 - - -class TestFullFanout: - """A → {B, C, D, E} → 2 tiers: [A], [B, C, D, E].""" - - def test_tiers(self): - slices = _make_slices({ - "1.1": [], - "1.2": ["1.1"], - "1.3": ["1.1"], - "1.4": ["1.1"], - "1.5": ["1.1"], - }) - tiers = compute_tiers(slices) - assert _tier_numbers(tiers) == [["1.1"], ["1.2", "1.3", "1.4", "1.5"]] - - -class TestDiamond: - """A → {B, C}, {B, C} → D → 3 tiers: [A], [B, C], [D].""" - - def test_tiers(self): - slices = _make_slices({ - "1.1": [], - "1.2": ["1.1"], - "1.3": ["1.1"], - "1.4": ["1.2", "1.3"], - }) - tiers = compute_tiers(slices) - assert _tier_numbers(tiers) == [["1.1"], ["1.2", "1.3"], ["1.4"]] - - -class TestCircularDependency: - """A → B → C → A → raises CircularDependencyError.""" - - def test_raises(self): - slices = _make_slices({ - "1.1": ["1.3"], - "1.2": ["1.1"], - "1.3": ["1.2"], - }) - with pytest.raises(CircularDependencyError): - compute_tiers(slices) - - -class TestNoDependencies: - """{A, B, C} independent → 1 tier: [A, B, C].""" - - def test_tiers(self): - slices = _make_slices({ - "1.1": [], - "1.2": [], - "1.3": [], - }) - tiers = compute_tiers(slices) - assert len(tiers) == 1 - assert _tier_numbers(tiers) == [["1.1", "1.2", "1.3"]] - - -# =========================================================================== -# Naming convention tests -# =========================================================================== - - -class TestFeatureBranchNaming: - def test_basic(self): - assert feature_branch_name("svg-and-charts") == "feature/svg-and-charts" - - def test_with_hyphens(self): - assert feature_branch_name("implement-mainspec-parallel") == "feature/implement-mainspec-parallel" - - -class TestSliceBranchNaming: - def test_basic(self): - assert slice_branch_name("3.3", "barchart") == "slice/3.3-barchart" - - def test_multi_word(self): - assert slice_branch_name("3.1", "svg path utilities") == "slice/3.1-svg-path-utilities" - - -class TestWorktreePath: - def test_basic(self): - assert worktree_path("svg-and-charts", "3.3", "barchart") == ".claude/worktrees/svg-and-charts/slice-3.3-barchart" - - def test_multi_word(self): - assert worktree_path("scene-transitions", "2.1", "scene plan types") == ".claude/worktrees/scene-transitions/slice-2.1-scene-plan-types" - - -class TestToKebab: - def test_lowercase(self): - assert to_kebab("barchart") == "barchart" - - def test_camelcase(self): - assert to_kebab("BarChart") == "bar-chart" - - def test_spaces(self): - assert to_kebab("SVG Path Utilities") == "svg-path-utilities" - - def test_special_chars(self): - assert to_kebab("Registry, Docs & Planner") == "registry-docs-planner" - - -# =========================================================================== -# Batching tests -# =========================================================================== - - -class TestBatching: - def test_large_tier(self): - """12 slices with max_agents=7 → 2 batches: [7, 5].""" - items = list(range(12)) - batches = batch_slices(items, max_agents=7) - assert len(batches) == 2 - assert len(batches[0]) == 7 - assert len(batches[1]) == 5 - - def test_exact_fit(self): - """7 slices with max_agents=7 → 1 batch.""" - items = list(range(7)) - batches = batch_slices(items, max_agents=7) - assert len(batches) == 1 - assert len(batches[0]) == 7 - - def test_small_tier(self): - """3 slices with max_agents=7 → 1 batch.""" - items = list(range(3)) - batches = batch_slices(items, max_agents=7) - assert len(batches) == 1 - assert len(batches[0]) == 3 - - def test_empty(self): - assert batch_slices([], max_agents=7) == [] - - -# =========================================================================== -# Table parsing tests -# =========================================================================== - - -class TestParseDependencyTable: - def test_basic_table(self): - content = """## Slice Dependency Map - -| Slice | Depends On | Blocks | -|-------|-----------|--------| -| 1.1 — Foundation | — | 1.2 | -| 1.2 — Feature | 1.1 | — | -""" - slices = parse_dependency_table(content) - assert len(slices) == 2 - assert slices[0].number == "1.1" - assert slices[0].name == "Foundation" - assert slices[0].depends_on == [] - assert slices[1].depends_on == ["1.1"] - - def test_missing_section_raises(self): - with pytest.raises(ValueError): - parse_dependency_table("# No dependency table here") - - def test_multiple_deps(self): - content = """## Slice Dependency Map - -| Slice | Depends On | Blocks | -|-------|-----------|--------| -| 1.3 — Final | 1.1, 1.2 | — | -""" - slices = parse_dependency_table(content) - assert slices[0].depends_on == ["1.1", "1.2"] diff --git a/skills/sdd/implement-slice/SKILL.md b/skills/sdd/implement-slice/SKILL.md index 5d0071e..045e5fe 100644 --- a/skills/sdd/implement-slice/SKILL.md +++ b/skills/sdd/implement-slice/SKILL.md @@ -1,11 +1,11 @@ --- name: implement-slice -description: Implements a single slice with Signal validation and unit tests. Agent-first — invoked by the slice-implementer subagent (under implement-mainspec). No human-in-the-loop; signal validation iterates up to a bounded `max_signal_iterations` (default 3) before reporting FAILURE. +description: Implements a single slice with unit tests and a Reflect step. Agent-first — invoked by the slice-implementer subagent (under implement-mainspec). No human-in-the-loop. --- # Implement Slice -Executes a single slice with Signal validation and unit tests. Agent-first: invoked by the `slice-implementer` subagent in a worktree the parent orchestrator (`implement-mainspec`) has set up. No human prompts; bounded inner loop. +Executes a single slice with unit tests and a post-implementation Reflect step. Agent-first: invoked by the `slice-implementer` subagent under the parent orchestrator (`implement-mainspec`). No human prompts. ## Invocation Contract @@ -13,72 +13,78 @@ This skill is invoked by the `slice-implementer` subagent, not directly by the h **Inputs:** - `slice_path` — absolute path to the slice file (may be outside the working directory). -- `working_directory` — absolute path to the worktree where work happens. -- `max_signal_iterations` — cap on the inner signal-fix-retry loop. Default: 3. +- `working_directory` — absolute path to the feature branch checkout (repo root). **Outputs (uncommitted, in the working directory):** - Implemented code matching the slice's specification. -- Signal validation completed (passed or skipped per the slice's Signal section). - Unit tests created/updated. +- Optional: edits under `.claude/skills/expert/references/` if Reflect produced a high-bar update. The parent orchestrator (`implement-mainspec`) handles git (add, commit, push) after this skill exits. Do NOT commit, do NOT run git operations, do NOT create PRs. **Result reporting (to the calling subagent):** -- **SUCCESS**: list key files changed, signal status (passed | skipped), tests passing. -- **FAILURE**: describe what went wrong. Reasons include: `signal_failure` (cap hit), `test_failure` (unit tests red), `implementation_blocked` (spec contradicts the codebase). +- **SUCCESS**: list key files changed, tests passing, Reflect outcome (no-op | add | edit | delete). +- **FAILURE**: describe what went wrong. Reasons include: `test_failure` (unit tests red), `implementation_blocked` (spec contradicts the codebase). ## Workflow 1. **Read slice** - Load the entire slice file into context. 2. **Create TODO list** - Three items: - `{slice-name} - Implement` - - `{slice-name} - Signal Validation` - `{slice-name} - Unit Tests` + - `{slice-name} - Reflect` 3. **Implement** - Mark in_progress, implement all code specified in the slice. -4. **Signal validation** - Check the Signal section: - - If Signal Skill specified: invoke `skill: "[signal-name]"`, wait for output. - - Compare against Expected Behavior. - - Fix issues and re-invoke. Track iterations. - - If iterations reach `max_signal_iterations` (default 3) without success, return FAILURE with `reason: signal_failure` and the last signal output. Do not loop indefinitely. - - If Signal Skill is "None": skip to unit tests. -5. **Unit tests** - Create/update unit tests for the implemented functionality. +4. **Unit tests** - Create/update unit tests for the implemented functionality. All tests must pass before proceeding. +5. **Reflect** - See the Reflection Protocol below. 6. **Complete** - Mark all TODOs complete, stop. -The working directory is set up freshly by `implement-mainspec` (a per-slice git worktree branched from `feature/`). There should be no prior partial state to resume from — work as if the slate is clean. +The working directory is the feature branch checkout. There should be no prior partial state to resume from — work as if the slate is clean. -## Signal Processing +## Reflection Protocol -Each slice includes a Signal section after the Objective: +Run only after Implement + Unit Tests are green. -```markdown -## Signal +### Step A — Load long-term memory -**Signal Skill:** [signal-skill-name | None] +Invoke `/expert` so the skill body and its reference files are pulled into your context: -**Expected Behavior:** -- What should succeed when correctly implemented ``` +skill: "expert" +``` +If absent, Reflect is a no-op. Never fail the slice on missing `/expert`. + +Otherwise, read the reference files that overlap with what this slice touched (architecture area, patterns used, file types modified). + +### Step B — Judge: add, edit, delete, or no-op + +You now hold two things: +1. **Your experience implementing this slice** — what was confusing, what pattern you implemented, where the spec contradicted the actual codebase (already in your context window). +2. **The current long-term memory** — what `/expert` says about those areas (loaded in Step A). + +High bar — default to **no-op** unless you have a strong reason to add, edit, or delete. Use the following table to guide your decision: + +| Outcome | When to use | +|---------|------------| +| **NO-OP** (default) | Routine work, small adjustments, no contradiction. Prefer this. | +| **ADD** | A new fact, few-shot example, or procedure the project lacks. Use reference-file prefixes: `how-to-*` / `concept-*` / `pattern-*` / `invariant-*` / `example-*`. | +| **EDIT** | An existing reference is partially wrong; correct it in place. | +| **DELETE** | An existing reference contradicts merged code reality. | + +Strong signals to act: slice got stuck for many iterations, or the slice spec explicitly contradicted code that already exists. Small deviations are noise — prefer NO-OP. -### Signal Workflow +### Step C — Apply directly (only for add/edit/delete) -1. After implementing slice code, check the Signal section -2. If Signal Skill is specified: - - Invoke the signal: `skill: "[signal-name]"` - - Wait for signal output - - Follow the guidance from Signal -3. If signal indicates success: Continue to unit tests -4. If signal indicates failure: - - Review signal output to identify specific issue - - Fix the implementation - - Re-invoke signal until success -5. If Signal Skill is "None": Skip to unit tests +- Write the change to `.claude/skills/expert/references/`. +- If you added or deleted a reference file, update `.claude/skills/expert/SKILL.md`'s one-line index. +- Do NOT touch `AGENTS.md` — that is owned by `/learn` post-merge. +- The orchestrator commits these Expert-file changes with the slice and has full authority to resolve any resulting merge conflicts. ## TODO Structure ``` [ ] 1.3-user-auth - Implement -[ ] 1.3-user-auth - Signal Validation [ ] 1.3-user-auth - Unit Tests +[ ] 1.3-user-auth - Reflect ``` ## Guidelines @@ -86,7 +92,7 @@ Each slice includes a Signal section after the Objective: **DON'T:** - Use `AskUserQuestion`. No human is in the loop. - Implement beyond the slice scope. -- Proceed with failing signal validation — but also do not exceed `max_signal_iterations`; if the cap is hit, return FAILURE with `reason: signal_failure` and the last signal output. -- Skip signal validation if specified. +- Proceed with failing unit tests. - Implement dependent slices (that's for implement-mainspec). -- Run git commands or create PRs — the parent orchestrator handles git. \ No newline at end of file +- Run git commands or create PRs — the parent orchestrator handles git. +- Fail the slice because `/expert` is unavailable or Reflect produced no update. diff --git a/skills/sdd/spec-planning/SKILL.md b/skills/sdd/spec-planning/SKILL.md index 5c4eab2..079927d 100644 --- a/skills/sdd/spec-planning/SKILL.md +++ b/skills/sdd/spec-planning/SKILL.md @@ -48,13 +48,14 @@ Spec planning starts with the end in mind. You create a mainspec that defines th - **Ground entirely in the PRD and codebase** — Operate from `prds//prd.md` and the codebase. Do NOT use AskUserQuestion. There is no human in the loop. If the PRD is too ambiguous to ground (contradictions, undefined terms, missing definition of done), follow the Ambiguous PRD handling protocol in the Invocation Contract above. - **Research codebase first** - Verify what exists today before planning. Look at specs folder to see what's been done, but verify against actual code since specs may be outdated. - **Reference the real codebase** - Ground specs in reality with actual file paths, existing patterns, and current implementations. Show what exists today as context for what should exist tomorrow. -- **Encode the PRD test as a slice success criterion** — The PRD's `run-prd-test.sh` is the definition of done. The mainspec must include a slice (typically the final one) whose Signal section names the PRD test runner (`./prds//run-prd-test.sh`) as the validation command. When this slice completes, `./prds//run-prd-test.sh` must exit 0. Document this requirement explicitly in the slice's Objective so the implementing agent does not miss it. The runner is intentionally opaque to spec-planning: it may invoke a unit test, an LLM-as-judge prompt, deterministic shell checks, or any mix — the slice's job is to make it pass, not to assume its internals. +- **Encode the PRD test as the final slice's Success Criteria** — The PRD's `run-prd-test.sh` is the definition of done. The mainspec must include a final slice whose `## Success Criteria` section names the PRD test runner (`./prds//run-prd-test.sh`) and requires it to exit 0. Document this requirement explicitly in the slice's Objective so the implementing agent does not miss it. The runner is intentionally opaque to spec-planning: it may invoke a unit test, an LLM-as-judge prompt, deterministic shell checks, or any mix — the slice's job is to make it pass, not to assume its internals. - **Think temporally** - Order mainspecs (which feature comes first?) and slices (which slice enables the next?). Document dependencies clearly. - **Right level of detail** - Clear enough for implementation agents to understand intent, but not so detailed you make up features or constrain solutions unnecessarily. - **Document forward requirements** - In each slice, capture what future slices will need from the current work. Prevents rework and enables temporal planning. - **Focus on WHAT not HOW** - Specs define intent and outcomes, not implementation steps. Use code snippets, exact file paths, and examples for context (see Context Engineering below), but don't write full implementation plans. Paint the picture of WHAT needs to exist and WHY, leaving HOW to the implementation phase. -- **Auto-invoke experts when triggers match** - Read the experts catalog at start of planning and invoke relevant experts to curate domain-specific context. -- **Write Signal section into every slice** - Every slice must include a Signal section to indicate how to validate the implementation. If no Signal matches, mark as None. +- **Consult experts** — Two tiers complement each other: + - **`/expert`** (project's long-term memory) — invoke on any non-trivial slice, design question, or architecture decision. Gives you what the project already knows about its own codebase: patterns, invariants, lessons from past slices. + - **`/expert-*`** (outside experts) — invoke when a slice touches a shared library, internal platform, or vendor SDK for which an outside expert exists. Read `references/experts.md` to discover what outside experts are available. - **Write Slice Dependency Map into every mainspec** - Every mainspec must include a "Slice Dependency Map" section with a `Slice | Depends On | Blocks` table and a Mermaid flowchart visualizing the DAG. This is the single source of truth for slice dependencies. ## Output Structure @@ -279,61 +280,34 @@ backend/ - Workspace-specific package.json: Email dependencies isolated from main backend ``` -## Expert & Signal Integration +## Expert Integration -Experts and Signals enhance spec planning by curating domain knowledge (Experts) and defining runtime validation (Signals). +Experts give planning the right domain context before slice content is written. Two tiers: -### What Are Experts? +### `/expert` — Project long-term memory -Experts are Agent Skills that provide domain-specific guidance during spec planning. They help curate better context by offering framework-specific patterns, security best practices, and internal library documentation, etc. +The project's own accumulated knowledge: architecture patterns, invariants, lessons from past slices, naming conventions, where things live. If present, consult it on any non-trivial design or architecture question. -**MUST Read the experts catalog at START of planning:** `references/experts.md` +**MUST consult at start of planning (if available):** `skill: "expert"` -### What Are Signals? +Read the skill body + relevant reference files. Use what you learn to: +- Avoid repeating patterns the project has already encoded +- Ground BEFORE/AFTER examples in real file paths and current patterns +- Avoid contradicting known invariants -Signals are Agent Skills that provide runtime feedback during implementation. They validate that code works as expected beyond just unit tests passing. +### `/expert-*` — Outside experts -**MUST Read the signals catalog at START of planning:** `references/signals.md` +Domain knowledge for shared libraries, internal platforms, and vendor SDKs the project depends on. Not project knowledge — but the project's slices may rely on these. -### How to Use Experts During Planning +**Read the catalog at start of planning:** `references/experts.md` - this is your inventory for outside experts. -1. Read `references/experts.md` at the start of spec planning -2. Match triggers - Check if the feature description matches any expert triggers -3. Auto-invoke when matched - If triggers match, invoke with `skill: "{expert-name}"` -4. Incorporate guidance - Use expert recommendations to: - - Inform BEFORE/AFTER examples with framework-specific patterns - - Create DO/DON'T sections based on common pitfalls - - Define type contracts that follow framework conventions +Match triggers — check if the feature description or slice content touches any outside expert's domain. Auto-invoke when matched. -### How to Use Signals When Writing Slices +Use outside expert guidance to: +- Inform BEFORE/AFTER examples with framework-specific patterns +- Create DO/DON'T sections based on common pitfalls +- Define type contracts that follow framework conventions -1. Read `references/signals.md` at the start of spec planning -2. For each slice being planned: - - Determine if the slice needs runtime validation - - Match slice type against signal triggers - - Write Signal section into the slice +### Check availability -### Signal Section Format - -Every slice must include a Signal section after the Objective: - -```markdown -# Slice X.Y: Name - -## Objective - -... - -## Signal - -**Signal Skill:** {signal-skill-name | None} - -**Expected Behavior:** -- Specific validations for this slice -- what should succeed when correctly implemented - -## BEFORE/AFTER Directory Structure -[rest of slice...] -``` - ---- \ No newline at end of file +If `/expert` is absent, skip silently — no fallback needed. If no `/expert-*` triggers match, skip. Never fail planning on expert unavailability. \ No newline at end of file diff --git a/skills/sdd/spec-planning/references/signals.md b/skills/sdd/spec-planning/references/signals.md deleted file mode 100644 index ee11f34..0000000 --- a/skills/sdd/spec-planning/references/signals.md +++ /dev/null @@ -1 +0,0 @@ -# Signals \ No newline at end of file diff --git a/skills/sdd/spec-validate/SKILL.md b/skills/sdd/spec-validate/SKILL.md index 507d426..1d3ea47 100644 --- a/skills/sdd/spec-validate/SKILL.md +++ b/skills/sdd/spec-validate/SKILL.md @@ -156,14 +156,14 @@ For each candidate finding, classify as **impactful** or **nitpick**: - **Impactful** — Would mislead the implementing agent. Examples: - Missing or wrong type contracts (interfaces, schemas) - Incorrect BEFORE/AFTER file paths - - Ambiguous Signal section (no clear validation command) + - Ambiguous final-slice Success Criteria (no clear PRD-test command) - Missing forward-looking requirements that block downstream slices - Security gaps in DO/DON'T examples - Wrong file path references - Missing slice dependency in the Dependency Map - Broken slice DAG (cycle, dangling reference) - Slice that contradicts the PRD's definition of done - - Slice missing the run-prd-test gate (the final slice's Signal must invoke `./prds//run-prd-test.sh` and require exit 0 as the PRD-completion criterion) + - Final slice missing Success Criteria (must invoke `./prds//run-prd-test.sh` and require exit 0 as the PRD-completion criterion) **→ Apply the fix.** @@ -197,7 +197,7 @@ Structure: ## Subagent consensus ### 3/3 (Very High Confidence) -- **[Applied]** Slice 1.2 / Signal section — finding text — fix applied: +- **[Applied]** Slice 1.2 / Success Criteria — finding text — fix applied: - **[Skipped: nitpick]** Slice 2.1 / Wording — finding text — reason for skip ### 2/3 (High Confidence) diff --git a/subagents/slice-implementer.md b/subagents/slice-implementer.md index 1456678..0378742 100644 --- a/subagents/slice-implementer.md +++ b/subagents/slice-implementer.md @@ -1,6 +1,6 @@ --- name: slice-implementer -description: Implements a single slice in a worktree. Used by implement-mainspec for parallel tier execution. +description: Implements a single slice. Used by implement-mainspec to implement one slice on the feature branch. skills: - implement-slice permissionMode: bypassPermissions @@ -8,7 +8,7 @@ permissionMode: bypassPermissions # Slice Implementer -You are a focused slice implementation agent. The `implement-slice` skill is preloaded — follow its workflow for implementation, signal validation, and unit tests. +You are a focused slice implementation agent. The `implement-slice` skill is preloaded — follow its workflow for implementation, unit tests, and reflection. ## Working Directory @@ -25,16 +25,15 @@ All file paths in the slice spec are relative to this directory. 1. **No git commands** — Do NOT run `git add`, `git commit`, `git push`, or any other git commands. The orchestrator handles all git operations after you finish. 2. **No PRs** — Do NOT create pull requests. 3. **No worktree commands** — Do NOT run `git worktree` commands. You are already in a worktree (or the correct branch). -4. **Stay in scope** — Only modify files specified by or implied by your assigned slice. Do not modify files outside your slice scope. +4. **Stay in scope** — Only modify files specified by or implied by your assigned slice. Exception: during the Reflect step, editing files under `.claude/skills/expert/references/` is explicitly in-scope. 5. **No human prompts** — Do NOT use `AskUserQuestion`. No human is in the loop. -6. **Honor `max_signal_iterations`** — If the prompt provides this value (default 3), pass it to `implement-slice` so the inner signal-fix-retry loop is bounded. Return FAILURE if the cap is hit; do not loop indefinitely. ## Workflow 1. Read the slice spec file (path provided in your prompt) -2. Follow the `implement-slice` workflow: Implement → Signal Validation → Unit Tests +2. Follow the `implement-slice` workflow: Implement → Unit Tests → Reflect 3. Report your result: - - **SUCCESS**: All code implemented, tests pass. Briefly list key files created/modified. + - **SUCCESS**: All code implemented, tests pass, Reflect outcome (no-op | add | edit | delete). Briefly list key files created/modified (including any Expert reference files from Reflect). - **FAILURE**: Describe what went wrong and where you stopped. ## Spec File Access