Skip to content

Latest commit

 

History

23 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

phasekit

Run a long refactor as a series of unattended agent sessions that you can actually review.

One plan, split into phases. One phase per headless run, on its own git branch, one commit per task, gates green before every commit. The runner survives usage limits, pins the conversation so your answers reach the right session, and prints what the agent is doing while it does it.

It exists because "let the agent go and see what happens" produces a diff nobody can read and a repository nobody can revert. The unit of trust here is the commit, not the session.

What it is not

It is not a framework, an orchestrator, or a way to avoid thinking about your codebase. The plan is the product; phasekit is the thing that presses play and refuses to let a bad run look like a good one.

Requirements

  • PowerShell 7+ (Windows, macOS or Linux)
  • claude on PATH (Claude Code)
  • git

Platform support

Running phases, verifying, merging, waiting out usage limits, detaching and following logs work the same everywhere. Two things are the operating system's to provide, and phasekit uses whatever is there:

Windows macOS Linux
Keep the machine awake during a long wait SetThreadExecutionState caffeinate -s systemd-inhibit
Notify when a sequence ends or stops sound + tray balloon afplay + osascript terminal bell + notify-send

Neither is required. If the mechanism is missing, phasekit says so once and carries on — a machine with no notification daemon must not take a run down, and a run that cannot prevent sleep is still a run. The terminal bell is the fallback everywhere, which is also the one that survives ssh.

tools/resume-at-logon.ps1 registers a Windows Task Scheduler task. Elsewhere it refuses and prints the launchd or systemd equivalent to set up by hand.

Install

git clone https://github.com/<you>/phasekit.git

Then either call it by path, or add a function to your PowerShell profile ($PROFILE) so phasekit works anywhere:

function phasekit { & "C:\path\to\phasekit\bin\phasekit.ps1" @args }

Quickstart

cd my-project
phasekit init          # writes phasekit.json, PLAN.md, PROMPTS.md, AGENT-RULES.md
  1. Edit phasekit.json: point codeDir at the git repo, and list the gates — the exact commands that must be green before every commit.
  2. Write PLAN.md. This is the work. See docs/method.md and docs/writing-a-plan.md.
  3. Write the phase blocks in PROMPTS.md. Each one opens by verifying the previous phase.
  4. Check the gates actually run:
phasekit gates
  1. Go:
phasekit run 0

Commands

Command What it does
phasekit init [-Force] Scaffold the four files into the current directory
phasekit run <phase> Create plan/phase-<n>, send the phase block, stream the run
phasekit reply <phase> -Text "…" Answer a question in the same conversation
phasekit reply <phase> -File answer.txt The same, from a file — easier to edit, survives shell quoting
phasekit continue <phase> Pick up an interrupted phase where it stopped
phasekit status [<phase>] Branch, dirty files, commits on the phase, ledger vs git log
phasekit gates Run the gates yourself, no agent, no cost
phasekit auto [-Push] Walk autoSequence unattended: run, verify, merge, next
phasekit logs [-Follow] Follow the run, rolling over to each new phase's log

Useful flags: -Detach (survives closing the terminal), -DryRun (print the prompt and the exact claude command, run nothing), -NoBranch, -Model, -Effort, -MaxRetries, -WaitMinutes.

-Detach then hands the terminal straight back to following the run, so one command both starts it and shows it. Ctrl+C there stops the following; the run is a separate process and carries on. -NoFollow leaves you at the prompt instead, and phasekit logs -Follow reattaches whenever you want. The follower rolls over on its own as each phase opens its own log, and ends when the sequence does.

Unattended sequences

phasekit auto walks a list of targets on its own: run, verify, merge, next. The list lives in phasekit.json, and each entry may name its own model — the cheap one where the gates fully cover the change, the expensive one where a mistake would be silent:

"autoSequence": [
  { "target": "4.2", "model": "sonnet", "note": "mechanical move, fully covered by tests" },
  { "target": "4.3", "model": "opus",   "note": "riskiest task in the plan" },
  { "target": "7.2", "model": "sonnet" }
]

It stops at the first target that needs a person and writes the reason to auto-stopped.txt. Skipping ahead would build every later target on top of something nobody looked at — and unlike a human, an unattended loop would never notice. A target that is already ticked and merged is skipped, so rerunning after a stop picks up where it left off.

Leave irreversible steps out of the sequence and run them by hand.

It tells you when it ends, either way — sound and a desktop notice on both the completion and the stop. The stop is the one that pays for it: a sequence waiting for an answer costs nothing to fix and everything to not notice. Set "notify": false to turn it off.

Everything above survives inside the running process. Nothing inside a process survives that process dying, so a reboot is the one interruption the runner cannot handle on its own:

tools/resume-at-logon.ps1 -Install -Push -Config path\to\phasekit.json

That registers a logon task which resumes the sequence, and does nothing once it has finished. Resuming is safe to repeat: merged targets are skipped, and a target with a pinned session is continued rather than restarted.

Configuration

phasekit.json, found by walking up from the current directory:

{
  "workingDir": ".",
  "codeDir": ".",
  "plan": "PLAN.md",
  "prompts": "PROMPTS.md",
  "logDir": ".phasekit/logs",
  "model": "opus",
  "effort": "high",
  "branchPrefix": "plan/phase-",
  "requireCleanTree": true,
  "usageLimit": { "maxRetries": 6, "waitMinutes": 20 },
  "gates": [
    { "name": "tests", "cwd": "backend",  "run": "pytest -q" },
    { "name": "types", "cwd": "backend",  "run": "pyright" },
    { "name": "build", "cwd": "frontend", "run": "npm run build" }
  ]
}

workingDir is where the agent is launched; codeDir is the git repository that receives the branch and the commits. They differ when the plan lives in a separate notes repo — set workingDir to their common parent so the agent can see both.

Every relative path is resolved against the config file, never against the shell's current directory, so the same command means the same thing wherever you type it.

The three things that make it work

One commit per task. A phase you dislike is one git branch -D away. A task you dislike is one git revert away. Without this the only available verdict on an hour of agent work is "keep all of it" or "throw all of it away".

Every phase verifies the previous one. Not by reading the ledger — by running the gates and grepping for the consequences. A ticked box with no commit behind it is the single most expensive failure in this workflow, because every later phase compounds it. phasekit status prints the ledger next to the git log for exactly this reason.

Waiting is not failing. When the subscription allowance runs out, claude -p exits with an error — it does not wait and resume by itself. phasekit reads the reset time out of the message and sleeps until it, then resumes the same conversation, so the agent carries on from the task it was on rather than restarting the phase. A run in that wait has no claude process and a log that stopped growing; it is alive, and it is the single most common thing to mistake for a crash.

Stopping is a success. An agent that stops to say "this task's premise is false" has done the most valuable thing it can do. The runner shows the log tail when a run ends non-zero, because a stop-to-ask and a crash look identical from the outside, and answering costs one turn while restarting the phase costs the whole baseline.

Documentation

Licence

MIT.

About

Run a long refactor as a series of unattended Claude Code sessions you can actually review. One plan split into phases, one phase per headless run on its own git branch, one commit per task, gates green before every commit — and a runner that survives usage limits, standby and reboots.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages