What it is
Long-running work spans many sessions. Each one ends with context exhausted and its findings in
scrollback; the next re-derives position from the code, repeats settled decisions, and loses the
expensive lessons — the ones a defect taught, not a design. programme gives a repo a place to put
that: docs/programmes/<slug>/ledger.md, kept under ~250 lines, read in full at the start of every
session.
It does not summarize your work for you. /programme:handoff interviews — "what did you measure",
"what did you get wrong", "which claims in the ledger are now false" — because a prompt that asks
"what did you do?" produces a changelog, and a changelog is not what makes a handoff worth reading.
The evidence that the artifact is the mechanism, not the command
This plugin was extracted from a repo that solved session continuity over roughly fifteen sessions
before any of this existed. Measured against docs/programme-ledger.md's git history,
2026-08-06 → 2026-08-12:
| measure | value |
|---|---|
| commits touching the ledger | 58 in 7 days |
| per session (~15 sessions) | ~4 |
| growth | 150 → 2322 lines, monotonic, 122 headers |
| commands that existed to trigger a write | none |
| hooks that existed to trigger a write | none |
| skills that existed to trigger a write | none |
Four writes per session, at phase boundaries, with no hook and no skill in existence, and no command
that ever triggered one — a read-only precursor to /programme:resume existed for 42 of the 58
commits (added 2026-08-07) but only ever read the ledger, never wrote to it. What drove the writes
was two pieces of prose that were always loaded: the ledger header, which states its
own maintenance rule and is re-read every time the file is opened, and a CLAUDE.md pointer with a
precedence rule. This plugin's first job is to install those two things — a ledger that teaches its
own upkeep, and the pointer that carries the cadence — giving a new repo in minutes what took the
source repo several sessions to grow on its own. The commands are conveniences layered on top of
that artifact, not the mechanism itself.
Install
/plugin marketplace add longthread/claude-plugins
/plugin install programme@longthread
Verified 2026-08-14: installs, enables, and the hooks resolve a real programme. /programme:init
was run against a 2710-line ledger, and the Stop guard was confirmed to fire when code moves and
the ledger does not, then latch silent.
To install from a local clone instead — edits go live on the next session start, which is what you
want while developing — give marketplace add the path to the marketplace root (the one holding
.claude-plugin/marketplace.json), not this plugin's directory:
/plugin marketplace add /abs/path/to/claude-plugins
A local marketplace is read in place, never copied. The recorded installLocation is the path
you gave. Point it at a clone that stays put — never at a directory inside a repo whose branch you
switch, or the plugin vanishes globally on the first checkout that predates it.
The four commands
/programme:init <name>— bootstraps a programme: copies the templates intodocs/programmes/<slug>/, adds a row todocs/programmes/INDEX.md, installs theCLAUDE.mdpointer, and interviews for the starting position, the terminal condition, and the project's gates (with their trust). Refuses on a repo that looks like a series of unrelated tickets rather than a running thread, unless told to proceed anyway./programme:resume [name]— resolves the active programme from the current branch againstINDEX.md, reads the ledger in full andNEXT-SESSION.mdverbatim, then runs every command in the State table'sverify withcolumn rather than trusting the values next to them — the source repo has a live example of two documents disagreeing about the same figure./programme:handoff [name]— the deliberate end-of-thread write. Measures state itself (git fetch, ahead-of-compare count, every declared gate), asks the four interview questions, corrects the ledger in place, archives a closed phase, and rewritesNEXT-SESSION.mdwholesale. On a closing handoff, refuses to mark the programme closed whiledeferred.mdstill has open rows./programme:status [name] [--gates]— where the programme is, then whether its record is still true: runs everyverify withcommand in the State table and reports what has drifted, lists what is owed, and stops. It writes nothing, deliberately — a status that quietly corrects the record cannot tell you whether the record is honest, because running it makes the answer yes. Slow gates are printed as recorded, with theirtrustandlast run, and only run with--gates.
The config file
.claude/session-continuity.json, created by /programme:init if absent, with docsRoot and
compareBranch only:
{
"docsRoot": "docs",
"compareBranch": "origin/main"
}
docsRoot— whereprogrammes/lives. Defaults todocs.compareBranch— whatresumeandhandoffmeasure "ahead of" against.codePathspec— optional, not written by/programme:init. Overrides what theStopguard counts as "code changed"; its default (everything except markdown,docsRoot, and.claude/) is derived fromdocsRoot, not hardcoded, so a repo whosedocsRootisn'tdocsstill gets it right without this key.
A dedicated file rather than settings.json: the gate list is structured, which rules out env, and
unknown keys in settings.json are not a contract worth relying on.
When NOT to use it
This fits multi-session programme work with a running thread — the kind where a session would ever
have to ask "where were we?" It does not fit a repo that is a series of unrelated tickets. Point it
at one of those and the ledger goes stale, and a stale ledger is worse than no ledger: it actively
misleads, the way a comment describing behaviour the code no longer has misleads. /programme:init
asks before bootstrapping for exactly this reason, and will refuse on silence.
With no .claude/session-continuity.json and no docs/programmes/ directory, every hook exits 0
silently — a repo that hasn't opted in feels nothing.
Deliberately not built
- A checkpoint-detection hook (watching tool calls for merges, gate runs, version bumps). It would add machinery to enforce a behaviour that already happened 58 times without it, and a hook that fires on every qualifying tool call risks nagging on ordinary work.
- A session journal. The ledger already serves that role — it's written at each checkpoint, not only at session end.
- A
SessionEndwarning. By session end the model can no longer act and the record most in need of saving is already lost — a scolding, not a save. TheStopguard, latched to fire once, catches the same lapse while the work is still fresh enough to act on. - A
ledger-disciplineskill. It had no moment where it was the only thing loaded: editing the ledger requires reading it, and the header is right there. It was also the largest source of duplicated rules in an earlier draft of this design. Its content lives in the two files that are already loaded at the moments it would have fired — the ledger header and theCLAUDE.mdpointer.
PreCompact is unverified
hooks/pre-compact.sh warns when the ledger hasn't moved and a compaction is about to discard
whatever hasn't been written. This channel is unverified: PreCompact is not a confirmed member
of Claude Code's hookSpecificOutput union and does not support prompt-type hooks, so its output may
reach only the user, never the model. Nothing in this design depends on it. The load-bearing
mechanism is the ledger header and the CLAUDE.md pointer (see above); SessionStart's
additionalContext re-injection after compaction is confirmed and does the real work of
post-compaction recovery.
Where each rule lives — exactly one home
Every rule below has exactly one home, in prose where the rule is about when or why, and as a
table schema where the rule can be a column instead. tests/test-templates.sh reads the six
markdown templates and tests/test-commands.sh reads the four commands; both assert that a rule
appears in its home and that it does not appear anywhere else. Neither reaches the hooks.
Both readers fold newlines to spaces before matching. Without that, a rule restated across a formatter's line wrap satisfies the negative assertion while the duplication is right there — the guard passes precisely when it should fire. That hole was live until 2026-08-14.
session-start.sh restates two of these rules
(the ledger-outranks-handoff precedence and "verify, don't trust") when it re-injects the ledger
position after compaction; whether that is duplication or the only thing carrying those two rules
across a compaction boundary is open (the live-run checklist's Question 2, in the repo this was extracted from).
| rule | home | form |
|---|---|---|
| when to write (the cadence) | CLAUDE.md pointer |
prose — needed away from the file |
| ledger outranks every handoff | CLAUDE.md pointer |
prose — the only file above both |
| correct in place | ledger.md header |
prose — genuinely about that file |
cite or flag UNVERIFIED |
ledger.md header |
prose — governs its narrative |
| absolute dates | ledger.md header |
prose |
| archive past 250 lines | ledger.md header |
prose — about its own size |
| who decided, and why | ledger.md |
schema — who | why columns |
| gate trust | ledger.md Gates table |
schema — trust column |
| don't trust a figure, re-derive | NEXT-SESSION.md |
schema — verify with column |
| terminal conditions as observables | NEXT-SESSION.md |
schema — command + expected |
| fix shape, not a title | both deferred tables | schema — current behaviour | fix shape |
| promotion before close | deferred.md header |
prose — that file's lifecycle |
| verify against the tree | commands/resume.md |
procedure |
| resolve which programme is active | commands/resume.md |
procedure — the others defer to it |
| report without repairing | commands/status.md |
procedure |
| the four interview questions | commands/handoff.md |
procedure |
fix shape appears in two tables — deferred.md and deferred-work.md — because deferred work has
two lifetimes: some items die with the programme, some outlive it. That is one schema definition
shared by two tables, not a rationale stated twice.
One pair is prose on both sides deliberately: deferred.md's header says an item that outlives the
programme belongs in deferred-work.md, and deferred-work.md's header says an item scoped to one
programme belongs back in deferred.md. Each file states its own scope, which is unique per file;
the routing half is the same rule read from the opposite side, and both halves have to exist so a
maintainer who opens only one of the two files can still find the door.
Full rationale for every design decision here, including the three corrections made against measured
evidence: docs/tooling/2026-08-12-session-continuity-plugin-design.md in the repo this plugin was
extracted from.