Skip to content

Efforts, waves & worktrees

How cckit runs many issues at once without the changes colliding.

A big piece of work is an effort. cckit breaks it into sub-issues, then sorts them into waves so the ones that don’t depend on each other run together, and the ones that do wait their turn.

An effort is one goal that’s too big for a single issue. It becomes a parent issue with sub-issues under it — each sub-issue is one shippable step. cckit effort new creates the effort and its subs: it fills the parent’s four-section plan body (Goal / Scope / For agents / Verification), applies the ctx/kind/priority/role/flow labels, and lints every title. The /kit-effort-new skill produces the identical effort — both call one shared core, so there is no drift between the verb and the skill.

The GitHub issue number is the canonical key for an effort — the sub-issue links, PRs, labels, and dependencies all reference it. But a bare number is hard to remember, so every effort also has a memorable slug handle: the one that already lives in its effort/<N>-<slug> branch.

cckit effort start, pr, and close accept the slug or the number interchangeably:

Terminal window
cckit effort new "Slug handles for efforts" --slug slug-handles # optional explicit handle
cckit effort start slug-handles # identical to: cckit effort start 93
cckit effort pr slug-handles
cckit effort close slug-handles

A pure-digits argument is always treated as a number; anything else is resolved to the canonical number by matching the effort/* branches (local and remote), then the slug:<slug> label, then the titles of open efforts. An unknown or ambiguous slug fails with a clear error instead of guessing the wrong effort. Listings and titles render efforts as slug #N.

  • cckit effort new — flags (--flow, --role, --priority, --par, …) may appear in any position, before or after the name and sub specs. --par seq | wide | <N> records how the effort’s subs are meant to run as a par: label on the parent. The --flow values (the [Flow] title tags) come from a controlled vocabulary your project sets once as effort.flows in cckit.config.json / .claude/kit.config.json — e.g. "effort": { "flows": ["Growth", "Checkout"] }. Unset, it defaults to Core UI API Docs Infra Auth Data Web App; an explicit EFFORT_FLOWS env var still overrides both for one invocation.
  • cckit effort start gives the effort’s worktree the same full setup as cckit start: it copies your gitignored env files, assigns a per-worktree dev port, and installs dependencies (opt out with KIT_WT_INSTALL=0). The worktree lives at .claude/worktrees/effort+<N>-<slug>, a name cckit gc recognizes so it is never pruned while the effort issue is open.
  • cckit effort pr and the /kit-effort-pr skill compose the PR title from one shared composer, and both refuse to open a PR whose title lacks the mandatory [#N] effort id.
  • cckit effort close is the single close implementation — the /kit-effort-close skill is a thin caller of the same function, so the two can never drift apart. It snapshots each sub-issue’s diff and captures effort metrics before the squash, refuses to squash-merge when no work trace was captured (the squash is irreversible — override with KIT_FORCE=1), squash-merges, closes the parent and every sub and sets the board Status=Done for each, garbage-collects the worktree/branch, runs an optional, config-gated knowledge-ingest hook (effort.knowledgeIngestHook), and finishes with an advisory kit-sync drift check that flags any kit-managed files the effort touched (they belong upstream via /kit-contribute).
  • Wave-driven efforts close too. A wave run ships each sub as its own task PR squash-merged straight to the base branch, so no effort/<N> integration branch ever exists. cckit effort close <N> detects that (no such branch locally or on origin) and closes wave-style: it verifies every sub is closed with a merged PR (refusing and listing the stragglers otherwise), snapshots the work trace from the merged sub PR diffs instead of branch commits, and then runs the same close tail — metrics judge/sync, parent close, board Status=Done for parent and subs, the knowledge-ingest hook, and the kit-sync drift check. If the integration branch does exist, the close still asks you to run from it.

Some sub-issues depend on others: sub #4 can’t start until sub #1 is merged. cckit reads those blocked-by links and groups the work into waves:

  1. Wave 0 — every sub-issue that nothing is blocking. These can all run at the same time.
  2. Wave 1 — sub-issues whose blockers were all in wave 0. They start once wave 0 merges.
  3. …and so on, until everything is merged.

cckit plan shows the waves. Each wave is exactly “what can run in parallel right now”.

Running several issues at once raises one question: how do the changes not overwrite each other? The answer is a worktree — a separate working copy of the repo on disk, on its own branch.

Each issue gets its own branch and worktree, so each piece of work edits its own copy. The changes only meet again when their pull requests merge into main — and because the issues in a wave touch different files, they merge cleanly.

Independent, educational project — not affiliated with or endorsed by Anthropic. Claude and Claude Code are trademarks of Anthropic PBC. Disclaimer & trademarks ·

From Mexico with love by josegtz