pma 0.3.1

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

```sh
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

```sh
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

```markdown
# 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:

```sh
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:

```sh
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:

```sh
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:

```sh
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:

```sh
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

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

## Licence

MIT.