artshelf 0.12.0 → 0.13.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 +20 -0
- package/SPEC.md +17 -9
- package/dist/src/commands/put.js +1 -1
- package/dist/src/commands/review.js +1 -1
- package/dist/src/commands/shared.js +14 -3
- package/dist/src/reconcile.js +3 -0
- package/dist/src/renderers/doctor.js +3 -3
- package/dist/src/renderers/review.js +73 -9
- package/dist/src/renderers/status.js +4 -3
- package/dist/src/shared/help-text.js +2 -1
- package/docs/agent-monitor.html +7 -0
- package/docs/agent-review.html +9 -3
- package/docs/examples/artshelf-review-report.json +74 -19
- package/docs/reference.html +5 -4
- package/docs/schemas/artshelf-review-report.schema.json +318 -73
- package/examples/artshelf-review-report.json +74 -19
- package/package.json +1 -1
- package/schemas/artshelf-review-report.schema.json +318 -73
- package/skills/artshelf/SKILL.md +10 -10
- package/skills/artshelf/examples/artshelf-review-report.json +74 -19
- package/skills/artshelf/schemas/artshelf-review-report.schema.json +318 -73
- package/skills/artshelf/scripts/render-review-report.mjs +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -110,6 +110,26 @@
|
|
|
110
110
|
state drifted since review, stamps the reconcile audit trail (`previousPath`,
|
|
111
111
|
`reconcilePlanId`, `reconcileReceiptPath`, `reconciledAt`, `reconcileReason`) on
|
|
112
112
|
every touched row, and writes an Artshelf-owned reconcile receipt.
|
|
113
|
+
- Integrated reconcile findings into `review --agent`, `status --agent`, and
|
|
114
|
+
`doctor --agent` triage: missing-path warnings now route to reconcile dry-run
|
|
115
|
+
guidance before approval, reconciled plans escalate to ready-for-approval, and
|
|
116
|
+
the `ArtshelfReviewReport` schema adds the `reconcile` action type (NGX-438).
|
|
117
|
+
- Moved `artshelf put` registry-warning output from stdout to stderr in human
|
|
118
|
+
mode; `--json` output is unchanged (NGX-429).
|
|
119
|
+
|
|
120
|
+
## [0.13.0](https://github.com/calvinnwq/artshelf/compare/artshelf-v0.12.0...artshelf-v0.13.0) (2026-06-15)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
### Features
|
|
124
|
+
|
|
125
|
+
* **review:** integrate reconcile findings into agent review packets ([878785e](https://github.com/calvinnwq/artshelf/commit/878785e72c4e65bd8e09572525b05cc020d2f1e1))
|
|
126
|
+
* **review:** integrate reconcile findings into agent triage; move put registry-warning to stderr (NGX-438, NGX-429) ([2573470](https://github.com/calvinnwq/artshelf/commit/25734701b439f617a33609ac98c3fae895199640))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
### Bug Fixes
|
|
130
|
+
|
|
131
|
+
* **review:** include reconcile counts in all-ledger triage ([2eeb2fe](https://github.com/calvinnwq/artshelf/commit/2eeb2fea6eae58bfd652be959f2bb6e28d7cb90f))
|
|
132
|
+
* **review:** keep reconcile approval schema and blocked triage consistent ([0c8925a](https://github.com/calvinnwq/artshelf/commit/0c8925a851023622796f2b8d847fcc89cab3c5f0))
|
|
113
133
|
|
|
114
134
|
## [0.12.0](https://github.com/calvinnwq/artshelf/compare/artshelf-v0.11.0...artshelf-v0.12.0) (2026-06-15)
|
|
115
135
|
|
package/SPEC.md
CHANGED
|
@@ -99,8 +99,9 @@ Defaults:
|
|
|
99
99
|
`put` should refuse to record a path that does not exist unless a future flag
|
|
100
100
|
explicitly supports planned artifacts. After appending the record, `put`
|
|
101
101
|
registers the ledger in the ledger registry. Registry registration is
|
|
102
|
-
best-effort: if it fails, the record remains appended and
|
|
103
|
-
|
|
102
|
+
best-effort: if it fails, the record remains appended and a registry warning is
|
|
103
|
+
printed to stderr in human mode, or surfaced as a `registryError` field in
|
|
104
|
+
`--json` output, so stdout stays machine-clean.
|
|
104
105
|
|
|
105
106
|
### `artshelf ledgers`
|
|
106
107
|
|
|
@@ -253,10 +254,13 @@ included with a `not-created` plan instead of writing a plan file.
|
|
|
253
254
|
|
|
254
255
|
In `--all` mode, review emits an aggregate triage summary on top of the
|
|
255
256
|
per-ledger detail. JSON includes a `summary` block with affected-ledger, due,
|
|
256
|
-
manual-review, missing-path, executable,
|
|
257
|
-
plan ids; JSON also includes the next safe action.
|
|
258
|
-
|
|
259
|
-
|
|
257
|
+
manual-review, missing-path, executable, skipped, and reconcile entry/blocked
|
|
258
|
+
counts plus the preview plan ids; JSON also includes the next safe action. The
|
|
259
|
+
per-ledger human detail appends a `reconcile` count when a ledger has reconcile
|
|
260
|
+
drift. Human output adds a one-line triage count with the same reconcile counts
|
|
261
|
+
and states the same next safe action (repair broken ledgers, dry-run cleanup,
|
|
262
|
+
dry-run reconcile for missing-path or reconcile drift, or nothing to do). Review
|
|
263
|
+
never writes a plan, so
|
|
260
264
|
the next action always points at an explicit follow-up command.
|
|
261
265
|
|
|
262
266
|
`review`, `status`, and `doctor` share three render modes. The default human
|
|
@@ -265,9 +269,13 @@ stays the full, backward-compatible public audit report; and `--agent` emits a c
|
|
|
265
269
|
deterministic single-line JSON decision packet for agents, taking precedence over
|
|
266
270
|
`--json` when both are passed. For `review`, the packet sorts records into
|
|
267
271
|
ready-for-approval, needs-review-first, and blocked groups. Because review is
|
|
268
|
-
read-only and never mints a cleanup plan, the
|
|
269
|
-
|
|
270
|
-
|
|
272
|
+
read-only and never mints a cleanup plan, the exact approval targets it emits are
|
|
273
|
+
`resolve missing` and `reconcile`; the `reconcile` target appears only when a
|
|
274
|
+
prior reviewed reconcile plan still matches the live drift. Cleanup-eligible
|
|
275
|
+
records and reconcile drift without a reviewed plan stay needs-review-first and
|
|
276
|
+
point at `cleanup --dry-run` or `reconcile --dry-run`, which mint the reviewed
|
|
277
|
+
plan id to approve. Blocked or ambiguous reconcile findings surface in the
|
|
278
|
+
blocked group with no approval target.
|
|
271
279
|
|
|
272
280
|
### `artshelf doctor`
|
|
273
281
|
|
package/dist/src/commands/put.js
CHANGED
|
@@ -31,6 +31,6 @@ export function handlePut(parsed, ledgerPath, json) {
|
|
|
31
31
|
return printJson({ ok: true, record, ledgerPath, registryPath, ...(ledger ? { ledger } : {}), ...(registryError ? { registryError } : {}) });
|
|
32
32
|
process.stdout.write(`recorded ${record.id}\npath: ${record.path}\nretains until: ${record.retainUntil ?? "manual review"}\nledger: ${ledgerPath}\n`);
|
|
33
33
|
if (registryError)
|
|
34
|
-
process.
|
|
34
|
+
process.stderr.write(`registry warning: ${registryError}\n`);
|
|
35
35
|
return 0;
|
|
36
36
|
}
|
|
@@ -15,7 +15,7 @@ export function handleReview(parsed, ledgerPath, json) {
|
|
|
15
15
|
printCompactJson(buildReviewAgentPacketAll(results, summary, { path: registryPath, exists: existsSync(registryPath) }));
|
|
16
16
|
return ok ? 0 : 1;
|
|
17
17
|
}
|
|
18
|
-
const nextAction = reviewNextAction(summary, "all");
|
|
18
|
+
const nextAction = reviewNextAction(summary, "all", undefined, registryPath);
|
|
19
19
|
if (json) {
|
|
20
20
|
printJson({ ok, registryPath, summary, nextAction, ledgers: results.map(reviewJsonResult) });
|
|
21
21
|
return ok ? 0 : 1;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { dueEntries, previewCleanupPlan, readLedger, validateLedger } from "../ledger.js";
|
|
3
3
|
import { listRegisteredLedgers } from "../registry.js";
|
|
4
|
+
import { matchingReconcilePlan, previewReconcilePlan } from "../reconcile.js";
|
|
4
5
|
import { printJson } from "../renderers/json.js";
|
|
5
6
|
export function registeredLedgersOrThrow(registryPath) {
|
|
6
7
|
const ledgers = listRegisteredLedgers(registryPath);
|
|
@@ -43,15 +44,19 @@ export function reviewLedger(ledger, registered = true) {
|
|
|
43
44
|
ledgerExists,
|
|
44
45
|
validate,
|
|
45
46
|
due: [],
|
|
46
|
-
plan: emptyReviewPlan(ledger.path)
|
|
47
|
+
plan: emptyReviewPlan(ledger.path),
|
|
48
|
+
reconcile: null
|
|
47
49
|
};
|
|
48
50
|
}
|
|
51
|
+
const reconcilePlan = previewReconcilePlan(ledger.path);
|
|
52
|
+
const reviewedReconcilePlan = reconcilePlan.entries.length > 0 || reconcilePlan.blocked.length > 0 ? matchingReconcilePlan(ledger.path, reconcilePlan) : null;
|
|
49
53
|
return {
|
|
50
54
|
ledger,
|
|
51
55
|
ledgerExists,
|
|
52
56
|
validate,
|
|
53
57
|
due: dueEntries(readLedger(ledger.path)),
|
|
54
|
-
plan: previewCleanupPlan(ledger.path)
|
|
58
|
+
plan: previewCleanupPlan(ledger.path),
|
|
59
|
+
reconcile: { plan: reconcilePlan, reviewedPlan: reviewedReconcilePlan }
|
|
55
60
|
};
|
|
56
61
|
}
|
|
57
62
|
export function reviewJsonResult(result) {
|
|
@@ -147,6 +152,8 @@ export function summarizeReview(results) {
|
|
|
147
152
|
missingPath: 0,
|
|
148
153
|
executable: 0,
|
|
149
154
|
skipped: 0,
|
|
155
|
+
reconcileEntries: 0,
|
|
156
|
+
reconcileBlocked: 0,
|
|
150
157
|
previewPlanIds: []
|
|
151
158
|
};
|
|
152
159
|
for (const result of results) {
|
|
@@ -162,14 +169,18 @@ export function summarizeReview(results) {
|
|
|
162
169
|
const due = result.due.filter((entry) => entry.dueStatus === "due").length;
|
|
163
170
|
const manualReview = result.due.filter((entry) => entry.dueStatus === "manual-review").length;
|
|
164
171
|
const missingPath = result.due.filter((entry) => entry.dueStatus === "missing-path").length;
|
|
172
|
+
const reconcileEntries = result.reconcile?.plan.entries.length ?? 0;
|
|
173
|
+
const reconcileBlocked = result.reconcile?.plan.blocked.length ?? 0;
|
|
165
174
|
summary.due += due;
|
|
166
175
|
summary.manualReview += manualReview;
|
|
167
176
|
summary.missingPath += missingPath;
|
|
168
177
|
summary.executable += result.plan.entries.length;
|
|
169
178
|
summary.skipped += result.plan.skipped.length;
|
|
179
|
+
summary.reconcileEntries += reconcileEntries;
|
|
180
|
+
summary.reconcileBlocked += reconcileBlocked;
|
|
170
181
|
if (result.plan.planId !== "not-created")
|
|
171
182
|
summary.previewPlanIds.push(result.plan.planId);
|
|
172
|
-
if (!result.validate.ok || due + manualReview + missingPath > 0 || result.plan.entries.length > 0) {
|
|
183
|
+
if (!result.validate.ok || due + manualReview + missingPath + reconcileEntries + reconcileBlocked > 0 || result.plan.entries.length > 0) {
|
|
173
184
|
summary.affected += 1;
|
|
174
185
|
}
|
|
175
186
|
}
|
package/dist/src/reconcile.js
CHANGED
|
@@ -64,6 +64,9 @@ export function createReconcilePlan(ledgerPath) {
|
|
|
64
64
|
});
|
|
65
65
|
return reviewed;
|
|
66
66
|
}
|
|
67
|
+
export function matchingReconcilePlan(ledgerPath, plan) {
|
|
68
|
+
return matchingExistingReconcilePlan(ledgerPath, plan);
|
|
69
|
+
}
|
|
67
70
|
// Apply a reviewed reconcile plan (NGX-437 `reconcile --execute`). This is the only
|
|
68
71
|
// mutating reconcile entrypoint and it is deliberately conservative:
|
|
69
72
|
// * It refuses up front when the plan id is missing, the plan file is absent, or the
|
|
@@ -3,12 +3,12 @@ const DOCTOR_ATTENTION_CATEGORIES = ["stale", "invalid", "warnings"];
|
|
|
3
3
|
function doctorAttention(summary) {
|
|
4
4
|
return DOCTOR_ATTENTION_CATEGORIES.filter((key) => summary[key] > 0);
|
|
5
5
|
}
|
|
6
|
-
function doctorNextAction(blockers, summary) {
|
|
6
|
+
function doctorNextAction(blockers, summary, registryPath) {
|
|
7
7
|
if (blockers.length > 0) {
|
|
8
8
|
return `repair ${blockers.length} registry/ledger issue(s) above, then re-run \`artshelf doctor\``;
|
|
9
9
|
}
|
|
10
10
|
if (summary.warnings > 0) {
|
|
11
|
-
return `healthy, but ${summary.warnings} warning(s) noted — run \`artshelf
|
|
11
|
+
return `healthy, but ${summary.warnings} warning(s) noted — run \`artshelf reconcile --dry-run --all --registry ${registryPath}\` to prepare reconcile-ready approvals, then run \`artshelf review --all --registry ${registryPath}\`; nothing is auto-executed`;
|
|
12
12
|
}
|
|
13
13
|
return "artshelf is healthy on this machine — cleanup safety enforced; no action needed";
|
|
14
14
|
}
|
|
@@ -39,7 +39,7 @@ export function buildDoctorAgentPacket(report) {
|
|
|
39
39
|
attention: doctorAttention(report.summary),
|
|
40
40
|
blockers,
|
|
41
41
|
cleanupSafety: report.cleanupSafety,
|
|
42
|
-
nextAction: doctorNextAction(blockers, report.summary),
|
|
42
|
+
nextAction: doctorNextAction(blockers, report.summary, report.registryPath),
|
|
43
43
|
verification: `artshelf doctor --agent --registry ${report.registryPath}`
|
|
44
44
|
};
|
|
45
45
|
}
|
|
@@ -7,7 +7,7 @@ const REVIEW_SAFETY = {
|
|
|
7
7
|
noResolveRan: true,
|
|
8
8
|
noDeleteRan: true
|
|
9
9
|
};
|
|
10
|
-
export function reviewNextAction(summary, scope, ledgerPath) {
|
|
10
|
+
export function reviewNextAction(summary, scope, ledgerPath, registryPath) {
|
|
11
11
|
const broken = summary.invalid + summary.stale;
|
|
12
12
|
const review = statusCommand(scope, "review", ledgerPath);
|
|
13
13
|
if (broken > 0) {
|
|
@@ -18,25 +18,30 @@ export function reviewNextAction(summary, scope, ledgerPath) {
|
|
|
18
18
|
const dryRun = scope === "all" ? "artshelf cleanup --dry-run --all" : `artshelf cleanup --dry-run${ledgerPath ? ` --ledger ${ledgerPath}` : ""}`;
|
|
19
19
|
return `run \`${dryRun}\` to generate plans, then \`artshelf cleanup --execute --plan-id <id> --ledger <path>\` for each reviewed plan`;
|
|
20
20
|
}
|
|
21
|
-
if (summary.missingPath > 0) {
|
|
22
|
-
|
|
21
|
+
if (summary.missingPath > 0 || summary.reconcileEntries > 0 || summary.reconcileBlocked > 0) {
|
|
22
|
+
const reconcile = scope === "all" ? `artshelf reconcile --dry-run --all${registryPath ? ` --registry ${registryPath}` : ""}` : `artshelf reconcile --dry-run --ledger ${ledgerPath}`;
|
|
23
|
+
return `run \`${reconcile} --json\` and then \`${review}\` to surface reconcile-ready approvals; nothing is auto-executable`;
|
|
23
24
|
}
|
|
24
25
|
return "nothing to do — no broken ledgers and no due, manual-review, missing-path, or executable cleanup entries";
|
|
25
26
|
}
|
|
26
27
|
export function printReviewAll(results, summary, nextAction, registryPath) {
|
|
27
|
-
const needsAttention = summary.invalid + summary.stale + summary.executable + summary.due + summary.manualReview + summary.missingPath > 0;
|
|
28
|
+
const needsAttention = summary.invalid + summary.stale + summary.executable + summary.due + summary.manualReview + summary.missingPath + summary.reconcileEntries + summary.reconcileBlocked > 0;
|
|
28
29
|
process.stdout.write(`${attentionGlyph(needsAttention)} artshelf review --all: ${needsAttention ? "needs attention" : "all clear"}\n`);
|
|
29
30
|
process.stdout.write(`registry: ${registryPath} — ${summary.ledgers} ledgers (${summary.ok} ok, ${summary.invalid} invalid, ${summary.stale} stale)\n`);
|
|
30
31
|
printReview(results);
|
|
31
|
-
process.stdout.write(`triage: due ${summary.due} · manual-review ${summary.manualReview} · missing ${summary.missingPath} · executable ${summary.executable} · skipped ${summary.skipped}\n`);
|
|
32
|
+
process.stdout.write(`triage: due ${summary.due} · manual-review ${summary.manualReview} · missing ${summary.missingPath} · executable ${summary.executable} · skipped ${summary.skipped} · reconcile ${summary.reconcileEntries} · blocked ${summary.reconcileBlocked}\n`);
|
|
32
33
|
process.stdout.write(`next: ${nextAction}\n`);
|
|
33
34
|
}
|
|
34
35
|
export function printReview(results) {
|
|
35
36
|
for (const result of results) {
|
|
36
37
|
const visibleDue = result.due.filter((entry) => entry.dueStatus !== "kept");
|
|
37
|
-
const
|
|
38
|
+
const reconcileEntries = result.reconcile?.plan.entries.length ?? 0;
|
|
39
|
+
const reconcileBlocked = result.reconcile?.plan.blocked.length ?? 0;
|
|
40
|
+
const reconcileDrift = reconcileEntries + reconcileBlocked;
|
|
41
|
+
const needsAttention = !result.validate.ok || visibleDue.length > 0 || result.plan.entries.length > 0 || reconcileDrift > 0;
|
|
42
|
+
const reconcileDetail = reconcileDrift > 0 ? `; reconcile: ${reconcileEntries} entries, ${reconcileBlocked} blocked` : "";
|
|
38
43
|
process.stdout.write(`${attentionGlyph(needsAttention)} [${result.ledger.name}] ${result.validate.ok ? "ok" : "invalid"}: ${result.validate.entries} entries, ${result.validate.errors.length} errors, ${result.validate.warnings.length} warnings\n`);
|
|
39
|
-
process.stdout.write(`due/manual/missing: ${visibleDue.length}; plan ${result.plan.planId}: ${result.plan.entries.length} entries, ${result.plan.skipped.length} skipped\n`);
|
|
44
|
+
process.stdout.write(`due/manual/missing: ${visibleDue.length}; plan ${result.plan.planId}: ${result.plan.entries.length} entries, ${result.plan.skipped.length} skipped${reconcileDetail}\n`);
|
|
40
45
|
process.stdout.write(`ledger: ${result.ledger.path}\n`);
|
|
41
46
|
}
|
|
42
47
|
}
|
|
@@ -60,7 +65,15 @@ function buildReviewDecisions(results, scope) {
|
|
|
60
65
|
});
|
|
61
66
|
continue;
|
|
62
67
|
}
|
|
63
|
-
const
|
|
68
|
+
const handledReconcileIds = new Set([
|
|
69
|
+
...(result.reconcile?.plan.entries.map((entry) => entry.id) ?? []),
|
|
70
|
+
...(result.reconcile?.plan.blocked.map((entry) => entry.id) ?? [])
|
|
71
|
+
]);
|
|
72
|
+
const reconcileActions = buildReconcileDecisions(result, scope);
|
|
73
|
+
readyForApproval.push(...reconcileActions.readyForApproval);
|
|
74
|
+
needsReviewFirst.push(...reconcileActions.needsReviewFirst);
|
|
75
|
+
blocked.push(...reconcileActions.blocked);
|
|
76
|
+
const missingPath = due.filter((entry) => entry.dueStatus === "missing-path" && !handledReconcileIds.has(entry.id));
|
|
64
77
|
const trashSafe = due.filter((entry) => entry.dueStatus === "due" && entry.cleanup === "trash");
|
|
65
78
|
const inspectItems = due.filter((entry) => entry.dueStatus === "manual-review" ||
|
|
66
79
|
(entry.dueStatus === "due" && (entry.cleanup === "review" || entry.cleanup === "delete")));
|
|
@@ -103,6 +116,57 @@ function buildReviewDecisions(results, scope) {
|
|
|
103
116
|
}
|
|
104
117
|
return { readyForApproval, needsReviewFirst, blocked };
|
|
105
118
|
}
|
|
119
|
+
function buildReconcileDecisions(result, _scope) {
|
|
120
|
+
if (!result.reconcile)
|
|
121
|
+
return { readyForApproval: [], needsReviewFirst: [], blocked: [] };
|
|
122
|
+
const readyForApproval = [];
|
|
123
|
+
const needsReviewFirst = [];
|
|
124
|
+
const blocked = [];
|
|
125
|
+
const hasReviewedPlan = Boolean(result.reconcile.reviewedPlan && result.reconcile.reviewedPlan.planId !== "not-created");
|
|
126
|
+
const reviewedPlanId = result.reconcile.reviewedPlan?.planId ?? null;
|
|
127
|
+
const byCategory = {
|
|
128
|
+
remap: [],
|
|
129
|
+
"resolve-missing": [],
|
|
130
|
+
"resolve-stale-trash": [],
|
|
131
|
+
"registry-remap": [],
|
|
132
|
+
blocked: []
|
|
133
|
+
};
|
|
134
|
+
for (const finding of result.reconcile.plan.entries.concat(result.reconcile.plan.blocked)) {
|
|
135
|
+
byCategory[finding.category].push(finding);
|
|
136
|
+
}
|
|
137
|
+
const reconcileActionCategories = ["remap", "resolve-missing", "resolve-stale-trash", "registry-remap"];
|
|
138
|
+
for (const category of reconcileActionCategories) {
|
|
139
|
+
const entries = byCategory[category];
|
|
140
|
+
if (entries.length === 0)
|
|
141
|
+
continue;
|
|
142
|
+
const ids = entries.map((entry) => entry.id).sort();
|
|
143
|
+
const label = `Review ${entries.length} reconcile ${category} finding(s) in ${result.ledger.name}`;
|
|
144
|
+
const reason = `recorded paths are ${category === "remap" ? "safe to remap" : "stale and require manual review before execution"}`;
|
|
145
|
+
const decision = {
|
|
146
|
+
label,
|
|
147
|
+
itemIds: ids,
|
|
148
|
+
actionType: "reconcile",
|
|
149
|
+
approvalTarget: hasReviewedPlan ? `approve artshelf reconcile ledger ${result.ledger.path} plan ${reviewedPlanId}` : null,
|
|
150
|
+
reason,
|
|
151
|
+
nextStep: hasReviewedPlan
|
|
152
|
+
? `run \`artshelf reconcile --execute --plan-id ${reviewedPlanId} --ledger ${result.ledger.path}\``
|
|
153
|
+
: `run \`artshelf reconcile --dry-run --ledger ${result.ledger.path} --json\`, then approve with \`approve artshelf reconcile ledger ${result.ledger.path} plan <plan-id>\``
|
|
154
|
+
};
|
|
155
|
+
(hasReviewedPlan ? readyForApproval : needsReviewFirst).push(decision);
|
|
156
|
+
}
|
|
157
|
+
if (byCategory.blocked.length > 0) {
|
|
158
|
+
const entries = byCategory.blocked;
|
|
159
|
+
blocked.push({
|
|
160
|
+
label: `Review ${entries.length} blocked reconcile finding(s) in ${result.ledger.name}`,
|
|
161
|
+
itemIds: entries.map((entry) => entry.id).sort(),
|
|
162
|
+
actionType: "reconcile",
|
|
163
|
+
approvalTarget: null,
|
|
164
|
+
reason: "path drift is ambiguous or unsafe and needs manual investigation",
|
|
165
|
+
nextStep: `run \`artshelf reconcile --dry-run --ledger ${result.ledger.path} --json\`, then handle each item manually`
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return { readyForApproval, needsReviewFirst, blocked };
|
|
169
|
+
}
|
|
106
170
|
function reviewCounts(summary) {
|
|
107
171
|
return {
|
|
108
172
|
due: summary.due,
|
|
@@ -131,7 +195,7 @@ export function buildReviewAgentPacketAll(results, summary, registry) {
|
|
|
131
195
|
needsReviewFirst: groups.needsReviewFirst,
|
|
132
196
|
blocked: groups.blocked,
|
|
133
197
|
safety: REVIEW_SAFETY,
|
|
134
|
-
nextAction: reviewNextAction(summary, "all"),
|
|
198
|
+
nextAction: reviewNextAction(summary, "all", undefined, registry.path),
|
|
135
199
|
verification: `artshelf review --all --agent --registry ${registry.path}`
|
|
136
200
|
};
|
|
137
201
|
}
|
|
@@ -14,7 +14,7 @@ export function statusCommand(scope, command, ledgerPath) {
|
|
|
14
14
|
return `artshelf ${command} --all`;
|
|
15
15
|
return ledgerPath ? `artshelf ${command} --ledger ${ledgerPath}` : `artshelf ${command}`;
|
|
16
16
|
}
|
|
17
|
-
function statusNextAction(blockers, counts, scope, ledgerPath) {
|
|
17
|
+
function statusNextAction(blockers, counts, scope, ledgerPath, registryPath) {
|
|
18
18
|
if (blockers.length > 0) {
|
|
19
19
|
const verify = statusCommand(scope, "status", ledgerPath);
|
|
20
20
|
return `repair ${blockers.length} broken ledger(s) above, then re-run \`${verify}\``;
|
|
@@ -27,7 +27,8 @@ function statusNextAction(blockers, counts, scope, ledgerPath) {
|
|
|
27
27
|
return `run \`${review}\` to inspect manual-review records; nothing is auto-executed`;
|
|
28
28
|
}
|
|
29
29
|
if (counts.missingPath > 0) {
|
|
30
|
-
|
|
30
|
+
const reconcile = scope === "all" ? `artshelf reconcile --dry-run --all${registryPath ? ` --registry ${registryPath}` : ""}` : `artshelf reconcile --dry-run --ledger ${ledgerPath}`;
|
|
31
|
+
return `run \`${reconcile} --json\` and then \`${review}\` to surface reconcile-ready approvals; nothing is auto-executable`;
|
|
31
32
|
}
|
|
32
33
|
return "nothing due — no broken ledgers and no due, manual-review, missing-path, or pending cleanup entries";
|
|
33
34
|
}
|
|
@@ -58,7 +59,7 @@ export function buildStatusAgentPacketAll(report) {
|
|
|
58
59
|
counts,
|
|
59
60
|
attention: statusAttention(counts),
|
|
60
61
|
blockers,
|
|
61
|
-
nextAction: statusNextAction(blockers, counts, "all"),
|
|
62
|
+
nextAction: statusNextAction(blockers, counts, "all", undefined, report.registryPath),
|
|
62
63
|
verification: `artshelf status --all --agent --registry ${report.registryPath}`
|
|
63
64
|
};
|
|
64
65
|
}
|
|
@@ -188,7 +188,8 @@ Resolved records stay in the audit trail but no longer participate in due or cle
|
|
|
188
188
|
|
|
189
189
|
Review runs validate, due, and cleanup plan preview without moving files or
|
|
190
190
|
writing a plan. With --all, review adds aggregate triage counts and the next
|
|
191
|
-
safe action
|
|
191
|
+
safe action, including reconcile entry and blocked counts when path drift is
|
|
192
|
+
detected.
|
|
192
193
|
|
|
193
194
|
Render modes:
|
|
194
195
|
(default) Human summary of validation, triage counts, and the next safe action.
|
package/docs/agent-monitor.html
CHANGED
|
@@ -111,6 +111,13 @@ artshelf doctor --agent
|
|
|
111
111
|
<span class="c"># lightweight counts, cron-friendly</span>
|
|
112
112
|
artshelf status --all --json
|
|
113
113
|
artshelf status --all --agent</code></pre>
|
|
114
|
+
<p>
|
|
115
|
+
If any ledger is stale or missing-path, run the safe sequence in order:
|
|
116
|
+
<code>review --all --json</code> for affected classifications,
|
|
117
|
+
<code>validate --all --json</code> for provenance signals, then
|
|
118
|
+
<code>reconcile --dry-run --all --json --registry <registry-path></code>
|
|
119
|
+
before any approve/execute step.
|
|
120
|
+
</p>
|
|
114
121
|
<p>
|
|
115
122
|
Use <code>--agent</code> for concise monitor decisions and exact next
|
|
116
123
|
actions. Use <code>--json</code> instead when the job needs full
|
package/docs/agent-review.html
CHANGED
|
@@ -44,10 +44,15 @@
|
|
|
44
44
|
<section>
|
|
45
45
|
<h2>Daily review workflow</h2>
|
|
46
46
|
<ol>
|
|
47
|
-
<li>
|
|
47
|
+
<li>Start with health and registry-level context: <code>status --all --agent</code>,
|
|
48
|
+
then read raw pressure points from <code>review --all --agent</code>
|
|
49
|
+
and <code>trash list --all --json</code>.</li>
|
|
50
|
+
<li>For stale or missing path warnings, run <code>validate --all --json</code>
|
|
51
|
+
and then <code>reconcile --dry-run --all --json --registry <registry-path></code>
|
|
52
|
+
to produce reviewed-safe plans.</li>
|
|
48
53
|
<li>Run explicit-ledger purge dry-runs only when old trash needs review.</li>
|
|
49
54
|
<li>Classify each candidate: <code>trash-safe</code>, <code>needs-human-review</code>, <code>resolve-candidate</code>, or <code>registry-problem</code>.</li>
|
|
50
|
-
<li>
|
|
55
|
+
<li>Execute the exact approval path only after review packet verification: exact ledger path and reviewed plan id or ids.</li>
|
|
51
56
|
</ol>
|
|
52
57
|
<p>
|
|
53
58
|
Prefer the built-in <code>--agent</code> packet when an acting agent
|
|
@@ -104,7 +109,8 @@ Dry-run only. No execute, resolve, or delete ran.</code></pre>
|
|
|
104
109
|
</p>
|
|
105
110
|
<pre><code>approve artshelf cleanup ledger <ledger-path> plan <plan-id>
|
|
106
111
|
approve artshelf trash purge ledger <ledger-path> plan <purge-plan-id>
|
|
107
|
-
approve artshelf resolve missing ledger <ledger-path> ids <id...>
|
|
112
|
+
approve artshelf resolve missing ledger <ledger-path> ids <id...>
|
|
113
|
+
approve artshelf reconcile ledger <ledger-path> plan <plan-id></code></pre>
|
|
108
114
|
<div class="callout" data-kind="boundary">
|
|
109
115
|
<span class="callout-label">Hard boundary</span>
|
|
110
116
|
<p>
|
|
@@ -27,27 +27,41 @@
|
|
|
27
27
|
"skipped": 2,
|
|
28
28
|
"refused": 0,
|
|
29
29
|
"manualReview": 1,
|
|
30
|
-
"missingPath":
|
|
30
|
+
"missingPath": 2,
|
|
31
31
|
"trashed": 0
|
|
32
32
|
},
|
|
33
33
|
"decisionSummary": {
|
|
34
|
-
"readyForApproval":
|
|
35
|
-
"needsReviewFirst":
|
|
36
|
-
"blocked":
|
|
34
|
+
"readyForApproval": 3,
|
|
35
|
+
"needsReviewFirst": 2,
|
|
36
|
+
"blocked": 1
|
|
37
37
|
},
|
|
38
38
|
"decisionGroups": {
|
|
39
39
|
"readyForApproval": [
|
|
40
40
|
{
|
|
41
41
|
"label": "Clean up temp debug output",
|
|
42
|
-
"itemIds": [
|
|
42
|
+
"itemIds": [
|
|
43
|
+
"shf_20260606_120000_ab12"
|
|
44
|
+
],
|
|
43
45
|
"actionType": "cleanup",
|
|
44
46
|
"approvalTarget": "approve artshelf cleanup ledger /path/to/example-project/.artshelf/ledger.jsonl plan plan_20260606_120000_ab12",
|
|
45
47
|
"reason": "Disposable temp artifact has a reviewed cleanup plan.",
|
|
46
48
|
"nextStep": "Approve the exact cleanup plan to move the artifact into Artshelf trash."
|
|
47
49
|
},
|
|
50
|
+
{
|
|
51
|
+
"label": "Review 1 reconcile remap finding(s) in example-project",
|
|
52
|
+
"itemIds": [
|
|
53
|
+
"shf_20260606_121500_gh78"
|
|
54
|
+
],
|
|
55
|
+
"actionType": "reconcile",
|
|
56
|
+
"approvalTarget": "approve artshelf reconcile ledger /path/to/example-project/.artshelf/ledger.jsonl plan plan_20260606_122000_repl",
|
|
57
|
+
"reason": "Parent folder drift looks safe to reconcile after inspection and review.",
|
|
58
|
+
"nextStep": "run `artshelf reconcile --execute --plan-id plan_20260606_122000_repl --ledger /path/to/example-project/.artshelf/ledger.jsonl` after approval."
|
|
59
|
+
},
|
|
48
60
|
{
|
|
49
61
|
"label": "Resolve missing report record",
|
|
50
|
-
"itemIds": [
|
|
62
|
+
"itemIds": [
|
|
63
|
+
"shf_20260606_120500_cd34"
|
|
64
|
+
],
|
|
51
65
|
"actionType": "resolve-missing",
|
|
52
66
|
"approvalTarget": "approve artshelf resolve missing ledger /path/to/example-project/.artshelf/ledger.jsonl ids shf_20260606_120500_cd34",
|
|
53
67
|
"reason": "The report path is already missing.",
|
|
@@ -56,17 +70,40 @@
|
|
|
56
70
|
],
|
|
57
71
|
"needsReviewFirst": [
|
|
58
72
|
{
|
|
59
|
-
"label": "
|
|
60
|
-
"itemIds": [
|
|
61
|
-
|
|
73
|
+
"label": "Review 1 reconcile resolve-stale-trash finding(s) in example-project",
|
|
74
|
+
"itemIds": [
|
|
75
|
+
"shf_20260606_121800_jk90"
|
|
76
|
+
],
|
|
77
|
+
"actionType": "reconcile",
|
|
78
|
+
"approvalTarget": null,
|
|
79
|
+
"reason": "Trash references point to trashed artifacts whose originals moved or got deleted.",
|
|
80
|
+
"nextStep": "run `artshelf reconcile --dry-run --ledger /path/to/example-project/.artshelf/ledger.jsonl --json`, then approve with `approve artshelf reconcile ledger /path/to/example-project/.artshelf/ledger.jsonl plan <plan-id>`"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"label": "Review 1 reconcile registry-remap finding(s) in example-project",
|
|
84
|
+
"itemIds": [
|
|
85
|
+
"shf_20260606_121900_lm01"
|
|
86
|
+
],
|
|
87
|
+
"actionType": "reconcile",
|
|
62
88
|
"approvalTarget": null,
|
|
63
|
-
"reason": "
|
|
64
|
-
"nextStep": "
|
|
89
|
+
"reason": "Ledger name or registry path drift needs explicit review before remapping records.",
|
|
90
|
+
"nextStep": "run `artshelf reconcile --dry-run --ledger /path/to/example-project/.artshelf/ledger.jsonl --json`, then approve with `approve artshelf reconcile ledger /path/to/example-project/.artshelf/ledger.jsonl plan <plan-id>`"
|
|
65
91
|
}
|
|
66
92
|
],
|
|
67
|
-
"blocked": [
|
|
93
|
+
"blocked": [
|
|
94
|
+
{
|
|
95
|
+
"label": "Review 1 reconcile blocked finding(s) in example-project",
|
|
96
|
+
"itemIds": [
|
|
97
|
+
"shf_20260606_122200_qr11"
|
|
98
|
+
],
|
|
99
|
+
"actionType": "reconcile",
|
|
100
|
+
"approvalTarget": null,
|
|
101
|
+
"reason": "Path drift is ambiguous or unsafe and needs manual investigation.",
|
|
102
|
+
"nextStep": "Run a manual artifact-by-artifact review and decide whether to keep, resolve, or move the ledger entry."
|
|
103
|
+
}
|
|
104
|
+
]
|
|
68
105
|
},
|
|
69
|
-
"recommendation": "
|
|
106
|
+
"recommendation": "Run reconcile dry-run for stale/missing path entries, then follow the exact approvals returned in the review packet.",
|
|
70
107
|
"items": [
|
|
71
108
|
{
|
|
72
109
|
"id": "shf_20260606_120000_ab12",
|
|
@@ -87,13 +124,31 @@
|
|
|
87
124
|
"note": "Resolution updates only the ledger and does not move or delete files."
|
|
88
125
|
},
|
|
89
126
|
{
|
|
90
|
-
"id": "
|
|
91
|
-
"path": "/tmp/
|
|
92
|
-
"classification": "
|
|
93
|
-
"proposedAction": "
|
|
127
|
+
"id": "shf_20260606_121800_jk90",
|
|
128
|
+
"path": "/tmp/stale-trash-record.json",
|
|
129
|
+
"classification": "resolve-candidate",
|
|
130
|
+
"proposedAction": "reconcile stale trashed entry after review",
|
|
131
|
+
"dueStatus": "trashed",
|
|
132
|
+
"reason": "trash path is stale after source artifact delete",
|
|
133
|
+
"note": "Run reconcile to confirm whether to rewrite the artifact path or resolve manually."
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"id": "shf_20260606_121900_lm01",
|
|
137
|
+
"path": "/path/to/example-project/src/legacy/file.txt",
|
|
138
|
+
"classification": "registry-problem",
|
|
139
|
+
"proposedAction": "inspect registry-backed path drift",
|
|
140
|
+
"dueStatus": "missing-path",
|
|
141
|
+
"reason": "Artifact moved by repository migration from .shelf to .artshelf",
|
|
142
|
+
"note": "Review registry identity and run reconcile dry-run before executing any path updates."
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"id": "shf_20260606_122200_qr11",
|
|
146
|
+
"path": "/tmp/blocked-dupe-path",
|
|
147
|
+
"classification": "registry-problem",
|
|
148
|
+
"proposedAction": "manual investigation required",
|
|
94
149
|
"dueStatus": "manual-review",
|
|
95
|
-
"reason": "
|
|
96
|
-
"note": "
|
|
150
|
+
"reason": "reconstructed paths conflicted with another active artifact",
|
|
151
|
+
"note": "Blocked finding intentionally requires human disambiguation."
|
|
97
152
|
}
|
|
98
153
|
],
|
|
99
154
|
"alternatives": [
|
package/docs/reference.html
CHANGED
|
@@ -130,10 +130,11 @@ artshelf doctor [--agent] [--json]</code></pre>
|
|
|
130
130
|
<p>
|
|
131
131
|
<code>review</code> runs validate, due, and a cleanup plan preview in one pass; no-op
|
|
132
132
|
previews report <code>not-created</code>, and <code>--all</code> adds an aggregate triage
|
|
133
|
-
summary
|
|
134
|
-
counts; <code>--all --json</code> is
|
|
135
|
-
resolved paths, registry health,
|
|
136
|
-
<code>--agent</code> for a
|
|
133
|
+
summary, including reconcile entry and blocked counts, plus the next safe action.
|
|
134
|
+
<code>status</code> is the lightweight dashboard of counts; <code>--all --json</code> is
|
|
135
|
+
cron-friendly. <code>doctor</code> reports CLI version, resolved paths, registry health,
|
|
136
|
+
and the cleanup safety posture. All three also accept <code>--agent</code> for a
|
|
137
|
+
deterministic, token-efficient decision packet (see Output mode).
|
|
137
138
|
</p>
|
|
138
139
|
</section>
|
|
139
140
|
|