artshelf 0.3.0

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.
package/SPEC.md ADDED
@@ -0,0 +1,675 @@
1
+ # Artshelf V1 Spec
2
+
3
+ ## Problem
4
+
5
+ Agents and humans create temporary directories, backups, run artifacts, debug
6
+ outputs, and quarantine folders during work. Those artifacts often have a clear
7
+ reason when created, but that reason is lost later. Cleanup then becomes risky:
8
+ we either keep everything forever or delete based on weak filesystem age.
9
+
10
+ Artshelf makes artifact creation accountable at the moment it happens.
11
+
12
+ ## One-Line Product Definition
13
+
14
+ Artshelf is a tiny CLI for putting temporary artifacts, backups, and run outputs
15
+ somewhere accountable, with an expiry tag and a cleanup plan.
16
+
17
+ ## Goals
18
+
19
+ - Record why an artifact exists, who created it, and how long it should stay.
20
+ - Make due cleanup visible without guessing from filesystem timestamps.
21
+ - Make cleanup previewable and auditable.
22
+ - Give agents a deterministic tool they can call instead of leaving scratch
23
+ files behind.
24
+ - Stay small enough that agents actually use it.
25
+
26
+ ## Non-Goals
27
+
28
+ - Not a full backup system.
29
+ - Not a daemon.
30
+ - Not Kortex.
31
+ - Not a desired-state reconciler.
32
+ - Not a general disk cleaner.
33
+ - Not a content indexer.
34
+ - Not a credential scanner in v1.
35
+ - Not allowed to silently delete files.
36
+
37
+ ## V1 CLI
38
+
39
+ ### `artshelf put`
40
+
41
+ Records an existing file or directory in the ledger.
42
+
43
+ ```bash
44
+ artshelf put <path> --reason "why this exists" --ttl 7d --kind scratch
45
+ ```
46
+
47
+ Required:
48
+
49
+ - `path`
50
+ - `--reason`
51
+ - one of `--ttl`, `--retain-until`, or `--manual-review`
52
+
53
+ Optional:
54
+
55
+ - `--kind scratch|backup|run-artifact|evidence|cache|quarantine|other`
56
+ - `--cleanup trash|review|delete` (`delete` records intent, but cleanup
57
+ execution refuses it as `cleanup-refused`)
58
+ - `--owner <string>`
59
+ - `--label <label>` repeatable
60
+ - `--ledger <path>`
61
+ - `--registry <path>`
62
+ - `--json`
63
+
64
+ Defaults:
65
+
66
+ - `kind=other`
67
+ - `cleanup=review`
68
+ - `owner=manual`
69
+
70
+ `put` should refuse to record a path that does not exist unless a future flag
71
+ explicitly supports planned artifacts. After appending the record, `put`
72
+ registers the ledger in the ledger registry. Registry registration is
73
+ best-effort: if it fails, the record remains appended and output includes a
74
+ registry warning or `registryError`.
75
+
76
+ ### `artshelf ledgers`
77
+
78
+ Lists or registers known Artshelf ledgers.
79
+
80
+ ```bash
81
+ artshelf ledgers list
82
+ artshelf ledgers list --plain
83
+ artshelf ledgers add --ledger <path> --name <project> --scope repo
84
+ ```
85
+
86
+ Rules:
87
+
88
+ - `list` validates each registered ledger by default and reports
89
+ ok/missing/invalid status, entry counts, and warning/error counts so agents can
90
+ detect stale registry entries without a separate validate pass. It reads
91
+ ledgers but never mutates them, and exits non-zero when the registry or any
92
+ registered ledger is broken.
93
+ - `list --plain` is the fast path that lists registered ledgers without reading
94
+ them; it does not validate and exits zero whenever the registry itself is
95
+ readable.
96
+ - `add` requires an existing ledger path.
97
+ - `--name` defaults from the ledger path when omitted.
98
+ - `--scope` is optional; when omitted, Artshelf infers `repo`, `user`, or
99
+ `other` from the ledger path.
100
+
101
+ ### `artshelf list`
102
+
103
+ Shows ledger entries in a human-readable format.
104
+
105
+ ```bash
106
+ artshelf list
107
+ artshelf list --json
108
+ artshelf list --status active
109
+ artshelf list --status resolved --json
110
+ artshelf list --all --status active --json
111
+ ```
112
+
113
+ `--status` filters the audit trail to one record status:
114
+
115
+ - `active`
116
+ - `review-required`
117
+ - `trashed`
118
+ - `cleanup-refused`
119
+ - `resolved`
120
+
121
+ `--all` reads every registered ledger through the registry. All-mode reads
122
+ validate registered ledgers first and report stale or invalid entries before
123
+ returning records.
124
+
125
+ ### `artshelf find`
126
+
127
+ Read-only ledger query for integrations that need idempotent artifact
128
+ registration without parsing `list` output.
129
+
130
+ ```bash
131
+ artshelf find --path <path> --json
132
+ artshelf find --path <path> --owner <agent-or-runtime> --label <task-or-run-id> --status active --json
133
+ artshelf find --all --owner <agent-or-runtime> --json
134
+ ```
135
+
136
+ Accepted selectors:
137
+
138
+ - `--path <path>`: exact artifact path match after path normalization.
139
+ - `--owner <string>`
140
+ - `--label <label>` repeatable; all labels must match.
141
+ - `--status active|review-required|trashed|cleanup-refused|resolved`
142
+
143
+ `find` requires at least one selector. It never creates, resolves, moves, or
144
+ deletes records. `--all` applies the same selector set to every registered
145
+ ledger.
146
+
147
+ ### `artshelf get`
148
+
149
+ Read-only lookup of a single ledger record by Artshelf id.
150
+
151
+ ```bash
152
+ artshelf get <id>
153
+ artshelf get <id> --json
154
+ artshelf get <id> --all --json
155
+ ```
156
+
157
+ `get` is for audit and handoff follow-up. Missing ids are an error. `--all`
158
+ searches registered ledgers until the id is found.
159
+
160
+ ### `artshelf due`
161
+
162
+ Shows entries whose retention has expired or that need manual review.
163
+ Only `active` records participate in due classification; records already handled
164
+ by cleanup execution remain visible through `list` and validation.
165
+
166
+ ```bash
167
+ artshelf due
168
+ artshelf due --json
169
+ artshelf due --all --json
170
+ ```
171
+
172
+ V1 due statuses:
173
+
174
+ - `due`
175
+ - `manual-review`
176
+ - `missing-path`
177
+ - `kept`
178
+
179
+ `--all` classifies active entries across registered ledgers.
180
+
181
+ ### `artshelf validate`
182
+
183
+ Checks ledger health without mutating files.
184
+
185
+ ```bash
186
+ artshelf validate
187
+ artshelf validate --json
188
+ artshelf validate --all --json
189
+ ```
190
+
191
+ V1 validation checks:
192
+
193
+ - ledger file is parseable JSONL
194
+ - required fields are present
195
+ - IDs are unique
196
+ - paths are absolute or resolvable
197
+ - TTL/retain-until/manual-review is valid
198
+ - cleanup action is known
199
+ - resolved records include `resolvedAt` and `resolutionReason`
200
+ - handled cleanup records include required cleanup metadata (`cleanupPlanId`,
201
+ `receiptPath`, and `cleanedAt`; trashed records also require `targetPath`)
202
+ - active and review-required recorded paths still exist, reported as warnings not hard failures
203
+ - trashed `targetPath` values still exist, reported as warnings not hard failures
204
+
205
+ `--all` validates registered ledgers and reports stale registry entries when a
206
+ registered ledger is missing from disk.
207
+
208
+ ### `artshelf review`
209
+
210
+ Runs validation, due classification, and cleanup plan preview without mutating
211
+ files or writing a plan.
212
+
213
+ ```bash
214
+ artshelf review --json
215
+ artshelf review --all --json
216
+ ```
217
+
218
+ `review` is the compact report surface for scheduled checks. `--all` reads every
219
+ registered ledger from the registry; stale, invalid, and valid no-op ledgers are
220
+ included with a `not-created` plan instead of writing a plan file.
221
+
222
+ In `--all` mode, review emits an aggregate triage summary on top of the
223
+ per-ledger detail. JSON includes a `summary` block with affected-ledger, due,
224
+ manual-review, missing-path, executable, and skipped counts plus the preview
225
+ plan ids; JSON also includes the next safe action. Human output adds a one-line
226
+ triage count and states the same next safe action (repair broken ledgers, dry-run
227
+ cleanup, inspect missing paths, or nothing to do). Review never writes a plan, so
228
+ the next action always points at an explicit follow-up command.
229
+
230
+ ### `artshelf doctor`
231
+
232
+ Reports whether Artshelf is healthy on the current machine without mutating
233
+ anything.
234
+
235
+ ```bash
236
+ artshelf doctor
237
+ artshelf doctor --json
238
+ artshelf doctor --ledger <path>
239
+ artshelf doctor --registry <path>
240
+ ```
241
+
242
+ Doctor reports:
243
+
244
+ - CLI version and Node runtime version.
245
+ - The selected/default ledger path and selected/global registry path, and whether they exist.
246
+ - Registered ledger health, flagging stale (missing from disk) and invalid
247
+ (unparseable or malformed) entries.
248
+ - The cleanup safety posture, including that `cleanup --execute` is scoped to
249
+ one selected/default ledger and still requires a reviewed `--plan-id`, that
250
+ global execute is refused, that `cleanup=delete` is refused in v1, and that
251
+ physical trash purge requires a separate reviewed purge plan.
252
+
253
+ A healthy machine exits 0. A broken registry file or any stale or invalid
254
+ registered ledger exits non-zero with actionable errors. Humans should run
255
+ `artshelf doctor` after install or when `--all` commands behave unexpectedly; agents
256
+ may run it on a schedule to catch stale registry entries before relying on
257
+ cleanup planning. Doctor never creates plans, receipts, or records.
258
+
259
+ ### `artshelf status`
260
+
261
+ The lightweight daily "what is going on?" view across ledgers.
262
+
263
+ ```bash
264
+ artshelf status
265
+ artshelf status --json
266
+ artshelf status --all --json
267
+ artshelf status --all --registry <path> --json
268
+ ```
269
+
270
+ Status reports:
271
+
272
+ - Registry health and the number of registered ledgers (with single `--ledger`
273
+ it reports just that ledger).
274
+ - Per-ledger and aggregated counts of active artifacts, kept, due,
275
+ manual-review, and missing-path entries.
276
+ - The pending cleanup count: how many entries a cleanup plan would currently
277
+ contain, computed read-only without writing a plan.
278
+
279
+ `artshelf status --all --json` is suitable for cron and reporting, and the human
280
+ output is short enough to paste into a chat. Status is strictly read-only: it
281
+ never creates plans or receipts and never mutates records. A healthy machine
282
+ exits 0. In `--all` mode, a broken registry or any stale or invalid registered
283
+ ledger exits non-zero. Due entries are normal operational state and do not change
284
+ the exit code. With single `--ledger`, a not-yet-created ledger reports empty
285
+ counts.
286
+
287
+ ### `artshelf cleanup --dry-run`
288
+
289
+ Creates a cleanup plan when there are executable cleanup entries, but does not
290
+ mutate artifacts. If there are no executable cleanup entries, dry-run reports
291
+ `planId=not-created`, `planPath=null`, and does not write a plan file.
292
+ If an existing plan has the same executable cleanup entries, Artshelf reuses that
293
+ plan id, refreshes `generatedAt`, rewrites the same plan file, and refreshes the
294
+ Artshelf-owned plan artifact record instead of creating a duplicate plan.
295
+
296
+ ```bash
297
+ artshelf cleanup --dry-run
298
+ artshelf cleanup --dry-run --json
299
+ artshelf cleanup --dry-run --all --json
300
+ ```
301
+
302
+ Written plans must include:
303
+
304
+ - `planId`
305
+ - generated timestamp
306
+ - candidate entry IDs
307
+ - planned action per entry
308
+ - skipped/refused entries with reasons
309
+ - plan file path
310
+
311
+ `--all` creates dry-run plans only for registered ledgers that have executable
312
+ cleanup entries, and only after every registered ledger validates. Global
313
+ cleanup execution is refused.
314
+
315
+ When a dry-run writes a cleanup plan, Artshelf appends or refreshes an Artshelf-owned
316
+ ledger record for the plan file with `owner=artshelf`, `kind=run-artifact`,
317
+ `ttl=14d`, `cleanup=trash`, and labels including `artshelf`, `cleanup-plan`, and the
318
+ plan id.
319
+
320
+ ### `artshelf cleanup --execute`
321
+
322
+ Executes a previously generated cleanup plan.
323
+
324
+ ```bash
325
+ artshelf cleanup --execute --plan-id <id>
326
+ artshelf cleanup --execute --plan-id <id> --json
327
+ ```
328
+
329
+ Rules:
330
+
331
+ - Requires `--plan-id`.
332
+ - Refuses to generate a fresh live cleanup set during execute.
333
+ - Writes a cleanup receipt and appends or refreshes an Artshelf-owned ledger record
334
+ for that receipt with `owner=artshelf`, `kind=run-artifact`, `ttl=30d`,
335
+ `cleanup=review`, and labels including `artshelf`, `cleanup-receipt`, and the
336
+ plan id.
337
+ - Updates touched ledger records so handled artifacts stop appearing as active
338
+ cleanup candidates.
339
+ - Uses trash/review behavior by default.
340
+ - `delete` is refused in v1: even when a ledger entry says `cleanup=delete`,
341
+ execute records a `cleanup-refused` receipt (`delete is disabled in v1`) and
342
+ never removes the file. Physical deletion is only available later through a
343
+ separately reviewed `artshelf trash purge --execute` plan for quarantined trash.
344
+
345
+ ### `artshelf trash list`
346
+
347
+ Read-only listing of records that cleanup execution moved into Artshelf trash
348
+ (`status=trashed`).
349
+
350
+ ```bash
351
+ artshelf trash list
352
+ artshelf trash list --ledger <path> --json
353
+ artshelf trash list --all --json
354
+ ```
355
+
356
+ Rules:
357
+
358
+ - Reports `id`, `targetPath`, `cleanedAt`, `receiptPath`, `cleanupPlanId`, and a
359
+ human-readable `age` for each trashed record.
360
+ - Never moves, deletes, or resolves records.
361
+ - `--all` reads every registered ledger through the registry and validates those
362
+ ledgers first, the same way `list --all` and `review --all` do.
363
+
364
+ ### `artshelf trash purge`
365
+
366
+ Approval-first physical deletion of quarantined trash. Trashed artifacts stay in
367
+ Artshelf trash until a separately reviewed purge plan removes them, mirroring the
368
+ cleanup dry-run/execute boundary.
369
+
370
+ ```bash
371
+ artshelf trash purge --older-than <ttl> --dry-run --ledger <path> --json
372
+ artshelf trash purge --execute --plan-id <id> --ledger <path> --json
373
+ ```
374
+
375
+ Rules:
376
+
377
+ - Scoped to a single ledger. `--all` is refused for purge (it is only supported
378
+ by `trash list`); there is no global blind delete.
379
+ - Requires either `--dry-run` or `--execute`; there is no non-persisted preview
380
+ that looks like an executable reviewed plan.
381
+ - `--dry-run` builds an age-based purge plan from records whose `cleanedAt` is
382
+ older than `--older-than`, writes it to `<ledger-dir>/purge-plans/<id>.json`,
383
+ and registers an Artshelf-owned plan record (`ttl=14d`, `cleanup=review`, labels
384
+ including `artshelf`, `trash-purge-plan`, and the purge plan id). No-op dry-runs
385
+ report `not-created` and write no plan file.
386
+ - The purge plan records `purgePlanId`, `generatedAt`, `ledgerPath`,
387
+ `olderThan`, and the computed `cutoff`. Each executable entry includes
388
+ `id`, `targetPath`, `cleanedAt`, `receiptPath`, and `cleanupPlanId`; skipped
389
+ records include `id`, `targetPath`, and the skip `reason`.
390
+ - `--execute` requires a `--plan-id` produced by an earlier reviewed dry-run; it
391
+ refuses to compute a fresh purge set and refuses to rerun a purge plan with an
392
+ already completed receipt. It physically removes each planned trash target,
393
+ skipping entries whose record is missing, is no longer `trashed`, or whose
394
+ target is already gone. Before removal it also re-checks that the plan entry
395
+ still matches the live ledger record and that the target remains inside Artshelf's
396
+ ledger-local trash directory for that cleanup plan.
397
+ - Writes a `started` purge receipt to `<ledger-dir>/purge-receipts/<id>.json`
398
+ before deletion, records `pending` and `deleting` result states during the run,
399
+ then completes the receipt with `purged`, `skipped`, or `failed` results. If an
400
+ interrupted purge left a started receipt, a later execute resumes from those
401
+ results and reconciles a `deleting` entry whose target is already gone as
402
+ `purged`.
403
+ - Registers the completed receipt (`ttl=30d`, `cleanup=review`, labels including
404
+ `artshelf`, `trash-purge-receipt`, and the purge plan id) so the final deletion
405
+ stays auditable.
406
+ - Marks purged records `resolved` with `purgedAt`, `purgePlanId`, and
407
+ `purgeReceiptPath`, so they no longer reappear as trashed.
408
+
409
+ ### `artshelf resolve`
410
+
411
+ Marks a handled, missing, or no-longer-needed record as manually resolved while
412
+ keeping it in the ledger audit trail.
413
+
414
+ ```bash
415
+ artshelf resolve <id> --status resolved --reason <text>
416
+ artshelf resolve <id> --status resolved --reason <text> --json
417
+ ```
418
+
419
+ Rules:
420
+
421
+ - Requires `<id>`, `--status resolved`, and `--reason`.
422
+ - Does not move or delete files.
423
+ - Removes the record from future `due` and cleanup dry-run output.
424
+ - Keeps the record visible through `list` and `list --status resolved`.
425
+ - Refuses records that are already `resolved`; the original reason is preserved.
426
+
427
+ ## Ledger Storage
428
+
429
+ V1 supports two scopes:
430
+
431
+ - repo-local: `.shelf/ledger.jsonl`
432
+ - user-global: `~/.shelf/ledger.jsonl`
433
+
434
+ Default behavior:
435
+
436
+ - If the current directory is inside a git repo, write repo-local.
437
+ - Otherwise write user-global.
438
+ - Allow `--ledger <path>` for explicit tests and unusual workflows.
439
+
440
+ V1 also supports a user-level registry of known ledgers:
441
+
442
+ - registry: `~/.shelf/ledgers.json`
443
+ - `ARTSHELF_REGISTRY` or `--registry <path>` can override the registry path.
444
+ - `put` registers the ledger it writes to.
445
+ - `ledgers add` registers an existing ledger explicitly.
446
+ - `--all` reads registered ledgers as one review surface.
447
+ - `trash list --all` reads trashed records across registered ledgers after
448
+ registry validation.
449
+ - `cleanup --execute --all` and `trash purge --all` are refused; execution stays
450
+ scoped to one explicit ledger and one reviewed plan id.
451
+
452
+ ## Ledger Registry Schema
453
+
454
+ ```json
455
+ {
456
+ "version": 1,
457
+ "ledgers": [
458
+ {
459
+ "name": "my-repo",
460
+ "path": "/absolute/path/to/repo/.shelf/ledger.jsonl",
461
+ "scope": "repo",
462
+ "createdAt": "2026-06-01T05:42:00Z",
463
+ "updatedAt": "2026-06-01T05:42:00Z"
464
+ }
465
+ ]
466
+ }
467
+ ```
468
+
469
+ ## Ledger Record Schema
470
+
471
+ ```json
472
+ {
473
+ "id": "shf_20260601_154200_ab12",
474
+ "path": "/absolute/path/to/artifact",
475
+ "kind": "scratch",
476
+ "reason": "debug parser output",
477
+ "createdAt": "2026-06-01T05:42:00Z",
478
+ "retainUntil": "2026-06-04T05:42:00Z",
479
+ "retention": {
480
+ "mode": "ttl",
481
+ "ttl": "3d"
482
+ },
483
+ "cleanup": "trash",
484
+ "owner": "manual",
485
+ "labels": ["debug"],
486
+ "status": "active"
487
+ }
488
+ ```
489
+
490
+ V1 record statuses:
491
+
492
+ - `active`: eligible for `due` classification and cleanup dry-run planning.
493
+ - `review-required`: execution surfaced the artifact for manual review.
494
+ - `trashed`: execution moved a `cleanup=trash` artifact into Artshelf trash.
495
+ - `cleanup-refused`: execution refused the requested action, such as physical
496
+ delete in v1.
497
+ - `resolved`: a human or agent marked the record as manually handled.
498
+
499
+ Handled records may include cleanup outcome fields:
500
+
501
+ ```json
502
+ {
503
+ "cleanupPlanId": "plan_20260601_154200_cd34",
504
+ "receiptPath": "/absolute/path/.shelf/receipts/plan_20260601_154200_cd34.json",
505
+ "cleanedAt": "2026-06-01T05:45:00Z",
506
+ "targetPath": "/absolute/path/.shelf/trash/plan_20260601_154200_cd34/shf_20260601_154200_ab12-artifact",
507
+ "cleanupReason": "delete is disabled in v1"
508
+ }
509
+ ```
510
+
511
+ Manually resolved records include:
512
+
513
+ ```json
514
+ {
515
+ "resolvedAt": "2026-06-01T05:45:00Z",
516
+ "resolutionReason": "artifact inspected and no longer needed"
517
+ }
518
+ ```
519
+
520
+ Records removed by `artshelf trash purge --execute` become `resolved` and also carry
521
+ the purge provenance:
522
+
523
+ ```json
524
+ {
525
+ "resolvedAt": "2026-06-01T06:10:00Z",
526
+ "resolutionReason": "trash purge completed",
527
+ "purgedAt": "2026-06-01T06:10:00Z",
528
+ "purgePlanId": "purge_20260601_061000_ef56",
529
+ "purgeReceiptPath": "/absolute/path/.shelf/purge-receipts/purge_20260601_061000_ef56.json"
530
+ }
531
+ ```
532
+
533
+ ## Cleanup Safety Model
534
+
535
+ Cleanup execution is intentionally boring and approval-only. Five boundaries
536
+ hold, and every future feature (`status`, `doctor`, `review`, scheduled jobs,
537
+ ...) must preserve them rather than add a shortcut around them:
538
+
539
+ - **No daemon.** Artshelf never runs in the background or watches the clock. It
540
+ only does work while you are running a `artshelf` command.
541
+ - **No auto-execute.** No command cleans up as a side effect. The only commands
542
+ that move, trash, or delete files are `artshelf cleanup --execute` and
543
+ `artshelf trash purge --execute`, each run by a human against a separately
544
+ reviewed plan id.
545
+ - **No global execute.** `cleanup --execute --all` and `trash purge --all`
546
+ are refused; `--all` is read-only or dry-run reporting only. Execution is
547
+ always scoped to a single reviewed plan id.
548
+ - **No fresh-plan-then-execute.** `cleanup --execute` refuses to compute a new
549
+ live set. It acts only on a plan id that an earlier `cleanup --dry-run`
550
+ produced and a human reviewed; it will not plan and execute in one step.
551
+ - **No silent deletion.** Cleanup trashes or flags for review and writes a
552
+ receipt to the ledger. The `cleanup=delete` action stays refused in v1; the
553
+ one sanctioned physical deletion is `artshelf trash purge --execute`, which only
554
+ removes already-quarantined trash through its own reviewed purge plan and
555
+ receipt. Nothing leaves the filesystem without an auditable trail.
556
+
557
+ Operational rules that back those boundaries:
558
+
559
+ - Dry-run first.
560
+ - Execute only by plan id.
561
+ - Trash/review before delete.
562
+ - Execute updates ledger state after writing the cleanup receipt. A trashed,
563
+ review-required, or refused record no longer participates in future `due` or
564
+ cleanup dry-run output by default.
565
+ - Missing paths update the report; they are not treated as a successful cleanup
566
+ unless the user explicitly repairs the ledger later.
567
+ - Cleanup never scans arbitrary filesystem paths for deletion in v1.
568
+ - Cleanup only acts on ledger entries.
569
+ - Trash purge is scoped to one ledger, requires a reviewed purge plan id, and
570
+ writes a purge receipt before removing quarantined files.
571
+
572
+ ## Agent Usage Contract
573
+
574
+ Agents should call `artshelf put` immediately after creating:
575
+
576
+ - config backups
577
+ - quarantine folders
578
+ - debug output directories
579
+ - temporary repo artifacts
580
+ - one-off generated reports
581
+ - copied files kept for rollback
582
+
583
+ Agents should not run `artshelf cleanup --execute` or
584
+ `artshelf trash purge --execute` without explicit approval naming the ledger path
585
+ and reviewed plan id.
586
+
587
+ Agents may run `artshelf find` and `artshelf get` before `put` to avoid duplicate
588
+ registrations. `find`/`get` are read-only ledger queries; they must not be used
589
+ as permission to clean up or resolve a record.
590
+
591
+ Agents may run `artshelf resolve <id> --status resolved --reason <text>` only
592
+ after explicit confirmation that the record has been handled, is missing, or is
593
+ no longer needed. The reason must be specific; resolve does not move or delete
594
+ files.
595
+
596
+ Scheduled jobs may run:
597
+
598
+ ```bash
599
+ artshelf due --json
600
+ artshelf due --all --json
601
+ artshelf review --all --json
602
+ artshelf doctor --json
603
+ artshelf status --all --json
604
+ artshelf cleanup --dry-run --json
605
+ artshelf cleanup --dry-run --all --json
606
+ artshelf trash list --ledger <path> --json
607
+ artshelf trash list --all --json
608
+ artshelf trash purge --older-than <ttl> --dry-run --ledger <path> --json
609
+ ```
610
+
611
+ `artshelf review --all --json` is the read-only all-ledger triage surface;
612
+ scheduled reports should include its aggregate `summary` and `nextAction` when
613
+ whole-machine review is needed.
614
+
615
+ Scheduled trash reports may use `artshelf trash list --all --json` for
616
+ registered-ledger discovery and should include trashed record counts and target
617
+ ages. Purge dry-runs stay scoped to one explicit ledger and should report any
618
+ plan id, matching entries, and skipped entries.
619
+
620
+ Scheduled jobs must never run `artshelf cleanup --execute` or
621
+ `artshelf trash purge --execute`; they may only dry-run and report plans for later
622
+ human review.
623
+
624
+ ## Dogfood Scenarios
625
+
626
+ 1. Record a repo-local `tmp/` scratch directory with a 3-day TTL.
627
+ 2. Record a config backup with manual review retention.
628
+ 3. Generate a dry-run cleanup plan after TTL expiry using fixture data.
629
+ 4. Execute a cleanup plan in a temporary test fixture and verify receipt output.
630
+ 5. List trashed records, dry-run an old-trash purge, then execute the reviewed
631
+ purge plan in a fixture and verify receipt output plus resolved ledger state.
632
+
633
+ ## V1 Acceptance Criteria
634
+
635
+ - CLI can record entries to JSONL.
636
+ - CLI can register known ledgers and list them with per-ledger validation status
637
+ by default, or a `--plain` fast path that skips validation.
638
+ - CLI can review registered ledgers through `--all` read-only entry points,
639
+ emitting an aggregate triage summary and the next safe action.
640
+ - CLI refuses records without a reason.
641
+ - CLI requires TTL, retain-until, or manual-review.
642
+ - CLI can list, filter by status, and show due entries.
643
+ - CLI can find existing records by path/owner/label/status and get records by id.
644
+ - CLI can mark records manually resolved with a required reason.
645
+ - CLI validates ledger shape.
646
+ - CLI reports machine and registry health through `artshelf doctor`, exiting
647
+ non-zero when the registry or a registered ledger is broken.
648
+ - CLI reports a read-only daily dashboard through `artshelf status`, with
649
+ `--all --json` suitable for cron and human output short enough to paste into
650
+ a chat; status never creates plans, receipts, or records.
651
+ - Cleanup dry-run creates a plan id only when there are executable cleanup
652
+ entries; no-op dry-runs do not write plan files.
653
+ - Cleanup dry-run and execute register the plan/receipt artifacts that Artshelf
654
+ creates.
655
+ - Cleanup execute refuses to run without a plan id.
656
+ - Cleanup execute writes a receipt.
657
+ - CLI can list trashed records (single ledger or `--all`) and purge them through
658
+ an approval-first, ledger-scoped dry-run/execute boundary that writes a purge
659
+ receipt; purge refuses `--all` and never deletes without a reviewed plan id.
660
+ - All core commands support `--json`.
661
+ - Tests cover record/list/find/get/status-filter/due/validate/resolve/registry,
662
+ `artshelf doctor`, the `artshelf status` dashboard, `--all` review, stale-registry,
663
+ dry-run, global-dry-run, execute-plan, and trash list/purge behavior.
664
+
665
+ ## Deferred
666
+
667
+ - Cron integration.
668
+ - Agent skill adapters.
669
+ - GitHub Action.
670
+ - Fake/demo mode.
671
+ - Rollback command.
672
+ - Retention classes like keep-daily/weekly/monthly.
673
+ - Dependency roots and pinning.
674
+ - Credential scanning.
675
+ - Public package publishing.