https://github.com/avidrucker/pmtools
Project-agnostic PM CLI (status/claim/preflight) for the fruit-agent-orchestrate skill — per-language ports (Python + Node) kept in lockstep by shared golden fixtures.
https://github.com/avidrucker/pmtools
Last synced: 11 days ago
JSON representation
Project-agnostic PM CLI (status/claim/preflight) for the fruit-agent-orchestrate skill — per-language ports (Python + Node) kept in lockstep by shared golden fixtures.
- Host: GitHub
- URL: https://github.com/avidrucker/pmtools
- Owner: avidrucker
- Created: 2026-06-22T21:53:28.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-07-07T16:49:13.000Z (17 days ago)
- Last Synced: 2026-07-07T18:14:23.862Z (17 days ago)
- Language: Python
- Size: 675 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 12
-
Metadata Files:
- Readme: README.md
- Roadmap: ROADMAP.md
Awesome Lists containing this project
README
# pmtools
Centralized, project-agnostic project-management helpers for the
`fruit-agent-orchestrate` skill — one behavioral contract, per-language ports.
- **`CONTRACT.md`** — the language-neutral source of truth (commands, I/O, exit codes).
- **`fixtures/`** — golden cases every port is graded against (keeps ports in lockstep).
- **`py/`** — Python port.
- **`js/`** — Node port.
- `bb/` — Babashka/Clojure port (later, on demand).
## Commands
| Command | Tier | Ports |
|---|---|---|
| `status` | solo-relevant | py + js |
| `preflight` | fleet-only | py + js |
| `claim` | fleet-only | py + js (lccjs-isms parameterized: `--worktree-dir`, `--roster`, `--lane-check` (off by default), `--copy-env`) |
| `close` | fleet-only | py + js (racy push loop + gated teardown; `--worktree-dir` accepted but ignored — deprecated post-#51, close self-discovers the worktree; **config-gated velocity-row guard** (#5); learnings/tracker guards omitted) |
| `release` | fleet-only | py + js (abandon a claim + tear down its worktree, issue stays OPEN; data-loss guard refuses unpushed/dirty without `--force`) |
| `error` | storage | py + js (js drives the `sqlite3` CLI; cross-port parity green) |
| `velocity` | storage | py + js (js drives the `sqlite3` CLI; cross-port parity green) |
`error` / `velocity` are a configurable SQLite-primary storage layer (SQLite is
the source of truth; CSV is a derived mirror). Per-store, per-project config via
the `.claude/orchestrate.json` `storage` block — see CONTRACT.md §storage.
`status` only flags **canonical** PDD markers (`@(todo|inprogress) #N:`)
and honors a repo-root `.pddignore` (gitignore-style globs; copy
`.pddignore.example`). Scanning is toggled by the `pdd` block (`pdd.enabled`,
default true) — see CONTRACT.md §status.
### Dogfooding
pmtools eats its own dog food: this repo ships a tracked `.claude/orchestrate.json`
that **enables both stores** (errors + velocity; the SQLite DB lives out-of-tree
at `~/.pmtools//`, CSV mirrors regenerated under `docs/` but **gitignored** —
derived from the DB, never tracked, #68) and a `.pddignore`
that keeps `pmtools status` on this tree clean (fixtures/tests carry marker-like
test data, not real puzzles). **Because velocity is enabled, `pmtools close` here
requires a velocity row for the ticket** (the config-gated guard from #5) — log
your session first, or pass `--skip-velocity-check` for PM/triage closes.
```bash
pmtools error log '{"occurred_iso":"2026-06-23T10:00:00-1000","error_type":"CLAIM_FAIL","message":"could not claim","ticket":3}'
pmtools velocity log '{"role":"DEV","agent":"apple","ticket":3,"h_min":30,"actual_min":25}'
```
## Install (one-time) — `pmtools` on PATH
Clone this repo **anywhere**, then put the self-locating dispatcher on your PATH:
```bash
./install.sh # symlinks bin/pmtools -> ~/.local/bin/pmtools
# or pick another PATH dir: ./install.sh /usr/local/bin
```
`bin/pmtools` resolves its **own** clone root (following the symlink), so the clone
can live anywhere and you never hardcode its path. Now from any cwd:
```bash
pmtools status --json
pmtools claim 42 --as apple
pmtools preflight 42
pmtools close 42 # from the MAIN checkout, after committing `Closes #42`
# (worktree resolves from the issue #; also works from
# inside the worktree, but from-main never strands the shell)
pmtools status --port js # force the Node port (default: py)
```
> `close` lands **trunk-based** — it pushes the close commit straight to
> `origin/main` (like lccjs's `npm run close`). If your `main` is push-protected
> (branch protection / a push-gating harness), that push is rejected and `close`
> exits with your work safe and local; land via a PR by hand instead. See
> CONTRACT.md §close "Landing model" for the recipe.
- Clone root: `$PMTOOLS_HOME` if set, else self-resolved from the dispatcher's location.
- Port: `--port py|js` > `$PMTOOLS_PORT` > `py`.
## Use from a project
Once `pmtools` is on PATH, a project's `.claude/orchestrate.json` needs **no path** —
point enrichment at the dispatcher:
```json
{ "enrichment": {
"statusCommand": "pmtools status",
"claimCommand": "pmtools claim",
"preflightCommand": "pmtools preflight"
} }
```
(If you don't install on PATH, the skill still derives `//`
from config, and lccjs keeps thin `npm run` shims — but PATH install is the
zero-hardcoded-path option.)
## Run the tests
```bash
./run-tests.sh # all stages: py unittest + node:test + integration
```
`run-tests.sh` runs four stages and exits non-zero if any fails:
```bash
python3 -m unittest discover -s py # 1. Python port vs fixtures (stdlib only, no pip install)
node --test 'js/*.test.js' # 2. Node port vs the SAME fixtures
bash tests/integration.sh # 3. impure claim/status/preflight/close/release CLIs (py + js) vs temp git repos
bash tests/dispatcher.sh # 4. the public bin/pmtools router (port resolution, exit codes)
```
The pure ports are graded against shared golden cases —
`fixtures//*.cases.json`, each a `{name, args, expected}` case; a few
status-reconcile edge fixtures use the older `fixtures/*.input.json` →
`fixtures/*.expected.json` form. The integration stage exercises the impure CLIs
(real `git worktree add`, real `refs/claims/*` push to a local bare `origin`,
lane-gate on/off, CLOSED guard, `--worktree-dir` parameterization) against
throwaway repos.
## Parity rule
A behavior change = edit `CONTRACT.md` + a fixture, then make **every** port
green. Never change one port's behavior without the contract and the others.