Important
This repository has moved into OpenCnid/dovetail.
upsum is now one of nine skills in that pack, at
skills/upsum/.
Install the whole pack with a plain clone — there are no submodules:
git clone https://github.com/OpenCnid/dovetail.git
cd dovetail && bash scripts/install.shThe eight skills were separate repositories while each was developed on its
own. They are used together, so they are now maintained together; keeping them
apart cost a pin-bumping step before every change and bought nothing a reader
could see. This repository is archived and read-only. Its history is the record
of how this skill got here, and docs/provenance.md in dovetail names the
commit its content arrived at.
UPdate and SUMmarize. A session ends whether or not anyone writes it down.
A Claude Code skill for closing a working session: append what happened to a growing record, re-derive a fixed-length summary, carry forward what is still open, and run the pre-publish checks that catch what a session forgets.
The record grows and never forgets. The summary stays the same size forever and forgets on purpose. Running them together is what stops a project's memory from becoming either a swamp or a slogan.
.upsum/RECORD.md |
append-only, one entry per session, never edited |
.upsum/SUMMARY.md |
rewritten whole, fixed word budget that never grows |
TODO.md |
rewritten whole, every item names its blocker |
The newest entry keeps nearly all its detail. The oldest collapse toward a label. Old work is done, and what stays useful about it is that it happened; recent work is what you are still holding, so its specifics are still actionable.
An entry ages by losing resolution, never by being dropped — a stretch of early setup work ends up as two words, and it is still there. That is also what keeps the word count honest without anyone policing it: every session the old end shrinks by a little and frees exactly the room the new end needs, so the budget holds by construction rather than by discipline.
Like spark-steering, this carries disable-model-invocation: true and is
invoked by typing /upsum. A close ceremony that volunteered itself would run
on every throwaway session and become the tax it exists to prevent.
The cost of that choice is that it can be forgotten. That is a real trade and it is stated rather than hidden.
python skills/upsum/scripts/checks.py . # standing in a clone of this repo
python ~/.claude/skills/upsum/scripts/checks.py . # standing in the repo you are checkingThe first form works only inside a clone of upsum, because that is the only
place skills/upsum/ exists. Everywhere else — which is everywhere the skill is
actually used — the script is beside the installed skill and the trailing . is
your repository.
Four checks, each bought by something that shipped broken while nobody was looking for it:
- Inside baseball — references that resolve only for a reader inside one organisation: a private repository by name, a rule number in a document the reader does not have, a local filesystem path, a citation whose target ships nowhere.
- Repo state — uncommitted work, unpushed branches, missing upstreams. Work that exists in one place is work you are one reinstall from losing.
- Skill health —
SKILL.mdlength against the ~19,900-character surviving prefix, frontmatter validity,namematching its directory. - Credit and licence travel — whether
LICENSEand attribution survive the install path the README documents.
By default it reads reader-facing surfaces only — references/, vendor/,
tests/ and the like are archival by intent. Where a repo keeps a references/
mirror, citations are meant to resolve into it, so holding it to the
portability standard inverts the check. --all scans them anyway.
A check that could not run reports UNMEASURED, never clean. The exit
code is about measurement integrity, not findings — 0 every check ran, 1 at
least one could not, 2 the script itself failed. Findings never change it.
This reports; you gate.
One known imprecision, stated rather than papered over: an unresolved file citation is ambiguous by construction. A dead pointer and a path created at runtime look identical from outside, so that category needs a human read.
git clone https://github.com/OpenCnid/upsum.git
mkdir -p ~/.claude/skills
cp -r upsum/skills/upsum ~/.claude/skills/Do not skip the mkdir. If ~/.claude/skills/ does not already exist, cp
reads the trailing path as a rename target: you get ~/.claude/skills/SKILL.md
with no skill directory, exit code 0, and no error.
PowerShell:
git clone https://github.com/OpenCnid/upsum.git
New-Item -ItemType Directory -Force ~/.claude/skills
Copy-Item -Recurse -Force upsum/skills/upsum ~/.claude/skills/If you have set CLAUDE_CONFIG_DIR, that is your skills root, not ~/.claude.
Or install it with the rest of the stack: OpenCnid/dovetail.
Then type /upsum at the end of a session. It will not appear in Claude's own
skill listing, by design — the model cannot invoke it, and neither can asking
Claude to.
Not the loop. The record, the summary and the forward-carried TODO come from two years of daily practice. What is new here is the wiring into a skill, not the method — and the alternative it should be measured against is not a better close ceremony, it is closing nothing consistently.
It has to be remembered. disable-model-invocation buys freedom from an
every-session tax and pays for it in exactly this coin. That is the real soft
spot, and it is better read as a standing reminder than as a defect: if work
happened, type /upsum before the session ends.
The budget is a target, not a boundary. A hundred characters either way is fine. What must not happen is the budget growing because the record did.
The checks are the newer half and were earned differently — each was written after the defect it looks for had already shipped, and an adversarial pass over the first version found eighteen ways it reported clean when it should not have. They are correct against those fixtures and have not been run anywhere else.
Prose and skill: CC BY 4.0 © OpenCnid Labs.
The upsum concept — append everything, then summarize at a resolution that
decays with age so the word count stays honest — is
Matthew Murphy's.