artshelf 0.14.0 → 0.16.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/CHANGELOG.md +23 -0
- package/README.md +16 -10
- package/SPEC.md +152 -27
- package/dist/src/cli.js +2 -2
- package/dist/src/commands/dispose.js +88 -0
- package/dist/src/commands/get.js +24 -2
- package/dist/src/commands/index.js +4 -0
- package/dist/src/dispose.js +706 -0
- package/dist/src/inspect.js +207 -0
- package/dist/src/ledger.js +33 -22
- package/dist/src/renderers/inspect.js +108 -0
- package/dist/src/shared/flags.js +6 -0
- package/dist/src/shared/help-text.js +40 -0
- package/dist/src/shared/shell-quote.js +6 -0
- package/docs/agent-clean.html +30 -5
- package/docs/agent-monitor.html +2 -1
- package/docs/agent-review.html +37 -0
- package/docs/agent-usage.html +5 -3
- package/docs/agent-usage.md +12 -9
- package/docs/index.html +4 -0
- package/docs/install.html +2 -2
- package/docs/reference.html +43 -6
- package/package.json +1 -1
- package/skills/artshelf/SKILL.md +32 -40
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Added read-only `artshelf get <id> --inspect` decision cards with human,
|
|
6
|
+
`--json`, and `--agent` render modes, including registry-wide `--all` lookup,
|
|
7
|
+
metadata-only existence/size reporting, recommendation buckets, and exact
|
|
8
|
+
next-safe actions without reading file contents or mutating ledgers.
|
|
5
9
|
- Added approval-gated `artshelf ledgers prune` registry maintenance: dry-run
|
|
6
10
|
writes or reuses a reviewed plan for missing registered ledger files, `--agent`
|
|
7
11
|
emits the exact registry-prune approval target, execute binds to one registry
|
|
@@ -132,6 +136,25 @@
|
|
|
132
136
|
- Moved `artshelf put` registry-warning output from stdout to stderr in human
|
|
133
137
|
mode; `--json` output is unchanged (NGX-429).
|
|
134
138
|
|
|
139
|
+
## [0.16.0](https://github.com/calvinnwq/artshelf/compare/v0.15.0...v0.16.0) (2026-06-24)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
### Features
|
|
143
|
+
|
|
144
|
+
* **dispose:** add approval-gated artifact disposition ([ee2406e](https://github.com/calvinnwq/artshelf/commit/ee2406e1850f6f2775d5d581bb529fac295b5159))
|
|
145
|
+
* **dispose:** add disposition dry-run domain layer ([c7bdbb3](https://github.com/calvinnwq/artshelf/commit/c7bdbb3d8a40ffc3c9c14c7da6eb6acfedb42859))
|
|
146
|
+
* **dispose:** add disposition execute domain layer ([b9fda32](https://github.com/calvinnwq/artshelf/commit/b9fda3254576d99e5c222a6d1a04c25b1a08d8b0))
|
|
147
|
+
* **dispose:** wire disposition CLI command ([7fe7921](https://github.com/calvinnwq/artshelf/commit/7fe792123b2d4a05bef37d360ced573b82dcd322))
|
|
148
|
+
|
|
149
|
+
## [0.15.0](https://github.com/calvinnwq/artshelf/compare/v0.14.0...v0.15.0) (2026-06-20)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
### Features
|
|
153
|
+
|
|
154
|
+
* **inspect:** add read-only Artshelf review cards ([08478a8](https://github.com/calvinnwq/artshelf/commit/08478a871f624b048173a986511cc57cf2a2e609))
|
|
155
|
+
* **inspect:** add read-only inspect decision model ([334ea7d](https://github.com/calvinnwq/artshelf/commit/334ea7de99326ed0b0c3129228beaa0d33f378b9))
|
|
156
|
+
* **inspect:** wire get inspect output modes ([4a7270d](https://github.com/calvinnwq/artshelf/commit/4a7270df29f1895efe9032b2cfb7a2c1d47e971f))
|
|
157
|
+
|
|
135
158
|
## [0.14.0](https://github.com/calvinnwq/artshelf/compare/v0.13.1...v0.14.0) (2026-06-19)
|
|
136
159
|
|
|
137
160
|
|
package/README.md
CHANGED
|
@@ -83,7 +83,7 @@ Follow the instructions in https://github.com/calvinnwq/artshelf/blob/main/INSTA
|
|
|
83
83
|
It will install the CLI, copy the portable skill (with its bundled review-report
|
|
84
84
|
renderer), register any existing project ledgers, and — only with your approval —
|
|
85
85
|
schedule a **read-only** daily review. Scheduled jobs review and report only;
|
|
86
|
-
cleanup and purge execution always come back to you. See [INSTALL.md](INSTALL.md)
|
|
86
|
+
cleanup, dispose, and purge execution always come back to you. See [INSTALL.md](INSTALL.md)
|
|
87
87
|
for the full steps.
|
|
88
88
|
|
|
89
89
|
## How it works
|
|
@@ -109,7 +109,7 @@ destructive deletion.
|
|
|
109
109
|
- **Ledger-first**, not filesystem-scan-first — every artifact is a recorded decision.
|
|
110
110
|
- **Dry-run before mutation**, and execute only from a reviewed plan id.
|
|
111
111
|
- **No daemon, no auto-execute, no global execute** — `--all` is read-only or
|
|
112
|
-
dry-run reporting; cleanup and purge refuse it.
|
|
112
|
+
dry-run reporting; cleanup, dispose, and purge refuse it.
|
|
113
113
|
- **No fresh-plan-then-execute shortcut** — review the plan, then run that plan.
|
|
114
114
|
- **Trash before delete** — `cleanup=delete` stays refused; physical deletion
|
|
115
115
|
needs its own reviewed trash purge. No silent deletion, ever.
|
|
@@ -118,9 +118,9 @@ destructive deletion.
|
|
|
118
118
|
registry mutations take a cross-process lock so overlapping commands never
|
|
119
119
|
lose records or leave a half-written ledger.
|
|
120
120
|
- **`--json` on every command**, so agents can act on structured output.
|
|
121
|
-
- **`--agent` on `review`/`status`/`doctor
|
|
122
|
-
compact, token-efficient decision packet for agents,
|
|
123
|
-
stays human-scannable.
|
|
121
|
+
- **`--agent` on `review`/`status`/`doctor`, `ledgers prune --dry-run`,
|
|
122
|
+
`dispose --dry-run`, and `get --inspect`**, a compact, token-efficient decision packet for agents,
|
|
123
|
+
while the default render stays human-scannable.
|
|
124
124
|
|
|
125
125
|
## Reference
|
|
126
126
|
|
|
@@ -137,6 +137,8 @@ artshelf list [--all] [--status active]
|
|
|
137
137
|
artshelf find --path <path> --owner <agent-or-runtime> --label <task-or-run-id>
|
|
138
138
|
artshelf find --all --owner <agent-or-runtime>
|
|
139
139
|
artshelf get <id> [--all]
|
|
140
|
+
artshelf get <id> --inspect [--ledger <path>] [--json|--agent]
|
|
141
|
+
artshelf get <id> --inspect --all [--registry <path>] [--json|--agent]
|
|
140
142
|
artshelf due [--all]
|
|
141
143
|
artshelf validate [--all]
|
|
142
144
|
artshelf review [--all]
|
|
@@ -145,6 +147,8 @@ artshelf doctor
|
|
|
145
147
|
artshelf update [--json]
|
|
146
148
|
artshelf cleanup --dry-run [--all]
|
|
147
149
|
artshelf cleanup --execute --plan-id <id> [--ledger <path>] [--json]
|
|
150
|
+
artshelf dispose --id <id> --action trash-resolve|resolve-only|snooze|keep --dry-run [--reason <text>] [--ttl <ttl>|--retain-until <date>] [--ledger <path>] [--json|--agent]
|
|
151
|
+
artshelf dispose --execute --plan-id <id> [--ledger <path>] [--json]
|
|
148
152
|
artshelf reconcile --dry-run [--all] [--ledger <path>] [--json]
|
|
149
153
|
artshelf reconcile --execute --plan-id <id> --ledger <path> [--json]
|
|
150
154
|
artshelf trash list [--all] [--ledger <path>] [--json]
|
|
@@ -157,9 +161,10 @@ Use `artshelf help` for a grouped command list, then `artshelf <command> --help`
|
|
|
157
161
|
or `artshelf help <command>` for focused details. Nested commands such as
|
|
158
162
|
`artshelf trash purge --help`, `artshelf ledgers add --help`, and
|
|
159
163
|
`artshelf ledgers prune --help` show only that subcommand. All core commands
|
|
160
|
-
support `--json`; `review`, `status`, `doctor`,
|
|
161
|
-
also take `--agent` for a compact
|
|
162
|
-
|
|
164
|
+
support `--json`; `review`, `status`, `doctor`, `ledgers prune --dry-run`,
|
|
165
|
+
`dispose --dry-run`, and `get --inspect` also take `--agent` for a compact
|
|
166
|
+
decision packet; `--ledger`,
|
|
167
|
+
`--registry`, and `--all` are scope flags only on commands that list them.
|
|
163
168
|
</details>
|
|
164
169
|
|
|
165
170
|
<details>
|
|
@@ -200,8 +205,9 @@ The package includes an agent-facing skill at `skills/artshelf`. Agents that
|
|
|
200
205
|
support local skills can copy or reference this directory to learn when to call
|
|
201
206
|
`artshelf put`, how to report deterministic footnotes after JSON registration,
|
|
202
207
|
why `artshelf find` / `artshelf get` are the read-only idempotency lookup surface,
|
|
203
|
-
why `cleanup --execute`
|
|
204
|
-
dry-run cleanup and trash purge plans as review-report decision packets,
|
|
208
|
+
why `cleanup --execute` and `dispose --execute` require approved reviewed plan ids, how to render
|
|
209
|
+
dry-run cleanup and trash purge plans as review-report decision packets, how to use
|
|
210
|
+
`dispose --agent` for per-record approval packets, and when
|
|
205
211
|
`artshelf resolve <id> --status resolved --reason <text>` may mark a record handled
|
|
206
212
|
without moving or deleting files.
|
|
207
213
|
|
package/SPEC.md
CHANGED
|
@@ -199,10 +199,57 @@ Read-only lookup of a single ledger record by Artshelf id.
|
|
|
199
199
|
artshelf get <id>
|
|
200
200
|
artshelf get <id> --json
|
|
201
201
|
artshelf get <id> --all --json
|
|
202
|
+
artshelf get <id> --inspect
|
|
203
|
+
artshelf get <id> --inspect --json
|
|
204
|
+
artshelf get <id> --inspect --agent
|
|
205
|
+
artshelf get <id> --inspect --all --registry <path> --agent
|
|
202
206
|
```
|
|
203
207
|
|
|
204
208
|
`get` is for audit and handoff follow-up. Missing ids are an error. `--all`
|
|
205
|
-
searches registered ledgers until the id is found.
|
|
209
|
+
searches registered ledgers until the id is found. With `--inspect --all`, the
|
|
210
|
+
registry is only used for lookup; the decision card reports the concrete ledger
|
|
211
|
+
that owns the matching record.
|
|
212
|
+
|
|
213
|
+
`--inspect` turns a record into a review decision card. It never moves files or
|
|
214
|
+
mutates the ledger; it only reports existence, node kind, size, age, retention/due and
|
|
215
|
+
manual-review state, cleanup mode, reason, and a recommendation bucket with the
|
|
216
|
+
exact next-safe action. It never reads or previews arbitrary file contents:
|
|
217
|
+
|
|
218
|
+
- `keep` — held for manual review, already resolved, already trashed, or due
|
|
219
|
+
with `cleanup=review`; it needs your judgment but nothing auto-runs.
|
|
220
|
+
- `snooze` — retention has not expired yet; re-inspect after it is due.
|
|
221
|
+
- `trash-safe` — due with `cleanup=trash`; safe to plan a reviewed dispose
|
|
222
|
+
`trash-resolve` decision.
|
|
223
|
+
- `resolve-only` — the recorded path is gone; resolve the record (ledger-only)
|
|
224
|
+
rather than cleaning a file.
|
|
225
|
+
- `blocked` — needs a human decision first: `cleanup=delete` (refused at
|
|
226
|
+
execute), a review-required flag, or a prior cleanup refusal.
|
|
227
|
+
|
|
228
|
+
File-content previews are intentionally outside Artshelf core; an acting agent or
|
|
229
|
+
host runtime may inspect file contents separately when appropriate.
|
|
230
|
+
`--inspect --json` returns `{ inspect: <report> }`; `--inspect --agent` returns a compact single-line
|
|
231
|
+
decision packet with a read-only safety block, the next-safe action, and a
|
|
232
|
+
reproducer command, and takes precedence over `--json`. Both shapes are
|
|
233
|
+
deterministic so portable agent skills can act without re-deriving anything.
|
|
234
|
+
|
|
235
|
+
Example pattern — the dogfooding case that motivated this surface was an old
|
|
236
|
+
rollback `backup` registered with `cleanup=review` (the kind of stale record
|
|
237
|
+
`ledgers prune` and `review --all` surface). Inspect it before deciding, using
|
|
238
|
+
the record id and ledger path rather than hardcoding either:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
artshelf get <id> --inspect --ledger <ledger-path>
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
```text
|
|
245
|
+
✓ <id> [backup] — keep
|
|
246
|
+
path: <backup-path>
|
|
247
|
+
status: active · cleanup: review · owner: agent · labels: registry-prune
|
|
248
|
+
existence: present (directory, 49 B) · age: 14d · retention: manual-review · due: manual-review
|
|
249
|
+
reason: rollback backup before registry prune
|
|
250
|
+
next: Held for manual review — run `artshelf dispose --id <id> --action keep --dry-run --reason '<why>' --ledger <ledger-path>` to keep it quiet through a reviewed decision, or choose resolve-only/snooze deliberately.
|
|
251
|
+
ledger: <ledger-path>
|
|
252
|
+
```
|
|
206
253
|
|
|
207
254
|
### `artshelf due`
|
|
208
255
|
|
|
@@ -279,12 +326,14 @@ registry-prune dry-run for missing registered ledgers, dry-run reconcile for
|
|
|
279
326
|
missing-path or reconcile drift, or nothing to do). Review never writes a plan,
|
|
280
327
|
so the next action always points at an explicit follow-up command.
|
|
281
328
|
|
|
282
|
-
`review`, `status`, `doctor`,
|
|
283
|
-
agent-oriented render modes. For review/status/doctor, the default human render
|
|
329
|
+
`review`, `status`, `doctor`, `ledgers prune --dry-run`, `dispose --dry-run`,
|
|
330
|
+
and `get --inspect` expose agent-oriented render modes. For review/status/doctor, the default human render
|
|
284
331
|
leads each ledger and summary line with a `✓`/`⚠` attention glyph. `--json` stays
|
|
285
332
|
the full, backward-compatible public audit report; and `--agent` emits a compact,
|
|
286
333
|
deterministic single-line JSON decision packet for agents, taking precedence over
|
|
287
|
-
`--json` when both are passed. For `
|
|
334
|
+
`--json` when both are passed. For `dispose --dry-run`, `--agent` returns the per-request plan or blocked packet.
|
|
335
|
+
For `get --inspect`, `--agent` returns the
|
|
336
|
+
per-record decision packet and requires `--inspect`. For `review`, the packet sorts records into
|
|
288
337
|
ready-for-approval, needs-review-first, and blocked groups. Because review is
|
|
289
338
|
read-only and never mints a cleanup or registry-prune plan, the exact approval
|
|
290
339
|
targets it emits are `resolve missing` and `reconcile`; the `reconcile` target
|
|
@@ -640,6 +689,54 @@ Execute rules:
|
|
|
640
689
|
JSON output is deterministic (findings preserve ledger order) so agents can render a
|
|
641
690
|
decision packet and approve a specific plan id.
|
|
642
691
|
|
|
692
|
+
### `artshelf dispose`
|
|
693
|
+
|
|
694
|
+
Approval-gated disposition for one reviewed record. `dispose` is the command
|
|
695
|
+
surface that follows `get --inspect`: inspect stays read-only, then dispose
|
|
696
|
+
creates or executes the exact reviewed plan for the chosen decision.
|
|
697
|
+
|
|
698
|
+
```bash
|
|
699
|
+
artshelf dispose --id <id> --action trash-resolve --dry-run [--reason <text>] [--ledger <path>] [--json|--agent]
|
|
700
|
+
artshelf dispose --id <id> --action resolve-only --dry-run --reason <text> [--ledger <path>] [--json|--agent]
|
|
701
|
+
artshelf dispose --id <id> --action snooze --dry-run (--ttl <ttl>|--retain-until <date>) [--reason <text>] [--ledger <path>] [--json|--agent]
|
|
702
|
+
artshelf dispose --id <id> --action keep --dry-run [--reason <text>] [--ledger <path>] [--json|--agent]
|
|
703
|
+
artshelf dispose --execute --plan-id <id> --ledger <path> [--json]
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
Actions:
|
|
707
|
+
|
|
708
|
+
- `trash-resolve`: move the recorded path into plan-scoped Artshelf trash,
|
|
709
|
+
mark the row `trashed`, and leave physical deletion to a separate trash purge.
|
|
710
|
+
- `resolve-only`: resolve the ledger row only; requires `--reason`.
|
|
711
|
+
- `snooze`: extend retention; requires `--ttl` or `--retain-until`.
|
|
712
|
+
- `keep`: stamp that the record was reviewed and kept.
|
|
713
|
+
|
|
714
|
+
Dry-run rules:
|
|
715
|
+
|
|
716
|
+
- Classifies exactly one record id and action. There is no `--all` path.
|
|
717
|
+
- Writes a reviewed plan only when the request is actionable, under
|
|
718
|
+
`<ledger-dir>/dispose-plans/<id>.json`.
|
|
719
|
+
- Registers the plan as an Artshelf-owned artifact (`owner=artshelf`,
|
|
720
|
+
`kind=run-artifact`, `ttl=14d`, `cleanup=trash`, labels including
|
|
721
|
+
`artshelf`, `dispose-plan`, and the plan id).
|
|
722
|
+
- Prints the exact approval target:
|
|
723
|
+
`approve artshelf dispose ledger <ledger-path> plan <plan-id>`.
|
|
724
|
+
- A blocked request reports `planId=not-created`, writes no plan, and exits
|
|
725
|
+
non-zero while still returning a JSON/agent packet when requested.
|
|
726
|
+
|
|
727
|
+
Execute rules:
|
|
728
|
+
|
|
729
|
+
- Requires one explicit reviewed `--plan-id` and the target `--ledger`.
|
|
730
|
+
- Refuses missing, unknown, id-mismatched, ledger-mismatched, malformed, stale,
|
|
731
|
+
drifted, or target-conflicting plans before mutating.
|
|
732
|
+
- Re-snapshots the subject before execution; stale entries are skipped rather
|
|
733
|
+
than applied.
|
|
734
|
+
- Writes a dispose receipt to `<ledger-dir>/dispose-receipts/<id>.json` and
|
|
735
|
+
registers it as an Artshelf-owned artifact (`ttl=30d`, `cleanup=review`,
|
|
736
|
+
labels including `artshelf`, `dispose-receipt`, and the plan id).
|
|
737
|
+
- There is no fresh-plan-then-execute, no global execute, no daemon, and no
|
|
738
|
+
physical deletion.
|
|
739
|
+
|
|
643
740
|
## Ledger Storage
|
|
644
741
|
|
|
645
742
|
V1 supports two scopes:
|
|
@@ -691,9 +788,9 @@ V1 also supports a user-level registry of known ledgers:
|
|
|
691
788
|
registry validation.
|
|
692
789
|
- Registry-prune artifacts live next to the registry: `registry-prune-plans/`,
|
|
693
790
|
`registry-prune-rollbacks/`, and `registry-prune-receipts/`.
|
|
694
|
-
- `cleanup --execute --all`, `reconcile --execute --all`, and
|
|
695
|
-
are refused; execution stays scoped to one explicit ledger
|
|
696
|
-
reviewed plan id.
|
|
791
|
+
- `cleanup --execute --all`, `dispose --all`, `reconcile --execute --all`, and
|
|
792
|
+
`trash purge --all` are refused; execution stays scoped to one explicit ledger
|
|
793
|
+
or registry and one reviewed plan id.
|
|
697
794
|
|
|
698
795
|
## Ledger Registry Schema
|
|
699
796
|
|
|
@@ -776,6 +873,23 @@ the purge provenance:
|
|
|
776
873
|
}
|
|
777
874
|
```
|
|
778
875
|
|
|
876
|
+
Records touched by `artshelf dispose --execute` carry the dispose audit trail so
|
|
877
|
+
reviewed disposition stays traceable to the plan and receipt that produced it:
|
|
878
|
+
|
|
879
|
+
```json
|
|
880
|
+
{
|
|
881
|
+
"disposePlanId": "dispose_20260601_063000_ab12",
|
|
882
|
+
"disposeReceiptPath": "/absolute/path/.artshelf/dispose-receipts/dispose_20260601_063000_ab12.json",
|
|
883
|
+
"disposedAt": "2026-06-01T06:30:00Z",
|
|
884
|
+
"disposeAction": "trash-resolve",
|
|
885
|
+
"disposeReason": "reviewed and no longer needed"
|
|
886
|
+
}
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
`trash-resolve` also sets `previousPath` and `targetPath`; `resolve-only` sets
|
|
890
|
+
the resolve fields without moving the subject; `snooze` updates retention; and
|
|
891
|
+
`keep` leaves retention intact while making due classification quiet.
|
|
892
|
+
|
|
779
893
|
Records touched by `artshelf reconcile --execute` carry the reconcile audit trail so a
|
|
780
894
|
remap or resolve stays traceable to the reviewed plan that produced it:
|
|
781
895
|
|
|
@@ -848,15 +962,16 @@ hold, and every future feature (`status`, `doctor`, `review`, scheduled jobs,
|
|
|
848
962
|
- **No daemon.** Artshelf never runs in the background or watches the clock. It
|
|
849
963
|
only does work while you are running an `artshelf` command.
|
|
850
964
|
- **No auto-execute.** No command cleans up as a side effect. The only commands
|
|
851
|
-
that move, trash, or delete files are `artshelf cleanup --execute
|
|
852
|
-
`artshelf trash purge --execute`, each run
|
|
853
|
-
reviewed plan id.
|
|
854
|
-
- **No global execute.** `cleanup --execute --all
|
|
855
|
-
are refused; `--all` is read-only or dry-run reporting
|
|
856
|
-
always scoped to a single reviewed plan id.
|
|
857
|
-
- **No fresh-plan-then-execute.** `cleanup --execute`
|
|
858
|
-
live set.
|
|
859
|
-
produced and a human reviewed;
|
|
965
|
+
that move, trash, or delete files are `artshelf cleanup --execute`,
|
|
966
|
+
`artshelf dispose --execute`, and `artshelf trash purge --execute`, each run
|
|
967
|
+
by a human against a separately reviewed plan id.
|
|
968
|
+
- **No global execute.** `cleanup --execute --all`, `dispose --all`, and
|
|
969
|
+
`trash purge --all` are refused; `--all` is read-only or dry-run reporting
|
|
970
|
+
only where supported. Execution is always scoped to a single reviewed plan id.
|
|
971
|
+
- **No fresh-plan-then-execute.** `cleanup --execute` and `dispose --execute`
|
|
972
|
+
refuse to compute a new live set. They act only on plan ids that earlier
|
|
973
|
+
dry-runs produced and a human reviewed; they will not plan and execute in one
|
|
974
|
+
step.
|
|
860
975
|
- **No silent deletion.** Cleanup trashes or flags for review and writes a
|
|
861
976
|
receipt to the ledger. The `cleanup=delete` action stays refused in v1; the
|
|
862
977
|
one sanctioned physical deletion is `artshelf trash purge --execute`, which only
|
|
@@ -893,9 +1008,9 @@ Agents should call `artshelf put` immediately after creating:
|
|
|
893
1008
|
- one-off generated reports
|
|
894
1009
|
- copied files kept for rollback
|
|
895
1010
|
|
|
896
|
-
Agents should not run `artshelf cleanup --execute
|
|
897
|
-
`artshelf trash purge --execute` without
|
|
898
|
-
and reviewed plan id.
|
|
1011
|
+
Agents should not run `artshelf cleanup --execute`,
|
|
1012
|
+
`artshelf dispose --execute`, or `artshelf trash purge --execute` without
|
|
1013
|
+
explicit approval naming the ledger path and reviewed plan id.
|
|
899
1014
|
|
|
900
1015
|
Agents may run `artshelf find` and `artshelf get` before `put` to avoid duplicate
|
|
901
1016
|
registrations. `find`/`get` are read-only ledger queries; they must not be used
|
|
@@ -957,11 +1072,14 @@ then render a compact decision report from it. The packet schema is
|
|
|
957
1072
|
docs/skills carry matching copies for browsable docs and portable agent
|
|
958
1073
|
installs. The report groups decisions into ready-for-approval,
|
|
959
1074
|
needs-review-first, and blocked sections, and must still include exact approval
|
|
960
|
-
targets in the message body.
|
|
1075
|
+
targets in the message body. `dispose --dry-run --agent` already emits its own
|
|
1076
|
+
compact per-record approval packet, so it does not require wrapping in this
|
|
1077
|
+
review-report schema.
|
|
961
1078
|
|
|
962
1079
|
Scheduled jobs must never run `artshelf cleanup --execute`,
|
|
963
|
-
`artshelf ledgers prune --execute`,
|
|
964
|
-
only dry-run and report plans
|
|
1080
|
+
`artshelf ledgers prune --execute`, `artshelf dispose --execute`, or
|
|
1081
|
+
`artshelf trash purge --execute`; they may only dry-run and report plans
|
|
1082
|
+
for later human review.
|
|
965
1083
|
|
|
966
1084
|
## Dogfood Scenarios
|
|
967
1085
|
|
|
@@ -969,7 +1087,9 @@ only dry-run and report plans for later human review.
|
|
|
969
1087
|
2. Record a config backup with manual review retention.
|
|
970
1088
|
3. Generate a dry-run cleanup plan after TTL expiry using fixture data.
|
|
971
1089
|
4. Execute a cleanup plan in a temporary test fixture and verify receipt output.
|
|
972
|
-
5.
|
|
1090
|
+
5. Dispose one reviewed record through dry-run and exact execute approval, then
|
|
1091
|
+
verify the receipt, audit fields, and quiet review state.
|
|
1092
|
+
6. List trashed records, dry-run an old-trash purge, then execute the reviewed
|
|
973
1093
|
purge plan in a fixture and verify receipt output plus resolved ledger state.
|
|
974
1094
|
|
|
975
1095
|
## V1 Acceptance Criteria
|
|
@@ -1007,6 +1127,11 @@ only dry-run and report plans for later human review.
|
|
|
1007
1127
|
- Cleanup execute writes a started receipt before moving files, resumes or
|
|
1008
1128
|
replays the same plan id from receipt/trash evidence, and completes the
|
|
1009
1129
|
receipt idempotently.
|
|
1130
|
+
- CLI can dispose one reviewed record through `artshelf dispose`: `--dry-run`
|
|
1131
|
+
creates or reuses an exact reviewed plan for trash-resolve, resolve-only,
|
|
1132
|
+
snooze, or keep; `--execute` applies one plan id against one ledger, refuses
|
|
1133
|
+
`--all`, stale state, and target conflicts, writes a receipt, and never
|
|
1134
|
+
physically deletes.
|
|
1010
1135
|
- CLI can list trashed records (single ledger or `--all`) and purge them through
|
|
1011
1136
|
an approval-first, ledger-scoped dry-run/execute boundary that writes a purge
|
|
1012
1137
|
receipt; purge refuses `--all` and never deletes without a reviewed plan id.
|
|
@@ -1024,14 +1149,14 @@ only dry-run and report plans for later human review.
|
|
|
1024
1149
|
- Package includes the deterministic `ArtshelfReviewReport` schema, canonical
|
|
1025
1150
|
example, and portable renderer script for agent-rendered review reports.
|
|
1026
1151
|
- All core commands support `--json`.
|
|
1027
|
-
- `review`, `status`, `doctor`,
|
|
1028
|
-
`--agent`, a compact single-line JSON decision
|
|
1029
|
-
precedence over `--json`.
|
|
1152
|
+
- `review`, `status`, `doctor`, `ledgers prune --dry-run`, `dispose --dry-run`,
|
|
1153
|
+
and `get --inspect` also support `--agent`, a compact single-line JSON decision
|
|
1154
|
+
packet for agents that takes precedence over `--json`.
|
|
1030
1155
|
- Tests cover record/list/find/get/status-filter/due/validate/resolve/registry,
|
|
1031
1156
|
`artshelf doctor`, the `artshelf status` dashboard, `--all` review, stale-registry,
|
|
1032
1157
|
dry-run, global-dry-run, execute-plan, cleanup plan-id validation, concurrent
|
|
1033
1158
|
ledger writes, trash list/purge, path provenance validation, registry-prune,
|
|
1034
|
-
and
|
|
1159
|
+
reconcile dry-run/execute, and dispose dry-run/execute behavior.
|
|
1035
1160
|
|
|
1036
1161
|
## Deferred
|
|
1037
1162
|
|
package/dist/src/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { maybeNotifyAvailableUpdate, runCommand } from "./commands/index.js";
|
|
3
3
|
import { VERSION } from "./config/package.js";
|
|
4
4
|
import { formatCliError } from "./shared/errors.js";
|
|
5
|
-
import {
|
|
5
|
+
import { boolFlag, isBooleanFlag, VALUE_FLAGS } from "./shared/flags.js";
|
|
6
6
|
import { renderHelp, resolveHelpKey } from "./shared/help-text.js";
|
|
7
7
|
async function main(argv) {
|
|
8
8
|
try {
|
|
@@ -53,7 +53,7 @@ function parseArgs(argv) {
|
|
|
53
53
|
}
|
|
54
54
|
if (token.startsWith("--")) {
|
|
55
55
|
const name = token.slice(2);
|
|
56
|
-
if (
|
|
56
|
+
if (isBooleanFlag(name, command)) {
|
|
57
57
|
flags.set(name, true);
|
|
58
58
|
continue;
|
|
59
59
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { createDisposePlan, executeDisposePlan } from "../dispose.js";
|
|
2
|
+
import { printCompactJson, printJson } from "../renderers/json.js";
|
|
3
|
+
import { boolFlag, requiredStringFlag, stringFlag } from "../shared/flags.js";
|
|
4
|
+
export function handleDispose(parsed, ledgerPath, json) {
|
|
5
|
+
const dryRun = boolFlag(parsed, "dry-run");
|
|
6
|
+
const execute = boolFlag(parsed, "execute");
|
|
7
|
+
if (dryRun && execute)
|
|
8
|
+
throw new Error("dispose accepts either --dry-run or --execute, not both");
|
|
9
|
+
if (boolFlag(parsed, "all"))
|
|
10
|
+
throw new Error("dispose is scoped to one --ledger and one reviewed plan; --all is not supported");
|
|
11
|
+
if (execute) {
|
|
12
|
+
const planId = requiredStringFlag(parsed, "plan-id");
|
|
13
|
+
const execution = executeDisposePlan(ledgerPath, planId);
|
|
14
|
+
if (json) {
|
|
15
|
+
printJson({ ok: execution.result.status !== "skipped", ledgerPath, execution });
|
|
16
|
+
return execution.result.status === "skipped" ? 1 : 0;
|
|
17
|
+
}
|
|
18
|
+
printDisposeExecution(execution, ledgerPath);
|
|
19
|
+
return execution.result.status === "skipped" ? 1 : 0;
|
|
20
|
+
}
|
|
21
|
+
if (!dryRun)
|
|
22
|
+
throw new Error("dispose requires --dry-run or --execute");
|
|
23
|
+
const id = requiredStringFlag(parsed, "id");
|
|
24
|
+
const action = requiredStringFlag(parsed, "action");
|
|
25
|
+
const plan = createDisposePlan(ledgerPath, {
|
|
26
|
+
id,
|
|
27
|
+
action,
|
|
28
|
+
reason: stringFlag(parsed, "reason"),
|
|
29
|
+
ttl: stringFlag(parsed, "ttl"),
|
|
30
|
+
retainUntil: stringFlag(parsed, "retain-until")
|
|
31
|
+
});
|
|
32
|
+
const approve = disposeApprovalTarget(ledgerPath, plan.planId);
|
|
33
|
+
if (boolFlag(parsed, "agent")) {
|
|
34
|
+
printCompactJson({
|
|
35
|
+
ok: plan.entry !== null,
|
|
36
|
+
command: "dispose",
|
|
37
|
+
ledgerPath,
|
|
38
|
+
id,
|
|
39
|
+
action,
|
|
40
|
+
status: plan.entry ? "ready-for-approval" : "blocked",
|
|
41
|
+
planId: plan.entry ? plan.planId : null,
|
|
42
|
+
approve,
|
|
43
|
+
blocked: plan.blocked
|
|
44
|
+
});
|
|
45
|
+
return plan.entry ? 0 : 1;
|
|
46
|
+
}
|
|
47
|
+
if (json) {
|
|
48
|
+
printJson({ ok: plan.entry !== null, ledgerPath, plan, approve });
|
|
49
|
+
return plan.entry ? 0 : 1;
|
|
50
|
+
}
|
|
51
|
+
printDisposePlan(plan, ledgerPath, approve);
|
|
52
|
+
return plan.entry ? 0 : 1;
|
|
53
|
+
}
|
|
54
|
+
function disposeApprovalTarget(ledgerPath, planId) {
|
|
55
|
+
if (planId === "not-created")
|
|
56
|
+
return null;
|
|
57
|
+
return `approve artshelf dispose ledger ${ledgerPath} plan ${planId}`;
|
|
58
|
+
}
|
|
59
|
+
function printDisposePlan(plan, ledgerPath, approve) {
|
|
60
|
+
if (!plan.entry) {
|
|
61
|
+
process.stdout.write(`dispose plan not-created: blocked ${plan.request.id} ${plan.request.action}\n`);
|
|
62
|
+
if (plan.blocked)
|
|
63
|
+
process.stdout.write(`blocked: ${plan.blocked.reason} — ${plan.blocked.detail}\n`);
|
|
64
|
+
process.stdout.write(`ledger: ${ledgerPath}\n`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
process.stdout.write(`dispose plan ${plan.planId}: ${plan.entry.action} ${plan.entry.id}\n`);
|
|
68
|
+
process.stdout.write(`reason: ${plan.entry.reason}\n`);
|
|
69
|
+
if (plan.entry.targetPath)
|
|
70
|
+
process.stdout.write(`target: ${plan.entry.targetPath}\n`);
|
|
71
|
+
if (plan.entry.retainUntil)
|
|
72
|
+
process.stdout.write(`retain-until: ${plan.entry.retainUntil}\n`);
|
|
73
|
+
process.stdout.write(`plan: ${plan.planPath ?? "not created"}\n`);
|
|
74
|
+
process.stdout.write(`ledger: ${ledgerPath}\n`);
|
|
75
|
+
if (approve)
|
|
76
|
+
process.stdout.write(`approve: ${approve}\n`);
|
|
77
|
+
}
|
|
78
|
+
function printDisposeExecution(execution, ledgerPath) {
|
|
79
|
+
process.stdout.write(`dispose receipt ${execution.planId}: ${execution.result.status} ${execution.result.action} ${execution.result.id}\n`);
|
|
80
|
+
if (execution.result.reason)
|
|
81
|
+
process.stdout.write(`reason: ${execution.result.reason}\n`);
|
|
82
|
+
if (execution.result.targetPath)
|
|
83
|
+
process.stdout.write(`target: ${execution.result.targetPath}\n`);
|
|
84
|
+
if (execution.result.retainUntil)
|
|
85
|
+
process.stdout.write(`retain-until: ${execution.result.retainUntil}\n`);
|
|
86
|
+
process.stdout.write(`receipt: ${execution.receiptPath}\n`);
|
|
87
|
+
process.stdout.write(`ledger: ${ledgerPath}\n`);
|
|
88
|
+
}
|
package/dist/src/commands/get.js
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
|
+
import { buildInspectReport } from "../inspect.js";
|
|
1
2
|
import { getRecord, readLedger } from "../ledger.js";
|
|
2
3
|
import { normalizeRegistryPath } from "../registry.js";
|
|
3
|
-
import {
|
|
4
|
+
import { buildInspectAgentPacket, printInspect } from "../renderers/inspect.js";
|
|
5
|
+
import { printCompactJson, printJson } from "../renderers/json.js";
|
|
4
6
|
import { boolFlag, stringFlag } from "../shared/flags.js";
|
|
5
7
|
import { printRegisteredLedgerValidation, validateRegisteredLedgersOrThrow } from "./shared.js";
|
|
6
8
|
export function handleGet(parsed, ledgerPath, json) {
|
|
7
9
|
const id = parsed.positionals[0];
|
|
8
10
|
if (!id)
|
|
9
11
|
throw new Error("get requires <id>");
|
|
12
|
+
const inspect = boolFlag(parsed, "inspect");
|
|
13
|
+
const agent = boolFlag(parsed, "agent");
|
|
14
|
+
if (agent && !inspect)
|
|
15
|
+
throw new Error("--agent requires --inspect for get");
|
|
10
16
|
if (boolFlag(parsed, "all")) {
|
|
11
17
|
const registryPath = normalizeRegistryPath(stringFlag(parsed, "registry"));
|
|
12
18
|
const validation = validateRegisteredLedgersOrThrow(registryPath);
|
|
13
19
|
if (!validation.ok)
|
|
14
|
-
return printRegisteredLedgerValidation(registryPath, validation.results, json);
|
|
20
|
+
return printRegisteredLedgerValidation(registryPath, validation.results, json || agent);
|
|
15
21
|
for (const { ledger } of validation.results) {
|
|
16
22
|
const record = readLedger(ledger.path).find((entry) => entry.id === id);
|
|
17
23
|
if (record) {
|
|
24
|
+
if (inspect)
|
|
25
|
+
return renderInspect(record, ledger.path, json, agent);
|
|
18
26
|
if (json)
|
|
19
27
|
return printJson({ ok: true, registryPath, ledger, record });
|
|
20
28
|
process.stdout.write(`${record.id} ${record.kind} ${record.status} ${record.cleanup} ${record.path}\nreason: ${record.reason}\nledger: ${ledger.path}\nregistry: ${registryPath}\n`);
|
|
@@ -24,8 +32,22 @@ export function handleGet(parsed, ledgerPath, json) {
|
|
|
24
32
|
throw new Error(`Artshelf record not found: ${id}`);
|
|
25
33
|
}
|
|
26
34
|
const record = getRecord(readLedger(ledgerPath), id);
|
|
35
|
+
if (inspect)
|
|
36
|
+
return renderInspect(record, ledgerPath, json, agent);
|
|
27
37
|
if (json)
|
|
28
38
|
return printJson({ ok: true, ledgerPath, record });
|
|
29
39
|
process.stdout.write(`${record.id} ${record.kind} ${record.status} ${record.cleanup} ${record.path}\nreason: ${record.reason}\nledger: ${ledgerPath}\n`);
|
|
30
40
|
return 0;
|
|
31
41
|
}
|
|
42
|
+
// Read-only inspect surface (NGX-482): builds a deterministic decision report and
|
|
43
|
+
// renders it as a human card, full JSON, or a compact agent packet. --agent wins
|
|
44
|
+
// over --json, matching `review`. Never mutates the ledger or the filesystem.
|
|
45
|
+
function renderInspect(record, ledgerPath, json, agent) {
|
|
46
|
+
const report = buildInspectReport(record, { ledgerPath });
|
|
47
|
+
if (agent)
|
|
48
|
+
return printCompactJson(buildInspectAgentPacket(report, ledgerPath));
|
|
49
|
+
if (json)
|
|
50
|
+
return printJson({ ok: true, ledgerPath, inspect: report });
|
|
51
|
+
printInspect(report, ledgerPath);
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { normalizeLedgerPath } from "../ledger.js";
|
|
2
2
|
import { boolFlag, stringFlag } from "../shared/flags.js";
|
|
3
3
|
import { handleCleanup } from "./cleanup.js";
|
|
4
|
+
import { handleDispose } from "./dispose.js";
|
|
4
5
|
import { handleDoctor } from "./doctor.js";
|
|
5
6
|
import { handleDue } from "./due.js";
|
|
6
7
|
import { handleFind } from "./find.js";
|
|
@@ -44,6 +45,9 @@ export async function runCommand(parsed) {
|
|
|
44
45
|
case "cleanup":
|
|
45
46
|
status = handleCleanup(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
|
|
46
47
|
break;
|
|
48
|
+
case "dispose":
|
|
49
|
+
status = handleDispose(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
|
|
50
|
+
break;
|
|
47
51
|
case "reconcile":
|
|
48
52
|
status = handleReconcile(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
|
|
49
53
|
break;
|