SessionStart hook automatically injects the project briefing every time you start or resume, even after a context compaction. This guide covers the commands you’ll use every day once your project is initialized with /pm:init.
Starting a session
The moment Claude Code opens, PM is already working. TheSessionStart hook fires on startup, resume, and compact events and runs conductor.mjs brief automatically. Without typing a single command, you immediately see:
- The active epic — its lane, priority, and current story progress
- The detour stack — any paused work, with
⚠ reconcile-on-resumeflags where applicable - The next-up queue — your highest-priority
queuedepics ordered P0 → P3
/pm:init, the hook is a complete no-op — it exits silently and does not affect the session in any way.
The daily loop
A typical working day with PM follows a clear rhythm:1
Open Claude Code
The
SessionStart briefing fires automatically. You see the active epic, any paused work, and your queue — no command needed.2
Confirm with /pm:status
Run
/pm:status for a fresh-rendered view. This re-renders PROJECT.md from .conductor/state.json first, so the output always reflects the current state of record.3
Get a work recommendation
Run
/pm:next to receive a concrete recommendation for what to build right now. PM applies the full priority algorithm and tells you exactly which epic to pick up and why.4
Build the active epic in its lane
Work through the epic using whatever workflow its lane requires — an OpenSpec proposal flow, a Superpowers plan, or a direct claude-code build. PM tracks status but stays out of your way while you build.
5
Commit and update status
After each
git commit, PM’s commit hook nudges you to update the epic’s status with update-epic — the verb, never a hand-edit of .conductor/state.json. This keeps the index honest and re-renders PROJECT.md automatically.6
Sync new work
Run
/pm:sync to register any new OpenSpec proposals or Superpowers plans that appeared on disk since your last session. If a github-issues tracker is configured, this also pulls open issues in as untriaged epics.Checking status
/pm:status re-renders the briefing on demand. What you see:
- Active epic — the currently in-flight epic, its lane (e.g.
openspec,superpowers,claude-code), and live progress: its inline stories and the checkbox state intasks.mdor your plan file, counted together (0.50.0) — an archived change is read from its archivedtasks.md - Detour stack — every paused frame in order, with a
⚠ reconcile-on-resumewarning on any frame whose detour touched code the paused epic depends on - Next-up queue — the highest-priority
queuedepics sorted P0 → P3, withdepends-onblockers named inline - Per-lane counts — how many epics are active, queued, or paused in each lane
.conductor/state.json. PROJECT.md is a generated view — re-render it any time by running /pm:status or the render subcommand directly.
Deciding what’s next
/pm:next applies a strict priority algorithm so you never have to guess:
- If the detour stack is non-empty — the top frame is the answer. Finish or resume that work before picking up anything new.
- Otherwise — find the highest-priority
queuedepic, ordered P0 → P3. - Ties — if two epics share the same priority, PM surfaces both and asks you to decide rather than silently picking one.
depends-on link also shapes the queue: if an epic’s declared dependency is not yet archived, that epic is skipped entirely and the blocker is named explicitly in the output. You always know why something was skipped, not just that it was.
After a context compaction
PM is designed around the assumption that context will compact. Two hooks collaborate to make the recovery seamless:PreCompactfires right before the context window collapses. It runsconductor.mjs snapshot, which re-rendersPROJECT.mdand writes.conductor/brief.txtwith the freshest possible state — so nothing is lost in the collapse.SessionStartfires immediately after (on thecompactevent) and re-injects the full briefing viaadditionalContext. By the time you see the next response, PM has already re-oriented the session around the current queue.
After every git commit
PM runscommit-nudge after every Bash tool use — on PostToolUse when the call succeeded and, as of 0.45.0, on PostToolUseFailure when it failed, so a commit made inside a call that then exited non-zero is no longer invisible. The output envelope names the event it answers. A failed Bash call now spawns one hook process (63 ms per no-op run, mean of 20); a successful one gains none. The hook asks whether a commit actually happened and does two things:
- Re-renders
PROJECT.mdso the index stays current. - Inspects the commit to detect whether it looks like an unlogged minimal detour — a small, self-contained fix that happened silently. If it does, PM suggests running
log-detourto append a timestamped record to.conductor/detours.log.
As of 0.32.0 this observes the repository rather than reading the command. It used to check whether the command’s text contained
git commit, which fired on a grep, a heredoc, or a doc edit that merely mentioned it — asserting a commit that never happened. It now records a HEAD watermark on every Bash call and classifies any move through the reflog.That is flag-form-blind by construction: -am, -F, an editor commit with no message flag, escaped quotes, and forms git has not invented yet are all covered, because the subject comes from the commit itself rather than from a pattern over your command. It also fixes the opposite failure, which was the quieter one — a commit made inside a wrapper script, whose text never says git commit, used to go unnoticed entirely.Every read is three-valued: no git, not a repository, no reflog, or a missing watermark all fall back to the previous behaviour rather than erroring.As of 0.45.0 it reports EVERY commit since a reflog anchor, not just HEAD. The watermark was a single position, so a commit followed by a
checkout in the same call read as no commit at all — and the watermark had already moved past it, so no later call reported it either. Two commits in one call reported only the last. The hook now keeps an anchor in .conductor/commit-observe.json — the reflog’s byte size and its last line, stored as bytes (anchor.lineBase64, since a reflog is not guaranteed to be UTF-8) — plus the set of shas it has already named, and reports every commit… entry after the anchor, oldest first. The attribution command carries every --attribute-commit, in landing order, as one invocation.Every report states its provenance. The commits “landed since the last observation — this call, another terminal, or a parallel call; the hook cannot tell which.” It never claims your call made them. A commit made in another terminal is still reported, and can still be auto-logged against the active epic — a stated residual, corrected with retract-detour.A commit it could not have attributed safely is not offered. One rewritten by pull --rebase or reset away is named as rewritten or abandoned, with no row and no --attribute-commit. An amend replaces: the replaced commit’s automatic row is retracted and, for each epic that attributed it, update-epic <id> --withdraw-commit <replaced> --withdrawal-reason "amended into <new>" is printed before any attribution command — or, where update-epic would refuse that withdrawal for a delivered epic, the hook says so rather than printing a command that fails. An undone amend replaces nothing.Overlapping runs cannot drop or repeat a commit. The observation runs under an O_EXCL lock; a run that cannot take it within 200 ms skips and the next run reports. The lock is broken on liveness — at once for a dead holder, after 10 s where liveness cannot be confirmed, never for age alone while its holder is confirmed alive — and a run releases only its own lock.
