artshelf 0.6.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 +21 -3
- package/README.md +146 -219
- package/SPEC.md +56 -6
- package/dist/src/cli.js +258 -19
- package/docs/agent-clean.html +108 -0
- package/docs/agent-create.html +98 -0
- package/docs/agent-monitor.html +151 -0
- package/docs/agent-purge.html +111 -0
- package/docs/agent-review.html +120 -0
- package/docs/agent-usage.html +114 -402
- package/docs/agent-usage.md +52 -474
- package/docs/index.html +165 -152
- package/docs/install.html +214 -110
- package/docs/quickstart.html +105 -106
- package/docs/reference.html +239 -164
- package/docs/site.css +675 -490
- package/docs/site.js +398 -0
- package/package.json +1 -1
- package/skills/artshelf/SKILL.md +133 -333
- package/skills/artshelf/scripts/render-review-report.mjs +160 -0
- package/docs/theme.js +0 -42
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
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
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>Clean · Agent usage · Artshelf</title>
|
|
7
|
+
<meta name="description" content="How agents clean Artshelf artifacts after exact human approval.">
|
|
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-clean.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.4</span>Agents · Clean</p>
|
|
38
|
+
<h1>Execute only what was reviewed and approved.</h1>
|
|
39
|
+
<p class="lede">
|
|
40
|
+
Clean is meant to be boring. The human approves one plan, the agent runs
|
|
41
|
+
exactly that plan, writes a receipt, and checks that the next review
|
|
42
|
+
comes back quiet.
|
|
43
|
+
</p>
|
|
44
|
+
|
|
45
|
+
<section>
|
|
46
|
+
<h2>Cleanup boundary</h2>
|
|
47
|
+
<ul class="boundary-list">
|
|
48
|
+
<li>
|
|
49
|
+
<span class="stamp readonly">Allowed freely</span>
|
|
50
|
+
<span><code>validate</code>, <code>review</code>, and <code>cleanup --dry-run</code>.</span>
|
|
51
|
+
</li>
|
|
52
|
+
<li>
|
|
53
|
+
<span class="stamp approval">Needs approval</span>
|
|
54
|
+
<span><code>cleanup --execute --plan-id</code> for one reviewed plan.</span>
|
|
55
|
+
</li>
|
|
56
|
+
<li>
|
|
57
|
+
<span class="stamp refused">Refused</span>
|
|
58
|
+
<span>No daemon, no auto-execute, no global execute, no fresh-plan-then-execute.</span>
|
|
59
|
+
</li>
|
|
60
|
+
</ul>
|
|
61
|
+
<pre><code><span class="c"># free to run any time: read-only checks and plan previews</span>
|
|
62
|
+
artshelf validate --json
|
|
63
|
+
artshelf review --all --json
|
|
64
|
+
artshelf cleanup --dry-run --json
|
|
65
|
+
artshelf cleanup --dry-run --all --json
|
|
66
|
+
|
|
67
|
+
<span class="c"># only after a human approves this exact plan id</span>
|
|
68
|
+
artshelf cleanup --execute --plan-id <id></code></pre>
|
|
69
|
+
<div class="callout" data-kind="boundary">
|
|
70
|
+
<span class="callout-label">Hard boundary</span>
|
|
71
|
+
<p>
|
|
72
|
+
Cleanup execution needs explicit human approval for the reviewed plan id.
|
|
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>.
|
|
76
|
+
</p>
|
|
77
|
+
</div>
|
|
78
|
+
</section>
|
|
79
|
+
|
|
80
|
+
<section>
|
|
81
|
+
<h2>Resolve confirmed records</h2>
|
|
82
|
+
<p>Resolve only updates the ledger; it does not move or delete files.</p>
|
|
83
|
+
<pre><code><span class="c"># mark a record handled without touching the file</span>
|
|
84
|
+
artshelf resolve <id> --status resolved --reason <text></code></pre>
|
|
85
|
+
<p>The approval wording for resolving a batch of missing records:</p>
|
|
86
|
+
<pre><code>approve artshelf resolve missing ledger <ledger-path> ids <id...></code></pre>
|
|
87
|
+
</section>
|
|
88
|
+
|
|
89
|
+
<section>
|
|
90
|
+
<h2>Verify quiet</h2>
|
|
91
|
+
<p>After cleanup execute or resolve, verify with <code>artshelf review --all --json</code>.</p>
|
|
92
|
+
<p>
|
|
93
|
+
Execution writes a receipt and updates touched ledger records to
|
|
94
|
+
<code>trashed</code>, <code>review-required</code>, or
|
|
95
|
+
<code>cleanup-refused</code>. Generated plans and receipts are recorded as
|
|
96
|
+
<code>owner=artshelf</code> artifacts.
|
|
97
|
+
</p>
|
|
98
|
+
</section>
|
|
99
|
+
</article>
|
|
100
|
+
<footer class="pager" id="pager"></footer>
|
|
101
|
+
</main>
|
|
102
|
+
|
|
103
|
+
<aside class="toc-col"><p class="toc-title">On this page</p><nav id="toc" aria-label="On this page"></nav></aside>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<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>
|
|
107
|
+
</body>
|
|
108
|
+
</html>
|
|
@@ -0,0 +1,98 @@
|
|
|
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>Create · Agent usage · Artshelf</title>
|
|
7
|
+
<meta name="description" content="How agents create Artshelf records while artifact intent is fresh.">
|
|
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-create.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.1</span>Agents · Create</p>
|
|
38
|
+
<h1>Register artifacts while intent is fresh.</h1>
|
|
39
|
+
<p class="lede">
|
|
40
|
+
Create is one question: should this non-source artifact outlive the turn?
|
|
41
|
+
If yes, put it on the shelf while the reason is still obvious.
|
|
42
|
+
</p>
|
|
43
|
+
|
|
44
|
+
<section>
|
|
45
|
+
<h2>Completion gate</h2>
|
|
46
|
+
<p>
|
|
47
|
+
Before any final, status, handoff, or done report, check for files the agent
|
|
48
|
+
created, copied, exported, quarantined, backed up, or preserved.
|
|
49
|
+
</p>
|
|
50
|
+
<ul>
|
|
51
|
+
<li><strong>Register eligible artifact paths.</strong> Anything non-source that may outlive the command needs a record.</li>
|
|
52
|
+
<li><strong>State every skip reason.</strong> Source, cache, regeneratable, secret-bearing, or owned elsewhere.</li>
|
|
53
|
+
</ul>
|
|
54
|
+
<div class="callout" data-kind="boundary">
|
|
55
|
+
<span class="callout-label">Hard boundary</span>
|
|
56
|
+
<p>Do not call work done while known eligible artifacts are neither registered nor explicitly skipped.</p>
|
|
57
|
+
<p>Do not invent Artshelf entries after the fact just to make a handoff look tidy.</p>
|
|
58
|
+
</div>
|
|
59
|
+
</section>
|
|
60
|
+
|
|
61
|
+
<section>
|
|
62
|
+
<h2>Register these</h2>
|
|
63
|
+
<dl class="def-rows">
|
|
64
|
+
<div><dt>backup</dt><dd>rollback copy or snapshot</dd></div>
|
|
65
|
+
<div><dt>evidence</dt><dd>report, log, smoke output</dd></div>
|
|
66
|
+
<div><dt>quarantine</dt><dd>copied aside for review</dd></div>
|
|
67
|
+
<div><dt>run</dt><dd>artifact needed to resume a long task</dd></div>
|
|
68
|
+
</dl>
|
|
69
|
+
</section>
|
|
70
|
+
|
|
71
|
+
<section>
|
|
72
|
+
<h2>Lookup before put</h2>
|
|
73
|
+
<pre><code><span class="c"># is this path already on the shelf?</span>
|
|
74
|
+
artshelf find --path <path> --owner <agent-or-runtime> --label <task-or-run-id> --json
|
|
75
|
+
|
|
76
|
+
<span class="c"># inspect one record by its Artshelf id</span>
|
|
77
|
+
artshelf get <id> --json</code></pre>
|
|
78
|
+
<p>
|
|
79
|
+
If <code>find</code> returns a record, reuse that Artshelf id. If it returns
|
|
80
|
+
no entries, call <code>artshelf put --json</code> and report the new id.
|
|
81
|
+
</p>
|
|
82
|
+
</section>
|
|
83
|
+
|
|
84
|
+
<section>
|
|
85
|
+
<h2>Report the id</h2>
|
|
86
|
+
<p>Use a deterministic Artshelf footnote wherever restart or cleanup context will be read.</p>
|
|
87
|
+
<pre><code>Artshelf footnote: registered <artifact-path> as <artshelf-id>; reason: <short reason>; due: <YYYY-MM-DD|manual-review>; cleanup=<cleanup-mode>.</code></pre>
|
|
88
|
+
</section>
|
|
89
|
+
</article>
|
|
90
|
+
<footer class="pager" id="pager"></footer>
|
|
91
|
+
</main>
|
|
92
|
+
|
|
93
|
+
<aside class="toc-col"><p class="toc-title">On this page</p><nav id="toc" aria-label="On this page"></nav></aside>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<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>
|
|
97
|
+
</body>
|
|
98
|
+
</html>
|