artshelf 0.7.0 → 0.8.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 +11 -2
- package/README.md +144 -245
- package/SPEC.md +48 -0
- package/dist/src/cli.js +258 -19
- package/docs/agent-clean.html +6 -24
- package/docs/agent-monitor.html +1 -0
- package/docs/agent-purge.html +111 -0
- package/docs/agent-usage.html +13 -9
- package/docs/agent-usage.md +12 -7
- package/docs/index.html +9 -4
- package/docs/install.html +28 -2
- package/docs/reference.html +27 -2
- package/docs/site.js +2 -1
- package/package.json +1 -1
- package/skills/artshelf/SKILL.md +16 -13
package/dist/src/cli.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
3
6
|
import { appendPreparedRecord, createCleanupPlan, createTrashPurgePlan, dueEntries, executeCleanupPlan, executeTrashPurgePlan, filterRecordsByStatus, findRecords, getRecord, listTrashedRecords, normalizeLedgerPath, prepareRecord, previewCleanupPlan, readLedger, resolveRecord, validateLedger } from "./ledger.js";
|
|
4
7
|
import { listRegisteredLedgers, normalizeRegistryPath, registerLedger } from "./registry.js";
|
|
5
8
|
const VERSION = readPackageVersion();
|
|
9
|
+
const PACKAGE_NAME = "artshelf";
|
|
10
|
+
const NPM_REGISTRY_URL = process.env.ARTSHELF_NPM_REGISTRY_URL ?? `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
11
|
+
const UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1000;
|
|
6
12
|
const BOOLEAN_FLAGS = new Set(["all", "json", "manual-review", "dry-run", "execute", "help", "version", "plain"]);
|
|
7
13
|
const VALUE_FLAGS = new Set([
|
|
8
14
|
"cleanup",
|
|
@@ -29,56 +35,83 @@ function readPackageVersion() {
|
|
|
29
35
|
}
|
|
30
36
|
return packageJson.version;
|
|
31
37
|
}
|
|
32
|
-
function main(argv) {
|
|
38
|
+
async function main(argv) {
|
|
33
39
|
try {
|
|
34
40
|
const parsed = parseArgs(argv);
|
|
41
|
+
let status = 0;
|
|
42
|
+
let shouldCheckForUpdate = true;
|
|
35
43
|
if (parsed.command === "--version" || parsed.command === "-v" || boolFlag(parsed, "version")) {
|
|
36
44
|
process.stdout.write(`artshelf ${VERSION}\n`);
|
|
37
|
-
return 0;
|
|
45
|
+
return maybeNotifyUpdateAndReturn(0, parsed);
|
|
38
46
|
}
|
|
39
47
|
if (parsed.command === "help" || parsed.command === "--help" || parsed.command === "-h" || boolFlag(parsed, "help")) {
|
|
40
48
|
printHelp(parsed.command === "help" ? parsed.positionals[0] : parsed.command);
|
|
41
|
-
return 0;
|
|
49
|
+
return maybeNotifyUpdateAndReturn(0, parsed);
|
|
42
50
|
}
|
|
43
51
|
switch (parsed.command) {
|
|
44
52
|
case "put":
|
|
45
|
-
|
|
53
|
+
status = handlePut(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
|
|
54
|
+
break;
|
|
46
55
|
case "ledgers":
|
|
47
|
-
|
|
56
|
+
status = handleLedgers(parsed, boolFlag(parsed, "json"));
|
|
57
|
+
break;
|
|
48
58
|
case "list":
|
|
49
|
-
|
|
59
|
+
status = handleList(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
|
|
60
|
+
break;
|
|
50
61
|
case "find":
|
|
51
|
-
|
|
62
|
+
status = handleFind(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
|
|
63
|
+
break;
|
|
52
64
|
case "get":
|
|
53
|
-
|
|
65
|
+
status = handleGet(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
|
|
66
|
+
break;
|
|
54
67
|
case "due":
|
|
55
|
-
|
|
68
|
+
status = handleDue(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
|
|
69
|
+
break;
|
|
56
70
|
case "validate":
|
|
57
|
-
|
|
71
|
+
status = handleValidate(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
|
|
72
|
+
break;
|
|
58
73
|
case "cleanup":
|
|
59
|
-
|
|
74
|
+
status = handleCleanup(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
|
|
75
|
+
break;
|
|
60
76
|
case "trash":
|
|
61
|
-
|
|
77
|
+
status = handleTrash(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
|
|
78
|
+
break;
|
|
62
79
|
case "review":
|
|
63
|
-
|
|
80
|
+
status = handleReview(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
|
|
81
|
+
break;
|
|
64
82
|
case "doctor":
|
|
65
|
-
|
|
83
|
+
status = handleDoctor(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
|
|
84
|
+
break;
|
|
66
85
|
case "status":
|
|
67
|
-
|
|
86
|
+
status = handleStatus(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
|
|
87
|
+
break;
|
|
68
88
|
case "resolve":
|
|
69
|
-
|
|
89
|
+
status = handleResolve(parsed, normalizeLedgerPath(stringFlag(parsed, "ledger")), boolFlag(parsed, "json"));
|
|
90
|
+
break;
|
|
91
|
+
case "update":
|
|
92
|
+
shouldCheckForUpdate = false;
|
|
93
|
+
status = await handleUpdate(parsed, boolFlag(parsed, "json"));
|
|
94
|
+
break;
|
|
70
95
|
case undefined:
|
|
71
96
|
printHelp();
|
|
72
|
-
|
|
97
|
+
status = 0;
|
|
98
|
+
break;
|
|
73
99
|
default:
|
|
74
100
|
throw new Error(`Unknown command: ${parsed.command}`);
|
|
75
101
|
}
|
|
102
|
+
if (!shouldCheckForUpdate)
|
|
103
|
+
return status;
|
|
104
|
+
return maybeNotifyUpdateAndReturn(status, parsed);
|
|
76
105
|
}
|
|
77
106
|
catch (error) {
|
|
78
107
|
process.stderr.write(`artshelf: ${error.message}\nRun \`artshelf help\` for usage.\n`);
|
|
79
108
|
return 1;
|
|
80
109
|
}
|
|
81
110
|
}
|
|
111
|
+
async function maybeNotifyUpdateAndReturn(status, parsed) {
|
|
112
|
+
await maybeNotifyAvailableUpdate(parsed);
|
|
113
|
+
return status;
|
|
114
|
+
}
|
|
82
115
|
function handlePut(parsed, ledgerPath, json) {
|
|
83
116
|
const path = parsed.positionals[0];
|
|
84
117
|
if (!path)
|
|
@@ -726,6 +759,189 @@ function printStatusSingle(ledger) {
|
|
|
726
759
|
process.stdout.write(`error: ${message}\n`);
|
|
727
760
|
}
|
|
728
761
|
}
|
|
762
|
+
async function handleUpdate(parsed, json) {
|
|
763
|
+
if (parsed.positionals.length > 0)
|
|
764
|
+
throw new Error("update does not accept positional arguments");
|
|
765
|
+
const info = await getUpdateInfo({ force: true });
|
|
766
|
+
if (!info)
|
|
767
|
+
throw new Error("Could not check npm for the latest Artshelf version");
|
|
768
|
+
if (!info.updateAvailable) {
|
|
769
|
+
if (json)
|
|
770
|
+
return printJson({ ok: true, updated: false, current: info.current, latest: info.latest });
|
|
771
|
+
process.stdout.write(`artshelf is already up to date: v${info.current}\n`);
|
|
772
|
+
return 0;
|
|
773
|
+
}
|
|
774
|
+
if (process.env.ARTSHELF_UPDATE_DRY_RUN === "1") {
|
|
775
|
+
if (json) {
|
|
776
|
+
return printJson({
|
|
777
|
+
ok: true,
|
|
778
|
+
updated: false,
|
|
779
|
+
dryRun: true,
|
|
780
|
+
current: info.current,
|
|
781
|
+
latest: info.latest,
|
|
782
|
+
command: ["npm", "install", "-g", `${PACKAGE_NAME}@latest`]
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
process.stdout.write(`A new version of artshelf is available: v${info.current} -> v${info.latest}\n`);
|
|
786
|
+
process.stdout.write(`Dry run: would run "npm install -g ${PACKAGE_NAME}@latest"\n`);
|
|
787
|
+
return 0;
|
|
788
|
+
}
|
|
789
|
+
if (!json) {
|
|
790
|
+
process.stdout.write(`A new version of artshelf is available: v${info.current} -> v${info.latest}\n`);
|
|
791
|
+
process.stdout.write(`Updating with "npm install -g ${PACKAGE_NAME}@latest"...\n`);
|
|
792
|
+
}
|
|
793
|
+
const result = json
|
|
794
|
+
? spawnSync("npm", ["install", "-g", `${PACKAGE_NAME}@latest`], { encoding: "utf8" })
|
|
795
|
+
: spawnSync("npm", ["install", "-g", `${PACKAGE_NAME}@latest`], { stdio: "inherit" });
|
|
796
|
+
const status = result.status ?? 1;
|
|
797
|
+
const spawnError = result.error instanceof Error ? result.error.message : "";
|
|
798
|
+
if (json) {
|
|
799
|
+
const stderr = typeof result.stderr === "string" ? result.stderr : "";
|
|
800
|
+
printJson({
|
|
801
|
+
ok: status === 0,
|
|
802
|
+
updated: status === 0,
|
|
803
|
+
current: info.current,
|
|
804
|
+
latest: info.latest,
|
|
805
|
+
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
806
|
+
stderr: appendOutputMessage(stderr, spawnError)
|
|
807
|
+
});
|
|
808
|
+
return status;
|
|
809
|
+
}
|
|
810
|
+
if (spawnError)
|
|
811
|
+
process.stderr.write(`Update failed: ${spawnError}\n`);
|
|
812
|
+
if (status === 0)
|
|
813
|
+
process.stdout.write(`artshelf updated to v${info.latest}\n`);
|
|
814
|
+
return status;
|
|
815
|
+
}
|
|
816
|
+
function appendOutputMessage(output, message) {
|
|
817
|
+
if (!message)
|
|
818
|
+
return output;
|
|
819
|
+
if (!output)
|
|
820
|
+
return message;
|
|
821
|
+
return `${output}${output.endsWith("\n") ? "" : "\n"}${message}`;
|
|
822
|
+
}
|
|
823
|
+
async function maybeNotifyAvailableUpdate(parsed) {
|
|
824
|
+
if (process.env.ARTSHELF_NO_UPDATE_CHECK === "1")
|
|
825
|
+
return;
|
|
826
|
+
if (parsed.command === "update")
|
|
827
|
+
return;
|
|
828
|
+
const info = await getUpdateInfo({ force: false });
|
|
829
|
+
if (!info?.updateAvailable)
|
|
830
|
+
return;
|
|
831
|
+
process.stderr.write(`A new version of artshelf is available: v${info.current} -> v${info.latest}\n`);
|
|
832
|
+
process.stderr.write(`Run "artshelf update" to update npm installs\n`);
|
|
833
|
+
}
|
|
834
|
+
async function getUpdateInfo(options) {
|
|
835
|
+
const latest = await getLatestVersion(options);
|
|
836
|
+
if (!latest)
|
|
837
|
+
return null;
|
|
838
|
+
return {
|
|
839
|
+
current: VERSION,
|
|
840
|
+
latest,
|
|
841
|
+
updateAvailable: compareVersions(latest, VERSION) > 0
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
async function getLatestVersion(options) {
|
|
845
|
+
const override = process.env.ARTSHELF_LATEST_VERSION;
|
|
846
|
+
if (override)
|
|
847
|
+
return normalizeVersion(override);
|
|
848
|
+
if (!options.force) {
|
|
849
|
+
const cached = readUpdateCache();
|
|
850
|
+
if (cached)
|
|
851
|
+
return cached.latest;
|
|
852
|
+
}
|
|
853
|
+
const latest = await fetchLatestNpmVersion();
|
|
854
|
+
writeUpdateCache(latest);
|
|
855
|
+
return latest;
|
|
856
|
+
}
|
|
857
|
+
function readUpdateCache() {
|
|
858
|
+
const ttl = Number(process.env.ARTSHELF_UPDATE_CHECK_TTL_MS ?? UPDATE_CHECK_TTL_MS);
|
|
859
|
+
if (ttl < 0)
|
|
860
|
+
return null;
|
|
861
|
+
const cachePath = updateCachePath();
|
|
862
|
+
if (!existsSync(cachePath))
|
|
863
|
+
return null;
|
|
864
|
+
try {
|
|
865
|
+
const cache = JSON.parse(readFileSync(cachePath, "utf8"));
|
|
866
|
+
if (cache.latest !== null && typeof cache.latest !== "string")
|
|
867
|
+
return null;
|
|
868
|
+
if (typeof cache.checkedAt !== "number")
|
|
869
|
+
return null;
|
|
870
|
+
if (Date.now() - cache.checkedAt > ttl)
|
|
871
|
+
return null;
|
|
872
|
+
return { latest: cache.latest === null ? null : normalizeVersion(cache.latest) };
|
|
873
|
+
}
|
|
874
|
+
catch {
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
function writeUpdateCache(latest) {
|
|
879
|
+
try {
|
|
880
|
+
const cachePath = updateCachePath();
|
|
881
|
+
const dir = dirname(cachePath);
|
|
882
|
+
if (dir) {
|
|
883
|
+
mkdirSync(dir, { recursive: true });
|
|
884
|
+
writeFileSync(cachePath, `${JSON.stringify({ latest, checkedAt: Date.now() }, null, 2)}\n`);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
catch {
|
|
888
|
+
// Update checks should never affect normal CLI behavior.
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
async function fetchLatestNpmVersion() {
|
|
892
|
+
const controller = new AbortController();
|
|
893
|
+
const timeout = setTimeout(() => controller.abort(), 750);
|
|
894
|
+
try {
|
|
895
|
+
const response = await fetch(NPM_REGISTRY_URL, {
|
|
896
|
+
signal: controller.signal,
|
|
897
|
+
headers: { accept: "application/json", "user-agent": `artshelf/${VERSION}` }
|
|
898
|
+
});
|
|
899
|
+
if (!response.ok)
|
|
900
|
+
return null;
|
|
901
|
+
const body = await response.json();
|
|
902
|
+
if (!body || typeof body !== "object" || typeof body.version !== "string")
|
|
903
|
+
return null;
|
|
904
|
+
return normalizeVersion(body.version);
|
|
905
|
+
}
|
|
906
|
+
catch {
|
|
907
|
+
return null;
|
|
908
|
+
}
|
|
909
|
+
finally {
|
|
910
|
+
clearTimeout(timeout);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
function updateCachePath() {
|
|
914
|
+
return process.env.ARTSHELF_UPDATE_CACHE ?? join(homedir(), ".artshelf", "update-check.json");
|
|
915
|
+
}
|
|
916
|
+
function normalizeVersion(version) {
|
|
917
|
+
return version.trim().replace(/^v/i, "");
|
|
918
|
+
}
|
|
919
|
+
function compareVersions(left, right) {
|
|
920
|
+
const a = parseVersion(left);
|
|
921
|
+
const b = parseVersion(right);
|
|
922
|
+
for (let index = 0; index < Math.max(a.numbers.length, b.numbers.length); index += 1) {
|
|
923
|
+
const diff = (a.numbers[index] ?? 0) - (b.numbers[index] ?? 0);
|
|
924
|
+
if (diff !== 0)
|
|
925
|
+
return diff;
|
|
926
|
+
}
|
|
927
|
+
if (a.prerelease === b.prerelease)
|
|
928
|
+
return 0;
|
|
929
|
+
if (!a.prerelease)
|
|
930
|
+
return 1;
|
|
931
|
+
if (!b.prerelease)
|
|
932
|
+
return -1;
|
|
933
|
+
return a.prerelease.localeCompare(b.prerelease);
|
|
934
|
+
}
|
|
935
|
+
function parseVersion(version) {
|
|
936
|
+
const [main = "", prerelease = ""] = normalizeVersion(version).split("-", 2);
|
|
937
|
+
return {
|
|
938
|
+
numbers: main.split(".").map((part) => {
|
|
939
|
+
const parsed = Number.parseInt(part, 10);
|
|
940
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
941
|
+
}),
|
|
942
|
+
prerelease
|
|
943
|
+
};
|
|
944
|
+
}
|
|
729
945
|
function parseArgs(argv) {
|
|
730
946
|
const [command, ...rest] = argv;
|
|
731
947
|
const flags = new Map();
|
|
@@ -1107,6 +1323,21 @@ Human output is short enough to paste into a chat; \`artshelf status --all --jso
|
|
|
1107
1323
|
is suitable for cron and reporting. Status is read-only: it never creates plans
|
|
1108
1324
|
or receipts and never mutates records. A healthy selected ledger exits 0; with
|
|
1109
1325
|
--all, a broken registry or any stale or invalid registered ledger exits non-zero.
|
|
1326
|
+
`);
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
if (command === "update") {
|
|
1330
|
+
process.stdout.write(`Usage:
|
|
1331
|
+
artshelf update [--json]
|
|
1332
|
+
|
|
1333
|
+
Update checks compare the current CLI version with the latest published npm
|
|
1334
|
+
version. Normal commands may print a non-blocking update notice to stderr when a
|
|
1335
|
+
newer version is available. Run update to upgrade npm global installs only:
|
|
1336
|
+
|
|
1337
|
+
npm install -g artshelf@latest
|
|
1338
|
+
|
|
1339
|
+
pnpm global installs should update with pnpm add -g artshelf@latest; source
|
|
1340
|
+
installs should update by pulling, rebuilding, and linking the checkout.
|
|
1110
1341
|
`);
|
|
1111
1342
|
return;
|
|
1112
1343
|
}
|
|
@@ -1132,6 +1363,7 @@ Usage:
|
|
|
1132
1363
|
artshelf doctor [--json]
|
|
1133
1364
|
artshelf status [--json]
|
|
1134
1365
|
artshelf status --all [--json]
|
|
1366
|
+
artshelf update [--json]
|
|
1135
1367
|
artshelf cleanup --dry-run [--json]
|
|
1136
1368
|
artshelf cleanup --dry-run --all [--json]
|
|
1137
1369
|
artshelf cleanup --execute --plan-id <id> [--json]
|
|
@@ -1154,4 +1386,11 @@ Examples:
|
|
|
1154
1386
|
artshelf cleanup --execute --plan-id plan_20260601_120000_ab12
|
|
1155
1387
|
`);
|
|
1156
1388
|
}
|
|
1157
|
-
|
|
1389
|
+
main(process.argv.slice(2))
|
|
1390
|
+
.then((status) => {
|
|
1391
|
+
process.exitCode = status;
|
|
1392
|
+
})
|
|
1393
|
+
.catch((error) => {
|
|
1394
|
+
process.stderr.write(`artshelf: ${error.message}\nRun \`artshelf help\` for usage.\n`);
|
|
1395
|
+
process.exitCode = 1;
|
|
1396
|
+
});
|
package/docs/agent-clean.html
CHANGED
|
@@ -47,11 +47,11 @@
|
|
|
47
47
|
<ul class="boundary-list">
|
|
48
48
|
<li>
|
|
49
49
|
<span class="stamp readonly">Allowed freely</span>
|
|
50
|
-
<span><code>validate</code>, <code>review</code>,
|
|
50
|
+
<span><code>validate</code>, <code>review</code>, and <code>cleanup --dry-run</code>.</span>
|
|
51
51
|
</li>
|
|
52
52
|
<li>
|
|
53
53
|
<span class="stamp approval">Needs approval</span>
|
|
54
|
-
<span><code>cleanup --execute --plan-id</code>
|
|
54
|
+
<span><code>cleanup --execute --plan-id</code> for one reviewed plan.</span>
|
|
55
55
|
</li>
|
|
56
56
|
<li>
|
|
57
57
|
<span class="stamp refused">Refused</span>
|
|
@@ -70,31 +70,13 @@ artshelf cleanup --execute --plan-id <id></code></pre>
|
|
|
70
70
|
<span class="callout-label">Hard boundary</span>
|
|
71
71
|
<p>
|
|
72
72
|
Cleanup execution needs explicit human approval for the reviewed plan id.
|
|
73
|
-
<code>cleanup=
|
|
73
|
+
<code>cleanup=trash</code> quarantines files into Artshelf trash — recoverable,
|
|
74
|
+
not deleted — and <code>cleanup=delete</code> stays refused. Emptying the trash
|
|
75
|
+
for good is a separate stage: see <a href="agent-purge.html">Purge</a>.
|
|
74
76
|
</p>
|
|
75
77
|
</div>
|
|
76
78
|
</section>
|
|
77
79
|
|
|
78
|
-
<section>
|
|
79
|
-
<h2>Clear the trash</h2>
|
|
80
|
-
<p>
|
|
81
|
-
<code>cleanup=trash</code> moves artifacts into Artshelf trash, so Clean is
|
|
82
|
-
not finished until the trash is cleared. Purging runs the loop one more
|
|
83
|
-
time: list what is in trash, preview an age-based purge plan, and execute
|
|
84
|
-
only after a human approves that purge plan id. Physical deletion never
|
|
85
|
-
piggybacks on the cleanup plan that trashed the file; the purge plan is
|
|
86
|
-
always separate and separately reviewed.
|
|
87
|
-
</p>
|
|
88
|
-
<pre><code><span class="c"># what is in trash for this ledger</span>
|
|
89
|
-
artshelf trash list --ledger <ledger-path> --json
|
|
90
|
-
|
|
91
|
-
<span class="c"># preview an age-based purge and get a purge plan id</span>
|
|
92
|
-
artshelf trash purge --older-than 7d --dry-run --ledger <ledger-path> --json
|
|
93
|
-
|
|
94
|
-
<span class="c"># delete for real, only with the reviewed purge plan id</span>
|
|
95
|
-
artshelf trash purge --execute --plan-id <purge-plan-id> --ledger <ledger-path> --json</code></pre>
|
|
96
|
-
</section>
|
|
97
|
-
|
|
98
80
|
<section>
|
|
99
81
|
<h2>Resolve confirmed records</h2>
|
|
100
82
|
<p>Resolve only updates the ledger; it does not move or delete files.</p>
|
|
@@ -106,7 +88,7 @@ artshelf resolve <id> --status resolved --reason <text></code></pre>
|
|
|
106
88
|
|
|
107
89
|
<section>
|
|
108
90
|
<h2>Verify quiet</h2>
|
|
109
|
-
<p>After cleanup execute
|
|
91
|
+
<p>After cleanup execute or resolve, verify with <code>artshelf review --all --json</code>.</p>
|
|
110
92
|
<p>
|
|
111
93
|
Execution writes a receipt and updates touched ledger records to
|
|
112
94
|
<code>trashed</code>, <code>review-required</code>, or
|
package/docs/agent-monitor.html
CHANGED
|
@@ -89,6 +89,7 @@ artshelf find --all --owner <agent-or-runtime> --json</code></pre>
|
|
|
89
89
|
<ul>
|
|
90
90
|
<li><strong>Read-only.</strong> Validate, status, due, review, doctor, and trash list are fine.</li>
|
|
91
91
|
<li><strong>Quiet by default.</strong> Send nothing when the review is clean unless a summary was requested.</li>
|
|
92
|
+
<li><strong>No network mode.</strong> Set <code>ARTSHELF_NO_UPDATE_CHECK=1</code> when jobs must avoid npm update checks and cache writes.</li>
|
|
92
93
|
<li><strong>Never schedule execution.</strong> Scheduled jobs must not run cleanup execute or trash purge execute.</li>
|
|
93
94
|
</ul>
|
|
94
95
|
<pre><code><span class="c"># ledger health, current ledger or all registered ledgers</span>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Purge · Agent usage · Artshelf</title>
|
|
7
|
+
<meta name="description" content="How agents purge Artshelf trash: physical deletion only from a separate, separately reviewed purge plan.">
|
|
8
|
+
<script>(function(){var stored=null;try{stored=localStorage.getItem("artshelf-docs-theme");}catch(e){}var dark=false;try{dark=matchMedia("(prefers-color-scheme: dark)").matches;}catch(e){}document.documentElement.dataset.theme=stored||(dark?"dark":"light");})();</script>
|
|
9
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
+
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,450..680;1,9..144,450..680&family=Newsreader:ital,opsz,wght@0,6..72,400..650;1,6..72,400..650&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
12
|
+
<link rel="stylesheet" href="site.css">
|
|
13
|
+
<script src="site.js" defer></script>
|
|
14
|
+
</head>
|
|
15
|
+
<body data-page="agent-purge.html">
|
|
16
|
+
<a class="skip" href="#content">Skip to content</a>
|
|
17
|
+
<header class="masthead">
|
|
18
|
+
<div class="masthead-inner">
|
|
19
|
+
<button class="menu-btn" type="button" data-menu aria-label="Toggle navigation" aria-expanded="false"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M1 3.5h14M1 8h14M1 12.5h14" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></button>
|
|
20
|
+
<a class="brand" href="index.html">Artshelf<span class="brand-tag">docs</span></a>
|
|
21
|
+
<button class="search-btn" type="button" data-search-open><span>Search docs</span><kbd>/</kbd></button>
|
|
22
|
+
<div class="masthead-tools">
|
|
23
|
+
<a class="gh" href="https://github.com/calvinnwq/artshelf">GitHub</a>
|
|
24
|
+
<button class="theme-btn" type="button" data-theme-toggle aria-label="Toggle color theme" aria-pressed="false">
|
|
25
|
+
<svg class="icon-moon" viewBox="0 0 20 20" aria-hidden="true"><path d="M14.6 12.1A6.5 6.5 0 0 1 7.4 2.7a6.5 6.5 0 1 0 7.2 9.4z" fill="currentColor"/></svg>
|
|
26
|
+
<svg class="icon-sun" viewBox="0 0 20 20" aria-hidden="true"><circle cx="10" cy="10" r="3.4" fill="currentColor"/><g stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><line x1="10" y1="2" x2="10" y2="4"/><line x1="10" y1="16" x2="10" y2="18"/><line x1="2" y1="10" x2="4" y2="10"/><line x1="16" y1="10" x2="18" y2="10"/><line x1="4.2" y1="4.2" x2="5.6" y2="5.6"/><line x1="14.4" y1="14.4" x2="15.8" y2="15.8"/><line x1="4.2" y1="15.8" x2="5.6" y2="14.4"/><line x1="14.4" y1="5.6" x2="15.8" y2="4.2"/></g></svg>
|
|
27
|
+
</button>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</header>
|
|
31
|
+
|
|
32
|
+
<div class="frame">
|
|
33
|
+
<nav id="sidebar" class="sidebar" aria-label="Documentation"></nav>
|
|
34
|
+
|
|
35
|
+
<main id="content" class="article-col">
|
|
36
|
+
<article>
|
|
37
|
+
<p class="kicker"><span class="n">4.5</span>Agents · Purge</p>
|
|
38
|
+
<h1>Delete for real, from its own reviewed plan.</h1>
|
|
39
|
+
<p class="lede">
|
|
40
|
+
Purge is the only stage that physically deletes. Clean trashes; purge
|
|
41
|
+
empties the trash — and only from a separate plan a human reviewed and
|
|
42
|
+
approved. Trashed records stay discoverable until you deliberately
|
|
43
|
+
remove them.
|
|
44
|
+
</p>
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<h2>Purge boundary</h2>
|
|
48
|
+
<ul class="boundary-list">
|
|
49
|
+
<li>
|
|
50
|
+
<span class="stamp readonly">Allowed freely</span>
|
|
51
|
+
<span><code>trash list</code> and <code>trash purge --dry-run</code> — discover and preview, moving nothing.</span>
|
|
52
|
+
</li>
|
|
53
|
+
<li>
|
|
54
|
+
<span class="stamp approval">Needs approval</span>
|
|
55
|
+
<span><code>trash purge --execute --plan-id</code> for one reviewed purge plan, on one ledger.</span>
|
|
56
|
+
</li>
|
|
57
|
+
<li>
|
|
58
|
+
<span class="stamp refused">Refused</span>
|
|
59
|
+
<span>No global purge — <code>--all</code> is not supported for purge — and no piggybacking on the cleanup plan that trashed the file.</span>
|
|
60
|
+
</li>
|
|
61
|
+
</ul>
|
|
62
|
+
<div class="callout" data-kind="boundary">
|
|
63
|
+
<span class="callout-label">Hard boundary</span>
|
|
64
|
+
<p>
|
|
65
|
+
The purge plan is always separate from the cleanup plan and separately
|
|
66
|
+
reviewed. Physical deletion never happens as a side effect of cleanup.
|
|
67
|
+
</p>
|
|
68
|
+
</div>
|
|
69
|
+
</section>
|
|
70
|
+
|
|
71
|
+
<section>
|
|
72
|
+
<h2>Preview, then purge</h2>
|
|
73
|
+
<p>
|
|
74
|
+
Purge runs the loop one more time: list what is in trash, preview an
|
|
75
|
+
age-based purge plan to get a purge plan id, then execute only that id
|
|
76
|
+
after a human approves it.
|
|
77
|
+
</p>
|
|
78
|
+
<pre><code><span class="c"># what is in trash for this ledger</span>
|
|
79
|
+
artshelf trash list --ledger <ledger-path> --json
|
|
80
|
+
|
|
81
|
+
<span class="c"># preview an age-based purge and get a purge plan id</span>
|
|
82
|
+
artshelf trash purge --older-than 7d --dry-run --ledger <ledger-path> --json
|
|
83
|
+
|
|
84
|
+
<span class="c"># delete for real, only with the reviewed purge plan id</span>
|
|
85
|
+
artshelf trash purge --execute --plan-id <purge-plan-id> --ledger <ledger-path> --json</code></pre>
|
|
86
|
+
<p>The approval wording for a purge:</p>
|
|
87
|
+
<pre><code>approve artshelf trash purge ledger <ledger-path> plan <purge-plan-id></code></pre>
|
|
88
|
+
</section>
|
|
89
|
+
|
|
90
|
+
<section>
|
|
91
|
+
<h2>Verify quiet</h2>
|
|
92
|
+
<p>
|
|
93
|
+
After a purge executes, confirm the trash is empty and the shelf is
|
|
94
|
+
quiet with <code>artshelf trash list --all --json</code> and
|
|
95
|
+
<code>artshelf review --all --json</code>.
|
|
96
|
+
</p>
|
|
97
|
+
<p>
|
|
98
|
+
Purge writes a receipt and records it as an <code>owner=artshelf</code>
|
|
99
|
+
artifact, so the deletion stays auditable even after the files are gone.
|
|
100
|
+
</p>
|
|
101
|
+
</section>
|
|
102
|
+
</article>
|
|
103
|
+
<footer class="pager" id="pager"></footer>
|
|
104
|
+
</main>
|
|
105
|
+
|
|
106
|
+
<aside class="toc-col"><p class="toc-title">On this page</p><nav id="toc" aria-label="On this page"></nav></aside>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<footer class="colophon"><div class="colophon-inner"><span>Artshelf docs</span><span>MIT</span><a href="https://github.com/calvinnwq/artshelf">GitHub</a></div></footer>
|
|
110
|
+
</body>
|
|
111
|
+
</html>
|
package/docs/agent-usage.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>Agent usage · Artshelf</title>
|
|
7
|
-
<meta name="description" content="How agents use Artshelf safely: Create, Monitor, Review, Clean.">
|
|
7
|
+
<meta name="description" content="How agents use Artshelf safely: Create, Monitor, Review, Clean, Purge.">
|
|
8
8
|
<script>(function(){var stored=null;try{stored=localStorage.getItem("artshelf-docs-theme");}catch(e){}var dark=false;try{dark=matchMedia("(prefers-color-scheme: dark)").matches;}catch(e){}document.documentElement.dataset.theme=stored||(dark?"dark":"light");})();</script>
|
|
9
9
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
<main id="content" class="article-col">
|
|
36
36
|
<article>
|
|
37
37
|
<p class="kicker"><span class="n">04</span>Agents · Overview</p>
|
|
38
|
-
<h1>Create, monitor, review, clean.</h1>
|
|
38
|
+
<h1>Create, monitor, review, clean, purge.</h1>
|
|
39
39
|
<p class="lede">
|
|
40
|
-
An agent's whole relationship with Artshelf fits in
|
|
40
|
+
An agent's whole relationship with Artshelf fits in five stages. Each stage
|
|
41
41
|
has its own page with the exact commands; this page is the contract that
|
|
42
42
|
ties them together.
|
|
43
43
|
</p>
|
|
@@ -63,14 +63,18 @@
|
|
|
63
63
|
<a class="ledger-row" href="agent-clean.html">
|
|
64
64
|
<span class="n">4.4</span>
|
|
65
65
|
<span class="stage">Clean</span>
|
|
66
|
-
<span class="what"><span class="cmdline">artshelf cleanup --execute --plan-id <id></span>Run the one plan the human approved,
|
|
66
|
+
<span class="what"><span class="cmdline">artshelf cleanup --execute --plan-id <id></span>Run the one plan the human approved, resolve confirmed ids, and verify the next review is quiet.</span>
|
|
67
|
+
</a>
|
|
68
|
+
<a class="ledger-row" href="agent-purge.html">
|
|
69
|
+
<span class="n">4.5</span>
|
|
70
|
+
<span class="stage">Purge</span>
|
|
71
|
+
<span class="what"><span class="cmdline">artshelf trash purge --execute --plan-id <id></span>Clear old trash through its own separately reviewed purge plan. Physical deletion never piggybacks on the cleanup plan that trashed the file.</span>
|
|
67
72
|
</a>
|
|
68
73
|
</div>
|
|
69
74
|
<p>
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
plan that trashed the file.
|
|
75
|
+
Clean trashes; Purge deletes. Cleanup quarantines artifacts into Artshelf
|
|
76
|
+
trash, and only a separate, separately reviewed purge plan removes them for
|
|
77
|
+
good — a second approval boundary before destructive deletion.
|
|
74
78
|
</p>
|
|
75
79
|
</section>
|
|
76
80
|
|
|
@@ -78,7 +82,7 @@
|
|
|
78
82
|
<h2>Operating principles</h2>
|
|
79
83
|
<dl class="def-rows">
|
|
80
84
|
<div><dt>agents remember</dt><dd>use the portable skill so final, status, and handoff turns check artifacts</dd></div>
|
|
81
|
-
<div><dt>crons only read</dt><dd>scheduled jobs may validate, review, dry-run, and report, but never execute</dd></div>
|
|
85
|
+
<div><dt>crons only read</dt><dd>scheduled jobs may validate, review, dry-run, and report, but never execute; set <code>ARTSHELF_NO_UPDATE_CHECK=1</code> when they must avoid npm network checks and update-cache writes</dd></div>
|
|
82
86
|
<div><dt>humans approve</dt><dd>mutation needs exact approval targets: ledger path, reviewed plan id, or record id list</dd></div>
|
|
83
87
|
</dl>
|
|
84
88
|
</section>
|
package/docs/agent-usage.md
CHANGED
|
@@ -11,7 +11,7 @@ mutation.
|
|
|
11
11
|
|
|
12
12
|
## Workflow Summary
|
|
13
13
|
|
|
14
|
-
Use Artshelf as a
|
|
14
|
+
Use Artshelf as a five-stage loop around agent work:
|
|
15
15
|
|
|
16
16
|
1. **Create**: register durable temp artifacts with lookup-before-put and
|
|
17
17
|
`artshelf put`, or state the skip reason.
|
|
@@ -19,10 +19,12 @@ Use Artshelf as a four-stage loop around agent work:
|
|
|
19
19
|
paths, and trash state.
|
|
20
20
|
3. **Review**: turn raw output into an `ArtshelfReviewReport` decision packet
|
|
21
21
|
with exact approval targets.
|
|
22
|
-
4. **Clean**: execute approved plans
|
|
23
|
-
|
|
22
|
+
4. **Clean**: execute approved cleanup plans (which trash, never delete),
|
|
23
|
+
resolve confirmed ids, then verify quiet.
|
|
24
|
+
5. **Purge**: clear old trash only from a separate, separately reviewed purge
|
|
25
|
+
plan; physical deletion never piggybacks on the cleanup plan.
|
|
24
26
|
|
|
25
|
-
This maps to the product loop: **Create -> Monitor -> Review -> Clean**.
|
|
27
|
+
This maps to the product loop: **Create -> Monitor -> Review -> Clean -> Purge**.
|
|
26
28
|
|
|
27
29
|
## Child Pages
|
|
28
30
|
|
|
@@ -34,13 +36,16 @@ The browsable docs split the workflow into focused child pages:
|
|
|
34
36
|
and preview plans.
|
|
35
37
|
- [Review](agent-review.html): decision packet schema, classifications, and
|
|
36
38
|
exact approval wording.
|
|
37
|
-
- [Clean](agent-clean.html): approval-only cleanup,
|
|
38
|
-
|
|
39
|
+
- [Clean](agent-clean.html): approval-only cleanup, resolve, receipts, and
|
|
40
|
+
verify-quiet checks.
|
|
41
|
+
- [Purge](agent-purge.html): separately reviewed trash purge that physically
|
|
42
|
+
deletes, with its own approval target and receipts.
|
|
39
43
|
|
|
40
44
|
## Operating Principles
|
|
41
45
|
|
|
42
46
|
- Agents remember with the portable skill.
|
|
43
|
-
- Scheduled checks read and report only
|
|
47
|
+
- Scheduled checks read and report only; set `ARTSHELF_NO_UPDATE_CHECK=1` when
|
|
48
|
+
they must avoid npm network checks and update-cache writes.
|
|
44
49
|
- Review output is a decision packet, not raw counts.
|
|
45
50
|
- Approval names the exact ledger, plan id, or record ids.
|
|
46
51
|
- Every approved action ends with a read-only verification.
|