pma 0.4.0

Maintain many git repositories from one place: rank their TODO.md tasks and hand them to coding agents
pma-0.4.0 is not a library.

pma

Maintain many repositories from one place.

If you keep dozens of small projects, the work is not hard, it is scattered. Each repository has a TODO.md, failing CI, outdated dependencies and a dirty working tree, and finding out means opening all of them. pma reads them all in one pass, ranks every task across every project in one list, and can hand a task to a coding agent in a throwaway worktree. You read the diff, and pma commits it and opens a pull request or pushes.

Scanning 95 repositories takes about 15 seconds.

Nothing is published without you. An agent runs with no push credentials, pma runs the project's tests itself rather than trusting the agent's report, and every change waits in a worktree until you approve it.

pma root add ~/projects        # git repos directly under it are projects
pma project tier 1 myproject   # 1 is most important, 5 least
pma scan                       # TODO.md, git state, CI
pma                            # next: tasks for agents, and what waits on you
pma dispatch myproject:31      # hand line 31 to an agent
pma review 1                   # the diff, the test result, the cost
pma review 1 --approve
pma pr 1                       # commit and open a pull request; or `pma push 1`

Features

One view of everything

  • pma next, or pma alone: one list of tasks for agents, in the order pma dispatch --auto takes them, and one of what waits on you: runs to review, publish or merge, then critical or urgent tasks no agent may take.

  • Every task in every project in one Eisenhower matrix, ranked by project importance and item priority.

  • Failing CI, outdated dependencies, idle repositories and dirty working trees appear as tasks beside the written ones.

  • A terminal browser (pma tui) of next and the matrix, projects worst first by what needs attention (pma status), and an oldest-first list to prune (pma stale).

  • Portfolio notes that belong to no single project.

Tasks in plain Markdown

  • A root TODO.md per repository is the only task file. Nothing to keep in step, and no lock-in: remove pma and the files still read.

  • pma lint checks the format. pma prune removes what is finished.

  • pma sync mirrors Critical items to GitHub Issues and ticks items whose issue was closed.

Agents, with the brakes on

  • claude, opencode and omp come as templates. Any coding agent with a headless mode is a record you edit, not a code change.

  • sanduk is the same agent in a disposable container: the key stays on the host, the worktree is mounted at its own path, and the box is the bound. Needs sanduk and a container engine; pma agent show sanduk prints what it runs. Verify still runs on the host, so a run that edits the build file is contained and the build is not.

  • Each run gets its own worktree of the remote default branch, so your dirty checkout is never touched.

  • The agent runs without push credentials. pma runs the project's test command itself, before and after, and reports both ends.

  • Every run records the diff, the test result, the cost, the duration and the paths it touched. A run that changed a file its task had no business changing is flagged.

  • Limits you set: agents at once, dollars per run, dollars per batch, minutes per run.

  • pma report says what dispatching has produced: first-attempt passes, reworks, merges, cost, review time.

Which model, where

  • A preset names a worker, a model and the arguments that configure it: pma preset set omp-luna-high omp gpt-5.6-luna --thinking high.

  • pma runs agents and is not an API client. Provider keys stay the agent's, reached through its own configuration, and pma stores none of them.

  • A routing policy decides the worker, the model and how much autonomy per kind of task. It is a document you propose, activate, and can replay over past runs to see what it would have changed.

Repeating work

  • A campaign applies one task definition across many repositories, each verified on its own.

  • A workflow is a graph of agents over a project: review, then validate the review, then fix what it confirmed. Written as JSON or as a script, and costed before it runs.

  • pma workflow run prints a plan: each agent node it would run, over which units, with the worker, the model and a ceiling. Nothing is spent until you approve that plan by its id, and an approval runs that plan and nothing else. Its target is dispatch's: a project, a TODO.md line, a heading or a signal, whichever type the workflow reads.

  • One run of a workflow is an instance. It is frozen at the revision it started under, so activating a new one does not change work already under way, and --instance <id> resumes it where the last pass stopped. A node runs once every node before it has finished, so a join sees every branch. A node that edits a repository leaves a run in pma review like any other.

  • Each node can run on its own worker and model: a route that names the node, such as {"match": {"node": "*/review"}, "model": "haiku"}, picks them.

