Arrange the repository's public .pm adoption board — show status, create workstreams, capture inbox notes, promote notes into milestones, add tasks, mark work done, park blocked work with its unblock condition, and drop work that should no longer be done. Use only when the user explicitly invokes $pm or asks to update, arrange, or check the .pm board. Do not use for proposing or brainstorming new work (that is pm-brainstorm) or for ordinary code edits.
Install
npx skillscat add bex-co/beancount-io/pm Install via the SkillsCat registry.
Task: Arrange the .pm board
Usage: /pm [status | new workstream <title> | add <wN> <idea> | promote <wN/NNN> | new milestone <wN> <title> | add-task <wN/mN> <title> | done <wN/mN/tNNN or wN/NNN> | block <target> <reason> | unblock <target> | drop <wN/mN, wN/mN/tNNN or wN/NNN> <reason>]
/pm is the only skill that writes to .pm/. It arranges milestones and tasks under the conventions below. /pm-brainstorm proposes; /pm materializes. This file (.agents/skills/pm/SKILL.md) is the canonical definition of the board conventions — mission, hierarchy, sizing rule, quality gate, standing closing tasks, templates. /pm-brainstorm reads it at runtime and must not restate or diverge from it. Parse the subcommand from $ARGUMENTS (default = status).
Mission — the TPM lens
This board exists to grow adoption of Beancount.io in the open-source and agentic-coding community. Act as the technical program manager for that goal: sequence work by adoption impact, not by what is fun to build. Every milestone must advance one of these pillars and name it in its ## Source + Goal linkage:
| Pillar | Meaning |
|---|---|
| A1 — Agent-native accounting | A coding agent (Claude Code, Codex, …) can set up and maintain a beancount ledger end-to-end: skills, CLI, MCP surfaces, instruction-file quality. |
| A2 — Frictionless onboarding | A newcomer — human or agent — goes from zero to a working ledger in minutes: installers, beancount-init, quickstarts that work exactly as written. |
| A3 — Community & distribution | Reach and credibility: docs, examples, launches, package registries, GitHub presence, Telegram. Signals: installs, stars, skill invocations, contributors. |
The .pm hierarchy
| Level | Path | Meaning | Effort |
|---|---|---|---|
| Workstream | wN/ (w1, w2, w3, …) |
a general-purpose worker queue; README.md + inbox notes |
— |
| Inbox note | wN/NNN.md (w1/005.md) |
one idea or a sub-hour unit of work, plain markdown | ≤ ~1h |
| Milestone | wN/mN/ (m1, m2, …) |
a shippable chunk: README.md + task files |
> ~1h, multiple tasks |
| Task | wN/mN/tNNN.md |
a single unit | tens of minutes |
Rules (enforce every time)
The board is public.
.pm/is committed to a public repo. Write every board file for public consumption: no secrets or credentials, no user data, and no references to private repositories or their contents. Assume the community reads the board — it is itself an adoption surface.Respect the anti-goals. Read
.pm/DO_NOT_DO.mdbefore proposing or materializing work. Do not create milestones/tasks that conflict with it.Workers are generalists. A workstream is a worker queue, not a permanent topic, package, feature, or pillar lane. Keep workstream titles and provenance generic; put topic-specific scope in milestones, tasks, and inbox notes. Route new work by priority, dependencies, and available capacity, and allow one workstream to contain unrelated topics across A1/A2/A3.
Sizing rule. A milestone must be > ~1 hour of work across more than one task. If a chunk is ≤ ~1h (tens of minutes, a task or two), do NOT create an
mN/directory — record it as a loose inbox notewN/NNN.md. Tasks take tens of minutes; milestones take hours.IDs must match the path. A task's
id: wN/mN/tNNNfrontmatter must equal the directory it lives in. Never create a milestone dir whose path disagrees with the IDs inside it; if you find drift, flag and repair it, don't copy it.Keep status in sync across all three places it lives: the workstream
README.mdmilestone checkbox, the milestoneREADME.md**Status:**line + the— DONEmarker in the task table, and each task'sstatus:frontmatter.Completed work must exit the open tree. Moving completed work into
done/is a mandatory exit condition, not optional cleanup. A mutating subcommand must not report success while an affected task withstatus: doneremains atwN/mN/tNNN.md, or while an affected milestone with no open tasks remains atwN/mN/. Move completed tasks towN/mN/done/and completed milestones towN/done/mN/, then verify the old open paths no longer exist.Unwanted work leaves the tree with its reason on record. A milestone or inbox note that should no longer be done is removed by
drop, never by unchecking, editing in place, or leaving it to rot: its files go, the workstreamREADME.mdkeeps a one-line tombstone under## Dropped, and its number is never reused.doneis for finished work anddropis for unwanted work — a milestone whose definition of done already holds is closed withdone, not dropped, and work already underdone/is history and is never dropped.Blocked work waits in
blocked/with its unblock condition. Work that is still wanted but cannot proceed keeps its number, its files, and its pending checkbox, and moves aside: a milestone towN/blocked/mN/, an inbox note towN/blocked/NNN.md. A task cannot leave its milestone — itsidmust match its path — so a blocked task stays put withstatus: blockedand the milestoneREADME.mdcarries the reason. Every blocked item states the exact blocker and an Unblock: condition, and names who can clear it when that is not this repository (a deploy, a credential, a store review, a decision only the user can make). "Blocked" with no written condition to clear it is not a status, it is an abandonment.Numbering: next free zero-padded 3-digit for inbox notes (
NNN) and tasks (tNNN); next freewN/mN. Scan the tree first, including## Droppedtombstones and everything underdone/andblocked/; don't reuse a number.Use
worker: worker1unless the workstream README names another worker.Milestones must be meaningful. Every milestone must include direct pillar linkage (A1/A2/A3), an observable expected adoption outcome, and why this work matters now (dependency/risk/sequence rationale).
Every milestone ends with standing closing tasks, appended after the implementation tasks whenever a milestone is materialized:
- Adoption surface — only when the milestone ships something a user or a coding agent touches. Check the shipped change is discoverable and usable on every surface it exposes: the owning package's
README.md(install/quickstart steps still work exactly as written), the rootREADME.mdandAGENTS.mdpackage tables, the appropriate skill catalog (skills/AGENTS.mdfor customer workflows,.agents/AGENTS.mdfor development workflows) for new or changed skills, every scope's guidance in a realAGENTS.md, and the.claude/skillslink to the real.agents/skillsdirectory (rootAGENTS.mdcompatibility rules). For agent-facing work, verify the behavior holds for both Claude Code and Codex — same instructions, same triggers. Flag drift as follow-up work rather than silently diverging. Omit this task only for milestones with no user- or agent-facing surface (pure infra, board mechanics, internal refactors) — note why it was omitted in the milestone's## Source + Goal linkage. - Simplify — run
/simplifyover the code this milestone changed (reuse / simplification / efficiency; behavior-preserving). - Test coverage — add meaningful tests for the behavior this milestone shipped. Tests must assert real behavior and failure modes; never game coverage with trivial, tautological, or snapshot-everything tests.
- Closeout — the final task, added last. When the milestone's other tasks are all complete and its definition of done is actually met, close the milestone: set every remaining task's
status: done, move eachtNNN.mdtowN/mN/done/, mark every row— **DONE**and set**Status:** donein the milestoneREADME.md, move the wholewN/mN/directory towN/done/mN/, and check- [x]in the workstreamREADME.md. Completing this task is the move — running/pm done <wN/mN/tNNN>on it last triggers the milestone move (thedonesubcommand's step 4). Do not run it until the DoD holds: a milestone lands indone/when its observable end state is real, not merely when the code is written.
Each
depends_onthe last implementation task(s) (Simplify and Test coverage depend on Adoption surface when it's present; Closeout depends on Test coverage) and all count toward the(N tasks)total.add-taskinserts new work before these (before Closeout) and updates theirdepends_on.- Adoption surface — only when the milestone ships something a user or a coding agent touches. Check the shipped change is discoverable and usable on every surface it exposes: the owning package's
Subcommands
status (default)
Read the tree (find .pm -type f -name '*.md', skipping done/) and .pm/DO_NOT_DO.md. Print, per open workstream: its milestones with **Status:**, and the next actionable task per milestone — the first non-done task whose depends_on are all satisfied. Also list open inbox notes, and everything under blocked/ with the condition that would clear it. Then run a lightweight validation pass and flag:
- items conflicting with
.pm/DO_NOT_DO.md, - milestones missing
## Source + Goal linkageor a pillar (A1/A2/A3), - milestones whose definition of done is vague/non-testable,
- completed tasks or milestones that still sit in the open tree instead of their applicable
done/directory, - blocked items with no Unblock: condition on record, and blocked items whose condition now reads as met.
Touch no files.
new workstream <title>
Create the next free wN/ with README.md from the workstream template below. The title must describe a generic worker queue, not a topic specialty.
add <wN> <idea…>
Create the next free inbox note wN/NNN.md with the idea as plain terse markdown (no frontmatter). This is the default home for sub-hour work.
promote <wN/NNN> / new milestone <wN> <title>
Apply the sizing rule first.
- If the work is > ~1h and splits into more than one task: create
wN/mN/withREADME.md(milestone template) + onetNNN.mdper task (task template) + the standing closing tasks (Adoption surface when the milestone ships a user- or agent-facing change, then Simplify, then Test coverage, then Closeout), add the- [ ] **mN** — …line to the workstreamREADME.md, and fill## Source + Goal linkagewith source + pillar linkage + expected outcome + why-now rationale (note there why Adoption surface was included or omitted). - If it is ≤ ~1h: do NOT create a milestone. Keep/append it as an inbox note
wN/NNN.mdand tell the user why (too small for a milestone).
add-task <wN/mN> <title>
Create the next tNNN.md from the task template and add its row to the milestone README.md table before the standing closing tasks, updating their depends_on to include it. Update the (N tasks) count in the workstream README.
done <wN/mN/tNNN or wN/NNN>
- Set the task's frontmatter
status: done. If the task is being closed because its work already landed outside it (a triage close — the acceptance criteria hold onmainthrough another milestone or commit, not through work done for this task), append a## Closed by triagesection to the task with the evidence — commit SHA, paths, test names — so the public record shows why it closed without work. - In the milestone
README.md: mark the row— **DONE**and update the**Status:**line (e.g.todo (t001 done)). - Move the file to
wN/mN/done/tNNN.md. - If no open tasks remain in the milestone, move the whole milestone to
wN/done/mN/and check its box (- [x]) in the workstreamREADME.md. - Verify the exit condition before returning: the completed task exists only under
done/; and, when no open tasks remain, the milestone exists only atwN/done/mN/, itsREADME.mdsays**Status:** done, and the workstream checkbox is checked. Do not report success until these moves and status updates are complete.
For an inbox note wN/NNN.md (including one under blocked/): append a --- separator and one **Resolved <YYYY-MM-DD>** — <what changed>, shipped in () paragraph, so the public record says what closed it; move the file to wN/done/NNN.md; remove its bullet from the workstream README.md if it is listed there. Verify the open path no longer exists before returning.
Show the intended moves before mutating if the user passed DRY_RUN=1.
block <wN/mN, wN/mN/tNNN or wN/NNN> <reason>
Park work that cannot proceed, with its blocker on record. Refuse without a reason. Blocking is not dropping: the work is still wanted, so nothing is deleted, no number is retired, and the workstream checkbox stays unchecked.
- Confirm the target is in the open tree and read it, so the record can name what is stuck.
- Write the block into the item: a
## Blockedsection for a milestone or task (a note gets a**Blocked <YYYY-MM-DD>** — …paragraph) stating what is blocked, the exact blocker, an Unblock: condition that would clear it, and who can clear it when that is not this repository — a deploy, a credential, a store review, a decision only the user can make. Record any partial work already shipped, so the next worker knows where the baseline is. - Move it: a milestone to
wN/blocked/mN/, an inbox note towN/blocked/NNN.md. A task does not move — itsidmust match its path — so set its frontmatterstatus: blockedand put the reason in its milestone's## Blockedsection. When every open task in a milestone is blocked, block the milestone itself. - In the workstream
README.md: keep the milestone's- [ ]line (blocked work is still pending), retarget its link to./blocked/mN/README.md, and append— **blocked:** <one line>. - Verify the exit condition before returning: the item sits under
blocked/(or the task readsstatus: blocked), the blocker and its Unblock: condition are written down, and every link that pointed at the old path still resolves.
Show the intended moves before mutating if the user passed DRY_RUN=1.
unblock <wN/mN, wN/mN/tNNN or wN/NNN>
The reverse, once the unblock condition is actually met — verify it against the repository first, and say what cleared it.
- Move the item back to
wN/mN/orwN/NNN.md, or set the blocked task'sstatus:back totodo. - Replace the
## Blockedsection (or the note's blocked paragraph) with a one-line record of what cleared it and when. Keep the history; do not erase that it was blocked. - Restore the workstream
README.mdlink and drop the— **blocked:** …suffix. - Verify the exit condition before returning: the
blocked/path no longer exists, the item is live again, and its links resolve.
drop <wN/mN, wN/mN/tNNN or wN/NNN> <reason>
Remove work that should no longer be done. Refuse without a reason, and refuse for anything already under done/ — completed history is never rewritten. For a milestone whose definition of done already holds, use done on its tasks instead; for work that is still wanted but stuck, use block. drop records that the work is unwanted, not that it is finished or waiting.
For a task wN/mN/tNNN: delete the task file, remove its row from the milestone README.md table, update the depends_on of every task that named it, correct the (N tasks) count in the workstream README.md, and add the tombstone line under a ## Dropped section in the milestone README.md rather than the workstream one. Never drop a task to make a milestone look complete — if the work is still needed, the milestone is blocked, not done.
- Confirm the target exists in the open tree (
wN/mN/orwN/NNN.md) and read it, so the tombstone can name what it was. - Delete the milestone directory (including any
wN/mN/done/tasks) or the inbox note file. - In the workstream
README.md: remove the milestone's- [ ] **mN**line, then add- ~~**mN**~~ — <title> — dropped <YYYY-MM-DD>: <reason>under a## Droppedsection (create it directly after## Milestonesif absent). Inbox notes get the same line keyed byNNN. Keep the reason public-safe and cite what justifies it — theDO_NOT_DO.mdrule, ADR, commit, or superseding milestone. - If the reason is a rule the board should enforce going forward, say so in the response; add it to
.pm/DO_NOT_DO.mdonly when the user asks. - Verify the exit condition before returning: the old path no longer exists, the checkbox line is gone, and the tombstone is present. Do not report success until all three hold.
Show the intended removals before mutating if the user passed DRY_RUN=1.
Templates
Workstream README.md
# wN — <generic worker-queue title> (<worker>)
**Worker:** <worker> — general-purpose adoption worker; accepts the next highest-impact milestone across packages, topics, and A1/A2/A3 rather than owning a permanent specialty.
## Milestones
- [ ] **mN** — <title> (<N> tasks) ← from <source>Milestone README.md
# wN · mN — <name>
**Worker:** <worker> **Goal:** <what shipping this achieves> **Status:** todo
## Tasks (in order)
| id | title | est | depends_on |
| ---- | ------- | --- | ---------- |
| t001 | <title> | 30m | — |
## Definition of done
<observable, testable end state>
## Source + Goal linkage
- **Source:** <pointer to the inbox note / brainstorm / docs this came from>
- **Goal linkage:** <which adoption pillar (A1/A2/A3) this advances, and how>
- **Expected outcome:** <observable adoption impact after shipping — who can now do what>
- **Why now:** <dependency / risk / sequence rationale>Task tNNN.md
---
id: wN/mN/tNNN
title: <title>
worker: <worker>
status: todo
estimate: 30m
depends_on: [wN/mN/tMMM]
---
## Objective
<one paragraph>
## Context
- <concrete paths / packages / facts>
## Steps
1. <step>
## Files
- <paths to touch>
## Acceptance criteria
- [ ] <testable check>
## Out of scope
- <deferred adjacent work>Inbox note wN/NNN.md
Plain terse markdown, no frontmatter — one idea or a sub-hour unit of work.
Arguments
$ARGUMENTS