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
package/CHANGELOG.md CHANGED
@@ -80,6 +80,23 @@
80
80
  (falling back to `ARTSHELF_UPDATE_CHECK_TTL_MS` for compatibility), and a
81
81
  non-numeric TTL value falls back to the default instead of disabling expiry.
82
82
 
83
+ ## [0.10.2](https://github.com/calvinnwq/artshelf/compare/artshelf-v0.10.1...artshelf-v0.10.2) (2026-06-13)
84
+
85
+
86
+ ### Code Refactoring
87
+
88
+ * **cli:** extract command dispatch and shared modules from the monolithic
89
+ entrypoint ([c198c19](https://github.com/calvinnwq/artshelf/commit/c198c194693e756dd02b2525e2f6abbee5741d59))
90
+ * **cli:** separate status, doctor, review, and JSON renderers from command
91
+ orchestration ([4ec76b0](https://github.com/calvinnwq/artshelf/commit/4ec76b0b0e4f45562d0e98a1237602bc5d41ca67))
92
+ * **cli:** extract update environment, package, path, and process adapter seams
93
+ ([4ec76b0](https://github.com/calvinnwq/artshelf/commit/4ec76b0b0e4f45562d0e98a1237602bc5d41ca67))
94
+ * **cli:** restore real per-command modules, add the validate command module,
95
+ and strengthen architecture guardrails
96
+ ([a617ba3](https://github.com/calvinnwq/artshelf/commit/a617ba36e7de7d8a5725d1ba47eb3419ae3c6329))
97
+ * **cli:** move help rendering out of the entrypoint and into shared help text
98
+ ([8e2698b](https://github.com/calvinnwq/artshelf/commit/8e2698bce1b47073d434113cbe1e8cfb32ec34e2))
99
+
83
100
  ## [0.10.1](https://github.com/calvinnwq/artshelf/compare/artshelf-v0.10.0...artshelf-v0.10.1) (2026-06-12)
84
101
 
85
102
 
package/SPEC.md CHANGED
@@ -261,7 +261,7 @@ the next action always points at an explicit follow-up command.
261
261
 
262
262
  `review`, `status`, and `doctor` share three render modes. The default human
263
263
  render leads each ledger and summary line with a `✓`/`⚠` attention glyph; `--json`
264
- stays the full, backward-compatible audit report; and `--agent` emits a compact,
264
+ stays the full, backward-compatible public audit report; and `--agent` emits a compact,
265
265
  deterministic single-line JSON decision packet for agents, taking precedence over
266
266
  `--json` when both are passed. For `review`, the packet sorts records into
267
267
  ready-for-approval, needs-review-first, and blocked groups. Because review is
@@ -0,0 +1,7 @@
1
+ import { spawnSync } from "node:child_process";
2
+ export function installGlobalNpmPackage(packageSpec, mode) {
3
+ if (mode === "pipe") {
4
+ return spawnSync("npm", ["install", "-g", packageSpec], { encoding: "utf8" });
5
+ }
6
+ return spawnSync("npm", ["install", "-g", packageSpec], { encoding: "utf8", stdio: "inherit" });
7
+ }
@@ -0,0 +1,143 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { latestVersionOverride, noUpdateCheckTtlMs, updateCheckTtlMs } from "../config/env.js";
4
+ import { NO_UPDATE_CHECK_TTL_MS, UPDATE_CHECK_TTL_MS, VERSION, npmRegistryUrl } from "../config/package.js";
5
+ import { updateCachePath } from "../config/paths.js";
6
+ export async function getUpdateInfo(options) {
7
+ return createDefaultUpdateAdapter().getUpdateInfo(options);
8
+ }
9
+ export function createUpdateAdapter(options) {
10
+ async function getUpdateInfo(optionsForCheck) {
11
+ const latest = await getLatestVersion(optionsForCheck);
12
+ if (!latest)
13
+ return null;
14
+ return {
15
+ current: options.currentVersion,
16
+ latest,
17
+ updateAvailable: compareVersions(latest, options.currentVersion) > 0
18
+ };
19
+ }
20
+ async function getLatestVersion(optionsForCheck) {
21
+ const override = latestVersionOverride(options.env);
22
+ if (override)
23
+ return normalizeVersion(override);
24
+ if (!optionsForCheck.force) {
25
+ const cached = readUpdateCache();
26
+ if (cached)
27
+ return cached.latest;
28
+ }
29
+ const latest = await options.fetchLatestVersion(options.registryUrl);
30
+ writeUpdateCache(latest);
31
+ return latest;
32
+ }
33
+ function readUpdateCache() {
34
+ const cachePath = options.cachePath();
35
+ if (!options.fileExists(cachePath))
36
+ return null;
37
+ try {
38
+ const cache = JSON.parse(options.readTextFile(cachePath));
39
+ if (!("latest" in cache))
40
+ cache.latest = null;
41
+ if (cache.latest !== null && typeof cache.latest !== "string")
42
+ return null;
43
+ if (typeof cache.checkedAt !== "number")
44
+ return null;
45
+ const latest = cache.latest === null ? null : normalizeVersion(cache.latest);
46
+ const ttl = updateCacheTtlFor(latest);
47
+ if (ttl < 0)
48
+ return null;
49
+ if (options.now() - cache.checkedAt > ttl)
50
+ return null;
51
+ return { latest };
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ }
57
+ function updateCacheTtlFor(latest) {
58
+ if (latest && compareVersions(latest, options.currentVersion) > 0) {
59
+ return updateCheckTtlMs(options.env, UPDATE_CHECK_TTL_MS);
60
+ }
61
+ return noUpdateCheckTtlMs(options.env, NO_UPDATE_CHECK_TTL_MS);
62
+ }
63
+ function writeUpdateCache(latest) {
64
+ try {
65
+ const cachePath = options.cachePath();
66
+ const dir = dirname(cachePath);
67
+ if (dir) {
68
+ options.ensureDirectory(dir);
69
+ options.writeTextFile(cachePath, `${JSON.stringify({ latest, checkedAt: options.now() }, null, 2)}\n`);
70
+ }
71
+ }
72
+ catch {
73
+ // Update checks should never affect normal CLI behavior.
74
+ }
75
+ }
76
+ return { getUpdateInfo };
77
+ }
78
+ function createDefaultUpdateAdapter() {
79
+ const registryUrl = npmRegistryUrl();
80
+ return createUpdateAdapter({
81
+ currentVersion: VERSION,
82
+ registryUrl,
83
+ env: process.env,
84
+ now: () => Date.now(),
85
+ cachePath: () => updateCachePath(),
86
+ fileExists: existsSync,
87
+ readTextFile: (path) => readFileSync(path, "utf8"),
88
+ writeTextFile: writeFileSync,
89
+ ensureDirectory: (path) => mkdirSync(path, { recursive: true }),
90
+ fetchLatestVersion: (url) => fetchLatestNpmVersion(url)
91
+ });
92
+ }
93
+ async function fetchLatestNpmVersion(registryUrl) {
94
+ const controller = new AbortController();
95
+ const timeout = setTimeout(() => controller.abort(), 750);
96
+ try {
97
+ const response = await fetch(registryUrl, {
98
+ signal: controller.signal,
99
+ headers: { accept: "application/json", "user-agent": `artshelf/${VERSION}` }
100
+ });
101
+ if (!response.ok)
102
+ return null;
103
+ const body = await response.json();
104
+ if (!body || typeof body !== "object" || typeof body.version !== "string")
105
+ return null;
106
+ return normalizeVersion(body.version);
107
+ }
108
+ catch {
109
+ return null;
110
+ }
111
+ finally {
112
+ clearTimeout(timeout);
113
+ }
114
+ }
115
+ function normalizeVersion(version) {
116
+ return version.trim().replace(/^v/i, "");
117
+ }
118
+ function compareVersions(left, right) {
119
+ const a = parseVersion(left);
120
+ const b = parseVersion(right);
121
+ for (let index = 0; index < Math.max(a.numbers.length, b.numbers.length); index += 1) {
122
+ const diff = (a.numbers[index] ?? 0) - (b.numbers[index] ?? 0);
123
+ if (diff !== 0)
124
+ return diff;
125
+ }
126
+ if (a.prerelease === b.prerelease)
127
+ return 0;
128
+ if (!a.prerelease)
129
+ return 1;
130
+ if (!b.prerelease)
131
+ return -1;
132
+ return a.prerelease.localeCompare(b.prerelease);
133
+ }
134
+ function parseVersion(version) {
135
+ const [main = "", prerelease = ""] = normalizeVersion(version).split("-", 2);
136
+ return {
137
+ numbers: main.split(".").map((part) => {
138
+ const parsed = Number.parseInt(part, 10);
139
+ return Number.isFinite(parsed) ? parsed : 0;
140
+ }),
141
+ prerelease
142
+ };
143
+ }