Install

cargo install pma

From a clone: make install, which builds the release binary and copies it to ~/.local/bin. Set PREFIX to install elsewhere: make install PREFIX=/usr/local.

Requirements:

  • macOS or Linux. Rust 1.89 or later, and a C compiler: SQLite is compiled in.

  • git on PATH.

  • A coding agent for pma dispatch: claude, opencode or omp, logged in or holding its own provider key.

  • gh, authenticated, for CI status, for opening pull requests and for pma sync. Without it CI reads as unknown, and pma scan --offline skips GitHub on purpose.

TODO.md format

# TODO

## Critical

- [ ] segfault on empty input #bug gh:42

## High

- [ ] support ggml 0.9 due:2026-10-01

## Medium

### parser

- [ ] accept a trailing comma
  the fixture in tests/bad.json covers it

## Low

- [x] drop python 3.9

The rules, which pma lint checks:

  • The first non-blank line is # TODO, and there is no second # heading.

  • Priority comes from the enclosing ## section: Critical, High, Medium or Low, spelled exactly, at most one of each. Other ## sections are allowed and ignored.

  • An item is one line, - [ ] text or - [x] text. No other spelling of the checkbox.

  • Indented lines under an item are its description, carried to the agent verbatim.

  • A ### heading groups the items below it, until the next heading.

  • Trailing tokens, read from the end of the line: #tag, due:YYYY-MM-DD, gh:N for a linked issue, and an optional id, ^k3f9q: five characters of 0-9 and a-z without i, l, o or u. A token-shaped word earlier in the line is text, and so is a caret word that is not an id, such as ^1.2.

  • #urgent makes an item urgent whatever its date. #agent marks one an agent may take. #manual keeps agents off it entirely.

  • A finished item is ticked where it stands. pma prune removes it.

Two open items with the same text, or two items with the same gh:N or id, are errors. An item is identified by its id, else its issue number, else its text. Ids are optional and pma writes them: pma lint --ids --apply gives one to every open item, and an item a workflow adds gets one. With an id, rewording an item keeps its age and any run it has.

Commands

Projects and tasks:

pma lint                        # ./TODO.md; a directory means its TODO.md
pma lint ~/projects/*/          # every project
pma prune ~/projects/*/         # what would be removed; --apply to do it
pma root add ~/projects/        # also `pma root` and `pma root rm`
pma project                     # every project: tier, tags, last scanned
pma project tier 1 alpha beta   # or `none`; untiered projects are not ranked
pma project export tiers.csv    # every project's tier and tags, to edit
pma project import tiers.csv --apply   # read them back
pma project tag add rust myproject     # private groups; --tag <t> elsewhere
pma project forget gone         # an absent project's record
pma scan                        # TODO.md, git state, CI via gh
pma scan --offline myproject    # one project, without GitHub
pma scan --deps                 # also count outdated cargo, uv and go deps
pma next                        # for agents and for you; --all for every row
pma matrix                      # the ranked 2x2; -q q1 for one quadrant
pma status --explain            # projects worst first, with what placed each
pma status --all                # untiered projects too, e.g. to find failing CI
pma stale                       # open items by age, oldest first
pma tui                         # browse next; m for the matrix, q quits
pma note add "move CI to a reusable workflow"

next, matrix, status and tui read the last scan. They do not rescan.

Agents, models and presets:

pma agent                       # the workers; `pma agent show claude` for one
pma preset set claude-haiku claude haiku
pma preset set omp-luna-high omp gpt-5.6-luna --thinking high
pma preset                      # every named combination; * is the default
pma preset use omp-luna-high
pma config                      # every setting; `pma config <key> <value>` sets one
pma verify myproject            # run its check where a dispatch would, before one does

