pentimento 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pentimento-0.0.1/LICENSE +21 -0
- pentimento-0.0.1/MANIFEST.in +4 -0
- pentimento-0.0.1/PKG-INFO +268 -0
- pentimento-0.0.1/README.md +243 -0
- pentimento-0.0.1/demo/README.md +9 -0
- pentimento-0.0.1/demo/capture.sh +90 -0
- pentimento-0.0.1/demo/fixture.sh +219 -0
- pentimento-0.0.1/demo/pentimento.gif +0 -0
- pentimento-0.0.1/demo/pentimento.tape +51 -0
- pentimento-0.0.1/demo/record.sh +51 -0
- pentimento-0.0.1/docs/integrations.md +57 -0
- pentimento-0.0.1/docs/reference.md +45 -0
- pentimento-0.0.1/docs/troubleshooting.md +114 -0
- pentimento-0.0.1/docs/workflows.md +107 -0
- pentimento-0.0.1/integrations/claude/commands/plans.md +14 -0
- pentimento-0.0.1/integrations/claude/settings-snippet.json +25 -0
- pentimento-0.0.1/pentimento/__init__.py +0 -0
- pentimento-0.0.1/pentimento/__main__.py +6 -0
- pentimento-0.0.1/pentimento/backfill.py +139 -0
- pentimento-0.0.1/pentimento/cache.py +54 -0
- pentimento-0.0.1/pentimento/check.py +197 -0
- pentimento-0.0.1/pentimento/cli.py +809 -0
- pentimento-0.0.1/pentimento/corpus.py +83 -0
- pentimento-0.0.1/pentimento/counts.py +13 -0
- pentimento-0.0.1/pentimento/formats.py +46 -0
- pentimento-0.0.1/pentimento/frontmatter.py +198 -0
- pentimento-0.0.1/pentimento/history.py +69 -0
- pentimento-0.0.1/pentimento/hook.py +36 -0
- pentimento-0.0.1/pentimento/index.py +46 -0
- pentimento-0.0.1/pentimento/lineage.py +63 -0
- pentimento-0.0.1/pentimento/listing.py +75 -0
- pentimento-0.0.1/pentimento/markdown.py +516 -0
- pentimento-0.0.1/pentimento/plan.py +169 -0
- pentimento-0.0.1/pentimento/record.py +26 -0
- pentimento-0.0.1/pentimento/sessions.py +149 -0
- pentimento-0.0.1/pentimento/shortid.py +42 -0
- pentimento-0.0.1/pentimento/sources.py +93 -0
- pentimento-0.0.1/pentimento/status.py +74 -0
- pentimento-0.0.1/pentimento/style.py +200 -0
- pentimento-0.0.1/pentimento/table.py +124 -0
- pentimento-0.0.1/pentimento/tags.py +37 -0
- pentimento-0.0.1/pentimento/times.py +71 -0
- pentimento-0.0.1/pentimento/touches.py +104 -0
- pentimento-0.0.1/pentimento/tree.py +220 -0
- pentimento-0.0.1/pentimento/vocabulary.py +24 -0
- pentimento-0.0.1/pentimento.egg-info/PKG-INFO +268 -0
- pentimento-0.0.1/pentimento.egg-info/SOURCES.txt +77 -0
- pentimento-0.0.1/pentimento.egg-info/dependency_links.txt +1 -0
- pentimento-0.0.1/pentimento.egg-info/entry_points.txt +2 -0
- pentimento-0.0.1/pentimento.egg-info/top_level.txt +1 -0
- pentimento-0.0.1/pyproject.toml +44 -0
- pentimento-0.0.1/setup.cfg +4 -0
- pentimento-0.0.1/tests/__init__.py +22 -0
- pentimento-0.0.1/tests/fixtures/body-rule.md +8 -0
- pentimento-0.0.1/tests/test_backfill.py +491 -0
- pentimento-0.0.1/tests/test_cache.py +81 -0
- pentimento-0.0.1/tests/test_check.py +231 -0
- pentimento-0.0.1/tests/test_cli.py +1199 -0
- pentimento-0.0.1/tests/test_counts.py +26 -0
- pentimento-0.0.1/tests/test_formats.py +119 -0
- pentimento-0.0.1/tests/test_frontmatter.py +237 -0
- pentimento-0.0.1/tests/test_history.py +89 -0
- pentimento-0.0.1/tests/test_hook.py +116 -0
- pentimento-0.0.1/tests/test_index.py +58 -0
- pentimento-0.0.1/tests/test_lineage.py +128 -0
- pentimento-0.0.1/tests/test_listing.py +255 -0
- pentimento-0.0.1/tests/test_markdown.py +328 -0
- pentimento-0.0.1/tests/test_plan.py +208 -0
- pentimento-0.0.1/tests/test_reference.py +107 -0
- pentimento-0.0.1/tests/test_sessions.py +234 -0
- pentimento-0.0.1/tests/test_shortid.py +41 -0
- pentimento-0.0.1/tests/test_sources.py +165 -0
- pentimento-0.0.1/tests/test_status.py +37 -0
- pentimento-0.0.1/tests/test_style.py +195 -0
- pentimento-0.0.1/tests/test_table.py +114 -0
- pentimento-0.0.1/tests/test_tags.py +76 -0
- pentimento-0.0.1/tests/test_times.py +91 -0
- pentimento-0.0.1/tests/test_touches.py +232 -0
- pentimento-0.0.1/tests/test_tree.py +231 -0
pentimento-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kamil Jiwa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pentimento
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Status, intent, and lineage over agent plan files.
|
|
5
|
+
Author-email: Kamil Jiwa <kamil.jiwa@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kjiwa/pentimento
|
|
8
|
+
Project-URL: Repository, https://github.com/kjiwa/pentimento
|
|
9
|
+
Project-URL: Issues, https://github.com/kjiwa/pentimento/issues
|
|
10
|
+
Keywords: plans,claude-code,cursor,cli,lineage
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Utilities
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# pentimento
|
|
27
|
+
|
|
28
|
+
Status, intent, and lineage over agent plan files.
|
|
29
|
+
|
|
30
|
+
[](https://github.com/kjiwa/pentimento/actions/workflows/check.yml)
|
|
31
|
+
[](https://pypi.org/project/pentimento/)
|
|
32
|
+
[](https://pypi.org/project/pentimento/)
|
|
33
|
+
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
Claude Code and Cursor (and other harnesses) accumulate plan files with no
|
|
37
|
+
status, no starring, and no record of which plan supersedes which. pentimento
|
|
38
|
+
reads a directory of plan markdown files, derives a small frontmatter block
|
|
39
|
+
for each one, and gives you a CLI to list, filter, and render them as a
|
|
40
|
+
lineage tree.
|
|
41
|
+
|
|
42
|
+
Built with [Claude Code](https://claude.com/claude-code).
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
pip install pentimento
|
|
48
|
+
# or from source:
|
|
49
|
+
pip install git+https://github.com/kjiwa/pentimento.git
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Quick start
|
|
53
|
+
|
|
54
|
+
Point `AGENT_PLANS_DIR` at your plans directory (it defaults to
|
|
55
|
+
`~/.claude/plans`), then:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
pentimento backfill # derive status/intent/created/parent/project once
|
|
59
|
+
pentimento list # see the corpus
|
|
60
|
+
pentimento set some-plan-id --intent active
|
|
61
|
+
pentimento list --starred
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Sources and their default directories are covered in
|
|
65
|
+
[docs/integrations.md](https://github.com/kjiwa/pentimento/blob/main/docs/integrations.md);
|
|
66
|
+
Cursor's caveats are in
|
|
67
|
+
[docs/troubleshooting.md](https://github.com/kjiwa/pentimento/blob/main/docs/troubleshooting.md).
|
|
68
|
+
|
|
69
|
+
The samples below are regenerated by `sh demo/capture.sh`.
|
|
70
|
+
|
|
71
|
+
### list
|
|
72
|
+
|
|
73
|
+
`PLAN` shows the short id: the shortest trailing hyphen-segment run that's
|
|
74
|
+
unique across the corpus, so `is-it-possible-to-abundant-rabbit` displays as
|
|
75
|
+
`abundant-rabbit`.
|
|
76
|
+
|
|
77
|
+
<!-- sample:list -->
|
|
78
|
+
```
|
|
79
|
+
STATUS INTENT PROJECT PLAN TITLE UPDATED
|
|
80
|
+
superseded abandoned platform style-guide Write a docs style guide 6w
|
|
81
|
+
complete abandoned platform auth-redesign Redesign the auth API 5w
|
|
82
|
+
complete someday billing dunning-copy Rewrite dunning email copy 3w
|
|
83
|
+
partial active platform auth-rollout Roll out the new auth API 2w
|
|
84
|
+
not-started queued platform auth-cleanup Remove the old auth API 2w
|
|
85
|
+
unknown unset billing invoice-retry Retry failed invoice charges 1w
|
|
86
|
+
not-started active billing relevance-tuning Tune search relevance 5d
|
|
87
|
+
unknown unset onboarding-checklist Write the onboarding checklist 1d
|
|
88
|
+
|
|
89
|
+
8 plans
|
|
90
|
+
```
|
|
91
|
+
<!-- /sample -->
|
|
92
|
+
|
|
93
|
+
### tree
|
|
94
|
+
|
|
95
|
+
<!-- sample:tree -->
|
|
96
|
+
```
|
|
97
|
+
(no project)
|
|
98
|
+
└─ Write the onboarding checklist
|
|
99
|
+
onboarding-checklist unknown unset 1d
|
|
100
|
+
|
|
101
|
+
billing
|
|
102
|
+
├─ Rewrite dunning email copy
|
|
103
|
+
│ dunning-copy complete someday [billing] 2026-08-20 3w
|
|
104
|
+
├─ Retry failed invoice charges (parent elided: no-such-plan)
|
|
105
|
+
│ invoice-retry unknown unset [billing] 2026-09-04 1w
|
|
106
|
+
└─ Tune search relevance
|
|
107
|
+
relevance-tuning not-started active [search] 2026-09-09 5d
|
|
108
|
+
|
|
109
|
+
platform
|
|
110
|
+
├─ Write a docs style guide
|
|
111
|
+
│ style-guide superseded abandoned 2026-07-31 6w
|
|
112
|
+
└─ Redesign the auth API
|
|
113
|
+
auth-redesign complete abandoned [auth, security] 2026-08-05 5w
|
|
114
|
+
└─ Roll out the new auth API
|
|
115
|
+
auth-rollout partial active [auth, security] 2026-08-25 2w
|
|
116
|
+
└─ Remove the old auth API
|
|
117
|
+
auth-cleanup not-started queued [auth, security] 2026-08-30 2w
|
|
118
|
+
|
|
119
|
+
8 plans
|
|
120
|
+
```
|
|
121
|
+
<!-- /sample -->
|
|
122
|
+
|
|
123
|
+
### show
|
|
124
|
+
|
|
125
|
+
<!-- sample:show -->
|
|
126
|
+
```
|
|
127
|
+
# Roll out the new auth API
|
|
128
|
+
|
|
129
|
+
id: api-auth-rollout
|
|
130
|
+
status: partial intent: active tags: [auth, security]
|
|
131
|
+
parent: api-auth-redesign project: platform
|
|
132
|
+
created: 2026-08-25 source: claude modified: 2026-08-25 12:30
|
|
133
|
+
|
|
134
|
+
Progress
|
|
135
|
+
|
|
136
|
+
✓ Ship behind a feature flag
|
|
137
|
+
☐ Flip the flag for all tenants
|
|
138
|
+
|
|
139
|
+
Context
|
|
140
|
+
|
|
141
|
+
Tenants opt in via the auth_v2 flag in tenant_settings. Watch error rates before flipping the
|
|
142
|
+
remaining cohort. See the rollout runbook.
|
|
143
|
+
|
|
144
|
+
Cohort Status
|
|
145
|
+
internal complete
|
|
146
|
+
beta in progress
|
|
147
|
+
```
|
|
148
|
+
<!-- /sample -->
|
|
149
|
+
|
|
150
|
+
### check
|
|
151
|
+
|
|
152
|
+
`check` validates the corpus and exits 1 on any finding. It takes no plan id
|
|
153
|
+
— it always checks the whole corpus. The table's `PLAN` column uses the same
|
|
154
|
+
short id as `list`/`tree`; `--format json|tsv` emits the full id in its
|
|
155
|
+
`plan_id` field. See
|
|
156
|
+
[docs/troubleshooting.md](https://github.com/kjiwa/pentimento/blob/main/docs/troubleshooting.md)
|
|
157
|
+
for what each `CODE` means and how to fix it.
|
|
158
|
+
|
|
159
|
+
<!-- sample:check -->
|
|
160
|
+
```
|
|
161
|
+
CODE PLAN MESSAGE
|
|
162
|
+
dangling-parent invoice-retry parent 'no-such-plan' does not resolve to a plan
|
|
163
|
+
status-behind-history auth-cleanup status 'not-started' but 1 later session worked this plan; see `pentime…
|
|
164
|
+
8 plans checked, 2 findings
|
|
165
|
+
```
|
|
166
|
+
<!-- /sample -->
|
|
167
|
+
|
|
168
|
+
### history
|
|
169
|
+
|
|
170
|
+
`history` shows which sessions touched a plan's file: the session whose id
|
|
171
|
+
matches the plan's own id authored it; any later session that read, edited,
|
|
172
|
+
or delegated work on it worked it. An empty result prints
|
|
173
|
+
`no session history for <id>` — that means no matching transcript was found
|
|
174
|
+
on this machine, never a claim the plan wasn't worked.
|
|
175
|
+
|
|
176
|
+
<!-- sample:history -->
|
|
177
|
+
```
|
|
178
|
+
WHEN WHAT SESSION TOUCHES
|
|
179
|
+
2026-08-30 12:30 authored api-auth-cleanup 1
|
|
180
|
+
2026-09-11 12:30 worked implement-api-auth-cleanup-eager-wolf 1
|
|
181
|
+
```
|
|
182
|
+
<!-- /sample -->
|
|
183
|
+
|
|
184
|
+
## Frontmatter
|
|
185
|
+
|
|
186
|
+
```yaml
|
|
187
|
+
---
|
|
188
|
+
pentimento:
|
|
189
|
+
status: not-started | partial | complete | superseded | unknown
|
|
190
|
+
intent: active | queued | someday | abandoned | unset
|
|
191
|
+
tags: [auth, security] # omitted if untagged
|
|
192
|
+
parent: some-other-plan-id # omitted for roots
|
|
193
|
+
project: platform # omitted if undetermined
|
|
194
|
+
created: 2026-09-08
|
|
195
|
+
---
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The vocabulary lives in one place:
|
|
199
|
+
[pentimento/vocabulary.py](https://github.com/kjiwa/pentimento/blob/main/pentimento/vocabulary.py).
|
|
200
|
+
|
|
201
|
+
| Field | Set by | How |
|
|
202
|
+
| --- | --- | --- |
|
|
203
|
+
| `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. |
|
|
204
|
+
| `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. |
|
|
205
|
+
| `tags` | operator | Never derived. `set --add-tag`/`--remove-tag`/`--clear-tags`; filter with `list`/`tree --tag`, which ANDs repeated tags. |
|
|
206
|
+
| `parent` | derived, or operator | `backfill` fills it in from a session-prompt or body reference 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. |
|
|
207
|
+
| `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. |
|
|
208
|
+
| `created` | derived once | A local date, set once and then immutable except through `backfill --recreate`. |
|
|
209
|
+
| `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. |
|
|
210
|
+
|
|
211
|
+
Lineage and source discovery are covered in full in
|
|
212
|
+
[docs/integrations.md](https://github.com/kjiwa/pentimento/blob/main/docs/integrations.md)
|
|
213
|
+
and
|
|
214
|
+
[docs/troubleshooting.md](https://github.com/kjiwa/pentimento/blob/main/docs/troubleshooting.md).
|
|
215
|
+
Cursor plans get body-only lineage and no `project` at all.
|
|
216
|
+
|
|
217
|
+
## Commands
|
|
218
|
+
|
|
219
|
+
| Command | Does |
|
|
220
|
+
| --- | --- |
|
|
221
|
+
| `list` | Flat table of plans, one line each. |
|
|
222
|
+
| `tree` | Plans nested under their parents, grouped by project. |
|
|
223
|
+
| `show <id>` | One plan's title, frontmatter, and rendered body. |
|
|
224
|
+
| `set <id>` | Rewrite one plan's frontmatter in place. |
|
|
225
|
+
| `backfill` | Derive and write missing frontmatter across the corpus. |
|
|
226
|
+
| `hook` | Run as a Claude Code `PostToolUse` hook, reading the payload on stdin. |
|
|
227
|
+
| `index` | Write `INDEX.md` into the plans directory. |
|
|
228
|
+
| `check` | Validate lineage and vocabulary; exits 1 on any finding. |
|
|
229
|
+
| `history <id>` | Every session that touched one plan, oldest first. |
|
|
230
|
+
|
|
231
|
+
Full flags for every command, plus the environment variables, are in
|
|
232
|
+
[docs/reference.md](https://github.com/kjiwa/pentimento/blob/main/docs/reference.md).
|
|
233
|
+
Run `pentimento <command> --help` for the same information from the CLI
|
|
234
|
+
itself.
|
|
235
|
+
|
|
236
|
+
## Requirements and limitations
|
|
237
|
+
|
|
238
|
+
Stdlib-only Python 3.9+, zero runtime dependencies, no PyYAML.
|
|
239
|
+
|
|
240
|
+
pentimento enriches `modified` and lineage by reading Claude Code's session
|
|
241
|
+
transcripts (`~/.claude/projects/*.jsonl`), an undocumented, private format.
|
|
242
|
+
If that format changes, or the transcripts are absent, this enrichment
|
|
243
|
+
degrades to file mtimes and plain body/preamble references — it does not
|
|
244
|
+
break, and the frontmatter itself stays plain, hand-editable markdown either
|
|
245
|
+
way.
|
|
246
|
+
|
|
247
|
+
## Development
|
|
248
|
+
|
|
249
|
+
```sh
|
|
250
|
+
python3 -m unittest discover
|
|
251
|
+
uvx ruff check
|
|
252
|
+
uvx ruff format --check
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
CI ([.github/workflows/check.yml](https://github.com/kjiwa/pentimento/blob/main/.github/workflows/check.yml))
|
|
256
|
+
runs all three on Ubuntu and macOS.
|
|
257
|
+
|
|
258
|
+
## Docs
|
|
259
|
+
|
|
260
|
+
- [docs/reference.md](https://github.com/kjiwa/pentimento/blob/main/docs/reference.md)
|
|
261
|
+
— every command's full flags, and the environment variables.
|
|
262
|
+
- [docs/integrations.md](https://github.com/kjiwa/pentimento/blob/main/docs/integrations.md)
|
|
263
|
+
— wiring `backfill` and `pentimento hook` into Claude Code and Cursor, a
|
|
264
|
+
slash command, `check` in CI.
|
|
265
|
+
- [docs/workflows.md](https://github.com/kjiwa/pentimento/blob/main/docs/workflows.md)
|
|
266
|
+
— triage, supersession, lineage trees, scripting with `--format json`.
|
|
267
|
+
- [docs/troubleshooting.md](https://github.com/kjiwa/pentimento/blob/main/docs/troubleshooting.md)
|
|
268
|
+
— every empty field and `check` finding, explained.
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# pentimento
|
|
2
|
+
|
|
3
|
+
Status, intent, and lineage over agent plan files.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/kjiwa/pentimento/actions/workflows/check.yml)
|
|
6
|
+
[](https://pypi.org/project/pentimento/)
|
|
7
|
+
[](https://pypi.org/project/pentimento/)
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
Claude Code and Cursor (and other harnesses) accumulate plan files with no
|
|
12
|
+
status, no starring, and no record of which plan supersedes which. pentimento
|
|
13
|
+
reads a directory of plan markdown files, derives a small frontmatter block
|
|
14
|
+
for each one, and gives you a CLI to list, filter, and render them as a
|
|
15
|
+
lineage tree.
|
|
16
|
+
|
|
17
|
+
Built with [Claude Code](https://claude.com/claude-code).
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
pip install pentimento
|
|
23
|
+
# or from source:
|
|
24
|
+
pip install git+https://github.com/kjiwa/pentimento.git
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
Point `AGENT_PLANS_DIR` at your plans directory (it defaults to
|
|
30
|
+
`~/.claude/plans`), then:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
pentimento backfill # derive status/intent/created/parent/project once
|
|
34
|
+
pentimento list # see the corpus
|
|
35
|
+
pentimento set some-plan-id --intent active
|
|
36
|
+
pentimento list --starred
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Sources and their default directories are covered in
|
|
40
|
+
[docs/integrations.md](https://github.com/kjiwa/pentimento/blob/main/docs/integrations.md);
|
|
41
|
+
Cursor's caveats are in
|
|
42
|
+
[docs/troubleshooting.md](https://github.com/kjiwa/pentimento/blob/main/docs/troubleshooting.md).
|
|
43
|
+
|
|
44
|
+
The samples below are regenerated by `sh demo/capture.sh`.
|
|
45
|
+
|
|
46
|
+
### list
|
|
47
|
+
|
|
48
|
+
`PLAN` shows the short id: the shortest trailing hyphen-segment run that's
|
|
49
|
+
unique across the corpus, so `is-it-possible-to-abundant-rabbit` displays as
|
|
50
|
+
`abundant-rabbit`.
|
|
51
|
+
|
|
52
|
+
<!-- sample:list -->
|
|
53
|
+
```
|
|
54
|
+
STATUS INTENT PROJECT PLAN TITLE UPDATED
|
|
55
|
+
superseded abandoned platform style-guide Write a docs style guide 6w
|
|
56
|
+
complete abandoned platform auth-redesign Redesign the auth API 5w
|
|
57
|
+
complete someday billing dunning-copy Rewrite dunning email copy 3w
|
|
58
|
+
partial active platform auth-rollout Roll out the new auth API 2w
|
|
59
|
+
not-started queued platform auth-cleanup Remove the old auth API 2w
|
|
60
|
+
unknown unset billing invoice-retry Retry failed invoice charges 1w
|
|
61
|
+
not-started active billing relevance-tuning Tune search relevance 5d
|
|
62
|
+
unknown unset onboarding-checklist Write the onboarding checklist 1d
|
|
63
|
+
|
|
64
|
+
8 plans
|
|
65
|
+
```
|
|
66
|
+
<!-- /sample -->
|
|
67
|
+
|
|
68
|
+
### tree
|
|
69
|
+
|
|
70
|
+
<!-- sample:tree -->
|
|
71
|
+
```
|
|
72
|
+
(no project)
|
|
73
|
+
└─ Write the onboarding checklist
|
|
74
|
+
onboarding-checklist unknown unset 1d
|
|
75
|
+
|
|
76
|
+
billing
|
|
77
|
+
├─ Rewrite dunning email copy
|
|
78
|
+
│ dunning-copy complete someday [billing] 2026-08-20 3w
|
|
79
|
+
├─ Retry failed invoice charges (parent elided: no-such-plan)
|
|
80
|
+
│ invoice-retry unknown unset [billing] 2026-09-04 1w
|
|
81
|
+
└─ Tune search relevance
|
|
82
|
+
relevance-tuning not-started active [search] 2026-09-09 5d
|
|
83
|
+
|
|
84
|
+
platform
|
|
85
|
+
├─ Write a docs style guide
|
|
86
|
+
│ style-guide superseded abandoned 2026-07-31 6w
|
|
87
|
+
└─ Redesign the auth API
|
|
88
|
+
auth-redesign complete abandoned [auth, security] 2026-08-05 5w
|
|
89
|
+
└─ Roll out the new auth API
|
|
90
|
+
auth-rollout partial active [auth, security] 2026-08-25 2w
|
|
91
|
+
└─ Remove the old auth API
|
|
92
|
+
auth-cleanup not-started queued [auth, security] 2026-08-30 2w
|
|
93
|
+
|
|
94
|
+
8 plans
|
|
95
|
+
```
|
|
96
|
+
<!-- /sample -->
|
|
97
|
+
|
|
98
|
+
### show
|
|
99
|
+
|
|
100
|
+
<!-- sample:show -->
|
|
101
|
+
```
|
|
102
|
+
# Roll out the new auth API
|
|
103
|
+
|
|
104
|
+
id: api-auth-rollout
|
|
105
|
+
status: partial intent: active tags: [auth, security]
|
|
106
|
+
parent: api-auth-redesign project: platform
|
|
107
|
+
created: 2026-08-25 source: claude modified: 2026-08-25 12:30
|
|
108
|
+
|
|
109
|
+
Progress
|
|
110
|
+
|
|
111
|
+
✓ Ship behind a feature flag
|
|
112
|
+
☐ Flip the flag for all tenants
|
|
113
|
+
|
|
114
|
+
Context
|
|
115
|
+
|
|
116
|
+
Tenants opt in via the auth_v2 flag in tenant_settings. Watch error rates before flipping the
|
|
117
|
+
remaining cohort. See the rollout runbook.
|
|
118
|
+
|
|
119
|
+
Cohort Status
|
|
120
|
+
internal complete
|
|
121
|
+
beta in progress
|
|
122
|
+
```
|
|
123
|
+
<!-- /sample -->
|
|
124
|
+
|
|
125
|
+
### check
|
|
126
|
+
|
|
127
|
+
`check` validates the corpus and exits 1 on any finding. It takes no plan id
|
|
128
|
+
— it always checks the whole corpus. The table's `PLAN` column uses the same
|
|
129
|
+
short id as `list`/`tree`; `--format json|tsv` emits the full id in its
|
|
130
|
+
`plan_id` field. See
|
|
131
|
+
[docs/troubleshooting.md](https://github.com/kjiwa/pentimento/blob/main/docs/troubleshooting.md)
|
|
132
|
+
for what each `CODE` means and how to fix it.
|
|
133
|
+
|
|
134
|
+
<!-- sample:check -->
|
|
135
|
+
```
|
|
136
|
+
CODE PLAN MESSAGE
|
|
137
|
+
dangling-parent invoice-retry parent 'no-such-plan' does not resolve to a plan
|
|
138
|
+
status-behind-history auth-cleanup status 'not-started' but 1 later session worked this plan; see `pentime…
|
|
139
|
+
8 plans checked, 2 findings
|
|
140
|
+
```
|
|
141
|
+
<!-- /sample -->
|
|
142
|
+
|
|
143
|
+
### history
|
|
144
|
+
|
|
145
|
+
`history` shows which sessions touched a plan's file: the session whose id
|
|
146
|
+
matches the plan's own id authored it; any later session that read, edited,
|
|
147
|
+
or delegated work on it worked it. An empty result prints
|
|
148
|
+
`no session history for <id>` — that means no matching transcript was found
|
|
149
|
+
on this machine, never a claim the plan wasn't worked.
|
|
150
|
+
|
|
151
|
+
<!-- sample:history -->
|
|
152
|
+
```
|
|
153
|
+
WHEN WHAT SESSION TOUCHES
|
|
154
|
+
2026-08-30 12:30 authored api-auth-cleanup 1
|
|
155
|
+
2026-09-11 12:30 worked implement-api-auth-cleanup-eager-wolf 1
|
|
156
|
+
```
|
|
157
|
+
<!-- /sample -->
|
|
158
|
+
|
|
159
|
+
## Frontmatter
|
|
160
|
+
|
|
161
|
+
```yaml
|
|
162
|
+
---
|
|
163
|
+
pentimento:
|
|
164
|
+
status: not-started | partial | complete | superseded | unknown
|
|
165
|
+
intent: active | queued | someday | abandoned | unset
|
|
166
|
+
tags: [auth, security] # omitted if untagged
|
|
167
|
+
parent: some-other-plan-id # omitted for roots
|
|
168
|
+
project: platform # omitted if undetermined
|
|
169
|
+
created: 2026-09-08
|
|
170
|
+
---
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The vocabulary lives in one place:
|
|
174
|
+
[pentimento/vocabulary.py](https://github.com/kjiwa/pentimento/blob/main/pentimento/vocabulary.py).
|
|
175
|
+
|
|
176
|
+
| Field | Set by | How |
|
|
177
|
+
| --- | --- | --- |
|
|
178
|
+
| `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. |
|
|
179
|
+
| `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. |
|
|
180
|
+
| `tags` | operator | Never derived. `set --add-tag`/`--remove-tag`/`--clear-tags`; filter with `list`/`tree --tag`, which ANDs repeated tags. |
|
|
181
|
+
| `parent` | derived, or operator | `backfill` fills it in from a session-prompt or body reference 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. |
|
|
182
|
+
| `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. |
|
|
183
|
+
| `created` | derived once | A local date, set once and then immutable except through `backfill --recreate`. |
|
|
184
|
+
| `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. |
|
|
185
|
+
|
|
186
|
+
Lineage and source discovery are covered in full in
|
|
187
|
+
[docs/integrations.md](https://github.com/kjiwa/pentimento/blob/main/docs/integrations.md)
|
|
188
|
+
and
|
|
189
|
+
[docs/troubleshooting.md](https://github.com/kjiwa/pentimento/blob/main/docs/troubleshooting.md).
|
|
190
|
+
Cursor plans get body-only lineage and no `project` at all.
|
|
191
|
+
|
|
192
|
+
## Commands
|
|
193
|
+
|
|
194
|
+
| Command | Does |
|
|
195
|
+
| --- | --- |
|
|
196
|
+
| `list` | Flat table of plans, one line each. |
|
|
197
|
+
| `tree` | Plans nested under their parents, grouped by project. |
|
|
198
|
+
| `show <id>` | One plan's title, frontmatter, and rendered body. |
|
|
199
|
+
| `set <id>` | Rewrite one plan's frontmatter in place. |
|
|
200
|
+
| `backfill` | Derive and write missing frontmatter across the corpus. |
|
|
201
|
+
| `hook` | Run as a Claude Code `PostToolUse` hook, reading the payload on stdin. |
|
|
202
|
+
| `index` | Write `INDEX.md` into the plans directory. |
|
|
203
|
+
| `check` | Validate lineage and vocabulary; exits 1 on any finding. |
|
|
204
|
+
| `history <id>` | Every session that touched one plan, oldest first. |
|
|
205
|
+
|
|
206
|
+
Full flags for every command, plus the environment variables, are in
|
|
207
|
+
[docs/reference.md](https://github.com/kjiwa/pentimento/blob/main/docs/reference.md).
|
|
208
|
+
Run `pentimento <command> --help` for the same information from the CLI
|
|
209
|
+
itself.
|
|
210
|
+
|
|
211
|
+
## Requirements and limitations
|
|
212
|
+
|
|
213
|
+
Stdlib-only Python 3.9+, zero runtime dependencies, no PyYAML.
|
|
214
|
+
|
|
215
|
+
pentimento enriches `modified` and lineage by reading Claude Code's session
|
|
216
|
+
transcripts (`~/.claude/projects/*.jsonl`), an undocumented, private format.
|
|
217
|
+
If that format changes, or the transcripts are absent, this enrichment
|
|
218
|
+
degrades to file mtimes and plain body/preamble references — it does not
|
|
219
|
+
break, and the frontmatter itself stays plain, hand-editable markdown either
|
|
220
|
+
way.
|
|
221
|
+
|
|
222
|
+
## Development
|
|
223
|
+
|
|
224
|
+
```sh
|
|
225
|
+
python3 -m unittest discover
|
|
226
|
+
uvx ruff check
|
|
227
|
+
uvx ruff format --check
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
CI ([.github/workflows/check.yml](https://github.com/kjiwa/pentimento/blob/main/.github/workflows/check.yml))
|
|
231
|
+
runs all three on Ubuntu and macOS.
|
|
232
|
+
|
|
233
|
+
## Docs
|
|
234
|
+
|
|
235
|
+
- [docs/reference.md](https://github.com/kjiwa/pentimento/blob/main/docs/reference.md)
|
|
236
|
+
— every command's full flags, and the environment variables.
|
|
237
|
+
- [docs/integrations.md](https://github.com/kjiwa/pentimento/blob/main/docs/integrations.md)
|
|
238
|
+
— wiring `backfill` and `pentimento hook` into Claude Code and Cursor, a
|
|
239
|
+
slash command, `check` in CI.
|
|
240
|
+
- [docs/workflows.md](https://github.com/kjiwa/pentimento/blob/main/docs/workflows.md)
|
|
241
|
+
— triage, supersession, lineage trees, scripting with `--format json`.
|
|
242
|
+
- [docs/troubleshooting.md](https://github.com/kjiwa/pentimento/blob/main/docs/troubleshooting.md)
|
|
243
|
+
— every empty field and `check` finding, explained.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# demo
|
|
2
|
+
|
|
3
|
+
`fixture.sh` writes a synthetic plan corpus (used by `capture.sh` to
|
|
4
|
+
regenerate the README's sample output and by `record.sh` for the GIF, so
|
|
5
|
+
neither ever touches a real plans directory).
|
|
6
|
+
Run `sh demo/capture.sh` to refresh the README samples and `sh demo/record.sh`
|
|
7
|
+
to regenerate `pentimento.gif` after changing output formatting.
|
|
8
|
+
Recording needs a vhs that is not 0.12.0 (it silently drops the GIF); pass
|
|
9
|
+
`VHS=/path/to/vhs` to select one.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Regenerate the README's captured command output from the demo fixture.
|
|
3
|
+
#
|
|
4
|
+
# Usage: sh demo/capture.sh
|
|
5
|
+
#
|
|
6
|
+
# Runs `pentimento list`, `tree`, `show`, and `check` against a fresh
|
|
7
|
+
# `demo/fixture.sh` corpus and splices each result into README.md between
|
|
8
|
+
# `<!-- sample:NAME -->` / `<!-- /sample -->` marker pairs, so the samples
|
|
9
|
+
# are regenerable. Pins `PENTIMENTO_NOW` unconditionally, overriding any
|
|
10
|
+
# value already in the environment, so the fixture's mtimes and the
|
|
11
|
+
# captured relative times agree byte-for-byte no matter the real wall
|
|
12
|
+
# clock at capture time.
|
|
13
|
+
set -eu
|
|
14
|
+
|
|
15
|
+
_capture() {
|
|
16
|
+
_capture_name=$1
|
|
17
|
+
shift
|
|
18
|
+
_capture_status=0
|
|
19
|
+
AGENT_PLANS_DIR="$FIXTURE_DIR" \
|
|
20
|
+
AGENT_SESSIONS_DIR="$FIXTURE_DIR/sessions" \
|
|
21
|
+
CURSOR_PLANS_DIR=/nonexistent \
|
|
22
|
+
COLUMNS=110 \
|
|
23
|
+
pentimento "$@" --color never >"$CAPTURE_DIR/$_capture_name.txt" || _capture_status=$?
|
|
24
|
+
|
|
25
|
+
# `check` exits 1 when it finds something; the fixture has a deliberate
|
|
26
|
+
# dangling parent, so that exit code is expected, not a capture failure.
|
|
27
|
+
if [ "$_capture_status" -ne 0 ] && [ "$_capture_name" != "check" ]; then
|
|
28
|
+
echo "capture failed: pentimento $* (exit $_capture_status)" >&2
|
|
29
|
+
return "$_capture_status"
|
|
30
|
+
fi
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
_splice() {
|
|
34
|
+
_splice_name=$1
|
|
35
|
+
_splice_content="$CAPTURE_DIR/$_splice_name.txt"
|
|
36
|
+
_splice_out="$CAPTURE_DIR/README.spliced"
|
|
37
|
+
|
|
38
|
+
awk -v marker="$_splice_name" -v contentfile="$_splice_content" '
|
|
39
|
+
BEGIN {
|
|
40
|
+
content = ""
|
|
41
|
+
while ((getline line < contentfile) > 0) {
|
|
42
|
+
content = content line "\n"
|
|
43
|
+
}
|
|
44
|
+
close(contentfile)
|
|
45
|
+
}
|
|
46
|
+
$0 == "<!-- sample:" marker " -->" {
|
|
47
|
+
print
|
|
48
|
+
print "```"
|
|
49
|
+
printf "%s", content
|
|
50
|
+
print "```"
|
|
51
|
+
skip = 1
|
|
52
|
+
next
|
|
53
|
+
}
|
|
54
|
+
skip && $0 == "<!-- /sample -->" {
|
|
55
|
+
print
|
|
56
|
+
skip = 0
|
|
57
|
+
next
|
|
58
|
+
}
|
|
59
|
+
skip { next }
|
|
60
|
+
{ print }
|
|
61
|
+
' "$README" >"$_splice_out"
|
|
62
|
+
mv "$_splice_out" "$README"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
main() {
|
|
66
|
+
SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
|
|
67
|
+
REPO_ROOT=$(CDPATH='' cd -- "$SCRIPT_DIR/.." && pwd)
|
|
68
|
+
README="$REPO_ROOT/README.md"
|
|
69
|
+
|
|
70
|
+
FIXTURE_DIR=$(mktemp -d "${TMPDIR:-/tmp}/pentimento-fixture.XXXXXX")
|
|
71
|
+
CAPTURE_DIR=$(mktemp -d "${TMPDIR:-/tmp}/pentimento-capture.XXXXXX")
|
|
72
|
+
trap 'rm -rf "$FIXTURE_DIR" "$CAPTURE_DIR"' EXIT
|
|
73
|
+
|
|
74
|
+
PENTIMENTO_NOW=2026-09-14T12:30:00Z
|
|
75
|
+
export PENTIMENTO_NOW
|
|
76
|
+
|
|
77
|
+
sh "$SCRIPT_DIR/fixture.sh" "$FIXTURE_DIR"
|
|
78
|
+
|
|
79
|
+
_capture list list
|
|
80
|
+
_capture tree tree
|
|
81
|
+
_capture show show api-auth-rollout
|
|
82
|
+
_capture check check
|
|
83
|
+
_capture history history api-auth-cleanup
|
|
84
|
+
|
|
85
|
+
for _name in list tree show check history; do
|
|
86
|
+
_splice "$_name"
|
|
87
|
+
done
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
main "$@"
|