Metadata-Version: 2.1
Name: cadence-runner
Version: 0.21.0
Summary: CLI tool for autonomous task execution via Claude Code
Keywords: claude,claude-code,cli,automation,agent
Author-Email: Mikhail Drozdetskiy <m.drozdetskiy@gmail.com>
License: MIT
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development
Classifier: Topic :: Utilities
Project-URL: Homepage, https://github.com/Drozdetskiy/cadence
Project-URL: Repository, https://github.com/Drozdetskiy/cadence
Project-URL: Issues, https://github.com/Drozdetskiy/cadence/issues
Requires-Python: >=3.14
Requires-Dist: typer>=0.9
Requires-Dist: rich>=13.0
Requires-Dist: PyYAML>=6.0
Description-Content-Type: text/markdown

# cadence

[![PyPI version](https://img.shields.io/pypi/v/cadence-runner.svg)](https://pypi.org/project/cadence-runner/) [![Python versions](https://img.shields.io/pypi/pyversions/cadence-runner.svg)](https://pypi.org/project/cadence-runner/) [![License](https://img.shields.io/pypi/l/cadence-runner.svg)](LICENSE) [![CI](https://github.com/Drozdetskiy/cadence/actions/workflows/ci.yml/badge.svg)](https://github.com/Drozdetskiy/cadence/actions/workflows/ci.yml) [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-261230.svg)](https://github.com/astral-sh/ruff)

Autonomous task-execution pipeline on top of [Claude Code](https://docs.anthropic.com/en/docs/claude-code).

Inspired by the [ralphex](https://ralphex.com/) project.

`cadence` drives Claude through a structured loop: plan → branch → iterative implementation → multi-agent code review → review-loop until clean. It is a thin orchestrator — Claude does the work, `cadence` keeps it on rails (signals, retries, idle/session timeouts, break/resume, per-phase models, git integration).

## What it does

| Subcommand | What happens |
|---|---|
| `cadence init <name>` | Scaffolds a new branch + `<tasks_root>/<name>/init` (plus `config.yaml` recording the parent branch when not on `default_branch`). No Claude. |
| `cadence run` | Infers the current branch's task directory and dispatches to the next phase (`run plan` if only `init` exists, `run task` if `plan` exists, "already completed" if `plan-completed` exists). |
| `cadence run plan` | Branch-bound plan creation: reads `<tasks_root>/<branch>/init` and writes `plan` next to it. |
| `cadence run task` | Branch-bound task execution: reads `<tasks_root>/<branch>/plan` and runs the full pipeline (branch already exists). |
| `cadence plan <path>` | Path-bound plan creation from an arbitrary file: interactive Q&A with Claude, draft review, final plan written to `<path>-plan.md` (or `plan` next to `init`). |
| `cadence task <path>` | Path-bound task execution on an arbitrary plan: branch created from plan filename, one `### Task N:` section per iteration, each completed and committed. |
| `cadence review [--base <branch>]` | Review-only of the current branch: first pass launches 4 parallel agents (quality, implementation, testing, simplification); subsequent passes loop on critical/major findings until no commits are produced. |
| `cadence squash` | Squashes all branch commits into one with a Claude-authored message summarizing the diff against the default branch. |
| `cadence chain <path>` | Reads an ordered list of task names from a file and runs each task end-to-end (plan → task → squash) on its own branch. Pre-flight checks every listed task has a directory + `init` file; fails fast and leaves the repo on the failing task's branch. |

Compose pipelines with shell `&&` — e.g. `cadence run && cadence run && cadence squash` to take a fresh task from `init` through plan, task, and squash.

Phases communicate with the runner via signal markers (e.g. `<<<CADENCE:PLAN_READY>>>`, `<<<CADENCE:ALL_TASKS_DONE>>>`, `<<<CADENCE:REVIEW_DONE>>>`, `<<<CADENCE:QUESTION>>>`, `<<<CADENCE:TASK_FAILED>>>`).

## Installation

```bash
# via Homebrew (macOS)
brew tap drozdetskiy/cadence
brew install drozdetskiy/cadence/cadence  # tap-qualified name avoids the homebrew-core `cadence` (Flow smart-contract language) clash

# via PyPI
pip install cadence-runner

# from source (for development)
pdm install
```

The PyPI distribution is named `cadence-runner`; the CLI binary is `cadence` regardless of how you installed it.

Requires:
- Python **3.14+** (Homebrew formula pulls this in automatically)
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) on `PATH`
- a git repository (the `task`, `run`, `review`, `squash`, and `chain` subcommands operate on the working tree)

Note: the `cadence init <name>` subcommand and the file named `init` inside the task directory are different things — `init` (subcommand) scaffolds a task, and `init` (file) is the free-form description Claude reads. The file name is configurable via `init_prompt_name` in `.cadence/config.yaml`.

## Usage

Tasks live in their own subdirectory under `cdc-tasks/<NNNN-slug>/` (configurable via `tasks_root`). The free-form description goes into a file named `init`; the generated plan is written next to it as `plan` (the `init` → `plan` mapping is configurable via `init_prompt_name`). A per-task `config.yaml` next to the prompt is auto-discovered.

```
cdc-tasks/
  0001-my-feature/
    init               # free-form task description (input)
    plan               # generated by `cadence run plan`, consumed by `cadence run task`
    config.yaml        # optional per-task overrides (auto-discovered)
```

```bash
# New task end-to-end (branch-bound, with auto-detect)
cadence init 0001-my-feature                  # scaffolds branch + cdc-tasks/0001-my-feature/init
$EDITOR cdc-tasks/0001-my-feature/init        # write the task description
cadence run && cadence run && cadence squash  # auto-detect: init → plan, plan → task, then squash

# Same flow, explicit phases
cadence run plan                              # init → plan
cadence run task                              # plan → task pipeline (branch + tasks + review)
cadence squash                                # one final commit summarizing the branch

# Plan from a third-party file (path-bound)
cadence plan path/to/spec.md                  # writes path/to/spec-plan.md (or `plan` next to `init`)

# Task on an existing plan (path-bound, creates the branch)
cadence task path/to/plan.md

# Review the current branch only (no plan, no branch creation)
cadence review
cadence review --base develop                 # override base branch

# Per-run overrides apply globally to any subcommand
cadence --config cdc-tasks/0001-my-feature/config.yaml run

# Chain of tasks (sequential plan → task → squash per task, each on its own branch)
cadence chain tasks.txt

# Misc
cadence --version
cadence --help
cadence run --help
```

Migration from the old flag-style CLI:

| Old | New |
|---|---|
| `cadence --plan <path>` | `cadence plan <path>` |
| `cadence --task <path>` | `cadence task <path>` |
| `cadence --review` | `cadence review` |
| `cadence --review --base X` | `cadence review --base X` |
| `cadence --task-init <name>` | `cadence init <name>` |
| `cadence --run` | `cadence run` |
| `cadence --run --impl` | `cadence run` (auto-detect) or explicit `cadence run plan && cadence run task` |
| `cadence --plan <path> --impl` | `cadence plan <path> && cadence task <derived-plan>` |
| `cadence --squash` | `cadence squash` |
| `cadence --task <p> --squash` | `cadence task <p> && cadence squash` |
| `cadence --chain <path>` | `cadence chain <path>` |

### Plan file format

Plans are markdown with a strict structure the runner parses on every iteration:

```markdown
# Title

## Overview
…

## Context
- Files involved: …

### Task 1: Setup
- [ ] step one
- [ ] step two
- [ ] write tests
- [ ] run test suite

### Task 2: …
- [ ] …
```

- `### Task N:` or `### Iteration N:` headers delimit work units.
- `- [ ]` / `- [x]` checkboxes inside Task sections drive progress.
- Checkboxes outside Task sections (Overview, Context, Success criteria) do not block completion.
- After a successful `task`/`run task` run the file is renamed in place to `<stem>-completed<ext>` (no commit — plan files are typically gitignored).

## Configuration

### Local config: `.cadence/config.yaml`

Project-scoped, no global config. Loaded from cwd (or `CADENCE_CONFIG_DIR`). All keys are optional — missing keys fall back to defaults.

```yaml
# Claude executor
claude_command: claude
claude_args: "--dangerously-skip-permissions --verbose"
plan_model:   claude-opus-4-7
task_model:   claude-opus-4-7
review_model: claude-opus-4-7

# Iteration / timing
iteration_delay_ms: 2000
task_retry_count:   1
max_iterations:     50
session_timeout:    "0"   # "30m", "1h30m", … 0 = disabled
idle_timeout:       "0"
wait_on_limit:      "0"   # >0 → retry on rate-limit instead of failing

# VCS / paths
tasks_root:     cdc-tasks # root for per-task subdirectories (init/plan/config.yaml)
default_branch: main      # override per-project in local config
commit_trailer: ""        # appended to all cadence-made commits
commit_format:  |         # appended to task/review prompts (default shown below)
  Format: a single line `<branch-name>. <Clause>: <what>.` where `<Clause>`
  is `Added`, `Changed`, or `Deleted`. Clauses joined by `. ` (period + space);
  items inside one clause joined by `; ` (semicolon + space). ...

# Output
colors:
  task: "#2e8b57"
  review: "#1a9e9e"
  warn: "#d4930d"
  error: "#cc0000"
```

See [`docs/config.md`](docs/config.md) for the full key reference (timeouts, error patterns, color palette).

### Per-run overrides: `--config`

The global `--config <path>` (placed before the subcommand: `cadence --config X.yaml run`) loads a YAML file that overrides per-phase models and/or `default_branch`. Each key is optional:

```yaml
plan:
  model: claude-opus-4-7
task:
  model: claude-opus-4-7
review:
  model: claude-opus-4-7
default_branch: develop
```

If `--config` is omitted, cadence auto-discovers `config.yaml` next to the plan/task file — typically `cdc-tasks/<NNNN-slug>/config.yaml` (no parent walk). For `review` (no plan/task file) auto-discovery is skipped — only an explicit `--config` is honored. An explicit path that does not exist is a hard error; an auto-discovered missing file is silently ignored. YAML parse errors are always fatal.

### Commit message format

`commit_format` is appended verbatim to every task and review prompt, telling Claude how to write the commit message. Plan creation does not commit, so the format is not added there.

The built-in default produces messages like:

```
0014-no-plan-commit-on-start. Changed: cadence no longer auto-commits the plan file when starting a task. Deleted: now-unused commit_plan_file; file_has_changes helpers.
```

Shape: a single line `<branch-name>. <Clause>: <what>.` where `<Clause>` is `Added`, `Changed`, or `Deleted`. Multiple clauses are joined by `. ` (period + space); multiple items inside one clause are joined by `; ` (semicolon + space). Include only the clauses that apply.

Override from `.cadence/config.yaml` with any free-form text. Example of a tighter restatement (the shipped default also includes Good/Bad examples and guidance about implementation details belonging in the diff — see `Config.commit_format` in `src/cadence/config.py` for the verbatim text):

```yaml
commit_format: |
  Format: a single line `<branch-name>. <Clause>: <what>.` where `<Clause>` is
  `Added`, `Changed`, or `Deleted`. Clauses joined by `. ` (period + space);
  items inside one clause joined by `; ` (semicolon + space). English.
  Each clause is one short item describing the user-visible outcome.
  Author as the user — no Co-Authored-By trailer (unless `commit_trailer` is configured).
```

Switch to Conventional Commits:

```yaml
commit_format: |
  Use Conventional Commits: <type>(<scope>): <subject>
  Types: feat, fix, refactor, docs, test, chore.
  Subject is imperative, lowercase, no trailing period, ≤72 chars.
  Example: feat(executor): add idle-timeout retry
```

If you need finer control than a free-form block (e.g. different wording per phase), drop a custom `task.txt` / `review_first.txt` / `review_second.txt` under `.cadence/prompts/` — the format block is appended to whatever prompt body you supply.

### Customizing prompts and agents

`cadence` ships with embedded defaults under `src/cadence/defaults/`. To customize, drop replacements into the project:

```
.cadence/
  config.yaml
  prompts/
    make_plan.txt        # overrides plan-creation prompt
    task.txt             # overrides task-iteration prompt
    review_first.txt     # overrides initial review prompt
    review_second.txt    # overrides review-loop prompt
  agents/
    quality.txt          # custom review agents (referenced as {{agent:quality}})
    implementation.txt
    testing.txt
    simplification.txt
    my-extra-agent.txt   # add new agents — auto-discovered
```

Per-file fallback: if a local file is empty or contains only `# comments`, the embedded default is used. Agents support optional YAML frontmatter:

```
---
model: sonnet         # haiku | sonnet | opus (or full IDs, normalized)
agent: code-reviewer  # subagent type for the Task tool
---
Agent prompt body…
```

Prompts can reference agents inline with `{{agent:name}}`; the runner expands these into full Task tool invocations, with base variables (`{{PLAN_FILE}}`, `{{DEFAULT_BRANCH}}`, etc.) substituted into the agent body.

## Runtime controls

- **Ctrl+C** — graceful shutdown (twice within 5s force-exits).
- **Ctrl+\\** (`SIGQUIT`, Unix) — break the current task; the runner kills the active Claude session and prompts to resume or abort. Resume restarts the same task with a fresh session and re-reads the plan file.
- **Rate limits** — if `wait_on_limit > 0` and Claude output matches `claude_limit_patterns`, cadence sleeps and retries indefinitely until cancellation.
- **Session / idle timeouts** — kill stuck sessions; review-loop iterations skip the no-commit detection if the previous session timed out.

## Project layout

```
src/cadence/
  cli.py            Typer entrypoint, subcommand dispatch (init/run/plan/task/review/squash/chain), signal handling
  config.py         Config dataclass, YAML loading, --config overrides
  status.py         Phase / Mode / Signal constants
  input.py          Interactive Q&A collector
  executor/         Claude subprocess + JSON-stream parsing
  git/              Service layer over `git` CLI
  plan/             Markdown plan parser, branch-name extraction
  processor/        Runner — orchestrates plan/task/review phases
  progress/         File+stdout logger with colors and flock
  defaults/
    prompts/        Embedded prompt templates
    agents/         Embedded review agents
```

Deeper module references live in [`docs/`](docs/): `config.md`, `processor.md`, `executor.md`, `git-and-plans.md`, `progress-and-input.md`.

## Development

```bash
make install     # pdm install --dev
make test        # pytest tests/ -v
make test-cov    # with coverage
make lint        # ruff check
make typecheck   # mypy --strict
make check       # lint + typecheck + test
```

Conventions:
- Python 3.14+, `mypy --strict`.
- `Protocol`-based interfaces for all `Runner` dependencies (Executor, Logger, InputCollector, GitChecker) — tests mock these directly, never the real Claude CLI or a real git repo.
- Signals are literal strings of the form `<<<CADENCE:NAME>>>` and matched against raw non-JSON CLI output only (so the same literal inside stream-json events does not trigger false positives).

## License

MIT. See [`LICENSE`](LICENSE).