Highest wins: the -a and -m flags, then -p <preset>, then an applied route, then the default preset.

Dispatch, review, publish:

pma dispatch myproject:31       # a TODO.md line from the last scan
pma dispatch myproject:ci       # failing CI; or :deps for dependencies
pma dispatch myproject:critical # every open item under that heading
pma dispatch myproject          # pick from a list
pma dispatch --auto -n 3        # the top 3 dispatchable tasks
pma dispatch -p claude-haiku myproject:31
pma review                      # runs not yet published or rejected
pma review 4                    # task, test result, cost, summary, diff
pma review 4 --approve          # or --reject, or --rework "feedback"
pma review 4 5 6 --approve      # a batch; every run is checked before any is approved
pma pr 4                        # commit, open a pull request, remove the worktree
pma push 4                      # commit and push to the default branch instead
pma pr --all-approved           # every approved run; also on push
pma report --by project         # also class or agent

A target naming one task fails if that task cannot run. A target naming many passes over each with its reason, and dispatches the rest.

Each run gets a worktree of the remote default branch under ~/.local/state/pma/worktrees, on a pma/ branch. The item must be open in the remote TODO.md, so commit and push before dispatching. Set the test command with pma config projects.myproject.verify "make check", or let it be detected.

A run opened with pma pr is pr-open until the pull request is merged or closed; pma review settles it and shows review activity meanwhile. A run published with pma push is pushed.

Repeating work:

pma campaign add ci-workflow "add a CI workflow" --projects a --projects b
pma campaign run ci-workflow    # one worktree per repository, verified apart
pma route propose routing.json  # worker, model and autonomy per kind of task
pma route activate 2            # --shadow records the decision without applying it
pma route replay routing.json   # what a candidate would have done differently
pma workflow check lib.rhai     # read a document and print its worst case
pma workflow propose lib.rhai   # store it as a draft revision
pma workflow activate 1         # refused if its worst case is over budget
pma workflow run review myproject --dry-run   # plan and price, starting nothing
pma workflow run review myproject -p claude-haiku
pma workflow run triage myproject:critical --set severity=high
pma workflow run triage --instance 3 --approve 3fa2c1e09b7d   # the plan it printed
pma workflow run triage --instance 3 --cap max_units=80   # resume a capped instance
pma workflow stop 3             # no further node; its runs stand
pma workflow                    # revisions, and every instance with its state

A pass runs every node a rule decides, then stops and prints a plan for the agent nodes that are ready: as many runs as batch_budget admits at agent_budget each. --approve <plan> runs exactly that plan, max_parallel agents at a time, then stops at the next plan. A plan that changed since it was printed has another id and is refused. A unit a node could not handle is named on stderr and goes no further than a default edge; a check waiting on a pull request keeps its unit until the next pass.

GitHub Issues:

pma sync                        # what would change
pma sync --apply myproject      # carry it out

Each ## Critical item gets an issue labelled pma:critical, and gh:N is written into its line. An item whose issue is closed is ticked. Edits to TODO.md are left uncommitted, for you to read first.

Where things live

  • The database is ~/.config/pma/projects.db, or $PMA_HOME/projects.db. It serves one machine: two copies cannot be merged.

  • Worktrees, logs and artifacts sit under ~/.local/state/pma ($XDG_STATE_HOME/pma), or $PMA_HOME/state when PMA_HOME is set; PMA_STATE overrides both. Worktrees are removed when a run is published or rejected. A workflow's agent nodes read the code in a worktree of their own and leave nothing behind; what they read and wrote stays in artifacts/<instance>/<node>/<n>/, numbered per run.

  • Settings live in the database rather than a file. pma config lists them, pma config <key> <value> sets one, and --reset clears one.

One command that runs agents or edits worktrees runs at a time. dispatch, pr, push, workflow run and review --reject take a lock and name the process holding it. Reading commands run alongside.

Development

make test     # cargo test, then pytest on scripts/
make check    # format check, clippy with warnings as errors, then test

Licence

MIT.