Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
.DS_Store
.claude/settings.local.json
.claude/settings.json
.claude/worktrees/
node_modules/
*.tsbuildinfo

# middle's per-repo bootstrap dir is local-only (mm init step 7). Skills under
# .claude/skills/ stay committed; the operational .middle/ does not.
.middle/
2 changes: 2 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

84 changes: 84 additions & 0 deletions docs/dogfooding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Dogfooding crossover β€” operator runbook

From Phase 3 onward, middle dispatches its own work. The crossover is the moment
the operator runs `mm init` **on the middle repo itself** and hands the wheel to
middle. The spec calls this out as a deliberate chicken-and-egg step
(build spec β†’ "Dogfooding rules" #5): bootstrap is manual up to here, and the
crossover is performed once, by hand, by the operator β€” not by an agent that is
itself mid-dispatch.

## Why an in-flight agent must not self-run the crossover

When middle dispatches an agent for an Epic, the agent runs inside a worktree
whose `.middle/` directory holds that dispatch's operational state (the brief,
the hook script, session bookkeeping). The crossover commands conflict with that
in two load-bearing ways:

1. **`mm dispatch . <issue>` nests agents.** It needs the dispatcher process
(`mm start`, port 8822), creates a *second* worktree, and spawns a *second*
`claude`/tmux session. Run from inside a live dispatch it collides with the
running dispatcher and consumes a slot against itself. It also needs a
**manually created** Epic to target.
2. **`mm init .` creates live GitHub state.** It opens the real
`agent-queue:state` issue and label on `thejustinwalsh/middle`. Done from an
unmerged PR branch, that issue is orphaned if the PR is reworked. The state
issue is meant to be "real from Phase 3" (dogfooding rule #2) β€” created once,
deliberately, at the canonical checkout.

So the capability is built and verified (PR #81, sub-issues #22–#25); the live
crossover is the operator's to run. The committable slice of the self-bootstrap β€”
gitignoring `.middle/` (mm init step 7) β€” is already in the repo.

## Runbook

Run from a **clean** checkout of `thejustinwalsh/middle` (not a middle worktree):

```bash
# 0. Preconditions β€” all green.
mm doctor

# 1. Inspect the plan first (no mutation).
mm init . --dry-run

# 2. Perform the crossover. Creates the agent-queue:state label + state issue,
# stages skills to .claude/skills/ and .codex/skills/, writes .middle/hooks/
# hook.sh, per-CLI hook config, and .middle/config.toml (with the issue number).
mm init .

# 3. Commit the shared skills (.claude/skills/ and .codex/skills/ are committed;
# .middle/ is gitignored).
git add .claude/skills .codex/skills .gitignore
git commit -m "chore: install middle skills (dogfooding crossover)"
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# 4. Verify the state issue exists and conforms.
gh issue list --repo thejustinwalsh/middle --label agent-queue:state
# Its body parses against schemas/state-issue.v1.md (validated by mm init).

# 5. Start the dispatcher and dispatch a manually created Epic against middle.
mm start
mm dispatch . <epic-number> # the Epic (see "Creating a dispatchable Epic" below)

# To reverse the crossover entirely:
mm uninit . # closes the state issue, removes staged files
```

### Creating a dispatchable Epic

`mm dispatch . <epic-number>` expects an **Epic**: a GitHub issue that has
**sub-issues**, where each sub-issue is one phase of the work and carries its own
acceptance criteria. The agent works the open sub-issues down, one per phase, on a
single branch/PR.

Don't hand-author these ad hoc β€” use the **`creating-github-issues`** skill (in
`.claude/skills/creating-github-issues/`): give it a plan and it files the parent
Epic plus sub-issues with acceptance criteria, the right labels, and the proper
parent/child hierarchy that the recommender and implementer expect. Minimum the
dispatch needs:

- a **parent** issue with one or more **sub-issues** attached (the phases),
- **acceptance criteria** on each sub-issue (an Epic whose children lack them is
classified `needs-human` and won't be dispatched),
- optionally a `phase:N` / `dogfood` label for grouping (not required to dispatch).

`mm init` is idempotent: a re-run with a matching `bootstrap.version` refreshes
skills/hooks but keeps the config and the existing state issue.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
],
"scripts": {
"test": "bun test",
"typecheck": "tsc --noEmit"
"typecheck": "tsc --noEmit",
"sync-skills": "bun scripts/sync-skills.ts",
"prepare": "git config core.hooksPath scripts/hooks 2>/dev/null && echo 'middle: enabled repo git hooks (core.hooksPath=scripts/hooks)' || true"
},
"devDependencies": {
"@types/bun": "latest",
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"@middle/adapter-claude": "workspace:*",
"@middle/core": "workspace:*",
"@middle/dispatcher": "workspace:*",
"commander": "^14.0.3"
"@middle/state-issue": "workspace:*",
"commander": "^14.0.3",
"smol-toml": "^1.6.1"
}
}
Loading