artshelf 0.14.0 → 0.15.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 +13 -0
- package/README.md +8 -6
- package/SPEC.md +54 -7
- package/dist/src/cli.js +2 -2
- package/dist/src/commands/get.js +24 -2
- package/dist/src/inspect.js +204 -0
- package/dist/src/renderers/inspect.js +108 -0
- package/dist/src/shared/flags.js +4 -0
- package/dist/src/shared/help-text.js +16 -0
- package/dist/src/shared/shell-quote.js +6 -0
- package/docs/agent-review.html +35 -0
- package/docs/agent-usage.html +4 -2
- package/docs/agent-usage.md +8 -5
- package/docs/reference.html +16 -6
- package/package.json +1 -1
- package/skills/artshelf/SKILL.md +7 -4
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,15 @@
|
|
|
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.15.0](https://github.com/calvinnwq/artshelf/compare/v0.14.0...v0.15.0) (2026-06-20)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
### Features
|
|
143
|
+
|
|
144
|
+
* **inspect:** add read-only Artshelf review cards ([08478a8](https://github.com/calvinnwq/artshelf/commit/08478a871f624b048173a986511cc57cf2a2e609))
|
|
145
|
+
* **inspect:** add read-only inspect decision model ([334ea7d](https://github.com/calvinnwq/artshelf/commit/334ea7de99326ed0b0c3129228beaa0d33f378b9))
|
|
146
|
+
* **inspect:** wire get inspect output modes ([4a7270d](https://github.com/calvinnwq/artshelf/commit/4a7270df29f1895efe9032b2cfb7a2c1d47e971f))
|
|
147
|
+
|
|
135
148
|
## [0.14.0](https://github.com/calvinnwq/artshelf/compare/v0.13.1...v0.14.0) (2026-06-19)
|
|
136
149
|
|
|
137
150
|
|
package/README.md
CHANGED
|
@@ -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`, and
|
|
122
|
+
`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]
|
|
@@ -157,9 +159,9 @@ Use `artshelf help` for a grouped command list, then `artshelf <command> --help`
|
|
|
157
159
|
or `artshelf help <command>` for focused details. Nested commands such as
|
|
158
160
|
`artshelf trash purge --help`, `artshelf ledgers add --help`, and
|
|
159
161
|
`artshelf ledgers prune --help` show only that subcommand. All core commands
|
|
160
|
-
support `--json`; `review`, `status`, `doctor`,
|
|
161
|
-
also take `--agent` for a compact decision packet; `--ledger`,
|
|
162
|
-
`--all` are scope flags only on commands that list them.
|
|
162
|
+
support `--json`; `review`, `status`, `doctor`, `ledgers prune --dry-run`, and
|
|
163
|
+
`get --inspect` also take `--agent` for a compact decision packet; `--ledger`,
|
|
164
|
+
`--registry`, and `--all` are scope flags only on commands that list them.
|
|
163
165
|
</details>
|
|
164
166
|
|
|
165
167
|
<details>
|
package/SPEC.md
CHANGED
|
@@ -199,10 +199,56 @@ 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 cleanup.
|
|
222
|
+
- `resolve-only` — the recorded path is gone; resolve the record (ledger-only)
|
|
223
|
+
rather than cleaning a file.
|
|
224
|
+
- `blocked` — needs a human decision first: `cleanup=delete` (refused at
|
|
225
|
+
execute), a review-required flag, or a prior cleanup refusal.
|
|
226
|
+
|
|
227
|
+
File-content previews are intentionally outside Artshelf core; an acting agent or
|
|
228
|
+
host runtime may inspect file contents separately when appropriate.
|
|
229
|
+
`--inspect --json` returns `{ inspect: <report> }`; `--inspect --agent` returns a compact single-line
|
|
230
|
+
decision packet with a read-only safety block, the next-safe action, and a
|
|
231
|
+
reproducer command, and takes precedence over `--json`. Both shapes are
|
|
232
|
+
deterministic so portable agent skills can act without re-deriving anything.
|
|
233
|
+
|
|
234
|
+
Example pattern — the dogfooding case that motivated this surface was an old
|
|
235
|
+
rollback `backup` registered with `cleanup=review` (the kind of stale record
|
|
236
|
+
`ledgers prune` and `review --all` surface). Inspect it before deciding, using
|
|
237
|
+
the record id and ledger path rather than hardcoding either:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
artshelf get <id> --inspect --ledger <ledger-path>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
```text
|
|
244
|
+
✓ <id> [backup] — keep
|
|
245
|
+
path: <backup-path>
|
|
246
|
+
status: active · cleanup: review · owner: agent · labels: registry-prune
|
|
247
|
+
existence: present (directory, 49 B) · age: 14d · retention: manual-review · due: manual-review
|
|
248
|
+
reason: rollback backup before registry prune
|
|
249
|
+
next: Held for manual review — keep it, change its retention, or resolve it explicitly; no cleanup is scheduled.
|
|
250
|
+
ledger: <ledger-path>
|
|
251
|
+
```
|
|
206
252
|
|
|
207
253
|
### `artshelf due`
|
|
208
254
|
|
|
@@ -279,12 +325,13 @@ registry-prune dry-run for missing registered ledgers, dry-run reconcile for
|
|
|
279
325
|
missing-path or reconcile drift, or nothing to do). Review never writes a plan,
|
|
280
326
|
so the next action always points at an explicit follow-up command.
|
|
281
327
|
|
|
282
|
-
`review`, `status`, `doctor`,
|
|
283
|
-
agent-oriented render modes. For review/status/doctor, the default human render
|
|
328
|
+
`review`, `status`, `doctor`, `ledgers prune --dry-run`, and `get --inspect`
|
|
329
|
+
expose agent-oriented render modes. For review/status/doctor, the default human render
|
|
284
330
|
leads each ledger and summary line with a `✓`/`⚠` attention glyph. `--json` stays
|
|
285
331
|
the full, backward-compatible public audit report; and `--agent` emits a compact,
|
|
286
332
|
deterministic single-line JSON decision packet for agents, taking precedence over
|
|
287
|
-
`--json` when both are passed. For `
|
|
333
|
+
`--json` when both are passed. For `get --inspect`, `--agent` returns the
|
|
334
|
+
per-record decision packet and requires `--inspect`. For `review`, the packet sorts records into
|
|
288
335
|
ready-for-approval, needs-review-first, and blocked groups. Because review is
|
|
289
336
|
read-only and never mints a cleanup or registry-prune plan, the exact approval
|
|
290
337
|
targets it emits are `resolve missing` and `reconcile`; the `reconcile` target
|
|
@@ -1024,9 +1071,9 @@ only dry-run and report plans for later human review.
|
|
|
1024
1071
|
- Package includes the deterministic `ArtshelfReviewReport` schema, canonical
|
|
1025
1072
|
example, and portable renderer script for agent-rendered review reports.
|
|
1026
1073
|
- All core commands support `--json`.
|
|
1027
|
-
- `review`, `status`, `doctor`,
|
|
1028
|
-
`--agent`, a compact single-line JSON decision packet for agents
|
|
1029
|
-
precedence over `--json`.
|
|
1074
|
+
- `review`, `status`, `doctor`, `ledgers prune --dry-run`, and `get --inspect`
|
|
1075
|
+
also support `--agent`, a compact single-line JSON decision packet for agents
|
|
1076
|
+
that takes precedence over `--json`.
|
|
1030
1077
|
- Tests cover record/list/find/get/status-filter/due/validate/resolve/registry,
|
|
1031
1078
|
`artshelf doctor`, the `artshelf status` dashboard, `--all` review, stale-registry,
|
|
1032
1079
|
dry-run, global-dry-run, execute-plan, cleanup plan-id validation, concurrent
|
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
|
}
|
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
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { existsSync, lstatSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { shellArg } from "./shared/shell-quote.js";
|
|
5
|
+
import { ageOf, now as currentTime } from "./time.js";
|
|
6
|
+
const DIRECTORY_SIZE_MAX_ENTRIES = 10_000;
|
|
7
|
+
const openDirectorySync = fs.opendirSync;
|
|
8
|
+
export function buildInspectReport(record, options) {
|
|
9
|
+
const at = options.now ?? currentTime();
|
|
10
|
+
const subjectPath = subjectPathOf(record);
|
|
11
|
+
const node = describeNode(subjectPath, record.status === "active");
|
|
12
|
+
const dueState = classifyDueState(record, at, node.existence);
|
|
13
|
+
const { recommendation, nextAction } = recommend(record, node.existence, dueState, options.ledgerPath);
|
|
14
|
+
return {
|
|
15
|
+
schemaVersion: 1,
|
|
16
|
+
id: record.id,
|
|
17
|
+
path: record.path,
|
|
18
|
+
subjectPath,
|
|
19
|
+
kind: record.kind,
|
|
20
|
+
owner: record.owner,
|
|
21
|
+
labels: record.labels,
|
|
22
|
+
status: record.status,
|
|
23
|
+
cleanup: record.cleanup,
|
|
24
|
+
reason: record.reason,
|
|
25
|
+
existence: node.existence,
|
|
26
|
+
nodeKind: node.nodeKind,
|
|
27
|
+
byteSize: node.byteSize,
|
|
28
|
+
byteSizeTruncated: node.byteSizeTruncated,
|
|
29
|
+
age: ageOf(at, record.createdAt),
|
|
30
|
+
retention: record.retention,
|
|
31
|
+
retainUntil: record.retainUntil ?? null,
|
|
32
|
+
dueState,
|
|
33
|
+
recommendation,
|
|
34
|
+
nextAction
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function subjectPathOf(record) {
|
|
38
|
+
if (record.status === "trashed" && record.targetPath)
|
|
39
|
+
return record.targetPath;
|
|
40
|
+
return record.path;
|
|
41
|
+
}
|
|
42
|
+
function describeNode(subjectPath, followSymlinkExistence = false) {
|
|
43
|
+
if (followSymlinkExistence && !existsSync(subjectPath)) {
|
|
44
|
+
return { existence: "missing", nodeKind: null, byteSize: null, byteSizeTruncated: false };
|
|
45
|
+
}
|
|
46
|
+
let stat;
|
|
47
|
+
try {
|
|
48
|
+
stat = lstatSync(subjectPath);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return { existence: "missing", nodeKind: null, byteSize: null, byteSizeTruncated: false };
|
|
52
|
+
}
|
|
53
|
+
if (stat.isSymbolicLink())
|
|
54
|
+
return { existence: "present", nodeKind: "other", byteSize: null, byteSizeTruncated: false };
|
|
55
|
+
if (stat.isFile())
|
|
56
|
+
return { existence: "present", nodeKind: "file", byteSize: stat.size, byteSizeTruncated: false };
|
|
57
|
+
if (stat.isDirectory()) {
|
|
58
|
+
const size = directorySize(subjectPath);
|
|
59
|
+
return { existence: "present", nodeKind: "directory", byteSize: size.bytes, byteSizeTruncated: size.truncated };
|
|
60
|
+
}
|
|
61
|
+
return { existence: "present", nodeKind: "other", byteSize: null, byteSizeTruncated: false };
|
|
62
|
+
}
|
|
63
|
+
// Bounded recursive size: sums regular-file bytes, never follows symlinks, and stops at
|
|
64
|
+
// a fixed entry budget so a pathological tree cannot stall a read-only inspect.
|
|
65
|
+
function directorySize(root) {
|
|
66
|
+
let total = 0;
|
|
67
|
+
let visited = 0;
|
|
68
|
+
let incomplete = false;
|
|
69
|
+
const stack = [root];
|
|
70
|
+
while (stack.length > 0) {
|
|
71
|
+
const dir = stack.pop();
|
|
72
|
+
if (dir === undefined)
|
|
73
|
+
break;
|
|
74
|
+
let entries;
|
|
75
|
+
try {
|
|
76
|
+
entries = openDirectorySync(dir);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
incomplete = true;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
while (true) {
|
|
84
|
+
let entry;
|
|
85
|
+
try {
|
|
86
|
+
entry = entries.readSync();
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
incomplete = true;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
if (entry === null)
|
|
93
|
+
break;
|
|
94
|
+
if (visited >= DIRECTORY_SIZE_MAX_ENTRIES)
|
|
95
|
+
return { bytes: total, truncated: true };
|
|
96
|
+
visited += 1;
|
|
97
|
+
const child = join(dir, entry.name);
|
|
98
|
+
let stat;
|
|
99
|
+
try {
|
|
100
|
+
stat = lstatSync(child);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
incomplete = true;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (stat.isSymbolicLink())
|
|
107
|
+
continue;
|
|
108
|
+
if (stat.isDirectory()) {
|
|
109
|
+
stack.push(child);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (stat.isFile())
|
|
113
|
+
total += stat.size;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
try {
|
|
118
|
+
entries.closeSync();
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
incomplete = true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return { bytes: total, truncated: incomplete };
|
|
126
|
+
}
|
|
127
|
+
// Mirrors ledger due classification, but reuses the existence already observed so a
|
|
128
|
+
// trashed/terminal record never re-stats. Only active records carry a due state.
|
|
129
|
+
function classifyDueState(record, at, existence) {
|
|
130
|
+
if (record.status !== "active")
|
|
131
|
+
return null;
|
|
132
|
+
if (existence === "missing")
|
|
133
|
+
return "missing-path";
|
|
134
|
+
if (record.retention.mode === "manual-review")
|
|
135
|
+
return "manual-review";
|
|
136
|
+
if (!record.retainUntil)
|
|
137
|
+
return "due";
|
|
138
|
+
return new Date(record.retainUntil).getTime() <= at.getTime() ? "due" : "kept";
|
|
139
|
+
}
|
|
140
|
+
function recommend(record, existence, dueState, ledgerPath) {
|
|
141
|
+
const idArg = shellArg(record.id);
|
|
142
|
+
const ledgerArg = shellArg(ledgerPath);
|
|
143
|
+
if (record.status === "resolved") {
|
|
144
|
+
return { recommendation: "keep", nextAction: "Already resolved — no action needed." };
|
|
145
|
+
}
|
|
146
|
+
if (record.status === "trashed") {
|
|
147
|
+
if (existence === "missing") {
|
|
148
|
+
return {
|
|
149
|
+
recommendation: "resolve-only",
|
|
150
|
+
nextAction: `Trashed target is missing — confirm the artifact is gone, then run \`artshelf resolve ${idArg} --ledger ${ledgerArg} --status resolved --reason '<why>'\` (ledger-only).`
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
recommendation: "keep",
|
|
155
|
+
nextAction: "Already trashed — permanent removal is the separate approval-gated `artshelf trash purge` flow."
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
if (record.status === "review-required") {
|
|
159
|
+
return {
|
|
160
|
+
recommendation: "blocked",
|
|
161
|
+
nextAction: "A cleanup run flagged this for manual review — inspect the artifact, then resolve or re-plan deliberately."
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (record.status === "cleanup-refused") {
|
|
165
|
+
return {
|
|
166
|
+
recommendation: "blocked",
|
|
167
|
+
nextAction: "A prior cleanup refused this record — handle it manually; Artshelf will not retry automatically."
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (existence === "missing" || dueState === "missing-path") {
|
|
171
|
+
return {
|
|
172
|
+
recommendation: "resolve-only",
|
|
173
|
+
nextAction: `Path is missing — confirm the artifact is gone, then run \`artshelf resolve ${idArg} --ledger ${ledgerArg} --status resolved --reason '<why>'\` (ledger-only).`
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
if (dueState === "kept") {
|
|
177
|
+
return {
|
|
178
|
+
recommendation: "snooze",
|
|
179
|
+
nextAction: `Retention holds until ${record.retainUntil ?? "the configured date"} — re-inspect after it expires; nothing is due now.`
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if (dueState === "manual-review") {
|
|
183
|
+
return {
|
|
184
|
+
recommendation: "keep",
|
|
185
|
+
nextAction: "Held for manual review — keep it, change its retention, or resolve it explicitly; no cleanup is scheduled."
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (record.cleanup === "trash") {
|
|
189
|
+
return {
|
|
190
|
+
recommendation: "trash-safe",
|
|
191
|
+
nextAction: `Due and disposable — run \`artshelf cleanup --dry-run --ledger ${ledgerArg}\`, then approve the reviewed plan id.`
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
if (record.cleanup === "delete") {
|
|
195
|
+
return {
|
|
196
|
+
recommendation: "blocked",
|
|
197
|
+
nextAction: "Due with cleanup=delete, which Artshelf refuses — switch it to cleanup=trash and plan a cleanup, or resolve it manually."
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
recommendation: "keep",
|
|
202
|
+
nextAction: "Due and held for review — keep it, change its retention, resolve it, or switch cleanup to trash and plan a cleanup."
|
|
203
|
+
};
|
|
204
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { shellArg } from "../shared/shell-quote.js";
|
|
2
|
+
import { attentionGlyph } from "./attention.js";
|
|
3
|
+
// Buckets whose next-safe action is something other than "leave it alone" get the
|
|
4
|
+
// attention glyph so a scanned card reads as a decision, not just a status line.
|
|
5
|
+
const ATTENTION_RECOMMENDATIONS = new Set([
|
|
6
|
+
"trash-safe",
|
|
7
|
+
"resolve-only",
|
|
8
|
+
"blocked"
|
|
9
|
+
]);
|
|
10
|
+
export function buildInspectAgentPacket(report, ledgerPath) {
|
|
11
|
+
return {
|
|
12
|
+
schemaVersion: 1,
|
|
13
|
+
command: "get",
|
|
14
|
+
mode: "inspect",
|
|
15
|
+
ledgerPath,
|
|
16
|
+
inspect: report,
|
|
17
|
+
safety: {
|
|
18
|
+
readOnly: true,
|
|
19
|
+
noFileMoves: true,
|
|
20
|
+
noLedgerMutation: true
|
|
21
|
+
},
|
|
22
|
+
nextAction: report.nextAction,
|
|
23
|
+
verification: `artshelf get ${shellArg(report.id)} --inspect --agent --ledger ${shellArg(ledgerPath)}`
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function printInspect(report, ledgerPath) {
|
|
27
|
+
const glyph = attentionGlyph(ATTENTION_RECOMMENDATIONS.has(report.recommendation));
|
|
28
|
+
const labels = report.labels.length > 0 ? report.labels.map(sanitizeHumanLine).join(", ") : "none";
|
|
29
|
+
const lines = [];
|
|
30
|
+
lines.push(`${glyph} ${sanitizeHumanLine(report.id)} [${sanitizeHumanLine(report.kind)}] — ${sanitizeHumanLine(report.recommendation)}`);
|
|
31
|
+
lines.push(`path: ${sanitizeHumanLine(report.path)}`);
|
|
32
|
+
// Trashed records point `path` at the now-empty original; existence and size
|
|
33
|
+
// describe the trash target, so name it explicitly when the two differ.
|
|
34
|
+
if (report.subjectPath !== report.path)
|
|
35
|
+
lines.push(`trash target: ${sanitizeHumanLine(report.subjectPath)}`);
|
|
36
|
+
lines.push(`status: ${sanitizeHumanLine(report.status)} · cleanup: ${sanitizeHumanLine(report.cleanup)} · owner: ${sanitizeHumanLine(report.owner)} · labels: ${labels}`);
|
|
37
|
+
lines.push(`existence: ${sanitizeHumanLine(formatExistence(report))} · age: ${sanitizeHumanLine(report.age)} · retention: ${sanitizeHumanLine(formatRetention(report.retention))} · due: ${sanitizeHumanLine(report.dueState ?? "n/a")}`);
|
|
38
|
+
lines.push(`reason: ${sanitizeHumanLine(report.reason)}`);
|
|
39
|
+
lines.push(`next: ${sanitizeHumanLine(report.nextAction)}`);
|
|
40
|
+
lines.push(`ledger: ${sanitizeHumanLine(ledgerPath)}`);
|
|
41
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
42
|
+
}
|
|
43
|
+
function formatExistence(report) {
|
|
44
|
+
if (report.existence === "missing")
|
|
45
|
+
return "missing";
|
|
46
|
+
if (report.nodeKind === "file" || report.nodeKind === "directory") {
|
|
47
|
+
const size = report.byteSize === null ? "size unavailable" : formatBytes(report.byteSize);
|
|
48
|
+
if (report.nodeKind === "directory" && report.byteSizeTruncated) {
|
|
49
|
+
return `present (directory, at least ${size}; scan capped)`;
|
|
50
|
+
}
|
|
51
|
+
return `present (${report.nodeKind}, ${size})`;
|
|
52
|
+
}
|
|
53
|
+
return "present (other)";
|
|
54
|
+
}
|
|
55
|
+
function formatRetention(retention) {
|
|
56
|
+
if (retention.mode === "ttl")
|
|
57
|
+
return `ttl ${retention.ttl}`;
|
|
58
|
+
if (retention.mode === "retain-until")
|
|
59
|
+
return `retain-until ${retention.retainUntil}`;
|
|
60
|
+
return "manual-review";
|
|
61
|
+
}
|
|
62
|
+
function formatBytes(bytes) {
|
|
63
|
+
if (bytes < 1024)
|
|
64
|
+
return `${bytes} B`;
|
|
65
|
+
if (bytes < 1024 * 1024)
|
|
66
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
67
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
68
|
+
}
|
|
69
|
+
function sanitizeHumanLine(value) {
|
|
70
|
+
let out = "";
|
|
71
|
+
for (const char of value) {
|
|
72
|
+
const code = char.codePointAt(0);
|
|
73
|
+
if (code === undefined)
|
|
74
|
+
continue;
|
|
75
|
+
if (char === "\\") {
|
|
76
|
+
out += "\\\\";
|
|
77
|
+
}
|
|
78
|
+
else if (char === "\n") {
|
|
79
|
+
out += "\\n";
|
|
80
|
+
}
|
|
81
|
+
else if (char === "\r") {
|
|
82
|
+
out += "\\r";
|
|
83
|
+
}
|
|
84
|
+
else if (char === "\t") {
|
|
85
|
+
out += "\\t";
|
|
86
|
+
}
|
|
87
|
+
else if (code < 32 || (code >= 0x7f && code <= 0x9f) || isUnicodeFormatControl(code)) {
|
|
88
|
+
out += `\\u${code.toString(16).padStart(4, "0")}`;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
out += char;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
function isUnicodeFormatControl(code) {
|
|
97
|
+
if (code === 0x00ad || code === 0x061c)
|
|
98
|
+
return true;
|
|
99
|
+
if (code >= 0x200b && code <= 0x200f)
|
|
100
|
+
return true;
|
|
101
|
+
if (code >= 0x202a && code <= 0x202e)
|
|
102
|
+
return true;
|
|
103
|
+
if (code >= 0x2060 && code <= 0x206f)
|
|
104
|
+
return true;
|
|
105
|
+
if (code >= 0xfe00 && code <= 0xfe0f)
|
|
106
|
+
return true;
|
|
107
|
+
return code >= 0xfff9 && code <= 0xfffb;
|
|
108
|
+
}
|
package/dist/src/shared/flags.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const BOOLEAN_FLAGS = new Set(["all", "json", "agent", "manual-review", "dry-run", "execute", "help", "version", "plain"]);
|
|
2
|
+
const COMMAND_BOOLEAN_FLAGS = new Map([["get", new Set(["inspect"])]]);
|
|
2
3
|
export const VALUE_FLAGS = new Set([
|
|
3
4
|
"cleanup",
|
|
4
5
|
"kind",
|
|
@@ -16,6 +17,9 @@ export const VALUE_FLAGS = new Set([
|
|
|
16
17
|
"status",
|
|
17
18
|
"ttl"
|
|
18
19
|
]);
|
|
20
|
+
export function isBooleanFlag(name, command) {
|
|
21
|
+
return BOOLEAN_FLAGS.has(name) || (command !== undefined && (COMMAND_BOOLEAN_FLAGS.get(command)?.has(name) ?? false));
|
|
22
|
+
}
|
|
19
23
|
export function requiredStringFlag(parsed, name) {
|
|
20
24
|
const value = stringFlag(parsed, name);
|
|
21
25
|
if (!value)
|
|
@@ -170,8 +170,24 @@ Find is read-only. Use it before put when an integration needs idempotent artifa
|
|
|
170
170
|
return `Usage:
|
|
171
171
|
artshelf get <id> [--ledger <path>] [--json]
|
|
172
172
|
artshelf get <id> --all [--registry <path>] [--json]
|
|
173
|
+
artshelf get <id> --inspect [--ledger <path>] [--json|--agent]
|
|
174
|
+
artshelf get <id> --inspect --all [--registry <path>] [--json|--agent]
|
|
173
175
|
|
|
174
176
|
Get is read-only and returns one ledger record by Artshelf id.
|
|
177
|
+
|
|
178
|
+
--inspect adds a read-only review decision card for one record: existence,
|
|
179
|
+
size, age, retention/due state, a recommendation bucket (keep, snooze,
|
|
180
|
+
trash-safe, resolve-only, blocked), and the exact next-safe action. It never
|
|
181
|
+
moves files or touches the ledger. It does not read or preview arbitrary
|
|
182
|
+
file contents; agents can inspect contents separately when appropriate.
|
|
183
|
+
With --all, the registry is only used to find the id; the card reports the
|
|
184
|
+
concrete ledger that owns the record.
|
|
185
|
+
|
|
186
|
+
Render modes:
|
|
187
|
+
(default) Human record line, or a decision card with --inspect.
|
|
188
|
+
--json Full read-only report (record, or { inspect } with --inspect).
|
|
189
|
+
--agent Compact single-line JSON decision packet (requires --inspect);
|
|
190
|
+
takes precedence over --json.
|
|
175
191
|
`;
|
|
176
192
|
}
|
|
177
193
|
if (command === "resolve") {
|
package/docs/agent-review.html
CHANGED
|
@@ -65,6 +65,41 @@
|
|
|
65
65
|
</p>
|
|
66
66
|
</section>
|
|
67
67
|
|
|
68
|
+
<section>
|
|
69
|
+
<h2>Inspect a single record</h2>
|
|
70
|
+
<p>
|
|
71
|
+
When a review flags one record — most often a stale
|
|
72
|
+
<code>cleanup=review</code> backup that <code>ledgers prune</code> and
|
|
73
|
+
<code>review --all</code> surface — drill into it with
|
|
74
|
+
<code>get <id> --inspect</code> instead of running <code>get</code>,
|
|
75
|
+
<code>ls</code>, and <code>du</code> by hand. It is read-only: it never
|
|
76
|
+
moves files or mutates the ledger.
|
|
77
|
+
</p>
|
|
78
|
+
<pre><code><span class="c"># review one record as a decision card</span>
|
|
79
|
+
artshelf get <id> --inspect --ledger <ledger-path>
|
|
80
|
+
|
|
81
|
+
<span class="c"># compact, deterministic packet for an acting agent</span>
|
|
82
|
+
artshelf get <id> --inspect --agent --ledger <ledger-path></code></pre>
|
|
83
|
+
<p>
|
|
84
|
+
Each card reports existence, size, age, retention/due state, cleanup
|
|
85
|
+
mode, and reason, then ends with a recommendation bucket and the exact
|
|
86
|
+
next-safe action. It does not read or preview arbitrary file contents:
|
|
87
|
+
<code>keep</code>, <code>snooze</code>, <code>trash-safe</code>,
|
|
88
|
+
<code>resolve-only</code>, or <code>blocked</code>. These map onto the
|
|
89
|
+
daily-review classifications — a missing-path record reads as
|
|
90
|
+
<code>resolve-only</code> and points at <code>artshelf resolve</code>,
|
|
91
|
+
while a due disposable record reads as <code>trash-safe</code> and points
|
|
92
|
+
at a reviewed cleanup plan.
|
|
93
|
+
</p>
|
|
94
|
+
<pre><code>✓ <id> [backup] — keep
|
|
95
|
+
path: <backup-path>
|
|
96
|
+
status: active · cleanup: review · owner: agent · labels: registry-prune
|
|
97
|
+
existence: present (directory, 49 B) · age: 14d · retention: manual-review · due: manual-review
|
|
98
|
+
reason: rollback backup before registry prune
|
|
99
|
+
next: Held for manual review — keep it, change its retention, or resolve it explicitly; no cleanup is scheduled.
|
|
100
|
+
ledger: <ledger-path></code></pre>
|
|
101
|
+
</section>
|
|
102
|
+
|
|
68
103
|
<section>
|
|
69
104
|
<h2>Review plan report schema</h2>
|
|
70
105
|
<p>For richer host cards, attachments, or audit packets, construct an <code>ArtshelfReviewReport</code> JSON packet first, then render a compact decision card.</p>
|
package/docs/agent-usage.html
CHANGED
|
@@ -90,13 +90,15 @@
|
|
|
90
90
|
<section>
|
|
91
91
|
<h2>Render modes</h2>
|
|
92
92
|
<p>
|
|
93
|
-
<code>review</code>, <code>status</code>, <code>doctor</code>,
|
|
93
|
+
<code>review</code>, <code>status</code>, <code>doctor</code>, <code>ledgers prune --dry-run</code>, and per-record
|
|
94
|
+
<code>get --inspect</code> share agent-oriented render modes
|
|
94
95
|
so the same data fits both people and agents. Reach for <code>--agent</code> when an agent
|
|
95
96
|
decides and acts; reach for <code>--json</code> for full record, plan, or health detail.
|
|
97
|
+
On <code>get</code>, <code>--agent</code> requires <code>--inspect</code>.
|
|
96
98
|
</p>
|
|
97
99
|
<dl class="def-rows">
|
|
98
100
|
<div><dt>default</dt><dd>human render: scannable grouped counts, attention states, and a short next action for a person at the terminal</dd></div>
|
|
99
|
-
<div><dt>--agent</dt><dd>a deterministic, token-efficient decision packet (single-line compact JSON) with health, counts, classifications, blockers, approval targets where applicable, and a verification command</dd></div>
|
|
101
|
+
<div><dt>--agent</dt><dd>a deterministic, token-efficient decision packet (single-line compact JSON) with health, counts, classifications, blockers, record recommendations, approval targets where applicable, and a verification command</dd></div>
|
|
100
102
|
<div><dt>--json</dt><dd>the backward-compatible public audit contract: complete machine-readable JSON for debugging and integrations</dd></div>
|
|
101
103
|
</dl>
|
|
102
104
|
</section>
|
package/docs/agent-usage.md
CHANGED
|
@@ -53,20 +53,23 @@ The browsable docs split the workflow into focused child pages:
|
|
|
53
53
|
|
|
54
54
|
## Render modes
|
|
55
55
|
|
|
56
|
-
`review`, `status`, `doctor`,
|
|
57
|
-
|
|
56
|
+
`review`, `status`, `doctor`, `ledgers prune --dry-run`, and per-record
|
|
57
|
+
`get --inspect` share agent-oriented render modes so the same data fits both
|
|
58
|
+
people and agents:
|
|
58
59
|
|
|
59
60
|
- **default**: a human render — scannable grouped counts, attention states, and a
|
|
60
61
|
short next action for a person at the terminal.
|
|
61
62
|
- **`--agent`**: a deterministic, token-efficient decision packet (single-line
|
|
62
|
-
compact JSON) with health, counts, classifications, blockers,
|
|
63
|
-
|
|
63
|
+
compact JSON) with health, counts, classifications, blockers, record
|
|
64
|
+
recommendations, approval targets where applicable, and a verification
|
|
65
|
+
command. Use it when an agent acts on the result.
|
|
64
66
|
- **`--json`**: the backward-compatible public audit contract — complete
|
|
65
67
|
machine-readable JSON for debugging and integrations.
|
|
66
68
|
|
|
67
69
|
Reach for `--agent` when an agent needs to decide and act cheaply; reach for
|
|
68
70
|
`--json` when you want the full record, plan, or health detail for audit or
|
|
69
|
-
debugging. `--agent` takes precedence if both flags are passed
|
|
71
|
+
debugging. `--agent` takes precedence if both flags are passed; on `get`, it
|
|
72
|
+
requires `--inspect`.
|
|
70
73
|
|
|
71
74
|
## Portable Skill
|
|
72
75
|
|
package/docs/reference.html
CHANGED
|
@@ -117,12 +117,22 @@ artshelf list [--all] [--status active|review-required|trashed|cleanup-refused|r
|
|
|
117
117
|
artshelf find --path <path> [--owner <o>] [--label <l>] [--json]
|
|
118
118
|
|
|
119
119
|
<span class="c"># fetch one record by id</span>
|
|
120
|
-
artshelf get <id> [--all] [--json]
|
|
120
|
+
artshelf get <id> [--all] [--json]
|
|
121
|
+
|
|
122
|
+
<span class="c"># review one record as a decision card (read-only, no mutation)</span>
|
|
123
|
+
artshelf get <id> --inspect [--ledger <path>] [--json|--agent]
|
|
124
|
+
artshelf get <id> --inspect --all [--registry <path>] [--json|--agent]</code></pre>
|
|
121
125
|
<p>
|
|
122
126
|
<code>list</code> shows ledger entries and current status. <code>find</code> looks up
|
|
123
127
|
existing records by path, owner, labels, and status. Use it before <code>put</code>
|
|
124
128
|
for idempotent registration; it requires at least one selector and never mutates records.
|
|
125
|
-
<code>get</code> is the audit lookup for one record by Artshelf id
|
|
129
|
+
<code>get</code> is the audit lookup for one record by Artshelf id;
|
|
130
|
+
<code>--inspect</code> renders it as a review decision card — existence, size, age,
|
|
131
|
+
retention/due state, and a recommendation bucket (<code>keep</code>, <code>snooze</code>, <code>trash-safe</code>,
|
|
132
|
+
<code>resolve-only</code>, or <code>blocked</code>) with the exact next-safe action,
|
|
133
|
+
without moving files, touching the ledger, or previewing arbitrary file contents.
|
|
134
|
+
Combine <code>--inspect --all</code> to find the id through the registry while the card
|
|
135
|
+
reports the concrete ledger that owns the record.
|
|
126
136
|
</p>
|
|
127
137
|
</section>
|
|
128
138
|
|
|
@@ -276,9 +286,9 @@ artshelf reconcile --execute --plan-id <id> --ledger <path> [--json]
|
|
|
276
286
|
output stays clean.
|
|
277
287
|
</p>
|
|
278
288
|
<p>
|
|
279
|
-
<code>review</code>, <code>status</code>, <code>doctor</code>,
|
|
289
|
+
<code>review</code>, <code>status</code>, <code>doctor</code>, <code>ledgers prune --dry-run</code>, and <code>get --inspect</code> add <code>--agent</code>:
|
|
280
290
|
a deterministic, token-efficient decision packet emitted as a single line of compact JSON.
|
|
281
|
-
It names the relevant health
|
|
291
|
+
It names the relevant health, prune counts, or record recommendation, classifications, blockers, approval targets where applicable, and the command
|
|
282
292
|
to re-run for verification, so an agent can act without parsing decorative output.
|
|
283
293
|
</p>
|
|
284
294
|
<p>
|
|
@@ -289,8 +299,8 @@ artshelf reconcile --execute --plan-id <id> --ledger <path> [--json]
|
|
|
289
299
|
</p>
|
|
290
300
|
<table class="opts">
|
|
291
301
|
<tr><th>option</th><th>meaning</th></tr>
|
|
292
|
-
<tr><td>(default)</td><td>human render: grouped counts, ✓/⚠ attention glyphs, and a short next action</td></tr>
|
|
293
|
-
<tr><td>--agent</td><td>token-efficient decision packet for agents on <code>review</code>, <code>status</code>, <code>doctor</code>,
|
|
302
|
+
<tr><td>(default)</td><td>human render: grouped counts or a record card, ✓/⚠ attention glyphs, and a short next action</td></tr>
|
|
303
|
+
<tr><td>--agent</td><td>token-efficient decision packet for agents on <code>review</code>, <code>status</code>, <code>doctor</code>, <code>ledgers prune --dry-run</code>, and <code>get --inspect</code></td></tr>
|
|
294
304
|
<tr><td>--json</td><td>full machine-readable audit JSON on commands that return data</td></tr>
|
|
295
305
|
</table>
|
|
296
306
|
</section>
|
package/package.json
CHANGED
package/skills/artshelf/SKILL.md
CHANGED
|
@@ -96,9 +96,9 @@ artshelf trash list --all --json
|
|
|
96
96
|
|
|
97
97
|
`artshelf ledgers list --json` reports per-ledger validation status. `--plain`
|
|
98
98
|
skips validation. `--all` is for discovery and review, not mutation permission.
|
|
99
|
-
Use `--agent` on `review`, `status`, `doctor`,
|
|
100
|
-
for compact decisions; use `--json` for full audit/API payloads,
|
|
101
|
-
rendering, or debugging.
|
|
99
|
+
Use `--agent` on `review`, `status`, `doctor`, `ledgers prune --dry-run`, and
|
|
100
|
+
`get --inspect` for compact decisions; use `--json` for full audit/API payloads,
|
|
101
|
+
custom rendering, or debugging. On `get`, `--agent` requires `--inspect`.
|
|
102
102
|
|
|
103
103
|
Register existing project ledgers explicitly:
|
|
104
104
|
|
|
@@ -150,7 +150,10 @@ count dump.
|
|
|
150
150
|
3. If missing-path warnings exist inside valid ledgers, run `artshelf validate --all --json` then `artshelf reconcile --dry-run --all --json --registry <registry-path>` for renames, moves, deletes, topology after handoff/finalization, and `.shelf`/`.artshelf` migration fallout.
|
|
151
151
|
4. If cleanup attention exists, run `artshelf cleanup --dry-run --all --json`.
|
|
152
152
|
5. Classify candidates as `trash-safe`, `needs-human-review`,
|
|
153
|
-
`resolve-candidate`, or `registry-problem`.
|
|
153
|
+
`resolve-candidate`, or `registry-problem`. For one flagged record (e.g. a
|
|
154
|
+
stale `cleanup=review` backup), read-only `artshelf get <id> --inspect --agent`
|
|
155
|
+
returns a per-record decision (`keep`, `snooze`, `trash-safe`, `resolve-only`,
|
|
156
|
+
`blocked`) and the exact next-safe action without mutating anything.
|
|
154
157
|
6. Use the built-in `--agent` packet when the CLI output is enough to decide,
|
|
155
158
|
because it is deterministic and token-efficient. Use
|
|
156
159
|
`ArtshelfReviewReport` from `schemas/artshelf-review-report.schema.json` and `examples/artshelf-review-report.json` when you need a host-specific card, attachment, or richer audit record.
|