A pi package for working in parallel git worktrees and for
the spec-driven coordinator, plus chook tool-call policy enforcement. Plain
pi, any provider, Linux or macOS.
Prompts: /track, /tracks, /handoff, /pickup, /land, /spec,
/spec-watch, /track-ops, and /waiting, which provides a read-only list of what needs a person's
action across every spec-driven repository under $TRACKS_REPO_ROOTS (default
$HOME/workspace and $HOME/misc), including repositories no session has
open. For example:
/waiting
Extensions:
path.ts puts this package's bin/ on PATH, so prompts and the bash
tool call the scripts by bare name.
chook/ runs every tool call past chook and adds Pi-specific guards
(path containment, the track repo root guard).
track-context/ reports at session start whether the session is in a
track worktree or the primary checkout.
subagent/ is pi's example subagent tool, vendored; it also reads this
package's agents/, applies SPEC_WATCH_REVIEWER_MODEL to spec-reviewer,
and accepts an optional per-call model.
track-monitor/ posts read-only track/CI change notifications into an
interactive session, and only in repositories that opt in (see "Track
monitor (opt-in)" below).
track-loop/ is the coordinator's default-on wake loop: in an interactive/RPC
session standing in the primary checkout of a repo declaring tracks.json,
it polls spec/worktree state every 30 s and wakes the agent like the
operator-run watcher does (read-only framing, batched, rate-limited). Each
wake names the change and a suggested next move — e.g. awaiting-approval
→ prep /land; a parked track → tell the person. Advisory only: no new
authority, lease-held exactly one session per checkout, /track-loop off
silences it for the session (on restores).
track-reporter/ injects finished background-work reports from the shared
report directory into the session (see "Track reporter" below).
Agents: spec-reviewer, the read-only reviewer /spec-watch runs before
marking work awaiting-approval; track-worker, which /track uses to
spawn a track's first writer and /pickup uses to resume one, both through
the active harness's agent facility.
Scripts in bin/: track placement and status, spec status, the
coordinator's runtime and writer launcher, the scheduled tick, and the
scheduler. out-of-boundary.sh is the agent-run audited channel for writes
outside a session's tree: the session writes an idempotent, secret-free
script with a # side effects: header and runs it through the wrapper,
which keeps a private run log and appends start/end lines (with the exit
code) to .operator-run/journal.jsonl — the audit ledger the person can
tail. Interactive or credential-bearing steps still belong to the person
via the operator-run skill. This bin/ is shared: the Claude adapter links Claude Code
directly to these files rather than maintaining copies.
In every repository that declares tracks.json — this package included —
feature and fix work goes through the track system: /track <name> <task>
opens a track with its own worktree, branch, writer, review and landing;
/spec <name> writes a spec the schedule or the loop then drives. The
primary checkout's committed surface is coordination files only:
tracks.json, specs/, .gitignore (handoff.md is never committed). No
drive-by commits to the base branch, and no subagent produces repo work
outside a track — a subagent dispatch is reserved for the sanctioned
spec-reviewer review leg and for a track's own writer.
The one exception: the person explicitly says "commit direct", and the
handoff records that instruction. Work the person asks for in conversation
otherwise gets a track like any other work.
Watched sessions — print mode, a subagent, a background writer — were
already blocked from editing the primary checkout and stay blocked.
Install the adapter once on each machine that runs these workflows in Claude
Code. It is machine-wide, not per repository:
bin/install-claude-adapter.sh installbin/install-claude-adapter.sh doctor
It creates the stable machine-local link
$HOME/.local/share/pi-tracks -> <this checkout>, backs up conflicting old
Claude files, and links this package's scripts, prompts, and agents into
$HOME/.claude/. Changes in the checkout then affect both Pi and Claude
without copying or regeneration. remove deletes only adapter-owned links
and never deletes its backups.
The links are derived from the package's bin/, prompts/ and
claude/agents/ directories, so a file added there needs no edit to the
installer; only the few helpers in bin/ that are not entry points are listed
by name in install-claude-adapter.sh. Nor does landing such a file need a
manual install: each Claude tick (bin/spec-watch-tick.sh) runs doctor and,
when the only problems are adapter links, runs install, logging what it did to
$HOME/.local/state/spec-watch/spec-watch.log. A wrong stable link is only
reported there. Run install by hand once per machine, the first time.
Set TRACKS_TOOL=claude in $HOME/.config/pi-tracks/env for schedules that
should launch Claude; use pi or omit it for Pi.
The adapter is installed only once per machine, but schedules are configured
per watched repository. After installing the adapter for the first time, reinstall
each repository's existing spec-watch schedule once so its command uses the
stable canonical path:
The chook extension runs the first of $CHOOK_PATH, /usr/local/bin/chook
and $HOME/chook/chook.mjs that exists, and is disabled with a warning if
none does. Rules come from $HOME/.config/chook-pi.toml if present, else the
package's chook-pi.toml.
extensions/track-monitor/ watches a repository's tracks and one configured
CI workflow from inside an interactive/RPC Pi session and posts a short
read-only notification when something changes. It is strictly opt-in: a
repository declares it with .pi/track-monitor.json or with a monitor
object in tracks.json (.pi/track-monitor.json wins when both exist). A
repository that declares neither gets no monitor, no lease, no polling and no
warning; /track-monitor on there warns that nothing is configured instead
of inventing defaults. It starts only in an interactive/RPC session in the
primary checkout, never in print/JSON writer processes or linked worktrees.
Configuration fields, revalidated on every poll:
targets: up to 16 explicit {name, kind} watches; kind: "spec" (name
must start with spec-, state comes from canonical spec status) or
kind: "manual" (state comes from Git and writer runtime metadata).
githubRepo + workflow: the one CI workflow whose latest run is watched
(via gh run list).
discover: boolean, default true; also watch direct registered worktrees
under the tracks.json worktree directory. false selects explicit
targets only.
exclude: exact track names to drop. Names on tracks.json's parked
list are excluded even when explicitly targeted.
One coordinator session per checkout owns .tmp/pi-track-monitor.lock
(gitignored scratch); existing or malformed locks are never stolen. The
monitor samples every 30 s, batches notifications at least 60 s apart, and
sends at most 30 automatic updates per hour; pending changes survive
throttling and runtime-reload checkpoints.
The monitor is read-only: bounded local git reads, spec-status.mjs --tool pi --read-only resolved from this package's bin/, and gh run list for
the configured repository/workflow. No fetch/pull/push, no token-file reads,
no raw test/CI/session log reads, no shell interpolation. Commands:
/track-monitor status — activation, last sample, pending changes, quotas.
/track-monitor off / on — stop/resume for this session branch.
/track-monitor test — one clearly labelled synthetic wake-up.
extensions/track-reporter/ watches the shared directory
/tmp/track-reports-global for *.report files written by a finished
background worker's launcher, and injects each matching report into the
session as a message that starts a turn, so a coordinator reports finished
work without being pinged. Reports are repo-scoped by their first line: a
report beginning repo=<name> is delivered only in a session whose working
directory ends in <name>, and a report with no tag goes to every session. A
report for another repository stays on disk for that repository's session;
leftover reports are purged on session start.
The extension reads only the report file itself. It never captures
credentials, token files, environment values, session transcripts or raw
worker/CI logs, runs no commands, and keeps no UI ledger — delivery is a
message only.
The poller is unref'd and both the poller and the watcher are closed on
session_shutdown; otherwise every pi --print process stays alive after its
turn. extensions/track-monitor/ follows the same rule, and
test/extension-lifetime.mjs asserts both with a real bounded print run.
Remove the older global copy at ~/.pi/agent/extensions/track-reporter.ts
(and its .bak-* files) once this package is installed: two loaded copies
deliver every finished report twice.
A repository that uses these tracks starts a writer in a worktree through one
harness-neutral command and never names a harness binary, a harness config
directory or a harness guard package itself:
PI_TRACKS_HOME is the package location, default
$HOME/.local/share/pi-tracks.
Run it with the working directory set to the worktree the writer should work
in. --model "" leaves the harness on its default model.
<name> is claude or pi. An unknown name exits 2 with one line on stderr
naming the known adapters.
It first brings the harness's own configuration up to date on this host, then
starts one background session with the brief read from the file, unmodified,
and returns as soon as the session has started. For claude the
configuration is the config repository ($CLAUDE_HOME, default
$HOME/.claude), the guard repository ($CHOOK_HOME, default
$HOME/chook) and the package itself, each fast-forwarded from its upstream
(a repository with no upstream is left alone; a missing one or a failed update
refuses). For pi it is the package.
Exit 0 means started, and stdout carries only what the harness prints. Any
refusal (untrusted folder, missing binary, failed update) exits nonzero with
one line on stderr saying why; a folder the harness does not trust exits 3.
Unattended-run settings live in the adapter: claude runs claude --bg with
CHOOK_UNATTENDED=1 set through --settings (a --bg session runs under the
background daemon's environment, never the launcher's) and the accept-edits
permission mode; pi runs
pi -p --no-session in its own session with CHOOK_UNATTENDED=1.
Optional, for a caller that runs on the host it is already running from:
PI_TRACKS_WRITER_PREPARE=skip skips the configuration update. The pi
adapter appends the session's output to PI_TRACKS_WRITER_LOG and writes its
process id to PI_TRACKS_WRITER_PID_FILE when those are set. The binaries
are found at the SPEC_WATCH_CLAUDE_BIN / SPEC_WATCH_PI_BIN override, then
$HOME/.local/bin, then PATH.
To support another harness, add adapters/<name>.sh; bin/writer-exec.sh
needs no edit. It is sourced with the working directory, model and brief
set, and defines two functions: prepare (bring the harness's configuration up
to date) and start (start the session and return). Adapters call
refuse <exit-code> <one-line reason> to refuse, and may use `update_repo