pma 0.3.1

Lint TODO.md files across many git repositories and rank their tasks in an Eisenhower matrix
pma-0.3.1 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 and publishes it.

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 myproject 1   # 1 is most important, 5 least
pma scan                       # TODO.md, git state, CI
pma matrix                     # every project's tasks, ranked, in one view
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 ship                       # commit, push or open a pull request

Features

One view of everything

  • 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), a per-project health ranking (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.

  • 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 what it would run, the worker, the model and a ceiling, and spends nothing until you approve it.

Install

cargo install pma

From a clone: make install.

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, and gh:N for a linked issue. A token-shaped word earlier in the line is text.

  • #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, are errors. An item is identified by its text until an issue number is written into it.

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 myproject 1    # or `none`; untiered projects are not ranked
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 matrix                      # the ranked view; -q q1 for one quadrant
pma status --explain            # projects by health, with each signal's share
pma stale                       # open items by age, oldest first
pma tui                         # browse the matrix; q quits
pma note add "move CI to a reusable workflow"

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

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

Dispatch, review, ship:

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 shipped 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 ship                        # commit, push or open a PR, remove worktrees
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 ~/.config/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. publish is pr by default; pma config publish push pushes to the default branch instead.

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
pma workflow run review myproject -p claude-haiku

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. That directory can be a git repository, which is how two machines share it.

  • Worktrees sit under the same directory and are removed when a run ships or is rejected.

  • 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, ship, 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.