pentimento
Status, intent, and lineage over agent plan files.
Coding agents leave plan files behind. After a few hundred of them you cannot tell which finished, which you still care about, or which plan replaced which — the filenames are random and the files say nothing about their own state. pentimento derives that state and gives you a CLI to list, filter, and render the lot as a lineage tree.
Two kinds of plan are worth finding again:
- The long one: an infrastructure migration designed over months grows
subplans, absorbs decisions made in discussion, and leaves behind the
branches you rejected — the part you want back a quarter later. A
superseded plan is a decision record, not garbage, which is why
supersededis the one status no derivation produces or overwrites;tree <id>brings the whole thread back, tags or no tags. - The finished one that is not over: execution ends with findings you will
not act on today. Tag it and set an intent, and
list --tagorlist --starredbrings it back when you are ready to pick the thread up again.
A pentimento is the earlier composition showing through a repainted canvas; that is what a plans directory is.
Built with Claude Code.
Install
pip install pentimento
# or from source:
pip install git+https://github.com/kjiwa/pentimento.git
Tab completion for bash, zsh, and fish is in
docs/reference.md#shell-completion.
Quick start
Point AGENT_PLANS_DIR at your plans directory (it defaults to
~/.claude/plans), then:
pentimento backfill # derive status/intent/created/parent/project once
pentimento list # see the corpus
pentimento set some-plan-id --intent active
pentimento list --starred
To park a finished plan you mean to return to, tag it and set an intent, then find it by tag later:
pentimento set some-plan-id --add-tag auth --intent someday
pentimento list --tag auth
To pull one thread of subplans back out by lineage, rather than by tag:
pentimento tree some-plan-id
pentimento tree some-plan-id --ancestors
docs/workflows.md walks all three loops end to end.
Sources and their default directories are covered in docs/integrations.md; Cursor's caveats are in docs/troubleshooting.md.
The samples below are regenerated by sh demo/capture.sh.
list
PLAN shows the short id: the shortest trailing hyphen-segment run that's
unique across the corpus, so is-it-possible-to-abundant-rabbit displays as
abundant-rabbit. The table adapts to terminal width, dropping columns
before truncating any; --columns (or PENTIMENTO_COLUMNS) overrides which
columns show and in what order — see
docs/reference.md#columns.
STATUS INTENT PROJECT SOURCE PLAN TITLE UPDATED
superseded abandoned platform claude style-guide Write a docs style guide 6w
complete abandoned platform claude auth-redesign Redesign the auth API 5w
complete someday billing claude dunning-copy Rewrite dunning email copy 3w
partial active platform claude auth-rollout Roll out the new auth API 2w
not-started queued platform claude auth-cleanup Remove the old auth API 2w
unknown unset billing claude invoice-retry Retry failed invoice charges 1w
not-started active billing claude relevance-tuning Tune search relevance 5d
unknown unset claude onboarding-checklist Write the onboarding checklist 1d
8 plans
tree
Plans nested under their parents, grouped by project. Plain tree renders
the whole corpus this way; tree <id> roots it at one plan instead — that
plan plus everything beneath it, resolved against the whole corpus, so
--project is unnecessary. --ancestors also walks up to <id>'s topmost
ancestor, spine only, for pulling a single thread out of a larger forest.
(no project)
└─ Write the onboarding checklist
onboarding-checklist unknown unset 1d
billing
├─ Rewrite dunning email copy
│ dunning-copy complete someday [billing] 2026-08-20 3w
├─ Retry failed invoice charges (parent elided: no-such-plan)
│ invoice-retry unknown unset [billing] 2026-09-04 1w
└─ Tune search relevance
relevance-tuning not-started active [search] 2026-09-09 5d
platform
├─ Write a docs style guide
│ style-guide superseded abandoned 2026-07-31 6w
└─ Redesign the auth API
auth-redesign complete abandoned [auth, security] 2026-08-05 5w
└─ Roll out the new auth API
auth-rollout partial active [auth, security] 2026-08-25 2w
└─ Remove the old auth API
auth-cleanup not-started queued [auth, security] 2026-08-30 2w
8 plans
platform
└─ Redesign the auth API
auth-redesign complete abandoned [auth, security] 2026-08-05 5w
└─ Roll out the new auth API
auth-rollout partial active [auth, security] 2026-08-25 2w
└─ Remove the old auth API
auth-cleanup not-started queued [auth, security] 2026-08-30 2w
3 of 8 plans
show
# Roll out the new auth API
id: api-auth-rollout
path: ~/.claude/plans/api-auth-rollout.md
status: partial intent: active tags: [auth, security]
parent: api-auth-redesign project: platform
created: 2026-08-25 source: claude modified: 2026-08-25 12:30
Progress
✓ Ship behind a feature flag
☐ Flip the flag for all tenants
Context
Tenants opt in via the auth_v2 flag in tenant_settings. Watch error rates before flipping the
remaining cohort. See the rollout runbook.
Cohort Status
internal complete
beta in progress
check
check validates the corpus and exits 1 on any finding. It takes no plan id
— it always checks the whole corpus. The table's PLAN column uses the same
short id as list/tree; --format json|tsv emits the full id in its
plan_id field. See
docs/troubleshooting.md
for what each CODE means and how to fix it.
CODE PLAN MESSAGE
dangling-parent invoice-retry parent 'no-such-plan' does not resolve to a plan
status-behind-history auth-cleanup status 'not-started' but 1 later session worked this plan; see `pentime…
8 plans checked, 2 findings
history
history shows which sessions touched a plan's file: the session whose id
matches the plan's own id authored it; any later session that read, edited,
or delegated work on it worked it. An empty result prints
no session history for <id> — that means no matching transcript was found
on this machine, never a claim the plan wasn't worked.
WHEN WHAT SESSION TOUCHES
2026-08-30 12:30 authored api-auth-cleanup 1
2026-09-11 12:30 worked implement-api-auth-cleanup-eager-wolf 1
Frontmatter
---
pentimento:
status: not-started | partial | complete | superseded | unknown
pinned: true # omitted unless set
intent: active | queued | someday | abandoned | unset
tags: [auth, security] # omitted if untagged
parent: some-other-plan-id # omitted for roots
project: platform # omitted if undetermined
created: 2026-09-08
---
The vocabulary lives in one place: pentimento/vocabulary.py.
| Field | Set by | How |
|---|---|---|
status |
derived | Every backfill run (including the per-write pentimento hook) recomputes it from ## Progress checkboxes. The hook caps the result at partial; only a full backfill sweep advances it to complete. set --status overrides it directly — the only way to set superseded, which no derivation ever produces or overwrites — and pins it (see pinned). |
pinned |
operator | Never derived. set --status sets it to true automatically; set --unpin clears it. While set, backfill (with or without --rederive) leaves status untouched. |
intent |
operator | Gap-filled to unset by backfill the first time it sees the plan, then left alone. Only set --intent changes it after that. |
tags |
operator | Never derived. set --add-tag/--remove-tag/--clear-tags; filter with list/tree --tag, which ANDs repeated tags. |
parent |
derived, or operator | backfill fills it in from a session-prompt or body reference (an <id>.md literal or a trailing codename) to an earlier same-project, same-source plan. --rederive recomputes it from scratch, including removing one that no longer resolves. set --parent/--clear-parent set or clear it directly; set --parent rejects a value that would create a cycle. Read the chain back with tree <id>/tree <id> --ancestors. |
project |
derived, or operator | backfill derives it from a session's cwd. set --project/--clear-project set or clear it directly; --project . resolves to the current directory's name. |
created |
derived once | A local date, set once and then immutable except through backfill --recreate. |
modified |
derived, not stored | Not a frontmatter field: max(session end time, file mtime). Neither backfill nor set bumps it when the write only touches frontmatter bookkeeping. |
Lineage and source discovery are covered in full in
docs/integrations.md
and
docs/troubleshooting.md.
Cursor plans get body-only lineage and no project at all.
Commands
| Command | Does |
|---|---|
list |
Flat table of plans, one line each. |
tree [<id>] |
Plans nested under their parents, grouped by project; <id> roots the tree at one plan's thread instead, --ancestors walking up to its topmost ancestor. |
show <id> |
One plan's title, frontmatter, and rendered body. |
set <id> |
Rewrite one plan's frontmatter in place. |
backfill |
Derive and write missing frontmatter across the corpus. |
hook |
Run as a Claude Code PostToolUse hook, reading the payload on stdin. |
index |
Write INDEX.md into the plans directory. |
check |
Validate lineage and vocabulary; exits 1 on any finding. |
history <id> |
Every session that touched one plan, oldest first. |
completion <shell> |
Print a bash/zsh/fish tab-completion script. |
Full flags for every command, plus the environment variables, are in
docs/reference.md.
Run pentimento <command> --help for the same information from the CLI
itself.
Scope
pentimento is built for one operator's corpus on one machine. Shared,
concurrent, or multi-author planning is out of scope and not a gap this tool
intends to close — project, session-prompt lineage, and modified are all
derived from local Claude Code transcripts, and deriving them across authors
would need a different source, a sync, and an identity model.
Keeping the plans directory in git does get you review and history, and part
of the derived state travels with the files: status, operator-set
frontmatter, and body-referenced parent survive a checkout anywhere;
project, prompt-derived parent, and session history do not. See
docs/workflows.md
for the details, and pentimento index for an INDEX.md worth committing.
Prior art
planning-with-files keeps
an agent's plan on disk while it works and recovers it after /clear or
compaction; it manages the files it creates, not a directory of finished
ones. claude-log-viewer is a
local web app for Claude Code projects and session logs, not plan files.
dela lists markdown todos from the CLI, with no
status derived from checkboxes and no notion of supersession or lineage.
pentimento reads a corpus it did not author and derives status, project, and
parent across it. The longer comparison is in
Managing Claude Code plan files.
Requirements and limitations
Stdlib-only Python 3.9+, zero runtime dependencies, no PyYAML.
pentimento enriches modified and lineage by reading Claude Code's session
transcripts (~/.claude/projects/*.jsonl), an undocumented, private format.
If that format changes, or the transcripts are absent, this enrichment
degrades to file mtimes and plain body/preamble references — it does not
break, and the frontmatter itself stays plain, hand-editable markdown either
way.
Development
python3 -m unittest discover
uvx ruff check
uvx ruff format --check
CI (.github/workflows/check.yml) runs all three on Ubuntu and macOS.
Docs
- docs/reference.md — every command's full flags, and the environment variables.
- docs/integrations.md
— wiring
backfillandpentimento hookinto Claude Code and Cursor, a slash command, a triage-nudge pattern,checkin CI. - docs/workflows.md
— triage, picking a plan back up, supersession, lineage trees, scripting
with
--format json. - docs/troubleshooting.md
— every empty field and
checkfinding, explained.
Release files for pentimento 0.1.11
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pentimento-0.1.11.tar.gz | 864.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pentimento-0.1.11-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 925.8 kB
Release files / pentimento-0.1.11.tar.gz
| Download URL | pentimento-0.1.11.tar.gz |
|---|---|
| Size | 864.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
344e6c3385d5f2f09831e6bbc7b37bfdb2d2c3e38c4c929765ac9bd12e1cfbb1
|
|
BLAKE2b-256 checksum How to use checksums |
e86242a708d0fb1638640c99226a05a206666b908c142c8c8ad2796b6fca3664
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.
Transparency logRelease files / pentimento-0.1.11-py3-none-any.whl
| Download URL | pentimento-0.1.11-py3-none-any.whl |
|---|---|
| Size | 61.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a422156bd9aa92f6f0d3a1309df7aeff691b5a54ffb83aa07670348dd8a3a003
|
|
BLAKE2b-256 checksum How to use checksums |
a9fc96db678b3af85edc8d3fc91fd15c0f0756a6de753e9a1f4a7106ec869922
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.
Transparency log