Metadata-Version: 2.4
Name: pentimento
Version: 0.1.5
Summary: Derives status and lineage for the plan files your coding agent leaves 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
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 over agent 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)

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.

The plans worth keeping are the long ones. An infrastructure migration you
have been designing for months grows subplans, absorbs decisions made in
discussion, and leaves behind the branches you rejected — which is 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. 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
```

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

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: the shortest trailing hyphen-segment run that's
unique across the corpus, so `is-it-possible-to-abundant-rabbit` displays as
`abundant-rabbit`.

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

### tree

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

### show

<!-- sample:show -->
```
# Roll out the new auth API

id: api-auth-rollout
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
```
<!-- /sample -->

### 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](https://github.com/kjiwa/pentimento/blob/main/docs/troubleshooting.md)
for what each `CODE` means and how to fix it.

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

<!-- 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`) 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. |
| `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, one line each. |
| `tree` | Plans nested under their parents, grouped by project. |
| `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. |

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

## 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, `check` in CI.
- [docs/workflows.md](https://github.com/kjiwa/pentimento/blob/main/docs/workflows.md)
  — triage, supersession, lineage trees, scripting with `--format json`.
- [docs/troubleshooting.md](https://github.com/kjiwa/pentimento/blob/main/docs/troubleshooting.md)
  — every empty field and `check` finding, explained.
