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.
Files changed (79) hide show
  1. pentimento-0.0.1/LICENSE +21 -0
  2. pentimento-0.0.1/MANIFEST.in +4 -0
  3. pentimento-0.0.1/PKG-INFO +268 -0
  4. pentimento-0.0.1/README.md +243 -0
  5. pentimento-0.0.1/demo/README.md +9 -0
  6. pentimento-0.0.1/demo/capture.sh +90 -0
  7. pentimento-0.0.1/demo/fixture.sh +219 -0
  8. pentimento-0.0.1/demo/pentimento.gif +0 -0
  9. pentimento-0.0.1/demo/pentimento.tape +51 -0
  10. pentimento-0.0.1/demo/record.sh +51 -0
  11. pentimento-0.0.1/docs/integrations.md +57 -0
  12. pentimento-0.0.1/docs/reference.md +45 -0
  13. pentimento-0.0.1/docs/troubleshooting.md +114 -0
  14. pentimento-0.0.1/docs/workflows.md +107 -0
  15. pentimento-0.0.1/integrations/claude/commands/plans.md +14 -0
  16. pentimento-0.0.1/integrations/claude/settings-snippet.json +25 -0
  17. pentimento-0.0.1/pentimento/__init__.py +0 -0
  18. pentimento-0.0.1/pentimento/__main__.py +6 -0
  19. pentimento-0.0.1/pentimento/backfill.py +139 -0
  20. pentimento-0.0.1/pentimento/cache.py +54 -0
  21. pentimento-0.0.1/pentimento/check.py +197 -0
  22. pentimento-0.0.1/pentimento/cli.py +809 -0
  23. pentimento-0.0.1/pentimento/corpus.py +83 -0
  24. pentimento-0.0.1/pentimento/counts.py +13 -0
  25. pentimento-0.0.1/pentimento/formats.py +46 -0
  26. pentimento-0.0.1/pentimento/frontmatter.py +198 -0
  27. pentimento-0.0.1/pentimento/history.py +69 -0
  28. pentimento-0.0.1/pentimento/hook.py +36 -0
  29. pentimento-0.0.1/pentimento/index.py +46 -0
  30. pentimento-0.0.1/pentimento/lineage.py +63 -0
  31. pentimento-0.0.1/pentimento/listing.py +75 -0
  32. pentimento-0.0.1/pentimento/markdown.py +516 -0
  33. pentimento-0.0.1/pentimento/plan.py +169 -0
  34. pentimento-0.0.1/pentimento/record.py +26 -0
  35. pentimento-0.0.1/pentimento/sessions.py +149 -0
  36. pentimento-0.0.1/pentimento/shortid.py +42 -0
  37. pentimento-0.0.1/pentimento/sources.py +93 -0
  38. pentimento-0.0.1/pentimento/status.py +74 -0
  39. pentimento-0.0.1/pentimento/style.py +200 -0
  40. pentimento-0.0.1/pentimento/table.py +124 -0
  41. pentimento-0.0.1/pentimento/tags.py +37 -0
  42. pentimento-0.0.1/pentimento/times.py +71 -0
  43. pentimento-0.0.1/pentimento/touches.py +104 -0
  44. pentimento-0.0.1/pentimento/tree.py +220 -0
  45. pentimento-0.0.1/pentimento/vocabulary.py +24 -0
  46. pentimento-0.0.1/pentimento.egg-info/PKG-INFO +268 -0
  47. pentimento-0.0.1/pentimento.egg-info/SOURCES.txt +77 -0
  48. pentimento-0.0.1/pentimento.egg-info/dependency_links.txt +1 -0
  49. pentimento-0.0.1/pentimento.egg-info/entry_points.txt +2 -0
  50. pentimento-0.0.1/pentimento.egg-info/top_level.txt +1 -0
  51. pentimento-0.0.1/pyproject.toml +44 -0
  52. pentimento-0.0.1/setup.cfg +4 -0
  53. pentimento-0.0.1/tests/__init__.py +22 -0
  54. pentimento-0.0.1/tests/fixtures/body-rule.md +8 -0
  55. pentimento-0.0.1/tests/test_backfill.py +491 -0
  56. pentimento-0.0.1/tests/test_cache.py +81 -0
  57. pentimento-0.0.1/tests/test_check.py +231 -0
  58. pentimento-0.0.1/tests/test_cli.py +1199 -0
  59. pentimento-0.0.1/tests/test_counts.py +26 -0
  60. pentimento-0.0.1/tests/test_formats.py +119 -0
  61. pentimento-0.0.1/tests/test_frontmatter.py +237 -0
  62. pentimento-0.0.1/tests/test_history.py +89 -0
  63. pentimento-0.0.1/tests/test_hook.py +116 -0
  64. pentimento-0.0.1/tests/test_index.py +58 -0
  65. pentimento-0.0.1/tests/test_lineage.py +128 -0
  66. pentimento-0.0.1/tests/test_listing.py +255 -0
  67. pentimento-0.0.1/tests/test_markdown.py +328 -0
  68. pentimento-0.0.1/tests/test_plan.py +208 -0
  69. pentimento-0.0.1/tests/test_reference.py +107 -0
  70. pentimento-0.0.1/tests/test_sessions.py +234 -0
  71. pentimento-0.0.1/tests/test_shortid.py +41 -0
  72. pentimento-0.0.1/tests/test_sources.py +165 -0
  73. pentimento-0.0.1/tests/test_status.py +37 -0
  74. pentimento-0.0.1/tests/test_style.py +195 -0
  75. pentimento-0.0.1/tests/test_table.py +114 -0
  76. pentimento-0.0.1/tests/test_tags.py +76 -0
  77. pentimento-0.0.1/tests/test_times.py +91 -0
  78. pentimento-0.0.1/tests/test_touches.py +232 -0
  79. pentimento-0.0.1/tests/test_tree.py +231 -0
@@ -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,4 @@
1
+ recursive-include tests *
2
+ graft demo
3
+ graft docs
4
+ graft integrations
@@ -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
+ [![check](https://github.com/kjiwa/pentimento/actions/workflows/check.yml/badge.svg)](https://github.com/kjiwa/pentimento/actions/workflows/check.yml)
31
+ [![PyPI](https://img.shields.io/pypi/v/pentimento)](https://pypi.org/project/pentimento/)
32
+ [![Python versions](https://img.shields.io/pypi/pyversions/pentimento)](https://pypi.org/project/pentimento/)
33
+
34
+ ![demo](https://raw.githubusercontent.com/kjiwa/pentimento/main/demo/pentimento.gif)
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
+ [![check](https://github.com/kjiwa/pentimento/actions/workflows/check.yml/badge.svg)](https://github.com/kjiwa/pentimento/actions/workflows/check.yml)
6
+ [![PyPI](https://img.shields.io/pypi/v/pentimento)](https://pypi.org/project/pentimento/)
7
+ [![Python versions](https://img.shields.io/pypi/pyversions/pentimento)](https://pypi.org/project/pentimento/)
8
+
9
+ ![demo](https://raw.githubusercontent.com/kjiwa/pentimento/main/demo/pentimento.gif)
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 "$@"