bex-co

pm

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.

bex-co 312 39 Updated 5d ago
GitHub

Install

npx skillscat add bex-co/beancount-io/pm

Install via the SkillsCat registry.

SKILL.md

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.md before 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 note wN/NNN.md. Tasks take tens of minutes; milestones take hours.

  • IDs must match the path. A task's id: wN/mN/tNNN frontmatter 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.md milestone checkbox, the milestone README.md **Status:** line + the — DONE marker in the task table, and each task's status: 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 with status: done remains at wN/mN/tNNN.md, or while an affected milestone with no open tasks remains at wN/mN/. Move completed tasks to wN/mN/done/ and completed milestones to wN/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 workstream README.md keeps a one-line tombstone under ## Dropped, and its number is never reused. done is for finished work and drop is for unwanted work — a milestone whose definition of done already holds is closed with done, not dropped, and work already under done/ 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 to wN/blocked/mN/, an inbox note to wN/blocked/NNN.md. A task cannot leave its milestone — its id must match its path — so a blocked task stays put with status: blocked and the milestone README.md carries 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 free wN / mN. Scan the tree first, including ## Dropped tombstones and everything under done/ and blocked/; don't reuse a number.

  • Use worker: worker1 unless 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:

    1. 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 root README.md and AGENTS.md package tables, the appropriate skill catalog (skills/AGENTS.md for customer workflows, .agents/AGENTS.md for development workflows) for new or changed skills, every scope's guidance in a real AGENTS.md, and the .claude/skills link to the real .agents/skills directory (root AGENTS.md compatibility 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.
    2. Simplify — run /simplify over the code this milestone changed (reuse / simplification / efficiency; behavior-preserving).
    3. 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.
    4. 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 each tNNN.md to wN/mN/done/, mark every row — **DONE** and set **Status:** done in the milestone README.md, move the whole wN/mN/ directory to wN/done/mN/, and check - [x] in the workstream README.md. Completing this task is the move — running /pm done <wN/mN/tNNN> on it last triggers the milestone move (the done subcommand's step 4). Do not run it until the DoD holds: a milestone lands in done/ when its observable end state is real, not merely when the code is written.

    Each depends_on the 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-task inserts new work before these (before Closeout) and updates their depends_on.

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 linkage or 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/ with README.md (milestone template) + one tNNN.md per 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 workstream README.md, and fill ## Source + Goal linkage with 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.md and 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>

  1. 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 on main through another milestone or commit, not through work done for this task), append a ## Closed by triage section to the task with the evidence — commit SHA, paths, test names — so the public record shows why it closed without work.
  2. In the milestone README.md: mark the row — **DONE** and update the **Status:** line (e.g. todo (t001 done)).
  3. Move the file to wN/mN/done/tNNN.md.
  4. If no open tasks remain in the milestone, move the whole milestone to wN/done/mN/ and check its box (- [x]) in the workstream README.md.
  5. Verify the exit condition before returning: the completed task exists only under done/; and, when no open tasks remain, the milestone exists only at wN/done/mN/, its README.md says **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.

  1. Confirm the target is in the open tree and read it, so the record can name what is stuck.
  2. Write the block into the item: a ## Blocked section 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.
  3. Move it: a milestone to wN/blocked/mN/, an inbox note to wN/blocked/NNN.md. A task does not move — its id must match its path — so set its frontmatter status: blocked and put the reason in its milestone's ## Blocked section. When every open task in a milestone is blocked, block the milestone itself.
  4. 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>.
  5. Verify the exit condition before returning: the item sits under blocked/ (or the task reads status: 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.

  1. Move the item back to wN/mN/ or wN/NNN.md, or set the blocked task's status: back to todo.
  2. Replace the ## Blocked section (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.
  3. Restore the workstream README.md link and drop the — **blocked:** … suffix.
  4. 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.

  1. Confirm the target exists in the open tree (wN/mN/ or wN/NNN.md) and read it, so the tombstone can name what it was.
  2. Delete the milestone directory (including any wN/mN/done/ tasks) or the inbox note file.
  3. In the workstream README.md: remove the milestone's - [ ] **mN** line, then add - ~~**mN**~~ — <title> — dropped <YYYY-MM-DD>: <reason> under a ## Dropped section (create it directly after ## Milestones if absent). Inbox notes get the same line keyed by NNN. Keep the reason public-safe and cite what justifies it — the DO_NOT_DO.md rule, ADR, commit, or superseding milestone.
  4. If the reason is a rule the board should enforce going forward, say so in the response; add it to .pm/DO_NOT_DO.md only when the user asks.
  5. 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