impl-tool

impl-tool runs an issue through an agent session and does the git work around it. Given an issue – an id, a title and a prompt – it prepares a branch and a working directory, starts an agent session seeded with the issue, and turns every iteration of that session into a conforming commit that is pushed and reflected in a pull request. The person at the keyboard reviews work and answers questions; the tool keeps the branch, the commits and the pull requests in the shape a reviewer expects.

It works on a single repository and on a repository that pins others as submodules. In the second case one issue is one branch in every repository it touches, one commit title and increment across all of them, and one pull request per repository, each describing only what changed there.

What you need

The ideas it works with

An issue

An issue is the unit of work: an id such as EXEC-0000000, a title, and the prompt the session starts from. It is written as a small document and named when a run starts:

issue: EXEC-0000000
title: Add login
prompt: |-
  Add a login form to the settings page

The id must match the format the repository configures, and a run that names an id of another shape is refused before anything is touched. The id names everything the run creates: the branch, the working directory, the commit subjects and the records kept about the work.

A workspace

Each issue is worked in a worktree of its own, beside the main checkout rather than in it, so several issues can be in flight at once and the main checkout stays where it was. The workspace is created on the first run of an issue and reused by every run after it. Which directory it is depends on the issue and not on where the command was typed: starting a run from the main checkout, from the issue’s own workspace or from another issue’s workspace reaches the same place. A repository that configures no workspaces is worked in place.

The remote and the default branch

Work starts from the default branch of the repository’s remote: origin/master unless the repository says otherwise. Both halves are configuration. remote names the remote the branch is pushed to and whose repository the pull request is opened in, and defaultBranch the branch a new one starts from, a pull request targets and merged work is looked for on. A repository whose default branch is not stated is read off its remote, so one on main needs nothing set.

# .impl-tool/config.yml
remote: upstream
defaultBranch: main

Each repository answers for itself. Where one repository pins another, the two are separate clones and neither’s settings apply to the other: the pinned one states its own, or takes the defaults. A repository that has remotes and none by the configured name is refused before anything is changed, naming the remotes it does have.

A session, and an iteration of it

A session is the agent working the issue, interactively or headless. Every time the agent finishes a turn that changed something, that turn is an iteration: the tool reads the agent’s account of the turn, commits the work in every repository it touched, pushes, and brings the pull requests up to date – without the session being left.

The agent’s account is the result block: a structured answer the prompt asks for at the end of each turn. It carries a short title, a description of what the work is for, what each repository changed, what was verified and how long it took, and the two lists described below. The block is validated against a schema. An answer that does not conform is refused, the agent is told why and asked again, and the refusal is put on record.

An increment

Commits on an issue’s branch are numbered. Each iteration is one increment, and the subject of its commit is <issue>: <title> (<N>). The tool owns the numbering: it reads the branch’s highest increment out of the history, so neither the agent nor the reviewer tracks it. Every repository an iteration touched takes the same title and the same number, which is how a reviewer finds one piece of work across several repositories.

A pull request per repository

A run opens a pull request for the branch in each repository the work touched, and rewrites its description as the branch grows. A description is written from that repository’s own changes and conformed to a template, so every pull request carries the same sections in the same order. Where one repository pins another, the pull requests name each other, and the description says how the repositories relate.

A run can also be told to leave the pull requests unopened; they are opened afterwards, from the same commits, by a command of their own.

The gate

Whether work is good enough to commit is the repository’s own answer: its checks and its tests, run by the agent after an edit. impl-tool does not define those checks, and does not ask the agent what they cost: every command run through the tools leaves a record of its run, and impl-tool reads each check and test run from that record – what ran, how long it took and whether it passed – and shows them after each commit, with each check’s average and pass rate over the issue.

Autonomy

A run states how much the session decides without asking, on a scale from 1 to 5. At a low level the agent stops and asks often; at a high level it decides everything reversible itself and asks only before acts that cannot be undone or that reach outside the repository. Some acts are asked about at every level.

Follow-ups

Work an iteration found and could not do within the issue is listed as a follow-up, under the repository it would be done in. Follow-ups are published in that repository’s pull request, and each one is ruled on before the work is finished: accepted as new work, rejected with a reason, or resolved by a later commit. The tool keeps a local working copy of a branch’s follow-ups, so they can be reviewed in one sitting and the rulings published together.

Release considerations

What somebody deploying the change has to know – a migration, a setting that must be present, an order two repositories deploy in – is listed per repository in the result block and published as its own section of the pull request, where a release reads it back.

The release stage

A run states whether the work is before or after its first production release. Before it, a backwards-incompatible change is allowed and has to be named; after it, a backwards-incompatible change goes behind a feature flag or comes with a bump of the contract version it breaks, and is named the same way.

What it keeps on record

The tool’s files live in .impl-tool/ at the root of the repository it is run in.

The commands

impl-tool is an executable, run by its name: a group, a command, and the command’s options.

impl-tool issue run --issue <issue file>
impl-tool issue --help

Commands are grouped by what they act on. The command reference lists every command with its options, and the configuration reference every setting; this is what each group is for.

Group What it is for
issue Run an issue: prepare its branch and workspace, start the session, and commit, push and describe each iteration. Also prepare an issue without starting a session.
session Open and close a development session by hand, where the steps around it are wanted without the whole run.
pull-request Open the pull requests a branch’s commits belong to, for a run that was told not to open them.
submodule Put a tree whose submodule checkouts have drifted back on one branch, and record a merged submodule’s work in the repository that pins it.
checkout Ask a checkout whether it is holding work that exists nowhere else – uncommitted changes, unpushed commits, branches with no remote – before it is removed.
observe Point the main checkout at the issue being watched, so its work can be read and run there while the session carries on in its workspace.
followups List, add and rule on a branch’s follow-ups locally, review them in one sitting, and publish the rulings to the pull requests.
failures List and show the refused model answers on record.

Every command answers --help, and reports in a machine-readable form when asked to.

A run, from start to finish

  1. The repository is made ready. A directory that is not yet a repository becomes one, on the configured default branch, and a repository with no remote is given a private one.
  2. The workspace is found or created, and the run moves there.
  3. The branch is prepared. The issue’s branch is created from the default branch, or from another branch the run names, in the repository and in each submodule the tree pins.
  4. The session starts, seeded with the issue’s prompt, the repository’s standing context, and the contract for the result block.
  5. Each iteration is committed. At the end of a turn the result block is read and validated, the work is committed in each repository under the next increment, pushed, and the pull requests are opened or brought up to date.
  6. The run closes out. When the session ends, anything the last turn left is committed, the pull requests are given their final descriptions, and a summary says what the run produced: the commits, the pull requests and how they relate, what was spent, and where the workspace is.

A run can be repeated. Running an issue again continues its branch from the highest increment it carries, in the same workspace.