From 6fc4cffe1407328d218944b8e21402801c061d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauricio=20S=C3=A1nchez?= Date: Tue, 6 Oct 2026 08:23:50 -0500 Subject: [PATCH 1/2] feat(parallel-agents): every task goes through /dispatch in the main checkout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the parallel-agents module, the main checkout's session now takes every task and hands it to a new session in a worktree of its own (decision 0020, amending 0008 and 0015). The project's worktree.conf picks the route, not the task: Claude Code's worktree (a task chip in the desktop app, or a claude --worktree command) when the worktrees need nothing from the scripts; the scripts' worktree, with a prompt to paste, when they need a port, setup or start commands, or a base branch other than the default. The developer can pick the other route for one task. A chip is never used for the scripts' worktree: it always creates a worktree of its own, without the env file, port, or setup, even when given another folder — a finding from another project's 2026-09-22 test, recorded in the decision. The session-context hook gives the dispatcher the instruction and its project's route; protect-hub's message points at /dispatch. Docs, the scenario, and the tests follow. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 38 +++++++ README.md | 2 +- docs/ONBOARDING.md | 20 ++-- docs/SKILLS-REFERENCE.md | 2 +- .../0008-dispatcher-and-worker-worktrees.md | 2 +- .../0015-tool-worktrees-are-workers.md | 2 +- .../0020-every-task-through-dispatch.md | 104 ++++++++++++++++++ docs/decisions/README.md | 5 +- docs/scenarios/parallel-agents.md | 46 ++++---- evals/static/test-hooks.sh | 7 +- modules/parallel-agents/MODULE.md | 16 +-- .../files/.claude/skills/dispatch/SKILL.md | 99 ++++++++++------- .../files/docs/PARALLEL-AGENTS.md | 24 +++- plugins/aplyca-adf/hooks/README.md | 2 +- plugins/aplyca-adf/hooks/protect-hub.sh | 2 +- plugins/aplyca-adf/hooks/session-context.sh | 9 +- .../aplyca-adf/skills/spec-workflow/SKILL.md | 4 +- skeleton/.claude/hooks/README.md | 2 +- skeleton/.claude/hooks/protect-hub.sh | 2 +- skeleton/.claude/hooks/session-context.sh | 9 +- .../.claude/skills/spec-workflow/SKILL.md | 4 +- 21 files changed, 300 insertions(+), 101 deletions(-) create mode 100644 docs/decisions/0020-every-task-through-dispatch.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 8633e04..4508e5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,44 @@ For each entry, **Upgrade impact** classifies the change against the [three-buck ## Unreleased +### Every task goes through `/dispatch` in the main checkout + +([0020](docs/decisions/0020-every-task-through-dispatch.md), amending [0008](docs/decisions/0008-dispatcher-and-worker-worktrees.md) and [0015](docs/decisions/0015-tool-worktrees-are-workers.md)) + +With the `parallel-agents` module, the session in the main checkout now takes every task, not only the +ones whose worktree needs the project's setup. It hands each task to a new session in a worktree of +its own, and that session runs the whole process. Before, developers chose each task's route and +started the common route's session by hand. Now the project's settings in `worktree.conf` pick the +route: + +- **When the project's worktrees need nothing from the scripts,** `/dispatch` takes Claude Code's + worktree. In the desktop app it offers a task chip carrying the worker prompt, which the developer + starts with one click. In a terminal it gives a `claude --worktree ""` command. +- **When they need a port, setup or start commands, or tasks start from another branch,** it takes + the scripts' worktree, created as before with `worktree-new.sh --no-start`, and gives its path and + the prompt to paste into a session opened on it. +- **Never a chip for the scripts' worktree.** A chip always creates a worktree of its own, without the + env file, port, or setup, even when it's given another folder. `/dispatch` and + `PARALLEL-AGENTS.md` now say so. +- **The developer can pick the other route for one task,** for example `/dispatch chip` for a + copy change in a project whose worktrees run a server. The dispatcher never picks it on its own: + whether a task runs the app is its triage's question. + +The session-context hook tells the dispatcher to give every task to `/dispatch` and names its +project's route. `protect-hub.sh`'s message points at `/dispatch`. A session started with the worktree +option, without `/dispatch`, is still a worker. + +**Upgrade impact:** + +- **Overwrite** `.claude/skills/dispatch/SKILL.md` (module) and, in a committed project, + `.claude/hooks/session-context.sh`, `.claude/hooks/protect-hub.sh`, `.claude/hooks/README.md`, and + `.claude/skills/spec-workflow/SKILL.md`. A packaged project gets the hooks and `/spec-workflow` with + its next pin. +- **Merge** `docs/PARALLEL-AGENTS.md`: its roles and routes sections changed, and your § Shared + services stays as it is. +- Nothing to migrate. A project whose worktrees need the scripts now takes every task through them; + ask for a chip when a task won't run the app. + ## v1.2.1 — 2026-10-05 — Drift the prompt audit found A patch release: fixes to the framework's instruction files, with nothing new to adopt diff --git a/README.md b/README.md index d19552a..2efbbc9 100644 --- a/README.md +++ b/README.md @@ -333,7 +333,7 @@ flowchart TD | The requester needs an update on a task | `/stakeholder-update` ("update the client") — in the client's terms, shown in chat, posted on the pull request for the team to relay; on the tracker only on your yes to the exact text | `sonnet` | [Tracker integration](skeleton/docs/TRACKER-INTEGRATION.md) | | Passing work on — a teammate, another machine, a fresh session | `/handoff` — the state committed to the record first, then a short message of pointers to it | session model | [Skills catalog](docs/SKILLS-REFERENCE.md) | | A change to how the team works | `/record-decision` → a PDR in `docs/process/` | `sonnet` | — | -| Several tasks at once | Each task in its own worktree (`parallel-agents` module): a new session with Claude Code's worktree option, or `/dispatch` from the main checkout when the worktree needs the project's setup | per task | [Parallel agents](docs/scenarios/parallel-agents.md) | +| Several tasks at once | Each task in its own worktree (`parallel-agents` module): `/dispatch` in the main checkout hands every task to a new session — a one-click task chip in the desktop app, or a prompt to paste when the project's worktrees need its setup | per task | [Parallel agents](docs/scenarios/parallel-agents.md) | | High stakes or a broad sweep | `/deep-review`, `/deep-spec-analysis`, `/deep-context-audit`, `/deep-drift-sweep` | each agent its own | [Skills catalog](docs/SKILLS-REFERENCE.md) | ### Effort, model, and cost diff --git a/docs/ONBOARDING.md b/docs/ONBOARDING.md index 23aaa61..678a3f3 100644 --- a/docs/ONBOARDING.md +++ b/docs/ONBOARDING.md @@ -313,14 +313,18 @@ if there's no tracker task). Check for business language, verified claims, and y Each agent session gets its own git worktree — never two in one checkout. With the [`parallel-agents` module](../modules/parallel-agents/MODULE.md), the main checkout **only -dispatches**. A task starts in a new session with Claude Code's worktree option (the desktop app's -worktree toggle, or `claude --worktree`), or — when its worktree needs the project's setup, such as a -port — `/dispatch` creates the worktree and branch and hands off a three-line prompt. The worker there -does everything from triage on. The main checkout is shared — an edit or a dev server -there collides with every other session — and analysis there is wasted: the dispatcher can't run the -app or the tests, so the worker re-reads everything where it can verify it. -([decisions 0008](decisions/0008-dispatcher-and-worker-worktrees.md) and -[0015](decisions/0015-tool-worktrees-are-workers.md)) +dispatches**: every task goes to `/dispatch` there, which hands it to a new session in a worktree of +its own. The project's settings pick the route. When its worktrees need nothing from the scripts, +that's Claude Code's worktree: a task chip you start with one click in the desktop app, or a +`claude --worktree` command. When they need the project's setup, such as a port, `/dispatch` creates +the worktree with the scripts and gives you a three-line prompt to paste into a session opened on it. +You can ask for the other route for one task. The worker there does everything from triage on. The +main checkout is shared — an edit or a dev server there collides with every other session — and +analysis there is wasted: the dispatcher can't run the app or the tests, so the worker re-reads +everything where it can verify it. +([decisions 0008](decisions/0008-dispatcher-and-worker-worktrees.md), +[0015](decisions/0015-tool-worktrees-are-workers.md), and +[0020](decisions/0020-every-task-through-dispatch.md)) **Exercise (module installed):** dispatch the typo and the topics estimate. The main checkout's `git status` stays clean, each worker's session context says WORKER, and diff --git a/docs/SKILLS-REFERENCE.md b/docs/SKILLS-REFERENCE.md index 618b72d..6c35f33 100644 --- a/docs/SKILLS-REFERENCE.md +++ b/docs/SKILLS-REFERENCE.md @@ -51,7 +51,7 @@ In order, for a change with something to decide. Commits: `spec:` → `docs:` | Skill | Module | Purpose | |---|---|---| -| `/dispatch` | `parallel-agents` | In the main checkout, for a task whose worktree needs the project's setup (a port, setup or start commands, a non-default base branch): name the task, create its worktree (`worktree-new.sh --no-start`), and hand off to a worker session with a three-line prompt. Other tasks start in Claude Code's own worktree. Reads only; no analysis, no edits. | +| `/dispatch` | `parallel-agents` | In the main checkout, every task: name it and hand it to a new session in a worktree of its own, by the project's route — Claude Code's worktree through a task chip (desktop app) or a `claude --worktree` command, or, when the project's worktrees need a port, setup or start commands, or a non-default base branch, the scripts' worktree (`worktree-new.sh --no-start`) with a three-line prompt to paste. The developer can pick the other route for one task. Reads only; no analysis, no edits. | ## Dynamic workflows diff --git a/docs/decisions/0008-dispatcher-and-worker-worktrees.md b/docs/decisions/0008-dispatcher-and-worker-worktrees.md index 4df2a10..b712757 100644 --- a/docs/decisions/0008-dispatcher-and-worker-worktrees.md +++ b/docs/decisions/0008-dispatcher-and-worker-worktrees.md @@ -1,6 +1,6 @@ # 0008: The main checkout dispatches; worktrees do the work (optional module) -- **Status:** accepted; amended by [0015](0015-tool-worktrees-are-workers.md) (Claude Code's own worktrees are workers too) +- **Status:** accepted; amended by [0015](0015-tool-worktrees-are-workers.md) (Claude Code's own worktrees are workers too) and [0020](0020-every-task-through-dispatch.md) (every task goes through `/dispatch`) - **Date:** 2026-10-01 ## Context diff --git a/docs/decisions/0015-tool-worktrees-are-workers.md b/docs/decisions/0015-tool-worktrees-are-workers.md index 8fc5775..702c6a0 100644 --- a/docs/decisions/0015-tool-worktrees-are-workers.md +++ b/docs/decisions/0015-tool-worktrees-are-workers.md @@ -1,6 +1,6 @@ # 0015: Worktrees that Claude Code creates are workers too (parallel-agents module) -- **Status:** accepted +- **Status:** accepted; amended by [0020](0020-every-task-through-dispatch.md) (every task goes through `/dispatch`, and the project picks its route) - **Date:** 2026-10-02 - **Amends:** [0008](0008-dispatcher-and-worker-worktrees.md) — how a task gets its worktree diff --git a/docs/decisions/0020-every-task-through-dispatch.md b/docs/decisions/0020-every-task-through-dispatch.md new file mode 100644 index 0000000..defd6ce --- /dev/null +++ b/docs/decisions/0020-every-task-through-dispatch.md @@ -0,0 +1,104 @@ +# 0020: Every task goes through `/dispatch` in the main checkout (parallel-agents module) + +- **Status:** accepted +- **Date:** 2026-10-06 +- **Amends:** [0008](0008-dispatcher-and-worker-worktrees.md) — what the dispatcher takes; [0015](0015-tool-worktrees-are-workers.md) — who picks a task's route + +## Context + +Decision 0008 made the main checkout a dispatcher: it names each task and hands it to a worker in a +worktree of its own. Decision 0015 then let developers skip it. A task whose worktree needs nothing +from the scripts starts in a new session with Claude Code's worktree option, and `/dispatch` covers +only the scripts' route. So the developer picks each task's route, and for the common route starts +the session by hand. + +A team using the module asked for one way in: every requirement goes to the session in the main +checkout. That session works out what the task needs and hands it to a new session, which runs the +whole process. No task work happens in the main checkout. + +**What the tooling can hand over** (Claude Code 2.1.286 and the desktop app, October 2026): + +- **A task chip.** In the desktop app, a session can offer work as a task chip. One click starts it in + a new session with a worktree of its own and the chip's prompt, and the first session carries on. + It's the only session a desktop session can start. +- **The chip's worktree is always its own.** It's created under `.claude/worktrees/`, from the default + branch, without the scripts' env file, port, or setup. Another project, whose worktrees run + containers, tested this on 2026-09-22 with a logging hook: + - A chip creates its own worktree even when it's given another worktree as its folder. + - A `WorktreeCreate` hook runs for a chip, but it's told neither that folder nor the task. + - The app skips the hook when it reuses a parked worktree. + + So a worktree the scripts set up can't be handed over by a chip. That project hands it over as a + prompt the developer pastes into a session opened on the worktree. +- **In a terminal**, `claude --worktree ""` starts a session in a new worktree with + that prompt. + +**Who should pick the route.** Whether a task will run the app is a triage question. It's answered +after reading the task in full, and often the code. Decision 0008 keeps that reading out of the main +checkout. A wrong guess also costs more one way than the other: + +- **A chip for a task that turns out to need the app** means a second dispatch and a new session, and + the triage starts again. +- **The scripts' worktree for a task that didn't need it** costs one pasted prompt. Nothing starts, + because the scripts create it with `--no-start`. + +In the project above, 7 of the last 40 merged changes touched only docs or agent-instruction files. +The other 33 touched code, tests, migrations, CI, or dependencies. + +## Decision + +- **Every task goes through `/dispatch` in the main checkout.** It names the task, takes the route, + and hands the task to a new session in a worktree of its own. The worker does everything from + triage on, as before. + - The session-context hook gives the dispatcher that instruction and its project's route. + - `protect-hub.sh`'s message points at `/dispatch`. + - A session a developer starts with the worktree option is still a worker. +- **The project picks the route, from `scripts/agent/worktree.conf`:** + - **Its worktrees need nothing from the scripts:** Claude Code's worktree. In the desktop app, + `/dispatch` offers a task chip carrying the worker prompt. In a terminal, it gives a + `claude --worktree` command. + - **They need a port, setup or start commands:** the scripts' worktree, created with + `worktree-new.sh --no-start`. `/dispatch` gives its path and the prompt for the developer to paste + into a new session opened on it. + - **Tasks start from a branch other than the default:** the scripts' worktree, always. +- **The developer can pick the other route for one task**, because they know the task. Examples: a + chip for a copy change in a project whose worktrees run a server, or the scripts anywhere. A chip is + never allowed where tasks start from another branch, and the dispatcher doesn't pick another route + on its own. +- **The scripts' worktree is never handed over as a chip.** `/dispatch` and `PARALLEL-AGENTS.md` + say why, so nobody switches the hand-off back to a chip. + +## Consequences + +- **Positive:** + - One way in for every task and every developer. + - The dispatcher's job stays a lookup: it reads only the task's title and type. + - The common route is one click in the desktop app. + - No route comes from a guess. +- **Negative / cost:** + - **Two sessions per task again.** Decision 0015 had removed the dispatcher for the common route. + The dispatcher's turn is short, but its conversation keeps growing, so `/dispatch` suggests + clearing it between tasks. + - **A pasted prompt per task in a project whose worktrees need the scripts,** docs-only tasks + included, unless the developer asks for a chip. + - **The one-click hand-off is the desktop app's.** A terminal gets a command to run. + +## Alternatives considered + +- **Keep 0015, where developers pick the route.** That's what the team asked to change: the developer + routes each task and starts the common route's session by hand. +- **The dispatcher picks per task.** It would read the task to guess whether it runs the app, which is + a triage question, and a wrong chip costs a second dispatch. The saving is one pasted prompt on the + tasks that touch only docs, about 1 in 5 in the project above. The developer can still make that + call for one task. +- **Set up the chip's worktree after triage**, for example a `worktree-new.sh --here` that adds the port + and setup in place. The app archives and reuses its own worktrees, so the containers and port + reservations the scripts added there would have no owner to stop them. `STOP_CMD` runs only from + `worktree-rm.sh`. +- **A `WorktreeCreate` hook that routes a chip through the scripts.** Ruled out by the test above: the + hook isn't told the folder or the task, and doesn't run for a reused worktree. +- **Agent teams.** Teammates share the lead's directory, with no worktree each, and the feature is + experimental. +- **Background sessions from agent view (`claude --bg`).** A research preview. A session started in + the main checkout is told it's the dispatcher until it moves into its worktree. Worth revisiting once + it's stable. diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 0364302..054d540 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -18,18 +18,19 @@ projects. | [0005](0005-outward-actions-and-draft-prs.md) | Outward actions only on request; pull requests stay drafts until a human QCs them | accepted | | [0006](0006-guardrails-as-configuration.md) | Guardrails that must hold are configuration and code, not prose | accepted | | [0007](0007-process-decision-records.md) | Process decisions are recorded as PDRs; the constitution is amended through them | accepted | -| [0008](0008-dispatcher-and-worker-worktrees.md) | The main checkout dispatches; worktrees do the work (optional module) | accepted; amended by 0015 | +| [0008](0008-dispatcher-and-worker-worktrees.md) | The main checkout dispatches; worktrees do the work (optional module) | accepted; amended by 0015, 0020 | | [0009](0009-optional-modules.md) | Host- and team-specific harness ships as optional modules | accepted; amended by 0016 | | [0010](0010-model-aliases.md) | Configure models with version-less aliases | accepted | | [0011](0011-lanes-ceremony-follows-risk.md) | Three lanes — ceremony follows risk and uncertainty, not size | accepted; partly superseded by 0014 | | [0012](0012-choose-the-model-by-the-work.md) | Choose the model by the work — Sonnet for well-specified work, Opus for judgment | accepted | | [0013](0013-adapt-practices-not-a-second-workflow.md) | Adapt practices from other skill collections into our skills — never a second workflow | accepted | | [0014](0014-test-first-in-every-lane.md) | Test first in every lane | accepted | -| [0015](0015-tool-worktrees-are-workers.md) | Worktrees that Claude Code creates are workers too (parallel-agents module) | accepted | +| [0015](0015-tool-worktrees-are-workers.md) | Worktrees that Claude Code creates are workers too (parallel-agents module) | accepted; amended by 0020 | | [0016](0016-packaged-install.md) | A packaged install — the framework's machinery from its pinned plugin, `aplyca-adf` (opt-in, Claude Code only) | accepted; amended by 0018, 0019 | | [0017](0017-semantic-versioning.md) | Releases follow semantic versioning | accepted | | [0018](0018-packaged-by-default.md) | The packaged install is the default | accepted | | [0019](0019-reference-docs-in-the-plugin.md) | A packaged project reads the framework's reference docs from the plugin | accepted | +| [0020](0020-every-task-through-dispatch.md) | Every task goes through `/dispatch` in the main checkout (parallel-agents module) | accepted | Changes that follow from these records are listed, with their upgrade impact, in [`CHANGELOG.md`](../../CHANGELOG.md). diff --git a/docs/scenarios/parallel-agents.md b/docs/scenarios/parallel-agents.md index d1524c2..f4f093e 100644 --- a/docs/scenarios/parallel-agents.md +++ b/docs/scenarios/parallel-agents.md @@ -20,7 +20,7 @@ a second session starts, it gets its own worktree. | | Main checkout — **dispatcher** | A worktree — **worker** | |---|---|---| -| Does | Routes each task to its worktree — Claude Code's own, or the scripts' through `/dispatch` | Everything from triage on: spec folder, gate, TDD, commits | +| Does | Takes every task with `/dispatch` and hands it to a new session in its own worktree, by the project's route | Everything from triage on: spec folder, gate, TDD, commits | | Never | Reads code, analyzes, edits, starts environments | Goes back to the main checkout to work | The `session-context` hook tells each session its role when it starts. @@ -35,9 +35,11 @@ again anyway, where it can check it. The dispatcher's context stays cheap: a bra ### In the main checkout — dispatch -A task whose worktree needs the project's setup — here, anything that runs the app — goes through -the scripts. (Other tasks can start in Claude Code's own worktree: see below.) `/dispatch ` does four things and nothing else: +Every task starts here, with `/dispatch `. The project's settings pick the route +([0020](../decisions/0020-every-task-through-dispatch.md)): the newsletter site runs a server in each +worktree, so its worktrees need a port and a start command, and every task goes through the scripts. +(A project whose worktrees need nothing takes Claude Code's own worktree instead: see below.) +`/dispatch` does four things and nothing else: 1. **Names the task** — its title and type only; no code reading, no requirements analysis. A short kebab-case slug. A task that names a delivered feature starts its slug with that feature's @@ -45,12 +47,12 @@ link>` does four things and nothing else: whether it really is a change request is the worker's triage to decide. 2. **Creates the worktree** — `scripts/agent/worktree-new.sh / --no-start`: a sibling directory named after the branch (`../feat-newsletter-signup-topics`), a fresh branch from the base - branch, the env file seeded from the main checkout's — and, on this site, which runs a server per - worktree, a reserved port. `--no-start` because whether the task needs anything running is the - worker's call. -3. **Starts the worker session** in the worktree — `cd && claude` — or, where the tool - can't start a session in a folder you choose, gives you the prompt to paste into a new session - opened on the worktree. + branch, the env file seeded from the main checkout's, and a reserved port. `--no-start` because + whether the task needs anything running is the worker's call. +3. **Hands it over** — the worktree's path and the prompt, to paste into a new session opened on the + worktree: in the desktop app, a new session with the worktree as its folder; in a terminal, + `cd && claude ""`. Never a task chip: a chip creates a worktree of its own, + without this one's env file and port, even when it's given this folder. 4. **Writes the handoff** — three lines, pointers only: ``` @@ -96,14 +98,18 @@ worktree's env file. ### Claude Code's own worktrees -A task can also start in a new session with Claude Code's worktree option — the desktop app's -worktree toggle, or `claude --worktree` ([0015](../decisions/0015-tool-worktrees-are-workers.md)). -It's a worker like any other: `.worktreeinclude` copies the env file, and the session renames its -generated branch to `/` after triage. On the newsletter site that's how copy changes and -investigations start. Tasks that run the app go through `/dispatch`, because their worktree needs a -port and the dev server's start command. The session-context hook names what a worktree is missing -when it starts, and `worktree-ls.sh` flags the ones left on a generated branch. Claude Code's -worktrees live under `.claude/worktrees/`, which stays in `.gitignore` and `.claudeignore`. +In a project whose worktrees need nothing from the scripts, `/dispatch` takes Claude Code's route +instead ([0015](../decisions/0015-tool-worktrees-are-workers.md)): in the desktop app it offers the +task as a task chip, and one click starts the worker in a worktree of its own; in a terminal it gives +you `claude --worktree ""`. The worker is like any other: `.worktreeinclude` copies the +env file, and the session renames its generated branch to `/` after triage. + +On the newsletter site, a developer can still ask for a chip for one task — `/dispatch chip` +for a copy change, which never runs the app. The session-context hook names what a worktree is +missing when it starts — here, the port and the start command — and `worktree-ls.sh` flags the ones +left on a generated branch. A session started with the worktree option, without `/dispatch`, is a +worker too. Claude Code's worktrees live under `.claude/worktrees/`, which stays in `.gitignore` and +`.claudeignore`. ### Cleanup @@ -201,7 +207,9 @@ cd ../feat-newsletter-signup && claude | Analyzing in the main checkout "to give the worker a head start" | Unverified conclusions, a burned context, drift in the handoff | Name the task; hand off | | A one-line fix in the main checkout | A stray edit collides with every other session | Even one line goes to a worktree | | Dispatching without `--no-start` out of habit | An environment built for a task that may not need one | The worker starts it after triage | -| Built-in worktree tools for task work | No env file, port, or branch convention; ports collide | `/dispatch` and `worktree-new.sh` | +| Built-in worktree tools for a task that runs the app, where worktrees need the scripts | No port or start command; the app collides with another worktree's | `/dispatch`, which takes the scripts' route | +| A task chip for the scripts' worktree | The chip creates a worktree of its own, without the env file or the port, even when given this folder | Paste the prompt into a session opened on the worktree | +| The dispatcher picking a chip because the task "won't need the app" | A wrong guess means a second dispatch and a new session | The project's route; the developer picks the exception | | Raw `git worktree add` with the module installed | Ports and env files drift from what the scripts track | The scripts | | Changing a service every worktree shares | Every other worker's environment changes under them | That worktree gets its own copy (§ Shared services) | | Keeping a squash-merged branch | The next change request on that feature starts from stale code | Delete it after merge | diff --git a/evals/static/test-hooks.sh b/evals/static/test-hooks.sh index 03d7667..e083d2b 100755 --- a/evals/static/test-hooks.sh +++ b/evals/static/test-hooks.sh @@ -189,7 +189,8 @@ check_ctx() { # check_ctx [] FAIL=$((FAIL+1)); echo "✘ session-context.sh: $1"; echo "$out" | sed 's/^/ /' fi } -check_ctx "dispatcher in the main checkout, offering Claude Code's worktree option" "$T" "Start each task in a new session with Claude Code's worktree option" +check_ctx "dispatcher in the main checkout, which gives every task to /dispatch" "$T" "Give every task to /dispatch" +check_ctx "a project whose worktrees need nothing takes Claude Code's route" "$T" "Route: Claude Code's worktree — a task chip" check_ctx "worker in a worktree the scripts set up, with nothing missing" "$WORK/feat-role-check" "Role: WORKER" "generated\|No .env\|Not set up" check_ctx "worker in Claude Code's worktree, told to rename its generated branch" "$T/.claude/worktrees/eager-lamport" "branch name is generated (claude/eager-lamport)" check_ctx "worker in Claude Code's worktree, told the env file is missing" "$T/.claude/worktrees/eager-lamport" "No .env here" @@ -200,11 +201,11 @@ cp "$T/.env" "$T/.claude/worktrees/eager-lamport/.env" check_ctx "a renamed branch with its env file needs nothing more" "$T/.claude/worktrees/eager-lamport" "Role: WORKER" "generated\|No .env\|Not set up" printf 'BASE_BRANCH="main"\nENV_FILE=".env"\nPORT_SLOTS=180\nSTART_CMD="npm run dev"\n' > "$T/.claude/worktrees/eager-lamport/scripts/agent/worktree.conf" cp "$T/.claude/worktrees/eager-lamport/scripts/agent/worktree.conf" "$T/scripts/agent/worktree.conf" -check_ctx "a project whose worktrees run a server: dispatch for app work" "$T" "A task that runs the app starts with /dispatch" +check_ctx "a project whose worktrees run a server takes the scripts' route for every task" "$T" "Route: the scripts' worktree, with a prompt to paste — this project's worktrees need a port" check_ctx "Claude Code's worktree there lacks the port and start command" "$T/.claude/worktrees/eager-lamport" "lacks a port and setup or start commands" printf 'BASE_BRANCH="staging"\nENV_FILE=".env"\n' > "$T/.claude/worktrees/eager-lamport/scripts/agent/worktree.conf" cp "$T/.claude/worktrees/eager-lamport/scripts/agent/worktree.conf" "$T/scripts/agent/worktree.conf" -check_ctx "tasks that start from another branch always dispatch" "$T" "Start each task with /dispatch: tasks here start from staging" +check_ctx "tasks that start from another branch always take the scripts' route" "$T" "Route: the scripts' worktree, with a prompt to paste — tasks here start from staging" check_ctx "Claude Code's worktree there started from the wrong base" "$T/.claude/worktrees/eager-lamport" "started this worktree from main, but tasks here start from staging" printf 'BASE_BRANCH="staging" # integration\nSETUP_CMD="$(touch %s/conf-ran)"\n' "$WORK" > "$T/scripts/agent/worktree.conf" check_ctx "worktree.conf is read as data, comments and all" "$T" "tasks here start from staging" diff --git a/modules/parallel-agents/MODULE.md b/modules/parallel-agents/MODULE.md index ec05c97..c82b5f1 100644 --- a/modules/parallel-agents/MODULE.md +++ b/modules/parallel-agents/MODULE.md @@ -14,16 +14,18 @@ project where the main checkout dispatched tasks and workers in sibling worktree | `scripts/agent/worktree-rm.sh` | Stop (when a stop command is set), remove, and safely delete the branch | | `scripts/agent/worktree.conf` | The project's settings — base branch, env file, setup/start/stop commands, environment info, and the server-only port settings | | `scripts/agent/_worktree-lib.sh` | Shared helpers | -| `.claude/skills/dispatch/SKILL.md` | `/dispatch`: which route a task takes, and the four-step handoff through the scripts | +| `.claude/skills/dispatch/SKILL.md` | `/dispatch`: takes every task in the main checkout and hands it to a new session in its own worktree, by the project's route | | `.worktreeinclude` | The env file Claude Code copies into the worktrees it creates — the desktop app's worktree option, `claude --worktree` | | `docs/PARALLEL-AGENTS.md` | The dispatcher/worker process, the scripts, shared vs isolated services | -A task gets its worktree one of two ways (decision 0015): **Claude Code's worktree** — a new session -with the desktop app's worktree option, or `claude --worktree` — by default, or **the scripts** -(`/dispatch`) when the worktree needs a port, setup or start commands, or a base branch other than -the default. With the module installed, the core session-context hook announces each session's role -— dispatcher in the main checkout, worker in any worktree — and, in a worktree the scripts didn't set -up, what it lacks: a task branch name, the env file, or the scripts' setup. The core `protect-hub.sh` +Every task goes through `/dispatch` in the main checkout (decision 0020), and the project's settings +pick its route: **Claude Code's worktree** — a task chip in the desktop app, or `claude --worktree` — +when the project's worktrees need nothing from the scripts, or **the scripts' worktree**, handed over +as a prompt to paste, when they need a port, setup or start commands, or a base branch other than the +default. The developer can pick the other route for one task. With the module installed, the core +session-context hook announces each session's role — dispatcher in the main checkout, with its +project's route, and worker in any worktree — and, in a worktree the scripts didn't set up, what it +lacks: a task branch name, the env file, or the scripts' setup. The core `protect-hub.sh` hook stops file edits in the main checkout once the module is there. The core `/handoff` skill covers passing work in progress on. diff --git a/modules/parallel-agents/files/.claude/skills/dispatch/SKILL.md b/modules/parallel-agents/files/.claude/skills/dispatch/SKILL.md index 21725f2..9b84b2c 100644 --- a/modules/parallel-agents/files/.claude/skills/dispatch/SKILL.md +++ b/modules/parallel-agents/files/.claude/skills/dispatch/SKILL.md @@ -1,34 +1,41 @@ --- name: dispatch -description: From the main checkout, hand a task to its own worktree — name the task, create the worktree and branch with scripts/agent/worktree-new.sh --no-start, and start (or hand the developer) a worker session there with a short prompt that points at the process. Does no analysis and edits nothing. Use when a task arrives in the main checkout and needs the scripts' worktree (a port, setup or start commands, a base branch other than the default) — or when the developer asks for it. -argument-hint: "[tracker link or task description]" +description: In the main checkout, hand every task to a new session in a worktree of its own — name the task, take the route the project's settings give (Claude Code's worktree, through a task chip in the desktop app or a claude --worktree command; or the scripts' worktree, with a prompt for the developer to paste), and point the worker at the process. Does no analysis and edits nothing. Use for every task that arrives in the main checkout of a project with the parallel-agents module. +argument-hint: "[tracker link or task description] [optional: chip | scripts]" --- # Dispatch The main checkout is the shared hub where every developer and agent session starts. **In the main -checkout you dispatch; you never work.** Editing there blocks every other session, and analysis done -there is wasted: the dispatcher can't run the app or the tests, its conclusions are unverified, and -the worker in the worktree has to redo the reading anyway — now with tools that can check it. The -dispatcher's context stays cheap: a branch name, not a plan. +checkout you dispatch every task; you never work.** Editing there blocks every other session, and +analysis done there is wasted: the dispatcher can't run the app or the tests, its conclusions are +unverified, and the worker in the worktree has to redo the reading anyway — now with tools that can +check it. The dispatcher's context stays cheap: a branch name, not a plan. A dispatcher **writes nothing outside this machine** and takes no outward-facing action. Its only network calls are reads: the task in step 1, and the `git fetch` inside the worktree script. +This skill needs the `parallel-agents` module: without `scripts/agent/worktree-new.sh` in this +repository, say the module isn't installed and stop. + ## Which route -A task gets its worktree one of two ways (decision 0015): +The project picks the route, not the task (decision 0020). Whether a task will run the app is a +question for its triage, after the hand-off, and a wrong guess here costs a second dispatch. Read the +route from `scripts/agent/worktree.conf`; the session-context line at the start of this session names +it too: -- **Claude Code's worktree** — a new session with the worktree option: the desktop app's worktree - toggle, or `claude --worktree `. No dispatcher needed: the worker renames the branch to - `/` after triage, and `.worktreeinclude` copies the env file. The default when the - project's worktrees run no server and tasks start from the default branch. -- **The scripts — this skill** — when the worktree needs what only they set up: a port, setup or - start commands, or a base branch other than the default branch (`scripts/agent/worktree.conf`). - The session-context hook says which applies when this session starts. +| The project's worktrees need | Route | +|---|---| +| Nothing from the scripts: `PORT_SLOTS=0`, no `SETUP_CMD` or `START_CMD`, and `BASE_BRANCH` is the default branch | **Claude Code's worktree** — one click in the desktop app | +| A port, or setup or start commands | **The scripts' worktree** — a prompt the developer pastes | +| A `BASE_BRANCH` other than the default branch | **The scripts' worktree**, for every task — Claude Code's worktrees start from the default branch | -When a task arrives here and Claude Code's worktree fits, say so and stop: the developer starts a new -session with the worktree option and gives it the task. Otherwise, follow the steps. +**The developer can pick the other route for one task**, in words ("use a chip for this one") or as +the argument (`chip`, `scripts`). They know the task: a copy change in a project whose worktrees run a +server never needs the server. Take their route and say what that worktree will lack — a chip's has no +port and no setup. Refuse only a chip where tasks start from another branch, and say why. Never pick +the other route on your own. ## Steps @@ -39,38 +46,54 @@ session with the worktree option and gives it the task. Otherwise, follow the st (`feat/newsletter-signup-topics`) — a directory listing (`ls specs/`) is enough; don't open the files. Whether it really is a change request is the worker's triage to decide. -2. **Create the worktree:** - ```bash - scripts/agent/worktree-new.sh / --no-start - ``` - It creates a sibling worktree named after the branch (`../feat-newsletter-signup-topics`) on a - new branch from the base branch — or reports the existing worktree unchanged. `--no-start` is - deliberate: the environment belongs to the worker, which starts it only if its triage says a step - needs it (`scripts/agent/worktree-new.sh ` from the worktree). A hotfix from a release - tag adds `--from `. - -3. **Start the worker session in the worktree.** - - Terminal: `cd && claude`, then paste the prompt from step 4. - - Desktop app: a new session with the worktree as its folder — not the worktree option, which - would create a second worktree without this one's port and setup. Give the developer the prompt - from step 4. - -4. **Write the worker's prompt** — pointers, not instructions: +2. **Hand it over by the route.** + - **Claude Code's worktree.** In the desktop app, offer the task as a task chip — the app's way to + start work in a new session with a worktree of its own. Its title is the task's; its prompt is + the worker prompt (step 3). The developer starts it with one click, and this session carries on. + In a terminal, give the developer one command to run in a new terminal: + ```bash + claude --worktree "" + ``` + - **The scripts' worktree.** Create it: + ```bash + scripts/agent/worktree-new.sh / --no-start + ``` + It creates a sibling worktree named after the branch (`../feat-newsletter-signup-topics`) on a + new branch from the base branch — or reports the existing worktree unchanged. `--no-start` is + deliberate: the environment belongs to the worker, which starts it only if its triage says a + step needs it (`scripts/agent/worktree-new.sh ` from the worktree). A hotfix from a + release tag adds `--from `. + + Then give the developer the worktree's path on a line of its own and the worker prompt in a code + block, to paste into a new session opened on that folder: in the desktop app, a new session with + the worktree as its folder and the worktree option off; in a terminal, + `cd && claude ""`. **Never a task chip here**, nor the worktree + option: both create a worktree of their own under `.claude/worktrees/`, without this one's env + file, port, or setup — even when given this worktree as their folder. + +3. **Write the worker's prompt** — pointers, not instructions: ``` Task: Worktree: — branch / Follow AGENTS.md end to end, starting with triage. ``` + For Claude Code's worktree the app names the branch, so the second line is + `Branch: / — rename the generated branch to it after triage`. Don't restate the workflow: the worker reads it in `AGENTS.md`, and a copy in a handoff message is one more thing that drifts. If the developer set a lane or asked for more effort, pass it on in their words — `Developer: full lane — the billing rules are fragile` — since triage honors it. +The hand-off ends the task's time here. The next task needs nothing from this conversation, so +clearing it (`/clear`) between dispatches keeps the hub's session cheap. + ## Rationalizations (do not accept these) | Agent says... | Why it's wrong | |---|---| | "I'll read the task properly first so the worker gets a head start" | The worker redoes the reading where it can verify it. Analysis here is unverified, burns the hub's context, and drifts in the handoff. | | "It's a one-line fix — I'll just do it here" | The main checkout is shared. A stray edit or a running server here collides with every other session. | +| "This one won't run the app, so a chip will do" | Whether it runs the app is its triage's question. A wrong chip means a second dispatch and a new session; the scripts' worktree costs one paste. The developer can choose the chip — you don't. | +| "I'll offer a chip and give it the scripts' worktree as its folder" | A chip always creates a worktree of its own, without the env file, port, or setup. Hand over the prompt to paste. | | "I'll start the environment so it's ready" | The worker may not need one (an answer-only task). It decides after triage. | | "I'll open a placeholder issue or take the next spec number for the branch" | That's an outward-facing write, and numbers collide between parallel dispatchers. The slug is the join. | | "I'll put the full workflow in the handoff to be safe" | Restated steps drift from `AGENTS.md`. Point at the process; don't copy it. | @@ -81,17 +104,19 @@ session with the worktree option and gives it the task. Otherwise, follow the st - `git status` in the main checkout shows changes you made — or the protect-hub hook stopped an edit: you were about to work in the hub. - The handoff prompt contains analysis, a plan, or a list of files to change. +- The route isn't the one the project's settings give, and the developer didn't ask for it. ## Verification - [ ] Only the task's title and type were read -- [ ] The route fits: the scripts only when the task needs their setup, or the developer asked for them -- [ ] The worktree was created (or reported) by `worktree-new.sh --no-start` — never by raw `git worktree add` +- [ ] The route is the project's, or the one the developer asked for — never a chip where tasks start from another branch +- [ ] The scripts' worktree was created (or reported) by `worktree-new.sh --no-start` — never by raw `git worktree add` — and handed over as a prompt to paste, never as a chip - [ ] Nothing was edited, committed, or posted from the main checkout -- [ ] The worker prompt has the task link, worktree path, branch, and "follow AGENTS.md, starting with triage" — nothing else +- [ ] The worker prompt has the task link, the worktree path or the branch to rename to, and "follow AGENTS.md, starting with triage" — nothing else ## Principles -- The hub dispatches; worktrees work. +- The hub dispatches every task; worktrees work. +- The project picks the route; the developer can pick the other one for a task. - Reads only, no writes, no analysis. - Point at the process; never restate it. diff --git a/modules/parallel-agents/files/docs/PARALLEL-AGENTS.md b/modules/parallel-agents/files/docs/PARALLEL-AGENTS.md index acc182c..eba08cd 100644 --- a/modules/parallel-agents/files/docs/PARALLEL-AGENTS.md +++ b/modules/parallel-agents/files/docs/PARALLEL-AGENTS.md @@ -11,7 +11,7 @@ the project needs them, its own copy of the env file and a port of its own. | | Main checkout — **dispatcher** | A worktree — **worker** | |---|---|---| | Where | The clone itself | Any linked worktree: Claude Code's own, or a sibling the scripts create (`../feat-newsletter-signup`) | -| Does | Routes the task to its worktree (§ Two routes); creates it with `/dispatch` when the scripts are needed | Everything else: triage, spec folder, plan, approval gate, TDD, commits | +| Does | Takes every task with `/dispatch` and hands it to a new session in a worktree of its own, by the project's route (§ Two routes) | Everything else: triage, spec folder, plan, approval gate, TDD, commits | | Never | Reads code, analyzes, edits, starts anything | Goes back to the hub to work | | Network | Reads only (the task, `git fetch`) | Whatever the workflow allows — outward actions only when asked | @@ -22,16 +22,30 @@ to a worktree of its own like any other task. ## Two routes to a worktree -| | Claude Code's worktree | The scripts (`/dispatch`) | +Every task starts in the main checkout with `/dispatch`, and the project's settings in +`worktree.conf` pick its route — not the task. Whether a task will run the app is a question for its +triage, after the hand-off, and a wrong guess at dispatch costs a second dispatch. + +| | Claude Code's worktree | The scripts' worktree | |---|---|---| -| How | A new session with the worktree option — the desktop app's worktree toggle, or `claude --worktree ` | `/dispatch` in the main checkout runs `scripts/agent/worktree-new.sh / --no-start` | +| The project's worktrees need | Nothing from the scripts, and tasks start from the default branch | A port, setup or start commands — or tasks start from a branch other than the default | +| How `/dispatch` hands it over | Desktop app: a task chip the developer starts with one click. Terminal: `claude --worktree ""` | It runs `scripts/agent/worktree-new.sh / --no-start`, then gives the worktree's path and a prompt to paste into a new session opened on it | | Branch | A generated name — the worker renames it after triage: `git branch -m /` | `/` from the start | | Env file | Copied from the main checkout by `.worktreeinclude` | Seeded from the main checkout by the script | | Port, setup, start commands | None | What `worktree.conf` sets | | Starts from | The default branch | `BASE_BRANCH`, or `--from ` | -| Use it when | The default: the worktree runs no server and tasks start from the default branch | The worktree needs a port or setup or start commands, the base branch isn't the default, or the developer asks | -Both are workers, and the session-context hook names what a worktree lacks when it starts: a +The developer can pick the other route for one task — `/dispatch chip` for a copy change in a +project whose worktrees run a server, or `scripts` anywhere — but never a chip when tasks start from +another branch. + +**Why the scripts' worktree is never handed over as a chip.** A chip always creates a worktree of its +own under `.claude/worktrees/`, without the env file, port, or setup, even when it's given the +scripts' worktree as its folder. A `WorktreeCreate` hook can't change that: it's told neither the +folder nor the task, and the app skips it when it reuses a parked worktree. So that route costs one +manual step per task: open a session on the worktree and paste the prompt. + +Both kinds are workers, and the session-context hook names what a worktree lacks when it starts: a generated branch, the env file, or the scripts' setup. Keep `.worktreeinclude` in step with `ENV_FILE`. The desktop app removes its worktrees when a session is archived — or once the pull request merges, with auto-archive on; `worktree-rm.sh` removes the scripts'. diff --git a/plugins/aplyca-adf/hooks/README.md b/plugins/aplyca-adf/hooks/README.md index 18ad88f..1b596ab 100644 --- a/plugins/aplyca-adf/hooks/README.md +++ b/plugins/aplyca-adf/hooks/README.md @@ -6,7 +6,7 @@ depending on what the model decides. They are wired in `../settings.json`. | Hook | Event | What it does | |---|---|---| -| `session-context.sh` | SessionStart | Adds a few lines to the session: main checkout or worktree, branch, uncommitted changes, the spec folder for the branch and its status, and — with the parallel-agents module — whether this session is a dispatcher or a worker, and what a worktree the scripts didn't set up lacks (a task branch name, the env file, the scripts' setup) | +| `session-context.sh` | SessionStart | Adds a few lines to the session: main checkout or worktree, branch, uncommitted changes, the spec folder for the branch and its status, and — with the parallel-agents module — whether this session is a dispatcher or a worker, the route a dispatcher's tasks take, and what a worktree the scripts didn't set up lacks (a task branch name, the env file, the scripts' setup) | | `guard-git.sh` | PreToolUse · Bash | Blocks `--no-verify` (and `git commit -n`), commits on protected branches, and pushes, force-pushes, or deletes targeting protected branches | | `protect-paths.sh` | PreToolUse · Edit/Write | Blocks hand-edits to generated files (lockfiles, generated types) and modifications to existing files in append-only history (migrations) | | `careful-paths.sh` | PreToolUse · Edit/Write | The first edit in each sensitive area (`CAREFUL_GLOBS`) is stopped once per session, so the agent confirms the change is in the careful or full lane before going on. Empty `CAREFUL_GLOBS` turns it off | diff --git a/plugins/aplyca-adf/hooks/protect-hub.sh b/plugins/aplyca-adf/hooks/protect-hub.sh index bdf055d..456b8ca 100755 --- a/plugins/aplyca-adf/hooks/protect-hub.sh +++ b/plugins/aplyca-adf/hooks/protect-hub.sh @@ -19,4 +19,4 @@ root="$(repo_root_for "$file")" [ "$(git -C "$root" rev-parse --git-dir)" = "$(git -C "$root" rev-parse --git-common-dir)" ] || exit 0 -block "$root is the main checkout — the shared hub, where the dispatcher edits nothing. Give the task its own worktree — a new session with Claude Code's worktree option, or /dispatch when it needs the project's worktree setup — and make this change from a session there." +block "$root is the main checkout — the shared hub, where the dispatcher edits nothing. Give the task to /dispatch, which hands it to a new session in a worktree of its own, and make this change from that session." diff --git a/plugins/aplyca-adf/hooks/session-context.sh b/plugins/aplyca-adf/hooks/session-context.sh index 882e94d..4cf6337 100755 --- a/plugins/aplyca-adf/hooks/session-context.sh +++ b/plugins/aplyca-adf/hooks/session-context.sh @@ -76,13 +76,14 @@ if [ -x "$root/scripts/agent/worktree-new.sh" ]; then main="$(dirname "$common_dir")" # a linked worktree's common dir is absolute:
/.git if [ "$checkout" = "main checkout" ]; then - echo "- Role: DISPATCHER. This is the shared main checkout — never edit here. Each task gets its own worktree, branch, and session." + echo "- Role: DISPATCHER. This is the shared main checkout — never edit here. Give every task to /dispatch: it hands the task to a new session in a worktree of its own." + # The project's settings pick the route, not the task (decision 0020). if [ -n "$other_base" ]; then - echo "- Start each task with /dispatch: tasks here start from $base_branch, and Claude Code's own worktrees start from $default_branch." + echo "- Route: the scripts' worktree, with a prompt to paste — tasks here start from $base_branch, and Claude Code's own worktrees start from $default_branch." elif [ -n "$needs" ]; then - echo "- A task that runs the app starts with /dispatch (its worktree needs $needs). Any other task can start in a new session with Claude Code's worktree option — the desktop app's worktree toggle, or claude --worktree." + echo "- Route: the scripts' worktree, with a prompt to paste — this project's worktrees need $needs. The developer can ask for a chip for a task that won't run the app." else - echo "- Start each task in a new session with Claude Code's worktree option — the desktop app's worktree toggle, or claude --worktree — or with /dispatch." + echo "- Route: Claude Code's worktree — a task chip in the desktop app, or claude --worktree in a terminal." fi else echo "- Role: WORKER. This worktree is yours for one task — start with triage (/aplyca-adf:triage)." diff --git a/plugins/aplyca-adf/skills/spec-workflow/SKILL.md b/plugins/aplyca-adf/skills/spec-workflow/SKILL.md index 3736cd2..b448b49 100644 --- a/plugins/aplyca-adf/skills/spec-workflow/SKILL.md +++ b/plugins/aplyca-adf/skills/spec-workflow/SKILL.md @@ -116,8 +116,8 @@ describes the old way in the same pull request. Constitution amendments get thei When several agent sessions run at once, each works in its own git worktree on its own branch — never two sessions in one checkout. With the parallel-agents module installed, the main checkout is a -**dispatcher** only (`/dispatch`): it names the task, creates the worktree, and hands off; the -**worker** in the worktree does everything from triage onward. Passing work in progress to a +**dispatcher** only (`/dispatch`): it names every task and hands it to a new session in a worktree +of its own; the **worker** there does everything from triage onward. Passing work in progress to a teammate, another machine, or a fresh session is `/aplyca-adf:handoff`: pointers to the record, never a copy. ## Effort beyond the lane diff --git a/skeleton/.claude/hooks/README.md b/skeleton/.claude/hooks/README.md index 18ad88f..1b596ab 100644 --- a/skeleton/.claude/hooks/README.md +++ b/skeleton/.claude/hooks/README.md @@ -6,7 +6,7 @@ depending on what the model decides. They are wired in `../settings.json`. | Hook | Event | What it does | |---|---|---| -| `session-context.sh` | SessionStart | Adds a few lines to the session: main checkout or worktree, branch, uncommitted changes, the spec folder for the branch and its status, and — with the parallel-agents module — whether this session is a dispatcher or a worker, and what a worktree the scripts didn't set up lacks (a task branch name, the env file, the scripts' setup) | +| `session-context.sh` | SessionStart | Adds a few lines to the session: main checkout or worktree, branch, uncommitted changes, the spec folder for the branch and its status, and — with the parallel-agents module — whether this session is a dispatcher or a worker, the route a dispatcher's tasks take, and what a worktree the scripts didn't set up lacks (a task branch name, the env file, the scripts' setup) | | `guard-git.sh` | PreToolUse · Bash | Blocks `--no-verify` (and `git commit -n`), commits on protected branches, and pushes, force-pushes, or deletes targeting protected branches | | `protect-paths.sh` | PreToolUse · Edit/Write | Blocks hand-edits to generated files (lockfiles, generated types) and modifications to existing files in append-only history (migrations) | | `careful-paths.sh` | PreToolUse · Edit/Write | The first edit in each sensitive area (`CAREFUL_GLOBS`) is stopped once per session, so the agent confirms the change is in the careful or full lane before going on. Empty `CAREFUL_GLOBS` turns it off | diff --git a/skeleton/.claude/hooks/protect-hub.sh b/skeleton/.claude/hooks/protect-hub.sh index dd2624b..68aa32e 100755 --- a/skeleton/.claude/hooks/protect-hub.sh +++ b/skeleton/.claude/hooks/protect-hub.sh @@ -19,4 +19,4 @@ root="$(repo_root_for "$file")" [ "$(git -C "$root" rev-parse --git-dir)" = "$(git -C "$root" rev-parse --git-common-dir)" ] || exit 0 -block "$root is the main checkout — the shared hub, where the dispatcher edits nothing. Give the task its own worktree — a new session with Claude Code's worktree option, or /dispatch when it needs the project's worktree setup — and make this change from a session there." +block "$root is the main checkout — the shared hub, where the dispatcher edits nothing. Give the task to /dispatch, which hands it to a new session in a worktree of its own, and make this change from that session." diff --git a/skeleton/.claude/hooks/session-context.sh b/skeleton/.claude/hooks/session-context.sh index d01f3eb..d7f8cdf 100755 --- a/skeleton/.claude/hooks/session-context.sh +++ b/skeleton/.claude/hooks/session-context.sh @@ -76,13 +76,14 @@ if [ -x "$root/scripts/agent/worktree-new.sh" ]; then main="$(dirname "$common_dir")" # a linked worktree's common dir is absolute:
/.git if [ "$checkout" = "main checkout" ]; then - echo "- Role: DISPATCHER. This is the shared main checkout — never edit here. Each task gets its own worktree, branch, and session." + echo "- Role: DISPATCHER. This is the shared main checkout — never edit here. Give every task to /dispatch: it hands the task to a new session in a worktree of its own." + # The project's settings pick the route, not the task (decision 0020). if [ -n "$other_base" ]; then - echo "- Start each task with /dispatch: tasks here start from $base_branch, and Claude Code's own worktrees start from $default_branch." + echo "- Route: the scripts' worktree, with a prompt to paste — tasks here start from $base_branch, and Claude Code's own worktrees start from $default_branch." elif [ -n "$needs" ]; then - echo "- A task that runs the app starts with /dispatch (its worktree needs $needs). Any other task can start in a new session with Claude Code's worktree option — the desktop app's worktree toggle, or claude --worktree." + echo "- Route: the scripts' worktree, with a prompt to paste — this project's worktrees need $needs. The developer can ask for a chip for a task that won't run the app." else - echo "- Start each task in a new session with Claude Code's worktree option — the desktop app's worktree toggle, or claude --worktree — or with /dispatch." + echo "- Route: Claude Code's worktree — a task chip in the desktop app, or claude --worktree in a terminal." fi else echo "- Role: WORKER. This worktree is yours for one task — start with triage (/triage)." diff --git a/skeleton/.claude/skills/spec-workflow/SKILL.md b/skeleton/.claude/skills/spec-workflow/SKILL.md index f43f240..35e1ffb 100644 --- a/skeleton/.claude/skills/spec-workflow/SKILL.md +++ b/skeleton/.claude/skills/spec-workflow/SKILL.md @@ -112,8 +112,8 @@ describes the old way in the same pull request. Constitution amendments get thei When several agent sessions run at once, each works in its own git worktree on its own branch — never two sessions in one checkout. With the parallel-agents module installed, the main checkout is a -**dispatcher** only (`/dispatch`): it names the task, creates the worktree, and hands off; the -**worker** in the worktree does everything from triage onward. Passing work in progress to a +**dispatcher** only (`/dispatch`): it names every task and hands it to a new session in a worktree +of its own; the **worker** there does everything from triage onward. Passing work in progress to a teammate, another machine, or a fresh session is `/handoff`: pointers to the record, never a copy. ## Effort beyond the lane From dfd676879f2dff19e065341c83ce1a48d208ae11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauricio=20S=C3=A1nchez?= Date: Tue, 6 Oct 2026 08:27:43 -0500 Subject: [PATCH 2/2] feat(plugin): the plugin carries /dispatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /dispatch was the one framework skill a packaged project still committed: it ships in the parallel-agents module, and decision 0016 keeps modules committed — a rule written for the scripts, templates, and configuration a project owns. The skill is generic machinery, so the plugin now carries it as /aplyca-adf:dispatch (decision 0020, amending 0016). scripts/build-aplyca-adf.sh copies every module's skills into the plugin, from the module, which stays their one source; a committed install doesn't change. The skill stops in a project without the module. /adopt leaves it out of a packaged project, and /upgrade deletes a packaged project's committed copy when it's unchanged since the baseline. The plugin's hooks and skills now name /aplyca-adf:dispatch. The static checks hold the plugin to carrying it, and check-packaged.sh checks a packaged project commits no copy on a release whose plugin has it. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 27 ++++ CLAUDE.md | 4 +- docs/SETUP.md | 14 +- docs/SKILLS-REFERENCE.md | 2 +- docs/UPGRADING.md | 4 +- docs/decisions/0016-packaged-install.md | 2 +- .../0020-every-task-through-dispatch.md | 18 ++- docs/decisions/README.md | 2 +- evals/dynamic/check-packaged.sh | 6 +- evals/static/check-skills.sh | 4 + modules/parallel-agents/MODULE.md | 4 + plugins/aplyca-adf/.generated | 1 + plugins/aplyca-adf/README.md | 9 +- plugins/aplyca-adf/hooks/protect-hub.sh | 2 +- plugins/aplyca-adf/hooks/session-context.sh | 6 +- plugins/aplyca-adf/skills/adopt/SKILL.md | 6 +- plugins/aplyca-adf/skills/dispatch/SKILL.md | 124 ++++++++++++++++++ plugins/aplyca-adf/skills/handoff/SKILL.md | 2 +- .../aplyca-adf/skills/spec-workflow/SKILL.md | 2 +- plugins/aplyca-adf/skills/upgrade/SKILL.md | 17 ++- scripts/build-aplyca-adf.sh | 23 +++- 21 files changed, 243 insertions(+), 36 deletions(-) create mode 100644 plugins/aplyca-adf/skills/dispatch/SKILL.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 4508e5f..7048d5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,33 @@ option, without `/dispatch`, is still a worker. - Nothing to migrate. A project whose worktrees need the scripts now takes every task through them; ask for a chip when a task won't run the app. +### The plugin carries `/dispatch` + +([0020](docs/decisions/0020-every-task-through-dispatch.md), amending [0016](docs/decisions/0016-packaged-install.md)) + +`/dispatch` was the one framework skill a packaged project still committed, because it ships in a +module, and decision 0016 keeps modules committed. It's generic machinery like the 20 skills the +plugin already carries, so the plugin now carries it too, as `/aplyca-adf:dispatch`. + +- **`scripts/build-aplyca-adf.sh`** copies every module's skills into the plugin. The module stays + their one source, so a committed install doesn't change. +- **The skill stops in a project without the `parallel-agents` module,** so the plugin can carry it + for every project. +- **`/aplyca-adf:adopt`** leaves the skill out of a packaged project. **`/aplyca-adf:upgrade`** + deletes a packaged project's committed copy when it's unchanged since the baseline. +- The plugin's hooks and skills name it `/aplyca-adf:dispatch`, like every other skill they name. + +The module's scripts, `worktree.conf`, `.worktreeinclude`, and `PARALLEL-AGENTS.md` stay committed: +they're the project's configuration, a doc it fills in, and scripts developers run from their own +terminal. + +**Upgrade impact:** + +- **Packaged projects with the `parallel-agents` module:** delete `.claude/skills/dispatch/`, and type + `/aplyca-adf:dispatch`. `/aplyca-adf:upgrade` deletes it when it's unchanged since your baseline. + One your team edited stays under a name of its own, or goes upstream. +- **Committed projects:** nothing changes. + ## v1.2.1 — 2026-10-05 — Drift the prompt audit found A patch release: fixes to the framework's instruction files, with nothing new to adopt diff --git a/CLAUDE.md b/CLAUDE.md index 63ed022..b55d935 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,7 +17,7 @@ The repo slug is `AgenticDevelopmentFramework` (renamed from `ai-dev-starter-kit - `skeleton/specs/` — `README.md` (the process) and `_templates/{spec,plan,tasks}.md` - `skeleton/docs/` — constitution, spec model, process (PDRs), reference, tracker integration, and documentation templates - `modules/` — optional additions (`github/`, `git-hooks/`, `clickup/`, `parallel-agents/`); each has a `MODULE.md` and a `files/` tree mirroring the target repo -- `plugins/aplyca-adf/` — the Claude Code plugin: the installer (`/aplyca-adf:adopt`, `:upgrade`, `:cost-report`, written by hand) and, for the packaged install (decisions 0016, 0017, 0019), the skeleton's skills, agents, workflows, hook scripts, and four reference docs — **generated** by `scripts/build-aplyca-adf.sh` into the paths its `.generated` file lists; never edit those by hand +- `plugins/aplyca-adf/` — the Claude Code plugin: the installer (`/aplyca-adf:adopt`, `:upgrade`, `:cost-report`, written by hand) and, for the packaged install (decisions 0016, 0017, 0019, 0020), the skeleton's skills, agents, workflows, hook scripts, and four reference docs, and the modules' skills (`/dispatch`) — **generated** by `scripts/build-aplyca-adf.sh` into the paths its `.generated` file lists; never edit those by hand - `ADOPT.md` — the adoption procedure for AI agents, which the top of `README.md` points to; keep it in step with `/adopt` - `docs/` — framework guides (setup, upgrading, onboarding, catalogs, examples, scenarios) and `docs/decisions/` (why the framework works the way it does) - `evals/` — static checks, hook and module functional tests, dynamic fixtures @@ -32,6 +32,6 @@ The repo slug is `AgenticDevelopmentFramework` (renamed from `ai-dev-starter-kit - No symlinks anywhere in the repository — the Claude Directory's checks reject them. A link a project needs (Antigravity's `.agents/skills`) is created by `/adopt` - Relative links inside `skeleton/` must resolve inside an adopting repo — never link to framework-only docs from the skeleton - Every change to `skeleton/` or `modules/` carries a `CHANGELOG.md` entry with its **Upgrade impact** (overwrite / merge / additive, plus migration steps when needed); significant design changes get a record in `docs/decisions/` -- After any change under `skeleton/.claude/`, or to a reference doc the plugin carries (`COST-MODEL.md`, `MCP-INTEGRATION.md`, `MEMORY-STRATEGY.md`, `SPEC-MODEL.md` in `skeleton/docs/`), run `scripts/build-aplyca-adf.sh` and commit `plugins/aplyca-adf/` with it — the static checks fail on drift. The plugin's `"version"` changes only in a release (decision 0017) +- After any change under `skeleton/.claude/`, to a module's skill (`modules/*/files/.claude/skills/`), or to a reference doc the plugin carries (`COST-MODEL.md`, `MCP-INTEGRATION.md`, `MEMORY-STRATEGY.md`, `SPEC-MODEL.md` in `skeleton/docs/`), run `scripts/build-aplyca-adf.sh` and commit `plugins/aplyca-adf/` with it — the static checks fail on drift. The plugin's `"version"` changes only in a release (decision 0017) - Run `./evals/run-evals.sh` before committing — structural checks plus functional tests of the hooks and module scripts; CI runs the same on every pull request - The repo is public — never include client, customer, or internal project names anywhere (files, examples, commit messages, PR descriptions); use the fictional newsletter feature from `docs/examples/` instead diff --git a/docs/SETUP.md b/docs/SETUP.md index b42d5eb..3e12881 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -174,9 +174,11 @@ Open the pull request as a draft; merge after review like any other change. ## Packaged install (Claude Code only) [Decision 0016](decisions/0016-packaged-install.md). The framework's machinery doesn't enter the -repository: 20 skills, 8 agents, 4 workflows, the hook scripts, and the framework's reference docs -([decision 0019](decisions/0019-reference-docs-in-the-plugin.md)) come from the `aplyca-adf` plugin, -pinned to a release tag. The repository commits its own layer as above, and every module's files. +repository: 20 skills, 8 agents, 4 workflows, the hook scripts, the framework's reference docs +([decision 0019](decisions/0019-reference-docs-in-the-plugin.md)), and the `parallel-agents` +module's `/dispatch` ([decision 0020](decisions/0020-every-task-through-dispatch.md)) come from the +`aplyca-adf` plugin, pinned to a release tag. The repository commits its own layer as above, and +every module's other files. Choose it when the team works in Claude Code only. Cursor, Copilot, and Gemini users would get `AGENTS.md` and the rules but no skills, and Claude Code's cloud sessions don't load the plugin. @@ -189,8 +191,10 @@ v1.0.0 or later; its entry in [`CHANGELOG.md`](../CHANGELOG.md) says what it bri 1. **Copy less** (step 1). Leave out `.claude/skills/`, `.claude/agents/`, `.claude/workflows/`, the scripts and helpers in `.claude/hooks/` (keep `config.sh`), `.claude/hooks/README.md`, `GEMINI.md`, `.agents/`, `.cursor/`, and the four reference docs: `docs/COST-MODEL.md`, - `docs/MCP-INTEGRATION.md`, `docs/MEMORY-STRATEGY.md`, and `docs/SPEC-MODEL.md`. Modules copy as usual: `/dispatch` is the one - skill a packaged repository commits. Then point the files that name the reference docs at the + `docs/MCP-INTEGRATION.md`, `docs/MEMORY-STRATEGY.md`, and `docs/SPEC-MODEL.md`. Modules copy as + usual, but leave out `.claude/skills/dispatch/` when the release's plugin carries it + (`plugins/aplyca-adf/skills/dispatch/` in the framework copy); an older release's plugin doesn't, + so copy it there. Then point the files that name the reference docs at the release you pin, from the framework copy you took the skeleton from: `python3 /scripts/link-reference-docs.py . --packaged v`. 2. **Wire the plugin, not the hooks** (step 5). Drop the `hooks` block from `.claude/settings.json`, diff --git a/docs/SKILLS-REFERENCE.md b/docs/SKILLS-REFERENCE.md index 6c35f33..cf9a4ac 100644 --- a/docs/SKILLS-REFERENCE.md +++ b/docs/SKILLS-REFERENCE.md @@ -51,7 +51,7 @@ In order, for a change with something to decide. Commits: `spec:` → `docs:` | Skill | Module | Purpose | |---|---|---| -| `/dispatch` | `parallel-agents` | In the main checkout, every task: name it and hand it to a new session in a worktree of its own, by the project's route — Claude Code's worktree through a task chip (desktop app) or a `claude --worktree` command, or, when the project's worktrees need a port, setup or start commands, or a non-default base branch, the scripts' worktree (`worktree-new.sh --no-start`) with a three-line prompt to paste. The developer can pick the other route for one task. Reads only; no analysis, no edits. | +| `/dispatch` | `parallel-agents` | In the main checkout, every task: name it and hand it to a new session in a worktree of its own, by the project's route — Claude Code's worktree through a task chip (desktop app) or a `claude --worktree` command, or, when the project's worktrees need a port, setup or start commands, or a non-default base branch, the scripts' worktree (`worktree-new.sh --no-start`) with a three-line prompt to paste. The developer can pick the other route for one task. Reads only; no analysis, no edits. A packaged install gets it from the plugin, as `/aplyca-adf:dispatch`. | ## Dynamic workflows diff --git a/docs/UPGRADING.md b/docs/UPGRADING.md index 8325d7d..374a60b 100644 --- a/docs/UPGRADING.md +++ b/docs/UPGRADING.md @@ -261,7 +261,9 @@ old plugin from each machine that installed it: A packaged project ([decision 0016](decisions/0016-packaged-install.md)) doesn't commit the skills, agents, workflows, hook scripts, or the framework's reference docs -([decision 0019](decisions/0019-reference-docs-in-the-plugin.md)): they come from the `aplyca-adf` +([decision 0019](decisions/0019-reference-docs-in-the-plugin.md)), nor the `parallel-agents` +module's `/dispatch` once its pinned release's plugin carries it +([decision 0020](decisions/0020-every-task-through-dispatch.md)): they come from the `aplyca-adf` plugin, pinned to a release tag in `.claude/settings.json`. Upgrading it means two things: - **Bump the pin:** the marketplace's `"ref"` moves to the new release tag, `vX.Y.Z`. That one line upgrades diff --git a/docs/decisions/0016-packaged-install.md b/docs/decisions/0016-packaged-install.md index a03229d..1d04641 100644 --- a/docs/decisions/0016-packaged-install.md +++ b/docs/decisions/0016-packaged-install.md @@ -1,6 +1,6 @@ # 0016: A packaged install — the framework's machinery from its pinned plugin, `aplyca-adf` (opt-in, Claude Code only) -- **Status:** accepted; amended by [0018](0018-packaged-by-default.md) (the packaged install is the default) and [0019](0019-reference-docs-in-the-plugin.md) (the reference docs come from the plugin) +- **Status:** accepted; amended by [0018](0018-packaged-by-default.md) (the packaged install is the default), [0019](0019-reference-docs-in-the-plugin.md) (the reference docs come from the plugin), and [0020](0020-every-task-through-dispatch.md) (so does a module's skill, `/dispatch`) - **Date:** 2026-10-02 - **Amends:** [0009](0009-optional-modules.md) — what ships as committed files diff --git a/docs/decisions/0020-every-task-through-dispatch.md b/docs/decisions/0020-every-task-through-dispatch.md index defd6ce..6ddf869 100644 --- a/docs/decisions/0020-every-task-through-dispatch.md +++ b/docs/decisions/0020-every-task-through-dispatch.md @@ -2,7 +2,7 @@ - **Status:** accepted - **Date:** 2026-10-06 -- **Amends:** [0008](0008-dispatcher-and-worker-worktrees.md) — what the dispatcher takes; [0015](0015-tool-worktrees-are-workers.md) — who picks a task's route +- **Amends:** [0008](0008-dispatcher-and-worker-worktrees.md) — what the dispatcher takes; [0015](0015-tool-worktrees-are-workers.md) — who picks a task's route; [0016](0016-packaged-install.md) — a module's skill comes from the plugin ## Context @@ -45,6 +45,12 @@ checkout. A wrong guess also costs more one way than the other: In the project above, 7 of the last 40 merged changes touched only docs or agent-instruction files. The other 33 touched code, tests, migrations, CI, or dependencies. +**Where `/dispatch` lives.** Decision 0016 keeps every module's files committed, a rule written for the +scripts, templates, and configuration a project owns. `/dispatch` is none of those: it's generic +machinery, like the skills the plugin already carries, and it's the only skill any module ships. A +packaged project still committed it, the one framework skill in its repository, and upgraded it by +merging files while every other skill moved with the pin. + ## Decision - **Every task goes through `/dispatch` in the main checkout.** It names the task, takes the route, @@ -67,6 +73,14 @@ The other 33 touched code, tests, migrations, CI, or dependencies. on its own. - **The scripts' worktree is never handed over as a chip.** `/dispatch` and `PARALLEL-AGENTS.md` say why, so nobody switches the hand-off back to a chip. +- **The plugin carries the modules' skills.** `scripts/build-aplyca-adf.sh` copies each one from its + module, which stays the one source, so a committed install doesn't change. + - A packaged project commits no `.claude/skills/dispatch/`. `/aplyca-adf:upgrade` deletes a copy + that's unchanged since the baseline. + - The skill stops in a project without its module, as the plugin's hooks already do. The rest of + the module stays committed: `worktree.conf` is the project's configuration, + `PARALLEL-AGENTS.md` has a section each project fills in, and developers run the scripts from their + own terminal. ## Consequences @@ -82,6 +96,8 @@ The other 33 touched code, tests, migrations, CI, or dependencies. - **A pasted prompt per task in a project whose worktrees need the scripts,** docs-only tasks included, unless the developer asks for a chip. - **The one-click hand-off is the desktop app's.** A terminal gets a command to run. + - **A packaged project without the module lists `/aplyca-adf:dispatch`,** a skill it can't use. It + stops when called. ## Alternatives considered diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 054d540..6152030 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -26,7 +26,7 @@ projects. | [0013](0013-adapt-practices-not-a-second-workflow.md) | Adapt practices from other skill collections into our skills — never a second workflow | accepted | | [0014](0014-test-first-in-every-lane.md) | Test first in every lane | accepted | | [0015](0015-tool-worktrees-are-workers.md) | Worktrees that Claude Code creates are workers too (parallel-agents module) | accepted; amended by 0020 | -| [0016](0016-packaged-install.md) | A packaged install — the framework's machinery from its pinned plugin, `aplyca-adf` (opt-in, Claude Code only) | accepted; amended by 0018, 0019 | +| [0016](0016-packaged-install.md) | A packaged install — the framework's machinery from its pinned plugin, `aplyca-adf` (opt-in, Claude Code only) | accepted; amended by 0018, 0019, 0020 | | [0017](0017-semantic-versioning.md) | Releases follow semantic versioning | accepted | | [0018](0018-packaged-by-default.md) | The packaged install is the default | accepted | | [0019](0019-reference-docs-in-the-plugin.md) | A packaged project reads the framework's reference docs from the plugin | accepted | diff --git a/evals/dynamic/check-packaged.sh b/evals/dynamic/check-packaged.sh index db17657..b69c295 100755 --- a/evals/dynamic/check-packaged.sh +++ b/evals/dynamic/check-packaged.sh @@ -4,7 +4,8 @@ # install), pinned to : no framework machinery committed, the plugin pinned and turned on, no # hooks block, the stamp, and the names people type — and, on a release whose plugin carries the # reference docs (decision 0019), none of them in docs/, links to them at the release, and the rule that -# lets Claude read them. Prints ✓ or ✘ per check. Read-only. +# lets Claude read them; on one whose plugin carries /dispatch (decision 0020), no committed copy of it. +# Prints ✓ or ✘ per check. Read-only. # Usage: check-packaged.sh [] # cd "$1" || exit 1 @@ -33,4 +34,7 @@ if [ -n "$fw" ] && git -C "$fw" cat-file -e "$release:plugins/aplyca-adf/docs" 2 "grep -q 'blob/$release/skeleton/docs/SPEC-MODEL.md' AGENTS.md && ! grep -qE '(^|[^/.A-Za-z0-9_-])docs/($names)\.md' AGENTS.md CLAUDE.md CONTRIBUTING.md README.md specs/README.md specs/_templates/spec.md 2>/dev/null" check "the settings let Claude read the plugin's folder" "python3 -c 'import json; s = json.load(open(\".claude/settings.json\")); exit(0 if \"Read(~/.claude/plugins/cache/aplyca/aplyca-adf/**)\" in s.get(\"permissions\", {}).get(\"allow\", []) else 1)'" fi +if [ -n "$fw" ] && git -C "$fw" cat-file -e "$release:plugins/aplyca-adf/skills/dispatch/SKILL.md" 2>/dev/null; then + check "no committed \`/dispatch\` — the plugin carries it (decision 0020)" "[ ! -e .claude/skills/dispatch ]" +fi check "everything is committed" "[ -z \"\$(git status --short)\" ]" diff --git a/evals/static/check-skills.sh b/evals/static/check-skills.sh index bad88e7..836dc12 100755 --- a/evals/static/check-skills.sh +++ b/evals/static/check-skills.sh @@ -609,6 +609,10 @@ check_practices() { file_contains "$HOOKS_DIR/session-context.sh" 'branch name is generated' || missing+=("session-context.sh: Claude Code's own worktrees are workers, told what they lack (0015)") [ -f "$MODULES_DIR/parallel-agents/files/.worktreeinclude" ] || missing+=("parallel-agents: .worktreeinclude") file_contains "$MODULES_DIR/parallel-agents/files/.claude/skills/dispatch/SKILL.md" '## Which route' || missing+=("/dispatch: which route a task takes") + file_contains "$MODULES_DIR/parallel-agents/files/.claude/skills/dispatch/SKILL.md" 'Never a task chip here' || missing+=("/dispatch: never a chip for the scripts' worktree (0020)") + file_contains "$MODULES_DIR/parallel-agents/files/.claude/skills/dispatch/SKILL.md" 'worktree-new.sh` in this' || missing+=("/dispatch: stops without the module, since the plugin carries it (0020)") + file_contains "$REPO_ROOT/plugins/aplyca-adf/.generated" '^skills/dispatch$' || missing+=("the plugin carries /dispatch (0020)") + file_contains "$HOOKS_DIR/session-context.sh" 'Give every task to /dispatch' || missing+=("session-context.sh: the dispatcher gives every task to /dispatch (0020)") file_contains "$MODULES_DIR/parallel-agents/files/scripts/agent/worktree.conf" '^PORT_SLOTS=0 ' || missing+=("worktree.conf: ports off by default") file_contains "$AGENTS_MD" 'write or update the test that asserts the new behavior and watch it fail' || missing+=("AGENTS.md: the fast lane is test-first") file_contains "$SKELETON/.claude/rules/testing.md" '^## Red, then green — every change, in every lane' || missing+=("testing rule: red then green in every lane") diff --git a/modules/parallel-agents/MODULE.md b/modules/parallel-agents/MODULE.md index c82b5f1..e4147c0 100644 --- a/modules/parallel-agents/MODULE.md +++ b/modules/parallel-agents/MODULE.md @@ -38,6 +38,10 @@ chmod +x scripts/agent/*.sh A repository that already has a `.worktreeinclude` keeps it: add the env file's line to it. +With the packaged install, the `aplyca-adf` plugin carries `/dispatch` (decision 0020): remove the +copied `.claude/skills/dispatch/` and type `/aplyca-adf:dispatch`. The skill stops in a project +without the module, so the plugin can carry it for everyone. + ## Customize 1. **`scripts/agent/worktree.conf`** — at least `BASE_BRANCH`; `ENV_FILE` / `ENV_TEMPLATE` if the diff --git a/plugins/aplyca-adf/.generated b/plugins/aplyca-adf/.generated index 4bb4f10..d95146c 100644 --- a/plugins/aplyca-adf/.generated +++ b/plugins/aplyca-adf/.generated @@ -4,6 +4,7 @@ hooks skills/commit skills/context-audit skills/debug +skills/dispatch skills/evaluate skills/handoff skills/implement diff --git a/plugins/aplyca-adf/README.md b/plugins/aplyca-adf/README.md index f728ed4..7e08d39 100644 --- a/plugins/aplyca-adf/README.md +++ b/plugins/aplyca-adf/README.md @@ -5,8 +5,9 @@ The [Agentic Development Framework](../../README.md)'s plugin for Claude Code. I - **Install and maintain the framework** in a repository: `/aplyca-adf:adopt`, `/aplyca-adf:upgrade`, and `/aplyca-adf:cost-report`, in every project that uses the framework. - **Carry the framework's machinery for a packaged install** ([decision 0016](../../docs/decisions/0016-packaged-install.md)): - 20 skills, 8 agents, 4 workflows, the guardrail hooks, and the framework's reference docs, pinned - to a release. Typed as + 20 skills, 8 agents, 4 workflows, the guardrail hooks, the framework's reference docs, and the + `parallel-agents` module's `/dispatch` ([decision 0020](../../docs/decisions/0020-every-task-through-dispatch.md)), + pinned to a release. Typed as `/aplyca-adf:triage`, `/aplyca-adf:deep-review`, and so on. By default, an adopting repository uses the **packaged** install @@ -21,8 +22,8 @@ aside: its hooks stand down, and its skills and agents hand over to the committe where the stamp on `CLAUDE.md`'s first line says `install: packaged`. The machinery under `skills/` (except `adopt`, `upgrade`, and `cost-report`), `agents/`, -`workflows/`, `hooks/`, and `docs/` is generated from the skeleton by `scripts/build-aplyca-adf.sh`; -never edit it here. +`workflows/`, `hooks/`, and `docs/` is generated from the skeleton — and `skills/dispatch` from its +module — by `scripts/build-aplyca-adf.sh`; never edit it here. ## Install diff --git a/plugins/aplyca-adf/hooks/protect-hub.sh b/plugins/aplyca-adf/hooks/protect-hub.sh index 456b8ca..de8e9fd 100755 --- a/plugins/aplyca-adf/hooks/protect-hub.sh +++ b/plugins/aplyca-adf/hooks/protect-hub.sh @@ -19,4 +19,4 @@ root="$(repo_root_for "$file")" [ "$(git -C "$root" rev-parse --git-dir)" = "$(git -C "$root" rev-parse --git-common-dir)" ] || exit 0 -block "$root is the main checkout — the shared hub, where the dispatcher edits nothing. Give the task to /dispatch, which hands it to a new session in a worktree of its own, and make this change from that session." +block "$root is the main checkout — the shared hub, where the dispatcher edits nothing. Give the task to /aplyca-adf:dispatch, which hands it to a new session in a worktree of its own, and make this change from that session." diff --git a/plugins/aplyca-adf/hooks/session-context.sh b/plugins/aplyca-adf/hooks/session-context.sh index 4cf6337..04c3ce5 100755 --- a/plugins/aplyca-adf/hooks/session-context.sh +++ b/plugins/aplyca-adf/hooks/session-context.sh @@ -76,7 +76,7 @@ if [ -x "$root/scripts/agent/worktree-new.sh" ]; then main="$(dirname "$common_dir")" # a linked worktree's common dir is absolute:
/.git if [ "$checkout" = "main checkout" ]; then - echo "- Role: DISPATCHER. This is the shared main checkout — never edit here. Give every task to /dispatch: it hands the task to a new session in a worktree of its own." + echo "- Role: DISPATCHER. This is the shared main checkout — never edit here. Give every task to /aplyca-adf:dispatch: it hands the task to a new session in a worktree of its own." # The project's settings pick the route, not the task (decision 0020). if [ -n "$other_base" ]; then echo "- Route: the scripts' worktree, with a prompt to paste — tasks here start from $base_branch, and Claude Code's own worktrees start from $default_branch." @@ -105,9 +105,9 @@ if [ -x "$root/scripts/agent/worktree-new.sh" ]; then echo "- No $env_file here. Claude Code copies it into the worktrees it creates when .worktreeinclude lists it." fi if [ -n "$other_base" ]; then - echo "- Claude Code started this worktree from $default_branch, but tasks here start from $base_branch: ask the developer to /dispatch the task before the first commit." + echo "- Claude Code started this worktree from $default_branch, but tasks here start from $base_branch: ask the developer to /aplyca-adf:dispatch the task before the first commit." elif [ -n "$needs" ]; then - echo "- Not set up by scripts/agent/worktree-new.sh, so it lacks $needs: fine for work that doesn't run the app. To run it, ask the developer to /dispatch the task." + echo "- Not set up by scripts/agent/worktree-new.sh, so it lacks $needs: fine for work that doesn't run the app. To run it, ask the developer to /aplyca-adf:dispatch the task." fi fi fi diff --git a/plugins/aplyca-adf/skills/adopt/SKILL.md b/plugins/aplyca-adf/skills/adopt/SKILL.md index c630faf..2f9f918 100644 --- a/plugins/aplyca-adf/skills/adopt/SKILL.md +++ b/plugins/aplyca-adf/skills/adopt/SKILL.md @@ -141,10 +141,12 @@ Present the table before going further. Wrong facts here poison every file downs - `git-hooks` — when the team wants local gates for every git client - `clickup` — when requirements arrive as ClickUp tasks (`app.clickup.com` links in pull requests, commits, or the README are good evidence) - - `parallel-agents` — recommend it whenever several agent sessions may work on the repository at once: each task gets its own worktree, branch, pull request, and session, and the main checkout only dispatches (ports and start commands only if each worktree runs a server) + - `parallel-agents` — recommend it whenever several agent sessions may work on the repository at once: each task gets its own worktree, branch, pull request, and session, and the main checkout only dispatches, every task through `/dispatch` (ports and start commands only if each worktree runs a server) Install each chosen one with `cp -R modules//files/. /` (same no-overwrite rule) — except `clickup`, which merges into `.mcp.json` and `.claude/settings.json`: - `modules/clickup/install.sh `. + `modules/clickup/install.sh `. Packaged: leave out a module's `.claude/skills/` when the + pinned release's plugin carries it (`/plugins/aplyca-adf/skills/dispatch/`, + decision 0020). - Make sure `.gitignore` covers `.env` files, `.claude/settings.local.json`, `CLAUDE.local.md`, and `.claude/worktrees/`. diff --git a/plugins/aplyca-adf/skills/dispatch/SKILL.md b/plugins/aplyca-adf/skills/dispatch/SKILL.md new file mode 100644 index 0000000..0dff047 --- /dev/null +++ b/plugins/aplyca-adf/skills/dispatch/SKILL.md @@ -0,0 +1,124 @@ +--- +name: dispatch +description: In the main checkout, hand every task to a new session in a worktree of its own — name the task, take the route the project's settings give (Claude Code's worktree, through a task chip in the desktop app or a claude --worktree command; or the scripts' worktree, with a prompt for the developer to paste), and point the worker at the process. Does no analysis and edits nothing. Use for every task that arrives in the main checkout of a project with the parallel-agents module. +argument-hint: "[tracker link or task description] [optional: chip | scripts]" +--- + +> **Step 0 — which copy.** This is the packaged copy ([decision 0016](https://github.com/aplyca/AgenticDevelopmentFramework/blob/main/docs/decisions/0016-packaged-install.md)). Unless this project's `CLAUDE.md` says "This project uses the packaged install", stop here: open `.claude/skills/dispatch/SKILL.md` and follow that file instead — it's the version this project upgraded to. If it doesn't exist, the project doesn't use this skill: say so and stop. + +# Dispatch + +The main checkout is the shared hub where every developer and agent session starts. **In the main +checkout you dispatch every task; you never work.** Editing there blocks every other session, and +analysis done there is wasted: the dispatcher can't run the app or the tests, its conclusions are +unverified, and the worker in the worktree has to redo the reading anyway — now with tools that can +check it. The dispatcher's context stays cheap: a branch name, not a plan. + +A dispatcher **writes nothing outside this machine** and takes no outward-facing action. Its only +network calls are reads: the task in step 1, and the `git fetch` inside the worktree script. + +This skill needs the `parallel-agents` module: without `scripts/agent/worktree-new.sh` in this +repository, say the module isn't installed and stop. + +## Which route + +The project picks the route, not the task (decision 0020). Whether a task will run the app is a +question for its triage, after the hand-off, and a wrong guess here costs a second dispatch. Read the +route from `scripts/agent/worktree.conf`; the session-context line at the start of this session names +it too: + +| The project's worktrees need | Route | +|---|---| +| Nothing from the scripts: `PORT_SLOTS=0`, no `SETUP_CMD` or `START_CMD`, and `BASE_BRANCH` is the default branch | **Claude Code's worktree** — one click in the desktop app | +| A port, or setup or start commands | **The scripts' worktree** — a prompt the developer pastes | +| A `BASE_BRANCH` other than the default branch | **The scripts' worktree**, for every task — Claude Code's worktrees start from the default branch | + +**The developer can pick the other route for one task**, in words ("use a chip for this one") or as +the argument (`chip`, `scripts`). They know the task: a copy change in a project whose worktrees run a +server never needs the server. Take their route and say what that worktree will lack — a chip's has no +port and no setup. Refuse only a chip where tasks start from another branch, and say why. Never pick +the other route on your own. + +## Steps + +1. **Name the task — nothing more.** Read only enough of the task to know its title and type + (`feat`, `fix`, `chore`, `docs`, `refactor` — `hotfix` where the branching model uses it). No code + reading, no requirement analysis, no planning. Choose a short kebab-case slug. If the task names + a delivered feature, start the slug with that feature's spec-folder slug and add the change + (`feat/newsletter-signup-topics`) — a directory listing (`ls specs/`) is enough; don't open the + files. Whether it really is a change request is the worker's triage to decide. + +2. **Hand it over by the route.** + - **Claude Code's worktree.** In the desktop app, offer the task as a task chip — the app's way to + start work in a new session with a worktree of its own. Its title is the task's; its prompt is + the worker prompt (step 3). The developer starts it with one click, and this session carries on. + In a terminal, give the developer one command to run in a new terminal: + ```bash + claude --worktree "" + ``` + - **The scripts' worktree.** Create it: + ```bash + scripts/agent/worktree-new.sh / --no-start + ``` + It creates a sibling worktree named after the branch (`../feat-newsletter-signup-topics`) on a + new branch from the base branch — or reports the existing worktree unchanged. `--no-start` is + deliberate: the environment belongs to the worker, which starts it only if its triage says a + step needs it (`scripts/agent/worktree-new.sh ` from the worktree). A hotfix from a + release tag adds `--from `. + + Then give the developer the worktree's path on a line of its own and the worker prompt in a code + block, to paste into a new session opened on that folder: in the desktop app, a new session with + the worktree as its folder and the worktree option off; in a terminal, + `cd && claude ""`. **Never a task chip here**, nor the worktree + option: both create a worktree of their own under `.claude/worktrees/`, without this one's env + file, port, or setup — even when given this worktree as their folder. + +3. **Write the worker's prompt** — pointers, not instructions: + ``` + Task: + Worktree: — branch / + Follow AGENTS.md end to end, starting with triage. + ``` + For Claude Code's worktree the app names the branch, so the second line is + `Branch: / — rename the generated branch to it after triage`. + Don't restate the workflow: the worker reads it in `AGENTS.md`, and a copy in a handoff message is + one more thing that drifts. If the developer set a lane or asked for more effort, pass it on in + their words — `Developer: full lane — the billing rules are fragile` — since triage honors it. + +The hand-off ends the task's time here. The next task needs nothing from this conversation, so +clearing it (`/clear`) between dispatches keeps the hub's session cheap. + +## Rationalizations (do not accept these) + +| Agent says... | Why it's wrong | +|---|---| +| "I'll read the task properly first so the worker gets a head start" | The worker redoes the reading where it can verify it. Analysis here is unverified, burns the hub's context, and drifts in the handoff. | +| "It's a one-line fix — I'll just do it here" | The main checkout is shared. A stray edit or a running server here collides with every other session. | +| "This one won't run the app, so a chip will do" | Whether it runs the app is its triage's question. A wrong chip means a second dispatch and a new session; the scripts' worktree costs one paste. The developer can choose the chip — you don't. | +| "I'll offer a chip and give it the scripts' worktree as its folder" | A chip always creates a worktree of its own, without the env file, port, or setup. Hand over the prompt to paste. | +| "I'll start the environment so it's ready" | The worker may not need one (an answer-only task). It decides after triage. | +| "I'll open a placeholder issue or take the next spec number for the branch" | That's an outward-facing write, and numbers collide between parallel dispatchers. The slug is the join. | +| "I'll put the full workflow in the handoff to be safe" | Restated steps drift from `AGENTS.md`. Point at the process; don't copy it. | + +## Red flags (stop and reassess) + +- You've opened source files, specs, or tests in the main checkout. +- `git status` in the main checkout shows changes you made — or the protect-hub hook stopped an edit: + you were about to work in the hub. +- The handoff prompt contains analysis, a plan, or a list of files to change. +- The route isn't the one the project's settings give, and the developer didn't ask for it. + +## Verification + +- [ ] Only the task's title and type were read +- [ ] The route is the project's, or the one the developer asked for — never a chip where tasks start from another branch +- [ ] The scripts' worktree was created (or reported) by `worktree-new.sh --no-start` — never by raw `git worktree add` — and handed over as a prompt to paste, never as a chip +- [ ] Nothing was edited, committed, or posted from the main checkout +- [ ] The worker prompt has the task link, the worktree path or the branch to rename to, and "follow AGENTS.md, starting with triage" — nothing else + +## Principles + +- The hub dispatches every task; worktrees work. +- The project picks the route; the developer can pick the other one for a task. +- Reads only, no writes, no analysis. +- Point at the process; never restate it. diff --git a/plugins/aplyca-adf/skills/handoff/SKILL.md b/plugins/aplyca-adf/skills/handoff/SKILL.md index 6941556..f9e85f1 100644 --- a/plugins/aplyca-adf/skills/handoff/SKILL.md +++ b/plugins/aplyca-adf/skills/handoff/SKILL.md @@ -22,7 +22,7 @@ incomplete handoff strands whoever receives it: they either ask, or guess. spec folder already carries everything — point at it). Work that stays here needs no handoff: continue, or `/compact` with what the next phase needs (`${CLAUDE_PLUGIN_ROOT}/docs/COST-MODEL.md` § Between phases). A side task found mid-work isn't a handoff either: note it for the developer — or, with the - parallel-agents module, `/dispatch` it to its own worktree. + parallel-agents module, `/aplyca-adf:dispatch` it to its own worktree. 2. **Put the state in the record first.** Commit finished tasks (`/aplyca-adf:commit`) and tick them in `tasks.md`. Write decisions taken in this session where they last: the spec's Clarifications or diff --git a/plugins/aplyca-adf/skills/spec-workflow/SKILL.md b/plugins/aplyca-adf/skills/spec-workflow/SKILL.md index b448b49..422b057 100644 --- a/plugins/aplyca-adf/skills/spec-workflow/SKILL.md +++ b/plugins/aplyca-adf/skills/spec-workflow/SKILL.md @@ -116,7 +116,7 @@ describes the old way in the same pull request. Constitution amendments get thei When several agent sessions run at once, each works in its own git worktree on its own branch — never two sessions in one checkout. With the parallel-agents module installed, the main checkout is a -**dispatcher** only (`/dispatch`): it names every task and hands it to a new session in a worktree +**dispatcher** only (`/aplyca-adf:dispatch`): it names every task and hands it to a new session in a worktree of its own; the **worker** there does everything from triage onward. Passing work in progress to a teammate, another machine, or a fresh session is `/aplyca-adf:handoff`: pointers to the record, never a copy. diff --git a/plugins/aplyca-adf/skills/upgrade/SKILL.md b/plugins/aplyca-adf/skills/upgrade/SKILL.md index 7eb13d3..064bf16 100644 --- a/plugins/aplyca-adf/skills/upgrade/SKILL.md +++ b/plugins/aplyca-adf/skills/upgrade/SKILL.md @@ -90,14 +90,16 @@ ask, and never discard it. **Offer the other install, when it fits** (decisions 0016, 0018; `docs/SETUP.md` § Packaged install): - **Committed → packaged** — recommend it to a team that works in Claude Code only, since packaged is - the default for new projects: remove the skills, agents, - workflows, hook scripts, and reference docs the plugin carries — only those unchanged since OLD_SHA; + the default for new projects: remove the skills (a module's `/dispatch` included, when the plugin + carries it), agents, workflows, hook scripts, and reference docs the plugin carries — only those + unchanged since OLD_SHA; one the team edited stays, under a name of its own, or goes upstream — and the `hooks` block. Add the pinned marketplace and `aplyca-adf`, the names note in `CLAUDE.md`, the full names in `DEV-SETUP.md`'s key commands, and `install: packaged` in the stamp, then add the plugin's read rule and run `link-reference-docs.py --packaged`, both in Step 5. - **Packaged → committed**, when the team adds another AI tool or needs Claude Code's cloud sessions: - copy the machinery, the reference docs, and the `hooks` block back, remove `install: packaged`, the + copy the machinery (with the installed modules' skills), the reference docs, and the `hooks` block + back, remove `install: packaged`, the names note, the plugin's read rule, and the `aplyca-adf:` prefix in `DEV-SETUP.md`, and run `python3 /scripts/link-reference-docs.py --committed`. Keep `aplyca-adf` turned on and pinned, for `/aplyca-adf:upgrade`. @@ -127,8 +129,9 @@ teammates still on the old name have no `/upgrade`. `git -C diff --name-status OLD_SHA NEW_SHA -- skeleton/ modules//files/` gives the changed set (module paths map into the repo by dropping `modules//files/`). In a -packaged project, leave out what the plugin carries — `.claude/skills/` (module skills stay), -`.claude/agents/`, `.claude/workflows/`, `.claude/hooks/` except `config.sh`, and, when the new +packaged project, leave out what the plugin carries — `.claude/skills/` (a module's skill too, when +the new release's plugin has it in `/plugins/aplyca-adf/skills/`, as it has +`dispatch` from decision 0020 on), `.claude/agents/`, `.claude/workflows/`, `.claude/hooks/` except `config.sh`, and, when the new release carries them in `/plugins/aplyca-adf/docs/`, the reference docs in `docs/` (decision 0019) — and never add a `hooks` block to the settings. Classify per the taxonomy in `docs/UPGRADING.md`: @@ -179,6 +182,10 @@ vs the OLD_SHA version) and confirm they will survive. Wait for approval. `.claude/settings.json` if it isn't there: the plugin's skills and agents read the docs from its folder, and Claude Code asks first without it. Add the last sentence of `docs/SETUP.md`'s names note to the project's, if it lacks it. +- Packaged, with the parallel-agents module, on a release whose plugin carries `/dispatch` + (`/plugins/aplyca-adf/skills/dispatch/`): delete the committed + `.claude/skills/dispatch/` when it's unchanged since OLD_SHA. One the team edited is the + developer's call — under a name of its own, or upstream as a change to the framework. - Restamp: `Skeleton source:` → ` · NEW_SHA () · modules: ` — the list includes the new ones; a packaged project keeps `· install: packaged`. diff --git a/scripts/build-aplyca-adf.sh b/scripts/build-aplyca-adf.sh index e21c650..30e2580 100755 --- a/scripts/build-aplyca-adf.sh +++ b/scripts/build-aplyca-adf.sh @@ -2,7 +2,8 @@ # # Builds the packaged half of plugins/aplyca-adf (docs/decisions/0016-packaged-install.md) from # skeleton/.claude/: the core skills, the agents as flat files, the workflows, and the hook scripts -# with their hooks.json — and, from skeleton/docs/, the framework's reference docs (decision 0019). Claude Code puts everything a plugin carries under the plugin's name, so the +# with their hooks.json — and, from skeleton/docs/, the framework's reference docs (decision 0019), +# and from modules/*/files/.claude/skills/, the modules' skills (decision 0020). Claude Code puts everything a plugin carries under the plugin's name, so the # copies name each other that way: `/triage` becomes `/aplyca-adf:triage`, `@code-reviewer` becomes # `@aplyca-adf:code-reviewer`. The hooks read the project's .claude/hooks/config.sh. The copies act only # in a packaged project: each skill and agent opens with a step that hands over to the committed copy @@ -31,9 +32,17 @@ PLUGIN = "aplyca-adf" skills = sorted(os.listdir(os.path.join(src, "skills"))) agents = sorted(os.listdir(os.path.join(src, "agents"))) workflows = sorted(f[:-3] for f in os.listdir(os.path.join(src, "workflows")) if f.endswith(".js")) +# A module's skills are machinery like the core's (decision 0020): the plugin carries them, and each +# one stops in a project without its module. The module stays their one source. +module_skills = {} +for module in sorted(os.listdir(os.path.join(root, "modules"))): + folder = os.path.join(root, "modules", module, "files", ".claude", "skills") + for name in sorted(os.listdir(folder)) if os.path.isdir(folder) else []: + assert name not in skills and name not in module_skills, f"two skills are named {name}" + module_skills[name] = os.path.join(folder, name) # A name counts only on its own: not inside a path (skills/review/SKILL.md), a URL, or a longer name. -command = re.compile(r"(?