artshelf 0.10.1 → 0.10.2

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/SPEC.md +1 -1
  3. package/dist/src/adapters/process.js +7 -0
  4. package/dist/src/adapters/update.js +143 -0
  5. package/dist/src/cli.js +44 -1847
  6. package/dist/src/commands/cleanup.js +52 -0
  7. package/dist/src/commands/doctor.js +79 -0
  8. package/dist/src/commands/due.js +32 -0
  9. package/dist/src/commands/find.js +44 -0
  10. package/dist/src/commands/get.js +31 -0
  11. package/dist/src/commands/index.js +69 -0
  12. package/dist/src/commands/ledgers.js +111 -0
  13. package/dist/src/commands/list.js +36 -0
  14. package/dist/src/commands/put.js +36 -0
  15. package/dist/src/commands/resolve.js +17 -0
  16. package/dist/src/commands/review.js +38 -0
  17. package/dist/src/commands/shared.js +160 -0
  18. package/dist/src/commands/status.js +101 -0
  19. package/dist/src/commands/trash.js +78 -0
  20. package/dist/src/commands/update.js +75 -0
  21. package/dist/src/commands/validate.js +35 -0
  22. package/dist/src/config/env.js +24 -0
  23. package/dist/src/config/package.js +17 -0
  24. package/dist/src/config/paths.js +5 -0
  25. package/dist/src/renderers/attention.js +3 -0
  26. package/dist/src/renderers/doctor.js +64 -0
  27. package/dist/src/renderers/json.js +10 -0
  28. package/dist/src/renderers/review.js +159 -0
  29. package/dist/src/renderers/status.js +112 -0
  30. package/dist/src/shared/cli-types.js +1 -0
  31. package/dist/src/shared/errors.js +4 -0
  32. package/dist/src/shared/flags.js +41 -0
  33. package/dist/src/shared/help-text.js +355 -0
  34. package/docs/agent-usage.html +1 -1
  35. package/docs/agent-usage.md +2 -2
  36. package/docs/reference.html +3 -3
  37. package/package.json +1 -1
