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 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` and `ledgers prune --dry-run`**, a
122
- compact, token-efficient decision packet for agents, while the default render
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`, and `ledgers prune --dry-run`
161
- also take `--agent` for a compact decision packet; `--ledger`, `--registry`, and
162
- `--all` are scope flags only on commands that list them.
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` requires an approved reviewed plan id, how to render
204
- dry-run cleanup and trash purge plans as review-report decision packets, and when
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`, and `ledgers prune --dry-run` expose
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 `review`, the packet sorts records into
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 `trash purge --all`
695
- are refused; execution stays scoped to one explicit ledger or registry and one
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` and
852
- `artshelf trash purge --execute`, each run by a human against a separately
853
- reviewed plan id.
854
- - **No global execute.** `cleanup --execute --all` and `trash purge --all`
855
- are refused; `--all` is read-only or dry-run reporting only. Execution is
856
- always scoped to a single reviewed plan id.
857
- - **No fresh-plan-then-execute.** `cleanup --execute` refuses to compute a new
858
- live set. It acts only on a plan id that an earlier `cleanup --dry-run`
859
- produced and a human reviewed; it will not plan and execute in one step.
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` or
897
- `artshelf trash purge --execute` without explicit approval naming the ledger path
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`, or `artshelf trash purge --execute`; they may
964
- only dry-run and report plans for later human review.
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. List trashed records, dry-run an old-trash purge, then execute the reviewed
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`, and `ledgers prune --dry-run` also support
1028
- `--agent`, a compact single-line JSON decision packet for agents that takes
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 reconcile dry-run/execute behavior.
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 { BOOLEAN_FLAGS, boolFlag, VALUE_FLAGS } from "./shared/flags.js";
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 (BOOLEAN_FLAGS.has(name)) {
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
+ }
@@ -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 { printJson } from "../renderers/json.js";
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;