Metadata-Version: 2.4
Name: pentimento
Version: 0.1.17
Summary: Derives status and lineage for the plan files Claude Code and Cursor leave behind, with a CLI to list, filter, and render them as a tree.
Author-email: Kamil Jiwa <kamil.jiwa@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/kjiwa/pentimento
Project-URL: Repository, https://github.com/kjiwa/pentimento
Project-URL: Issues, https://github.com/kjiwa/pentimento/issues
Keywords: plans,claude-code,cursor,cli,lineage,agent,markdown,frontmatter
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development
Classifier: Topic :: Text Processing :: Markup :: Markdown
Classifier: Topic :: Utilities
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# pentimento

Status, intent, and lineage for Claude Code and Cursor plan files.

[![check](https://github.com/kjiwa/pentimento/actions/workflows/check.yml/badge.svg)](https://github.com/kjiwa/pentimento/actions/workflows/check.yml)
[![PyPI](https://img.shields.io/pypi/v/pentimento)](https://pypi.org/project/pentimento/)
[![Python versions](https://img.shields.io/pypi/pyversions/pentimento)](https://pypi.org/project/pentimento/)

![demo](https://raw.githubusercontent.com/kjiwa/pentimento/main/demo/pentimento.gif)

Claude Code and Cursor 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.

Three 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
  `superseded` is 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 --tag` or
  `list --starred` brings it back when you are ready to pick the thread up
  again.
- The one that already answered your question: a plan from another project
  holds the reasoning behind a decision your current change would undo — why
  a CI matrix was cut, why one service avoids a library. A memory exists only
  if an agent chose to write one, and project docs only if you did; a plan
  exists whenever a decision was planned. `list --title` and `list --grep`
  search every project's plans at once, and the
  [`prior-plans` skill](https://github.com/kjiwa/pentimento/blob/main/docs/integrations.md#prior-plans-skill)
  has the agent run that search itself before it proposes a change.

A pentimento is the earlier composition showing through a repainted canvas;
that is what a plans directory is.

Built with [Claude Code](https://claude.com/claude-code).

## Install

```sh
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](https://github.com/kjiwa/pentimento/blob/main/docs/reference.md#shell-completion).

## Quick start

Point `AGENT_PLANS_DIR` at your plans directory (it defaults to
`~/.claude/plans`), then:

```sh
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:

```sh
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:

```sh
pentimento tree some-plan-id
pentimento tree some-plan-id --ancestors
```

To find how an earlier decision was made, search titles (or titles and
bodies) across every project, then reopen the plan; to see what you finished
recently, filter by date:

```sh
pentimento list --title 'github actions|\bGHA\b'
pentimento list --grep 'concurrency group' --status complete
pentimento show some-plan-id --full
pentimento list --status complete --since 1w
```

[docs/workflows.md](https://github.com/kjiwa/pentimento/blob/main/docs/workflows.md)
walks each of these end to end.

Sources and their default directories are covered in
[docs/integrations.md](https://github.com/kjiwa/pentimento/blob/main/docs/integrations.md);
Cursor's caveats are in
[docs/troubleshooting.md](https://github.com/kjiwa/pentimento/blob/main/docs/troubleshooting.md).

The samples below are regenerated by `sh demo/capture.sh`.

### list

`PLAN` shows the [short
id](https://github.com/kjiwa/pentimento/blob/main/docs/reference.md#plan-ids),
such as `abundant-rabbit` for `is-it-possible-to-abundant-rabbit`. Below the
width a table needs, each plan prints as a short record with every field kept;
`--columns` (or `PENTIMENTO_COLUMNS`) chooses which columns show and in what
order; see
[Columns](https://github.com/kjiwa/pentimento/blob/main/docs/reference.md#columns).
`list` and `tree` share their filters, listed in the
[synopsis](https://github.com/kjiwa/pentimento/blob/main/docs/reference.md#commands).

<!-- sample:list -->
```
PLAN                  STATUS       INTENT     PROJECT   SOURCE  TITLE                           TAGS             CREATED     UPDATED
style-guide           superseded   abandoned  platform  claude  Write a docs style guide                         2026-07-31       6w
auth-redesign         complete     abandoned  platform  claude  Redesign the auth API           [auth, +1]       2026-08-05       5w
dunning-copy          complete     someday    billing   claude  Rewrite dunning email copy      [billing]        2026-08-20       3w
auth-rollout          partial      active     platform  claude  Roll out the new auth API       [auth, +1]       2026-08-25       2w
auth-cleanup          not-started  queued     platform  claude  Remove the old auth API         [auth, +1]       2026-08-30       2w
auth-docs             not-started  unset      platform  claude  Document the new auth API                        2026-09-02       1w
invoice-retry         unknown      unset      billing   claude  Retry failed invoice charges    [billing]        2026-09-04       1w
relevance-tuning      not-started  active     billing   claude  Tune search relevance           [search]         2026-09-09       5d
onboarding-checklist  unknown      unset                claude  Write the onboarding checklist                   2026-09-13       1d

9 plans
```
<!-- /sample -->

Below the table threshold, `list` stacks each plan and drops nothing; see
[the layout rule](https://github.com/kjiwa/pentimento/blob/main/docs/reference.md#columns).

<!-- sample:list-narrow -->
```
Write a docs style guide
  style-guide  superseded  abandoned  platform  claude  2026-07-31  6w
Redesign the auth API
  auth-redesign  complete  abandoned  platform  claude  [auth, security]
  2026-08-05  5w
Rewrite dunning email copy
  dunning-copy  complete  someday  billing  claude  [billing]  2026-08-20  3w
Roll out the new auth API
  auth-rollout  partial  active  platform  claude  [auth, security]  2026-08-25
  2w
Remove the old auth API
  auth-cleanup  not-started  queued  platform  claude  [auth, security]
  2026-08-30  2w
Document the new auth API
  auth-docs  not-started  unset  platform  claude  2026-09-02  1w
Retry failed invoice charges
  invoice-retry  unknown  unset  billing  claude  [billing]  2026-09-04  1w
Tune search relevance
  relevance-tuning  not-started  active  billing  claude  [search]  2026-09-09
  5d
Write the onboarding checklist
  onboarding-checklist  unknown  unset  claude  2026-09-13  1d

9 plans
```
<!-- /sample -->

### 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.

<!-- sample:tree -->
```
(no project)
`-- Write the onboarding checklist
      onboarding-checklist  unknown  unset  2026-09-13  1d

billing
|-- Rewrite dunning email copy
|     dunning-copy  complete  someday  [billing]  2026-08-20  3w
|-- Retry failed invoice charges
|     invoice-retry  unknown  unset  [billing]  2026-09-04  1w  (parent elided: no-such-plan)
`-- 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
        `-- Document the new auth API
              auth-docs  not-started  unset  2026-09-02  1w

9 plans
```
<!-- /sample -->

<!-- sample:tree-thread -->
```
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
        `-- Document the new auth API
              auth-docs  not-started  unset  2026-09-02  1w

4 of 9 plans
```
<!-- /sample -->

### show

<!-- sample: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

[x] 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
```
<!-- /sample -->

### check

`check` validates the whole corpus and exits 1 on any finding; it takes no
plan id. `PLAN` uses the short id, and a line under the summary gives the fix
for each `CODE`. `--format json|tsv` emits the full id in `id` and the fix in
`hint`. To work through findings, see
[Working through `check`](https://github.com/kjiwa/pentimento/blob/main/docs/workflows.md#working-through-check);
[docs/troubleshooting.md](https://github.com/kjiwa/pentimento/blob/main/docs/troubleshooting.md#check-findings)
explains each `CODE`.

<!-- sample:check -->
```
CODE                    PLAN                  MESSAGE
dangling-parent         invoice-retry         parent 'no-such-plan' does not resolve to a plan
underivable-status      invoice-retry         '## Progress' has no checkboxes or recognized phrase
status-behind-history   auth-cleanup          status 'not-started' but 1 later session worked this plan
status-behind-progress  onboarding-checklist  status 'unknown' but '## Progress' derives 'not-started'
unadopted-tag           auth-docs             no tags, but its thread carries [auth, security]

9 plans checked, 5 findings
dangling-parent: pentimento set <id> --parent <id>, or --clear-parent
status-behind-history: pentimento history <id>, then pentimento set <id> --status <value>
status-behind-progress: pentimento backfill, or pentimento show <id>, then pentimento set <id> --status <value>
unadopted-tag: pentimento set <id> --add-tag <tag>
underivable-status: add a checklist to '## Progress', or pentimento show <id>, then pentimento set <id> --status <value>
narrow with: pentimento list --finding <code>
```
<!-- /sample -->

### 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. For an empty result, see
[`history` is empty](https://github.com/kjiwa/pentimento/blob/main/docs/troubleshooting.md#history-is-empty).

<!-- sample:history -->
```
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
```
<!-- /sample -->

## Frontmatter

```yaml
---
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](https://github.com/kjiwa/pentimento/blob/main/pentimento/vocabulary.py).

| Field | Set by | How |
| --- | --- | --- |
| `status` | derived | Every `backfill` run (including the per-write `pentimento hook`, which [caps it at `partial`](https://github.com/kjiwa/pentimento/blob/main/docs/reference.md#flags)) recomputes it from `## Progress` checkboxes. `set --status` overrides it directly and pins it (see `pinned`); it is the only way to set `superseded`, which no derivation produces or overwrites. |
| `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 and `check` reports only `pin-behind-progress` for it. |
| `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 or written; `check` suggests them (`unadopted-tag`). `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 plan reference (see [`parent` is empty](https://github.com/kjiwa/pentimento/blob/main/docs/troubleshooting.md#parent-is-empty)). `--rederive` recomputes it from scratch: it replaces a parent set with `set --parent` by the derived one, or removes it when no reference 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](https://github.com/kjiwa/pentimento/blob/main/docs/integrations.md)
and
[docs/troubleshooting.md](https://github.com/kjiwa/pentimento/blob/main/docs/troubleshooting.md).
Cursor plans get body-only lineage and no `project` at all.

## Commands

| Command | Does |
| --- | --- |
| `list` | flat table of plans |
| `tree [<id>]` | lineage tree, grouped by project |
| `show <id>` | H1, frontmatter, and the rendered body |
| `set <id>...` | rewrite frontmatter in place |
| `backfill` | gap-fill intent/created/project/parent; advance status |
| `hook` | run as a Claude Code PostToolUse hook; reads the payload on stdin |
| `index` | write INDEX.md into the plans directory |
| `check` | validate lineage, vocabulary, status, and tags; exits 1 on any finding |
| `history <id>` | session-touch history for a plan |
| `completion <shell>` | print a shell integration script |

Full flags for every command, plus the environment variables, are in
[docs/reference.md](https://github.com/kjiwa/pentimento/blob/main/docs/reference.md).
Run `pentimento <command> --help` for that command's own examples.

## 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](https://github.com/kjiwa/pentimento/blob/main/docs/workflows.md)
for the details, and `pentimento index` for an `INDEX.md` worth committing.

## Prior art

[ccplan](https://github.com/sorafujitani/ccplan) and
[planc](https://github.com/jakebf/planc) track Claude Code plans by a status
you set by hand, ccplan in a sidecar file and planc in frontmatter through a
TUI. [claude-plan-viewer](https://github.com/HelgeSverre/claude-plan-viewer)
browses and searches them in a web UI.
[planning-with-files](https://github.com/OthmanAdi/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. None of them records which plan replaced which. 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](https://dev.to/kjiwa/managing-claude-code-plan-files-lineage-status-and-existing-tools-37b6).

## 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

```sh
python3 -m unittest discover
uvx ruff check
uvx ruff format --check
```

CI ([.github/workflows/check.yml](https://github.com/kjiwa/pentimento/blob/main/.github/workflows/check.yml))
runs all three on Ubuntu and macOS.

## Docs

- [docs/reference.md](https://github.com/kjiwa/pentimento/blob/main/docs/reference.md)
  — every command's full flags, and the environment variables.
- [docs/integrations.md](https://github.com/kjiwa/pentimento/blob/main/docs/integrations.md)
  — wiring `backfill` and `pentimento hook` into Claude Code and Cursor, a
  slash command, a skill that searches past plans before a new one, a
  triage-nudge pattern, `check` in CI.
- [docs/workflows.md](https://github.com/kjiwa/pentimento/blob/main/docs/workflows.md)
  — triage, picking a plan back up, reusing a past decision, supersession,
  lineage trees, working through `check`, scripting with `--format json`.
- [docs/troubleshooting.md](https://github.com/kjiwa/pentimento/blob/main/docs/troubleshooting.md)
  — every empty field and `check` finding, explained.