@@ -0,0 +1,52 @@
1
+ import { createCleanupPlan, executeCleanupPlan } from "../ledger.js";
2
+ import { normalizeRegistryPath } from "../registry.js";
3
+ import { printJson } from "../renderers/json.js";
4
+ import { boolFlag, requiredStringFlag, stringFlag } from "../shared/flags.js";
5
+ import { printPlan, printPlans, registeredLedgersOrThrow, validateRegisteredLedger } from "./shared.js";
6
+ export function handleCleanup(parsed, ledgerPath, json) {
7
+ const dryRun = boolFlag(parsed, "dry-run");
8
+ const execute = boolFlag(parsed, "execute");
9
+ if (dryRun && execute)
10
+ throw new Error("cleanup accepts either --dry-run or --execute, not both");
11
+ if (boolFlag(parsed, "all") && execute)
12
+ throw new Error("cleanup --all is dry-run only; execute requires an explicit --ledger and reviewed --plan-id");
13
+ if (dryRun) {
14
+ if (boolFlag(parsed, "all")) {
15
+ const registryPath = normalizeRegistryPath(stringFlag(parsed, "registry"));
16
+ const ledgers = registeredLedgersOrThrow(registryPath);
17
+ const validations = ledgers.map((ledger) => ({ ledger, result: validateRegisteredLedger(ledger) }));
18
+ const ok = validations.every((entry) => entry.result.ok);
19
+ if (!ok) {
20
+ if (json) {
21
+ printJson({ ok, registryPath, ledgers: validations });
22
+ return 1;
23
+ }
24
+ for (const entry of validations.filter((item) => !item.result.ok)) {
25
+ process.stdout.write(`invalid ${entry.ledger.name}: ${entry.result.errors.join("; ")}\nledger: ${entry.ledger.path}\n`);
26
+ }
27
+ process.stdout.write(`registry: ${registryPath}\n`);
28
+ return 1;
29
+ }
30
+ const plans = ledgers.map((ledger) => ({ ledger, plan: createCleanupPlan(ledger.path) }));
31
+ if (json)
32
+ return printJson({ ok: true, registryPath, plans });
33
+ printPlans(plans);
34
+ process.stdout.write(`registry: ${registryPath}\n`);
35
+ return 0;
36
+ }
37
+ const plan = createCleanupPlan(ledgerPath);
38
+ if (json)
39
+ return printJson({ ok: true, plan });
40
+ printPlan(plan, ledgerPath);
41
+ return 0;
42
+ }
43
+ if (execute) {
44
+ const planId = requiredStringFlag(parsed, "plan-id");
45
+ const receipt = executeCleanupPlan(ledgerPath, planId);
46
+ if (json)
47
+ return printJson({ ok: true, receipt });
48
+ process.stdout.write(`receipt ${receipt.planId}: ${receipt.results.length} results\nreceipt: ${receipt.receiptPath}\nledger: ${ledgerPath}\n`);
49
+ return 0;
50
+ }
51
+ throw new Error("cleanup requires --dry-run or --execute");
52
+ }
@@ -0,0 +1,79 @@
1
+ import { existsSync } from "node:fs";
2
+ import { listRegisteredLedgers, normalizeRegistryPath } from "../registry.js";
3
+ import { VERSION } from "../config/package.js";
4
+ import { buildDoctorAgentPacket, printDoctor } from "../renderers/doctor.js";
5
+ import { printCompactJson, printJson } from "../renderers/json.js";
6
+ import { boolFlag, stringFlag } from "../shared/flags.js";
7
+ import { validateRegisteredLedger } from "./shared.js";
8
+ export function handleDoctor(parsed, ledgerPath, json) {
9
+ const report = buildDoctorReport(ledgerPath, normalizeRegistryPath(stringFlag(parsed, "registry")));
10
+ if (boolFlag(parsed, "agent")) {
11
+ printCompactJson(buildDoctorAgentPacket(report));
12
+ return report.ok ? 0 : 1;
13
+ }
14
+ if (json) {
15
+ printJson(report);
16
+ return report.ok ? 0 : 1;
17
+ }
18
+ printDoctor(report);
19
+ return report.ok ? 0 : 1;
20
+ }
21
+ function buildDoctorReport(ledgerPath, registryPath) {
22
+ const errors = [];
23
+ let registryOk = true;
24
+ let registryError = null;
25
+ let entries = [];
26
+ try {
27
+ entries = listRegisteredLedgers(registryPath);
28
+ }
29
+ catch (error) {
30
+ registryOk = false;
31
+ registryError = error.message;
32
+ errors.push(`registry could not be read: ${registryPath} (${registryError})`);
33
+ }
34
+ const ledgers = entries.map((entry) => {
35
+ const result = validateRegisteredLedger(entry);
36
+ const status = result.ok ? "ok" : existsSync(entry.path) ? "invalid" : "missing";
37
+ if (!result.ok) {
38
+ for (const message of result.errors)
39
+ errors.push(`${entry.name}: ${message}`);
40
+ }
41
+ return {
42
+ name: entry.name,
43
+ path: entry.path,
44
+ scope: entry.scope,
45
+ status,
46
+ ok: result.ok,
47
+ entries: result.entries,
48
+ errors: result.errors,
49
+ warnings: result.warnings
50
+ };
51
+ });
52
+ const summary = {
53
+ ledgers: ledgers.length,
54
+ ok: ledgers.filter((ledger) => ledger.status === "ok").length,
55
+ stale: ledgers.filter((ledger) => ledger.status === "missing").length,
56
+ invalid: ledgers.filter((ledger) => ledger.status === "invalid").length,
57
+ warnings: ledgers.reduce((count, ledger) => count + ledger.warnings.length, 0)
58
+ };
59
+ return {
60
+ ok: registryOk && summary.stale === 0 && summary.invalid === 0,
61
+ version: VERSION,
62
+ node: process.version,
63
+ ledgerPath,
64
+ ledgerExists: existsSync(ledgerPath),
65
+ registryPath,
66
+ registryExists: existsSync(registryPath),
67
+ registryOk,
68
+ registryError,
69
+ ledgers,
70
+ summary,
71
+ cleanupSafety: {
72
+ executeRequiresLedgerAndPlanId: true,
73
+ globalExecuteRefused: true,
74
+ deleteRefusedInV1: true,
75
+ dryRunBeforeMutation: true
76
+ },
77
+ errors
78
+ };
79
+ }
@@ -0,0 +1,32 @@
1
+ import { dueEntries, readLedger } from "../ledger.js";
2
+ import { normalizeRegistryPath } from "../registry.js";
3
+ import { printJson } from "../renderers/json.js";
4
+ import { boolFlag, stringFlag } from "../shared/flags.js";
5
+ import { printDueEntries, printRegisteredLedgerValidation, validateRegisteredLedgersOrThrow } from "./shared.js";
6
+ export function handleDue(parsed, ledgerPath, json) {
7
+ if (boolFlag(parsed, "all")) {
8
+ const registryPath = normalizeRegistryPath(stringFlag(parsed, "registry"));
9
+ const validation = validateRegisteredLedgersOrThrow(registryPath);
10
+ if (!validation.ok)
11
+ return printRegisteredLedgerValidation(registryPath, validation.results, json);
12
+ const results = validation.results.map(({ ledger }) => ({ ledger, entries: dueEntries(readLedger(ledger.path)) }));
13
+ if (json)
14
+ return printJson({ ok: true, registryPath, ledgers: results });
15
+ printDueEntries(results);
16
+ process.stdout.write(`registry: ${registryPath}\n`);
17
+ return 0;
18
+ }
19
+ const entries = dueEntries(readLedger(ledgerPath));
20
+ const visible = entries.filter((entry) => entry.dueStatus !== "kept");
21
+ if (json)
22
+ return printJson({ ok: true, ledgerPath, entries });
23
+ if (visible.length === 0) {
24
+ process.stdout.write(`nothing due\nledger: ${ledgerPath}\n`);
25
+ return 0;
26
+ }
27
+ for (const entry of visible) {
28
+ process.stdout.write(`${entry.dueStatus} ${entry.id} ${entry.cleanup} ${entry.path} :: ${entry.reason}\n`);
29
+ }
30
+ process.stdout.write(`ledger: ${ledgerPath}\n`);
31
+ return 0;
32
+ }
@@ -0,0 +1,44 @@
1
+ import { findRecords, readLedger } from "../ledger.js";
2
+ import { normalizeRegistryPath } from "../registry.js";
3
+ import { printJson } from "../renderers/json.js";
4
+ import { arrayFlag, boolFlag, stringFlag } from "../shared/flags.js";
5
+ import { printLedgerEntries, printRegisteredLedgerValidation, validateRegisteredLedgersOrThrow } from "./shared.js";
6
+ export function handleFind(parsed, ledgerPath, json) {
7
+ if (boolFlag(parsed, "all")) {
8
+ const registryPath = normalizeRegistryPath(stringFlag(parsed, "registry"));
9
+ const validation = validateRegisteredLedgersOrThrow(registryPath);
10
+ if (!validation.ok)
11
+ return printRegisteredLedgerValidation(registryPath, validation.results, json);
12
+ const results = validation.results.map(({ ledger }) => ({
13
+ ledger,
14
+ entries: findRecords(readLedger(ledger.path), {
15
+ path: stringFlag(parsed, "path"),
16
+ owner: stringFlag(parsed, "owner"),
17
+ labels: arrayFlag(parsed, "label"),
18
+ status: stringFlag(parsed, "status")
19
+ })
20
+ }));
21
+ if (json)
22
+ return printJson({ ok: true, registryPath, ledgers: results });
23
+ printLedgerEntries(results);
24
+ process.stdout.write(`registry: ${registryPath}\n`);
25
+ return 0;
26
+ }
27
+ const records = findRecords(readLedger(ledgerPath), {
28
+ path: stringFlag(parsed, "path"),
29
+ owner: stringFlag(parsed, "owner"),
30
+ labels: arrayFlag(parsed, "label"),
31
+ status: stringFlag(parsed, "status")
32
+ });
33
+ if (json)
34
+ return printJson({ ok: true, ledgerPath, entries: records });
35
+ if (records.length === 0) {
36
+ process.stdout.write(`no matching artshelf entries\nledger: ${ledgerPath}\n`);
37
+ return 0;
38
+ }
39
+ for (const record of records) {
40
+ process.stdout.write(`${record.id} ${record.kind} ${record.status} ${record.cleanup} ${record.path} :: ${record.reason}\n`);
41
+ }
42
+ process.stdout.write(`ledger: ${ledgerPath}\n`);
43
+ return 0;
44
+ }
@@ -0,0 +1,31 @@
1
+ import { getRecord, readLedger } from "../ledger.js";
2
+ import { normalizeRegistryPath } from "../registry.js";
3
+ import { printJson } from "../renderers/json.js";
4
+ import { boolFlag, stringFlag } from "../shared/flags.js";
5
+ import { printRegisteredLedgerValidation, validateRegisteredLedgersOrThrow } from "./shared.js";
6
+ export function handleGet(parsed, ledgerPath, json) {
7
+ const id = parsed.positionals[0];
8
+ if (!id)
9
+ throw new Error("get requires <id>");
10
+ if (boolFlag(parsed, "all")) {
11
+ const registryPath = normalizeRegistryPath(stringFlag(parsed, "registry"));
12
+ const validation = validateRegisteredLedgersOrThrow(registryPath);
13
+ if (!validation.ok)
14
+ return printRegisteredLedgerValidation(registryPath, validation.results, json);
15
+ for (const { ledger } of validation.results) {
16
+ const record = readLedger(ledger.path).find((entry) => entry.id === id);
17
+ if (record) {
18
+ if (json)
19
+ return printJson({ ok: true, registryPath, ledger, record });
20
+ process.stdout.write(`${record.id} ${record.kind} ${record.status} ${record.cleanup} ${record.path}\nreason: ${record.reason}\nledger: ${ledger.path}\nregistry: ${registryPath}\n`);
21
+ return 0;
22
+ }
23
+ }
24
+ throw new Error(`Artshelf record not found: ${id}`);
25
+ }
26
+ const record = getRecord(readLedger(ledgerPath), id);
27
+ if (json)
28
+ return printJson({ ok: true, ledgerPath, record });
29
+ process.stdout.write(`${record.id} ${record.kind} ${record.status} ${record.cleanup} ${record.path}\nreason: ${record.reason}\nledger: ${ledgerPath}\n`);
30
+ return 0;
31
+ }
@@ -0,0 +1,69 @@
1
+ import { normalizeLedgerPath } from "../ledger.js";
2
+ import { boolFlag, stringFlag } from "../shared/flags.js";
3
+ import { handleCleanup } from "./cleanup.js";
4
+ import { handleDoctor } from "./doctor.js";
5
+ import { handleDue } from "./due.js";
6
+ import { handleFind } from "./find.js";
7
+ import { handleGet } from "./get.js";
8
+ import { handleLedgers } from "./ledgers.js";
9
+ import { handleList } from "./list.js";
10
+ import { handlePut } from "./put.js";
11
+ import { handleResolve } from "./resolve.js";
12
+ import { handleReview } from "./review.js";
13
+ import { handleStatus } from "./status.js";
14
+ import { handleTrash } from "./trash.js";
15
+ import { handleUpdate, maybeNotifyAvailableUpdate } from "./update.js";
16
+ import { handleValidate } from "./validate.js";
17
+ export { maybeNotifyAvailableUpdate };
18
+ export async function runCommand(parsed) {
19
+ let status = 0;
20
+ let shouldCheckForUpdate = true;
21
+ switch (parsed.command) {
22
+ case "put":
23
+ status = handlePut(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
24
+ break;
25
+ case "ledgers":
26
+ status = handleLedgers(parsed, boolFlag(parsed, "json"));
27
+ break;
28
+ case "list":
29
+ status = handleList(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
30
+ break;
31
+ case "find":
32
+ status = handleFind(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
33
+ break;
34
+ case "get":
35
+ status = handleGet(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
36
+ break;
37
+ case "due":
38
+ status = handleDue(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
39
+ break;
40
+ case "validate":
41
+ status = handleValidate(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
42
+ break;
43
+ case "cleanup":
44
+ status = handleCleanup(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
45
+ break;
46
+ case "trash":
47
+ status = handleTrash(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
48
+ break;
49
+ case "review":
50
+ status = handleReview(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
51
+ break;
52
+ case "doctor":
53
+ status = handleDoctor(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
54
+ break;
55
+ case "status":
56
+ status = handleStatus(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
57
+ break;
58
+ case "resolve":
59
+ status = handleResolve(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
60
+ break;
61
+ case "update":
62
+ shouldCheckForUpdate = false;
63
+ status = await handleUpdate(parsed, boolFlag(parsed, "json"));
64
+ break;
65
+ default:
66
+ throw new Error(`Unknown command: ${parsed.command}`);
67
+ }
68
+ return { status, shouldCheckForUpdate };
69
+ }
@@ -0,0 +1,111 @@
1
+ import { existsSync } from "node:fs";
2
+ import { normalizeLedgerPath } from "../ledger.js";
3
+ import { listRegisteredLedgers, normalizeRegistryPath, registerLedger } from "../registry.js";
4
+ import { printJson } from "../renderers/json.js";
5
+ import { boolFlag, requiredStringFlag, stringFlag } from "../shared/flags.js";
6
+ import { LEDGERS_HELP } from "../shared/help-text.js";
7
+ import { validateRegisteredLedger } from "./shared.js";
8
+ export function handleLedgers(parsed, json) {
9
+ const action = parsed.positionals[0] ?? "list";
10
+ const registryPath = normalizeRegistryPath(stringFlag(parsed, "registry"));
11
+ if (action === "help") {
12
+ process.stdout.write(LEDGERS_HELP);
13
+ return 0;
14
+ }
15
+ if (action === "add") {
16
+ const ledgerPath = normalizeLedgerPath(requiredStringFlag(parsed, "ledger"));
17
+ if (!existsSync(ledgerPath))
18
+ throw new Error(`Ledger does not exist: ${ledgerPath}`);
19
+ const entry = registerLedger({
20
+ ledgerPath,
21
+ name: stringFlag(parsed, "name"),
22
+ scope: stringFlag(parsed, "scope"),
23
+ registryPath
24
+ });
25
+ if (json)
26
+ return printJson({ ok: true, registryPath, ledger: entry });
27
+ process.stdout.write(`registered ${entry.name}\nledger: ${entry.path}\nregistry: ${registryPath}\n`);
28
+ return 0;
29
+ }
30
+ if (action === "list") {
31
+ if (boolFlag(parsed, "plain")) {
32
+ const ledgers = listRegisteredLedgers(registryPath);
33
+ if (json)
34
+ return printJson({ ok: true, registryPath, ledgers });
35
+ if (ledgers.length === 0) {
36
+ process.stdout.write(`no registered Artshelf ledgers\nregistry: ${registryPath}\n`);
37
+ return 0;
38
+ }
39
+ for (const ledger of ledgers)
40
+ process.stdout.write(`${ledger.name} ${ledger.scope} ${ledger.path}\n`);
41
+ process.stdout.write(`registry: ${registryPath}\n`);
42
+ return 0;
43
+ }
44
+ const report = buildLedgersReport(registryPath);
45
+ if (json) {
46
+ printJson(report);
47
+ return report.ok ? 0 : 1;
48
+ }
49
+ printLedgersList(report);
50
+ return report.ok ? 0 : 1;
51
+ }
52
+ throw new Error(`Unknown ledgers action: ${action}`);
53
+ }
54
+ function buildLedgersReport(registryPath) {
55
+ let registryOk = true;
56
+ let registryError = null;
57
+ let entries = [];
58
+ try {
59
+ entries = listRegisteredLedgers(registryPath);
60
+ }
61
+ catch (error) {
62
+ registryOk = false;
63
+ registryError = error.message;
64
+ }
65
+ const ledgers = entries.map((entry) => {
66
+ const result = validateRegisteredLedger(entry);
67
+ const status = result.ok ? "ok" : existsSync(entry.path) ? "invalid" : "missing";
68
+ return {
69
+ ...entry,
70
+ status,
71
+ ok: result.ok,
72
+ entries: result.entries,
73
+ errors: result.errors,
74
+ warnings: result.warnings
75
+ };
76
+ });
77
+ const summary = {
78
+ ledgers: ledgers.length,
79
+ ok: ledgers.filter((ledger) => ledger.status === "ok").length,
80
+ stale: ledgers.filter((ledger) => ledger.status === "missing").length,
81
+ invalid: ledgers.filter((ledger) => ledger.status === "invalid").length,
82
+ warnings: ledgers.reduce((count, ledger) => count + ledger.warnings.length, 0)
83
+ };
84
+ return {
85
+ ok: registryOk && summary.stale === 0 && summary.invalid === 0,
86
+ registryPath,
87
+ registryExists: existsSync(registryPath),
88
+ registryOk,
89
+ registryError,
90
+ ledgers,
91
+ summary
92
+ };
93
+ }
94
+ function printLedgersList(report) {
95
+ process.stdout.write(`artshelf ledgers: ${report.ok ? "ok" : "needs attention"}\n`);
96
+ process.stdout.write(`registry: ${report.registryPath}${report.registryExists ? "" : " (absent)"} — ${report.summary.ledgers} ledgers (${report.summary.ok} ok, ${report.summary.stale} stale, ${report.summary.invalid} invalid)\n`);
97
+ if (report.registryError)
98
+ process.stdout.write(`registry error: ${report.registryError}\n`);
99
+ if (report.ledgers.length === 0) {
100
+ process.stdout.write("no registered Artshelf ledgers\n");
101
+ return;
102
+ }
103
+ for (const ledger of report.ledgers) {
104
+ if (ledger.status === "ok") {
105
+ process.stdout.write(`[${ledger.name}] ok ${ledger.scope}: ${ledger.entries} entries, ${ledger.warnings.length} warnings — ${ledger.path}\n`);
106
+ }
107
+ else {
108
+ process.stdout.write(`[${ledger.name}] ${ledger.status} ${ledger.scope}: ${ledger.errors.join("; ")} — ${ledger.path}\n`);
109
+ }
110
+ }
111
+ }
@@ -0,0 +1,36 @@
1
+ import { filterRecordsByStatus, readLedger } from "../ledger.js";
2
+ import { normalizeRegistryPath } from "../registry.js";
3
+ import { printJson } from "../renderers/json.js";
4
+ import { boolFlag, stringFlag } from "../shared/flags.js";
5
+ import { printLedgerEntries, printRegisteredLedgerValidation, validateRegisteredLedgersOrThrow } from "./shared.js";
6
+ export function handleList(parsed, ledgerPath, json) {
7
+ if (boolFlag(parsed, "all")) {
8
+ const registryPath = normalizeRegistryPath(stringFlag(parsed, "registry"));
9
+ const status = stringFlag(parsed, "status");
10
+ const validation = validateRegisteredLedgersOrThrow(registryPath);
11
+ if (!validation.ok)
12
+ return printRegisteredLedgerValidation(registryPath, validation.results, json);
13
+ const results = validation.results.map(({ ledger }) => ({
14
+ ledger,
15
+ entries: filterRecordsByStatus(readLedger(ledger.path), status)
16
+ }));
17
+ if (json)
18
+ return printJson({ ok: true, registryPath, ...(status ? { status } : {}), ledgers: results });
19
+ printLedgerEntries(results, status);
20
+ process.stdout.write(`registry: ${registryPath}\n`);
21
+ return 0;
22
+ }
23
+ const status = stringFlag(parsed, "status");
24
+ const records = filterRecordsByStatus(readLedger(ledgerPath), status);
25
+ if (json)
26
+ return printJson({ ok: true, ledgerPath, ...(status ? { status } : {}), entries: records });
27
+ if (records.length === 0) {
28
+ process.stdout.write(`no artshelf entries${status ? ` with status ${status}` : ""}\nledger: ${ledgerPath}\n`);
29
+ return 0;
30
+ }
31
+ for (const record of records) {
32
+ process.stdout.write(`${record.id} ${record.kind} ${record.status} ${record.cleanup} ${record.path} :: ${record.reason}\n`);
33
+ }
34
+ process.stdout.write(`ledger: ${ledgerPath}\n`);
35
+ return 0;
36
+ }
@@ -0,0 +1,36 @@
1
+ import { appendPreparedRecord, prepareRecord } from "../ledger.js";
2
+ import { normalizeRegistryPath, registerLedger } from "../registry.js";
3
+ import { printJson } from "../renderers/json.js";
4
+ import { arrayFlag, boolFlag, requiredStringFlag, stringFlag } from "../shared/flags.js";
5
+ export function handlePut(parsed, ledgerPath, json) {
6
+ const path = parsed.positionals[0];
7
+ if (!path)
8
+ throw new Error("put requires <path>");
9
+ const record = prepareRecord({
10
+ path,
11
+ reason: requiredStringFlag(parsed, "reason"),
12
+ ttl: stringFlag(parsed, "ttl"),
13
+ retainUntil: stringFlag(parsed, "retain-until"),
14
+ manualReview: boolFlag(parsed, "manual-review"),
15
+ kind: stringFlag(parsed, "kind"),
16
+ cleanup: stringFlag(parsed, "cleanup"),
17
+ owner: stringFlag(parsed, "owner"),
18
+ labels: arrayFlag(parsed, "label")
19
+ });
20
+ const registryPath = normalizeRegistryPath(stringFlag(parsed, "registry"));
21
+ appendPreparedRecord(ledgerPath, record);
22
+ let ledger;
23
+ let registryError;
24
+ try {
25
+ ledger = registerLedger({ ledgerPath, registryPath });
26
+ }
27
+ catch (error) {
28
+ registryError = error.message;
29
+ }
30
+ if (json)
31
+ return printJson({ ok: true, record, ledgerPath, registryPath, ...(ledger ? { ledger } : {}), ...(registryError ? { registryError } : {}) });
32
+ process.stdout.write(`recorded ${record.id}\npath: ${record.path}\nretains until: ${record.retainUntil ?? "manual review"}\nledger: ${ledgerPath}\n`);
33
+ if (registryError)
34
+ process.stdout.write(`registry warning: ${registryError}\n`);
35
+ return 0;
36
+ }
@@ -0,0 +1,17 @@
1
+ import { resolveRecord } from "../ledger.js";
2
+ import { printJson } from "../renderers/json.js";
3
+ import { requiredStringFlag } from "../shared/flags.js";
4
+ export function handleResolve(parsed, ledgerPath, json) {
5
+ const id = parsed.positionals[0];
6
+ if (!id)
7
+ throw new Error("resolve requires <id>");
8
+ const record = resolveRecord(ledgerPath, {
9
+ id,
10
+ status: requiredStringFlag(parsed, "status"),
11
+ reason: requiredStringFlag(parsed, "reason")
12
+ });
13
+ if (json)
14
+ return printJson({ ok: true, record, ledgerPath });
15
+ process.stdout.write(`resolved ${record.id}\nstatus: ${record.status}\nledger: ${ledgerPath}\n`);
16
+ return 0;
17
+ }
@@ -0,0 +1,38 @@
1
+ import { existsSync } from "node:fs";
2
+ import { normalizeRegistryPath } from "../registry.js";
3
+ import { printCompactJson, printJson } from "../renderers/json.js";
4
+ import { buildReviewAgentPacketAll, buildReviewAgentPacketSingle, printReview, printReviewAll, reviewNextAction } from "../renderers/review.js";
5
+ import { boolFlag, stringFlag } from "../shared/flags.js";
6
+ import { registeredLedgersOrThrow, reviewJsonResult, reviewLedger, summarizeReview } from "./shared.js";
7
+ export function handleReview(parsed, ledgerPath, json) {
8
+ const agent = boolFlag(parsed, "agent");
9
+ if (boolFlag(parsed, "all")) {
10
+ const registryPath = normalizeRegistryPath(stringFlag(parsed, "registry"));
11
+ const results = registeredLedgersOrThrow(registryPath).map((ledger) => reviewLedger(ledger));
12
+ const ok = results.every((entry) => entry.validate.ok);
13
+ const summary = summarizeReview(results);
14
+ if (agent) {
15
+ printCompactJson(buildReviewAgentPacketAll(results, summary, { path: registryPath, exists: existsSync(registryPath) }));
16
+ return ok ? 0 : 1;
17
+ }
18
+ const nextAction = reviewNextAction(summary, "all");
19
+ if (json) {
20
+ printJson({ ok, registryPath, summary, nextAction, ledgers: results.map(reviewJsonResult) });
21
+ return ok ? 0 : 1;
22
+ }
23
+ printReviewAll(results, summary, nextAction, registryPath);
24
+ return ok ? 0 : 1;
25
+ }
26
+ const result = reviewLedger({ name: "current", path: ledgerPath, scope: "other", createdAt: "", updatedAt: "" }, false);
27
+ const summary = summarizeReview([result]);
28
+ if (agent) {
29
+ printCompactJson(buildReviewAgentPacketSingle(result, summary, ledgerPath));
30
+ return result.validate.ok ? 0 : 1;
31
+ }
32
+ if (json) {
33
+ printJson({ ok: result.validate.ok, ledger: reviewJsonResult(result) });
34
+ return result.validate.ok ? 0 : 1;
35
+ }
36
+ printReview([result]);
37
+ return result.validate.ok ? 0 : 1;
38
+ }