akm-cli 0.9.8 → 0.9.9

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 (34) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/dist/commands/health/checks.js +40 -0
  3. package/dist/commands/health.js +57 -31
  4. package/dist/commands/migrate-cli.js +29 -189
  5. package/dist/commands/sources/add-cli.js +7 -0
  6. package/dist/commands/sources/installed-stashes.js +36 -8
  7. package/dist/commands/sources/self-update.js +104 -62
  8. package/dist/commands/sources/source-add.js +6 -5
  9. package/dist/commands/sources/sources-cli.js +7 -18
  10. package/dist/commands/tasks/tasks-cli.js +4 -3
  11. package/dist/commands/tasks/tasks.js +13 -6
  12. package/dist/core/adapter/adapter-ids.js +35 -0
  13. package/dist/core/adapter/adapters/index.js +29 -0
  14. package/dist/core/adapter/detect-adapter.js +91 -3
  15. package/dist/core/config/config.js +1 -1
  16. package/dist/core/config/schema/sources-bundles.js +23 -0
  17. package/dist/core/extra-params.js +1 -1
  18. package/dist/core/state/migrations.js +2 -4
  19. package/dist/core/state-db.js +31 -8
  20. package/dist/indexer/indexer.js +64 -1
  21. package/dist/scripts/akm-migrate-node.js +86534 -20434
  22. package/dist/scripts/akm-migrate.js +86415 -20280
  23. package/dist/tasks/backends/cron.js +21 -6
  24. package/dist/tasks/resolve-akm-bin.js +1 -1
  25. package/docs/README.md +1 -0
  26. package/docs/integration/bundling-akm.md +276 -0
  27. package/docs/migration/v0.9.0-troubleshooting.md +10 -14
  28. package/docs/migration/v0.9.1-to-v0.9.2.md +12 -16
  29. package/docs/reference/cli.md +59 -31
  30. package/docs/reference/tasks.md +4 -10
  31. package/package.json +2 -1
  32. package/dist/commands/migrate/config-extra-params.js +0 -61
  33. package/dist/commands/migrate/dead-residue.js +0 -113
  34. package/dist/commands/migrate/stale-txn.js +0 -49
@@ -21,7 +21,11 @@
21
21
  // `LOGNAME`/`USER` only). The cron line uses an absolute akm path
22
22
  // resolved at install time so it doesn't rely on the inherited PATH.
23
23
  // • BSD `crontab -l` returns exit 1 with "no crontab for <user>" on a
24
- // fresh user; we treat that as an empty crontab rather than an error.
24
+ // fresh user; a supercronic-managed PATH shim (#910, e.g. OpenPalm's
25
+ // `/tmp/openpalm-bin/crontab` before any spool exists) does the same
26
+ // with empty stdout instead. Both mean "empty crontab", not "broken
27
+ // install" — only a genuinely missing binary (ENOENT from the spawn
28
+ // itself) gets the "install crontab" remedy.
25
29
  //
26
30
  // Tests inject a fake exec so unit tests don't touch the real crontab.
27
31
  import { spawnSync } from "node:child_process";
@@ -480,12 +484,21 @@ function readCrontab(exec) {
480
484
  const result = exec.read();
481
485
  if (result.status === 0)
482
486
  return result.stdout ?? "";
483
- // BSD crontab returns 1 with "no crontab for <user>" on stderr treat as empty.
484
- if (/no crontab for/i.test(result.stderr ?? ""))
485
- return "";
486
- if (/no crontab/i.test(result.stdout ?? ""))
487
+ // The spawn itself failed to find the binary (ENOENT) this is the only
488
+ // case where "install/PATH the crontab binary" is the correct remedy.
489
+ if (result.enoent) {
490
+ throw new ConfigError("crontab -l failed: the `crontab` binary was not found on PATH.", "INVALID_CONFIG_FILE", "Install the `crontab` binary (e.g. cron/cronie/vixie-cron) or add one to PATH.");
491
+ }
492
+ // #910: a nonzero exit that says nothing at all, or whose stderr says
493
+ // "no crontab" (BSD's "no crontab for <user>"; a supercronic-managed PATH
494
+ // shim like OpenPalm's before any spool exists), is cron's own contract
495
+ // for "empty crontab" — not evidence the binary is missing or broken. A
496
+ // nonzero exit that DID say something else (a permission refusal) is
497
+ // reported as what it said, below.
498
+ const stderr = (result.stderr ?? "").trim();
499
+ if (((result.stdout ?? "").trim() === "" && stderr === "") || /no crontab/i.test(stderr))
487
500
  return "";
488
- throw new ConfigError(`crontab -l failed (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`, "INVALID_CONFIG_FILE", "Ensure the `crontab` binary is on PATH and your shell can read the user crontab.");
501
+ throw new ConfigError(`crontab -l failed (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`, "INVALID_CONFIG_FILE", "The `crontab` binary ran but did not report success; check its output above.");
489
502
  }
490
503
  function writeCrontab(exec, content) {
491
504
  const normalised = content.endsWith("\n") || content.length === 0 ? content : `${content}\n`;
@@ -517,6 +530,7 @@ function defaultCronExec() {
517
530
  status: r.status ?? 1,
518
531
  stdout: r.stdout ?? "",
519
532
  stderr: r.stderr ?? "",
533
+ enoent: r.error?.code === "ENOENT",
520
534
  };
521
535
  },
522
536
  write(content) {
@@ -525,6 +539,7 @@ function defaultCronExec() {
525
539
  status: r.status ?? 1,
526
540
  stdout: r.stdout ?? "",
527
541
  stderr: r.stderr ?? "",
542
+ enoent: r.error?.code === "ENOENT",
528
543
  };
529
544
  },
530
545
  };
@@ -159,7 +159,7 @@ function samePath(left, right) {
159
159
  // once-only flag) so a differently-invoked probe later in the same process
160
160
  // still gets its own answer instead of a stale one.
161
161
  let cachedNpmGlobalRoot;
162
- function resolveNpmGlobalRoot(nodePath, env) {
162
+ export function resolveNpmGlobalRoot(nodePath, env) {
163
163
  if (cachedNpmGlobalRoot && cachedNpmGlobalRoot.nodePath === nodePath) {
164
164
  return cachedNpmGlobalRoot.value;
165
165
  }
package/docs/README.md CHANGED
@@ -41,6 +41,7 @@ Package complete capabilities and turn knowledge into repeatable work.
41
41
  - [Bundle Author's Guide](https://github.com/itlackey/akm/blob/main/docs/guides/author-bundles.md) -- Build a bundle, make it discoverable, and share it so others can install it with `akm bundle add`
42
42
  - [Author's Guide: Writing Workflows](https://github.com/itlackey/akm/blob/main/docs/guides/author-workflows.md) -- Write and test a workflow definition, from a minimal example to gates and outputs
43
43
  - [Claude Code workflows vs. akm workflows](https://github.com/itlackey/akm/blob/main/docs/guides/claude-code-vs-akm-workflows.md) -- Short decision guide for choosing between a session-native workflow and an akm workflow ([full technical comparison](https://github.com/itlackey/akm/blob/main/docs/architecture/comparisons/claude-code-vs-akm-workflows-full.md))
44
+ - [Bundling akm](integration/bundling-akm.md) -- Ship akm inside your own product: the pin/migrate/health boot contract, plan JSON shapes, and exit codes for bundlers
44
45
 
45
46
  ### Maintainers
46
47
 
@@ -0,0 +1,276 @@
1
+ # Bundling akm
2
+
3
+ This is for anyone shipping `akm` inside their own product: a Docker image, a
4
+ plugin whose `node_modules` carries its own akm, or any other install a human
5
+ never runs `akm setup` inside. It says exactly what to call at boot and what
6
+ each call's JSON means — nothing about akm's internals you don't need.
7
+
8
+ ## The contract
9
+
10
+ Three lines cover it:
11
+
12
+ 1. **Pin** an exact `akm-cli` version and install it however you install
13
+ things (npm, the standalone binary, a `node_modules` dependency).
14
+ 2. **At boot, run `akm migrate apply`** (or `akm upgrade`, which runs it).
15
+ It is offline, idempotent, needs no root, takes its own safety copies,
16
+ prints one JSON plan, and exits `0` unless a file is genuinely blocked.
17
+ 3. **Read `akm health --format json`.** Never grep error text — akm's
18
+ wording is not a stable interface; the JSON fields are.
19
+
20
+ Everything below is detail in service of those three lines.
21
+
22
+ ## `akm migrate apply`
23
+
24
+ ```sh
25
+ akm migrate status # read-only: what would change
26
+ akm migrate apply --dry-run
27
+ akm migrate apply
28
+ ```
29
+
30
+ In order, every run applies (or, under `status`/`--dry-run`, plans):
31
+
32
+ 1. **Legacy config lift** — `extraParams` keys on an engine config moved onto
33
+ first-class fields.
34
+ 2. **Pending `state.db` migrations**, historical-destructive ones included.
35
+ This is the only path (besides `akm upgrade`, which calls the same code)
36
+ that is allowed to apply a destructive migration to an existing,
37
+ unversioned or behind-generation `state.db` — see
38
+ [One-way state.db](#one-way-note-statedb-migrations-are-one-way) below.
39
+ 3. **Task sources**: task-v2 files to task v3, then task-v3 files to task
40
+ source v4. Each generation keeps its own lock and backup, so a file
41
+ blocked in one generation does not stop the other from converting files
42
+ that are already current.
43
+ 4. **Stash residue sweeps**: superseded pre-0.9.0 `.akm` files and stale
44
+ filesystem transactions, scoped to the configured bundle (skipped
45
+ entirely when no bundle is configured yet).
46
+
47
+ ### The plan JSON
48
+
49
+ One JSON object on stdout, always. A current install, nothing to do:
50
+
51
+ ```json
52
+ {
53
+ "schemaVersion": 1,
54
+ "status": "current",
55
+ "blockers": [],
56
+ "configExtraParams": { "applied": false, "lifted": [], "conflicts": [] },
57
+ "stateMigrations": { "pending": [] },
58
+ "taskV3Migration": { "schemaVersion": 1, "generation": "task-v2-to-v3", "changed": 0, "skipped": 4, "blocked": 0, "files": [] },
59
+ "taskV4Migration": { "schemaVersion": 1, "generation": "task-v3-to-v4", "changed": 0, "skipped": 4, "blocked": 0, "files": [] }
60
+ }
61
+ ```
62
+
63
+ `akm migrate status` against a tree with one blocked task file (`taskV3Migration`
64
+ omitted below for brevity — its shape is identical):
65
+
66
+ ```json
67
+ {
68
+ "schemaVersion": 1,
69
+ "status": "blocked",
70
+ "blockers": ["with-on-non-command-target"],
71
+ "stateMigrations": { "pending": [] },
72
+ "taskV4Migration": {
73
+ "schemaVersion": 1,
74
+ "generation": "task-v3-to-v4",
75
+ "changed": 3,
76
+ "skipped": 1,
77
+ "blocked": 1,
78
+ "files": [
79
+ {
80
+ "filePath": "tasks/nightly.yml",
81
+ "status": "blocked",
82
+ "reason": "with-on-non-command-target",
83
+ "beforeHash": "…"
84
+ }
85
+ ]
86
+ }
87
+ }
88
+ ```
89
+
90
+ `status` is one of:
91
+
92
+ - **`current`** — no task-source file needs converting, and no config
93
+ extraParams lift is pending. A no-op boot.
94
+ - **`ready`** — task sources have eligible changes and no config-lift
95
+ conflict; a real `apply` will clear them.
96
+ - **`blocked`** — at least one item cannot be applied automatically (a
97
+ genuine authoring problem, e.g. a task file the migrator can't rewrite
98
+ unambiguously, or a config `extraParams` value that conflicts with its
99
+ first-class field). `blockers` names each one. `apply` still applies
100
+ everything it *can*, then reports `blocked` and exits non-zero.
101
+
102
+ A pending `state.db` migration needs no human decision, so it is never
103
+ `blocked`: under `akm migrate status` or `apply --dry-run` it reads as
104
+ `ready` (the run would change `state.db`) and is listed in
105
+ `stateMigrations.pending`; a real `apply` always applies it.
106
+
107
+ Key fields:
108
+
109
+ | Field | Meaning |
110
+ | --- | --- |
111
+ | `stateMigrations` | `{ pending: string[] }` under `status`/`--dry-run`; `{ applied: string[], safetyCopyPath?: string }` after a real `apply`. `safetyCopyPath` is present only when a historical-destructive migration ran — see below. |
112
+ | `taskV3Migration` / `taskV4Migration` | Per-generation summary: `changed`/`skipped`/`blocked` counts and a `files[]` array with each file's `status` and `reason`. |
113
+ | `backupPath` / `taskV4BackupPath` | Present after a real apply that changed at least one file in that generation — a timestamped snapshot directory. |
114
+ | `deadResidue` / `staleTxns` | Present only when a bundle is configured. `{ pending: [...] }` under a read-only run, `{ removed: [...] }` / `{ recovered: [...] }` after apply. |
115
+
116
+ **Backups and their retention:**
117
+
118
+ - Task-source backups (task-v2→v3 and v3→v4) go to
119
+ `<dataDir>/backups/task-v3/<timestamp>-<uuid>/` and
120
+ `<dataDir>/backups/task-v4/<timestamp>-<uuid>/` respectively — one
121
+ directory per apply run, pruned to the **5 most recent** automatically.
122
+ - Config backups go to `<cacheDir>/config-backups/config-<ISO-ts>.json`,
123
+ also capped at **5**.
124
+ - A `state.db` historical-destructive migration writes a verified sibling
125
+ snapshot next to `state.db` itself:
126
+ `state.db.pre-<migrationId>.<UTC-digits>.<UUID>.bak`. This one is **never
127
+ auto-pruned** — it is a one-time, rare event (a single destructive ledger
128
+ boundary), so clean it up yourself once you've confirmed the upgrade, if
129
+ disk space matters to your image.
130
+
131
+ **Exit codes:**
132
+
133
+ | Exit | Meaning |
134
+ | --- | --- |
135
+ | `0` | `current` or `ready`-and-applied — nothing left to do. |
136
+ | `1` | `blocked` — at least one item needs a human. |
137
+ | anything else | A crash, not a migration outcome: `{"ok":false,"error":"...","code"?:"...","hint"?:"..."}` on stderr, with the exit code from akm's normal error-classification table (`2` usage, `70` internal, `78` config). |
138
+
139
+ **Idempotency:** re-running `apply` against a `current` database/tree is a
140
+ no-op — safe to put in every boot, every time, on every replica.
141
+
142
+ **Offline, no root:** every step reads and writes files/SQLite it already
143
+ owns under your configured `$DATA`/`$CACHE`/bundle directories. Nothing here
144
+ touches the network or needs elevated privileges.
145
+
146
+ ## `akm upgrade` in a bundled install
147
+
148
+ ```sh
149
+ akm upgrade # install a newer release if there is one, then migrate
150
+ akm upgrade --check # check for updates only — no install, no migration step
151
+ ```
152
+
153
+ `akm upgrade` always runs the migration step after its install step — on
154
+ install success, on install failure, and when there was nothing to install.
155
+ Its result carries the same plan under `migration`:
156
+
157
+ ```json
158
+ {
159
+ "currentVersion": "0.9.8",
160
+ "newVersion": "0.9.9",
161
+ "upgraded": false,
162
+ "installMethod": "package-local",
163
+ "message": "akm runs as a dependency of the package at /opt/myapp; upgrade that package to move akm.",
164
+ "migration": { "schemaVersion": 1, "status": "current", "blockers": [], "stateMigrations": { "pending": [] } }
165
+ }
166
+ ```
167
+
168
+ **`installMethod: "package-local"`** is the detection that matters for a
169
+ bundler: an `akm` living inside *another* package's `node_modules` (your
170
+ image's `tools/` dependency, a plugin's own `node_modules/akm-cli`) is never
171
+ `npm install -g`'d over — that would silently diverge from the copy the
172
+ parent package actually executes. akm reports `upgraded: false` and names the
173
+ parent package to upgrade instead; **its migrations still run**, exactly as
174
+ in every other install method. `installMethod` is one of `"binary"`,
175
+ `"bun"`, `"npm"`, `"pnpm"`, `"package-local"`, or `"unknown"`.
176
+
177
+ **Exit code:** `akm upgrade` exits `1` when `migration.status` is `"blocked"`
178
+ or `"failed"`, even if the install itself succeeded — an upgrade whose
179
+ migration didn't finish is not done. This makes plain `akm upgrade` (no
180
+ flags) a safe, idempotent container entrypoint step on every boot: on an
181
+ already-current install with nothing pending it is a fast no-op that exits 0.
182
+
183
+ **`--check`** skips the migration step entirely — it only compares versions
184
+ and reports `updateAvailable`. Use it for a version-drift alert, not as your
185
+ boot check.
186
+
187
+ ## `akm health`
188
+
189
+ ```sh
190
+ akm health --format json
191
+ ```
192
+
193
+ Read the JSON. Never parse the human-readable text or grep for a phrase —
194
+ that wording is not a stable interface.
195
+
196
+ **Exit codes:** `0` (`status: "pass"`), `4` (`status: "warn"`), `1`
197
+ (`status: "fail"`).
198
+
199
+ The check that replaces grepping akm's refusal text is a **hard check**
200
+ named `state-db-migrations`:
201
+
202
+ ```json
203
+ {
204
+ "name": "state-db-migrations",
205
+ "status": "fail",
206
+ "message": "1 pending state.db migration(s) (018-drop-dead-lane-schema); run `akm migrate apply`.",
207
+ "evidence": { "path": "/data/akm/state.db", "pending": ["018-drop-dead-lane-schema"] }
208
+ }
209
+ ```
210
+
211
+ It reads `evidence.pending` — a read-only preflight, never the managed open
212
+ — so `akm health` can report this state even though **an ordinary command
213
+ that opens `state.db` refuses to touch a pending historical-destructive
214
+ migration by design**, naming `akm upgrade` and `akm migrate apply` as the
215
+ only two commands allowed to apply one. Before this check existed, that
216
+ refusal surfaced as a crash (config-error exit) instead of a normal `fail`
217
+ row — this is what a bundler should now watch for `state-db-migrations` to
218
+ report, instead of grepping error text for a fixed remedy string.
219
+
220
+ ## The `akm-migrate` executable
221
+
222
+ `akm-migrate` is the second `bin` entry the `akm-cli` npm package installs
223
+ (`akm` and `akm-migrate` side by side), and it is also embedded directly in
224
+ the compiled standalone binary — a release binary re-execs itself internally
225
+ to reach it, so it needs no separate download. Same surface either way:
226
+
227
+ ```sh
228
+ akm-migrate status
229
+ akm-migrate apply --dry-run
230
+ akm-migrate apply
231
+ ```
232
+
233
+ `akm migrate status`/`apply` (under the `akm` CLI) are a thin wrapper over
234
+ this same executable — same plan, same exit codes. Reach for `akm-migrate`
235
+ directly when you don't want `akm`'s `--format`/`--shape`/`--detail` output
236
+ handling in the way, e.g. a shell script that just wants the raw JSON line
237
+ on stdout and a plain exit code.
238
+
239
+ ## Container entrypoint sketch
240
+
241
+ ```sh
242
+ #!/bin/sh
243
+ set -e
244
+ akm --version # optional: confirm the pinned version actually landed
245
+ akm migrate apply # offline, idempotent — exits 1 only if genuinely blocked
246
+ akm task sync --rebind # only if you schedule akm tasks inside this image
247
+ akm health --format json # read this JSON; act on ok/status, never on message text
248
+ ```
249
+
250
+ ### Environment variables
251
+
252
+ A bundler that controls the filesystem layout should set these explicitly
253
+ rather than rely on `$HOME`-derived defaults (names verified against
254
+ `src/core/paths.ts` and `src/tasks/scheduler-invocation.ts`):
255
+
256
+ | Variable | What it points at |
257
+ | --- | --- |
258
+ | `AKM_BUNDLE_DIR` | The content bundle (`$STASH`) — assets akm indexes and serves. |
259
+ | `AKM_CONFIG_DIR` | `config.json`'s directory. |
260
+ | `AKM_DATA_DIR` | Durable, non-regenerable data: **`index.db` and `state.db` live here.** This is the directory a migration snapshot's safety copy sits beside. |
261
+ | `AKM_CACHE_DIR` | Regenerable cache: registry downloads, config backups, task logs. Safe to discard between image builds (not between boots of the same running install). |
262
+ | `AKM_STATE_DIR` | **Not** where `state.db` lives, despite the name — this is the XDG "state" directory used for scheduled-task invocation context and companion-plugin hook state (Claude Code / OpenCode hook logs). Set it anyway if you schedule akm tasks inside the image, so that context is captured consistently rather than falling back to `$HOME/.local/state/akm`. |
263
+
264
+ Set all five to paths that persist across container restarts (a mounted
265
+ volume), or `akm migrate apply` will see an empty `state.db` on every boot
266
+ and never actually converge.
267
+
268
+ ## One-way note: `state.db` migrations are one-way
269
+
270
+ Once a migration has run against `state.db`, an **older** akm binary that
271
+ later opens the same file refuses it outright — the migration ledger moved
272
+ forward, and there is no downgrade path. If you need the ability to roll
273
+ back a bundled akm version, take your own backup of the whole data directory
274
+ (`$AKM_DATA_DIR`) before running `akm upgrade`/`akm migrate apply` — the
275
+ per-migration safety copy described above exists to protect that one
276
+ destructive step, not as a general rollback mechanism for your deployment.
@@ -18,23 +18,19 @@ Managed current databases apply exact-prefix additive schema migrations
18
18
  automatically. Released migration 002's `task_history` table rebuild is also
19
19
  automatic and preserves every existing row. Released migration 018 is the
20
20
  exception: it removes retired dead-lane state, so an ordinary command stops
21
- before that migration and tells you to run:
21
+ before that migration and tells you to run either of:
22
22
 
23
23
  ```sh
24
- akm upgrade --force
24
+ akm upgrade # applies pending state migrations first, then installs if a release is newer
25
+ akm migrate apply # applies them alongside the task-source and config migrations
25
26
  ```
26
27
 
27
- Where akm cannot reinstall itself a container that ships the CLI globally,
28
- an unprivileged runtime user that command fails at its install step
29
- (`EACCES` on the global module directory) before it ever reaches the
30
- migration. Use the install-free route instead:
31
-
32
- ```sh
33
- akm upgrade --state-only
34
- ```
35
-
36
- Both admit migration 018 and both take the same verified safety copy described
37
- below; `--state-only` simply skips the executable replacement. Immediately before
28
+ Both run on an already-current install and never need an install to reach
29
+ the migration, so either is safe as a container entrypoint: on a current
30
+ database the step is a no-op. `akm upgrade` installs a newer release first
31
+ when there is one, then runs `akm-migrate apply` — the migrator that shipped
32
+ with whatever is now installed; `akm migrate apply` is that step without the
33
+ release check. Immediately before
38
34
  the migration, AKM takes a SQLite writer-exclusion lock, rechecks the exact
39
35
  ledger, and writes a consistent SQLite snapshot beside the database as
40
36
  `state.db.pre-018-drop-dead-lane-schema.<UTC-digits>.<UUID>.bak`. The randomized
@@ -51,7 +47,7 @@ general storage migrator.
51
47
 
52
48
  An existing database with no applied migration IDs is never treated as a fresh
53
49
  install, whether its `schema_migrations` table is absent or empty. Ordinary
54
- commands reject it without writing. The explicit `akm upgrade --force` (or `--state-only`) path
50
+ commands reject it without writing. The explicit `akm upgrade` / `akm migrate apply` path
55
51
  takes and verifies a descriptor-bound snapshot named
56
52
  `state.db.pre-001-initial-schema.<UTC-digits>.<UUID>.bak` before it creates the
57
53
  ledger or applies any migration from 001 through 022. A truly new database is
@@ -13,12 +13,14 @@ steps; no run data is lost.
13
13
  The released 0.9.1 state ledger already includes historical migration 018, so
14
14
  the normal 0.9.1→0.9.2 database step is additive and automatic. If a pre-release
15
15
  database has an exact prefix before 018, ordinary commands stop before its
16
- destructive dead-lane cleanup. Run `akm upgrade --force`; after successful
17
- executable replacement AKM creates and verifies a sibling pre-018 SQLite safety
18
- copy before applying that immutable released migration. Its locked ledger
19
- recheck, snapshot, and migration share one writer-exclusion window, so another
20
- WAL writer cannot commit between the copy and 018. Unknown or divergent ledgers
21
- remain unsupported.
16
+ destructive dead-lane cleanup. Run `akm migrate apply` (or `akm upgrade`, which
17
+ runs it after its install step regardless of whether an install happened): AKM
18
+ creates and verifies a sibling pre-018 SQLite safety copy before applying that
19
+ immutable released migration. Its locked ledger recheck, snapshot, and
20
+ migration share one writer-exclusion window, so another WAL writer cannot
21
+ commit between the copy and 018. Unknown or divergent ledgers remain
22
+ unsupported. See [Bundling akm](../integration/bundling-akm.md) for the full
23
+ migration contract.
22
24
 
23
25
  ## Before you upgrade
24
26
 
@@ -250,16 +252,10 @@ Common blocked reasons and what to do about each:
250
252
  | `read-only-source` | The owning source or file is not writable. | Move or re-source the file somewhere writable, or edit it by hand. |
251
253
  | `invalid-v3-task` | The v3 document itself is structurally invalid (unknown fields, missing selector, malformed trigger, etc). | Fix the underlying v3 document first — the migrator translates structure, it does not repair it. |
252
254
 
253
- If you only want to run this generation in isolation (for example, your
254
- tree is already all `version: 3` and you want to preview just this step),
255
- the frozen migrator's standalone entry points remain available as a
256
- separate executable, installed alongside `akm`:
257
-
258
- ```sh
259
- akm-migrate task-v4-status
260
- akm-migrate task-v4-apply --dry-run
261
- akm-migrate task-v4-apply
262
- ```
255
+ The migrator is also installed as its own executable, `akm-migrate`, with the
256
+ same `status` / `apply [--dry-run]` surface as `akm migrate`. A tree that is
257
+ already all `version: 3` simply reports the first generation as current and
258
+ runs this one.
263
259
 
264
260
  ## Task history result vocabulary (`target.kind`)
265
261
 
@@ -282,7 +282,7 @@ Primary result fields:
282
282
  | Field | Description |
283
283
  | --- | --- |
284
284
  | `status` | Overall health verdict: `pass`, `warn`, or `fail` |
285
- | `hardChecks` | Deterministic checks such as `state-db-schema`, `state-db-round-trip`, `task-log-backing`, `active-runs`, `default-engine`, and `model-map-files` |
285
+ | `hardChecks` | Deterministic checks such as `state-db-schema`, `state-db-round-trip`, `state-db-migrations`, `task-log-backing`, `active-runs`, `default-engine`, and `model-map-files` |
286
286
  | `advisories` | Non-fatal warnings including `semantic-search-runtime` and `session-extraction` (akmExtract pipeline health) |
287
287
  | `metrics` | Aggregate task/runtime metrics: `taskFailRate`, `agentFailureRate`, `stuckActiveRuns`, `logBackingRate`, `probeRoundTripMs` |
288
288
  | `improve` | Recent improve-loop counts derived from `improve_invoked`, `improve_skipped`, and `improve_completed` events |
@@ -292,6 +292,14 @@ memory-prune actions, memory-inference writes, graph-extraction refreshes,
292
292
  session-extraction outcomes (`sessionsScanned`, `sessionsExtracted`, `proposalsCreated`),
293
293
  dead-URL detections, and skip reasons observed in the selected time window.
294
294
 
295
+ `state-db-migrations` reports whether `state.db`'s migration ledger has any
296
+ pending entries (checked read-only, without applying anything). It `fail`s
297
+ when migrations are pending — naming them and pointing at `akm migrate apply`
298
+ — rather than the command crashing, which is what happens when `state.db`
299
+ holds a pending historical-destructive migration and something other than
300
+ `akm upgrade` / `akm migrate apply` opens it directly. Read this check's
301
+ `status` instead of grepping akm's error text for that case.
302
+
295
303
  The `session-extraction` advisory reflects the health of the `akmExtract` pipeline
296
304
  (Phase 0.4 of `akm improve`). It warns on harness errors or when no proposals are
297
305
  generated across five or more scanned sessions.
@@ -1010,33 +1018,45 @@ Upgrade `akm` itself to the latest release. Standalone binaries are downloaded,
1010
1018
  checksummed, and staged before replacement; npm, Bun, and pnpm global installs
1011
1019
  use their package manager.
1012
1020
 
1013
- Upgrade replaces the installed program and then rebuilds the derived index.
1014
- It does not run legacy config, database, or workflow migration paths. Standalone
1015
- downloads use a temporary rollback copy only during atomic executable replacement.
1021
+ Upgrade replaces the installed program when a newer release exists, then runs
1022
+ `akm-migrate apply` the migrator that shipped with whatever is now installed
1023
+ and rebuilds the derived index. The migration step runs on every
1024
+ `akm upgrade`, install or no install, and its plan is reported under
1025
+ `migration`; an upgrade whose migration is blocked or could not run exits 1.
1026
+ That makes `akm upgrade` safe as a container entrypoint: on a current
1027
+ installation it is a no-op. An akm installed as a dependency of another
1028
+ package (`installMethod: "package-local"`) is never reinstalled — the parent
1029
+ package owns that copy — but its migrations still run. Standalone downloads
1030
+ use a temporary rollback copy only during atomic executable replacement.
1016
1031
 
1017
1032
  Standalone downloads are streamed directly to the staged file while SHA-256 is
1018
1033
  computed, with a 256 MiB binary limit. Release/checksum metadata is capped at
1019
1034
  1 MiB; an oversized response is cancelled and the staged file is removed.
1020
1035
 
1021
1036
  ```sh
1022
- akm upgrade # Download and replace the running binary
1023
- akm upgrade --check # Check for updates without installing
1024
- akm upgrade --force # Force upgrade even if already on latest
1025
- akm upgrade --state-only # Apply pending state.db migrations; install nothing
1037
+ akm upgrade # Install a newer release if there is one, then run every pending migration
1038
+ akm upgrade --check # Check for updates without installing (no migration step)
1039
+ akm upgrade --force # Force the install even if already on latest
1026
1040
  ```
1027
1041
 
1028
1042
  | Flag | Description |
1029
1043
  | --- | --- |
1030
1044
  | `--check` | Check for updates without installing |
1031
1045
  | `--force` | Force upgrade even if on latest version |
1032
- | `--state-only` | Apply pending `state.db` migrations without installing a new akm. For installs that cannot rewrite their own binary — a container shipping akm globally, an unprivileged runtime user — where the install step would fail `EACCES` before the migration could run. Takes the same verified safety copy as `--force`; it changes who may request the migration, not what it does. |
1033
1046
  | `--skip-post-upgrade` | Skip the post-upgrade index rebuild |
1034
1047
 
1048
+ Offline, or to migrate without a release check, run `akm migrate apply`
1049
+ directly: it is the same step.
1050
+
1035
1051
  Checksum verification is not optional and has no flag. If a release's
1036
1052
  `checksums.txt` is genuinely unreachable, the recovery hatch is the
1037
1053
  `AKM_UPGRADE_SKIP_CHECKSUM=1` environment variable (Internal — deliberately
1038
1054
  not a discoverable, tab-completable flag). See STABILITY.md.
1039
1055
 
1056
+ Shipping akm inside your own product (a Docker image, a plugin's own
1057
+ `node_modules`)? See [Bundling akm](../integration/bundling-akm.md) for the
1058
+ full boot contract, JSON shapes, and exit codes.
1059
+
1040
1060
  ### clone
1041
1061
 
1042
1062
  Copy an asset from any source into a managed writable bundle or an unmanaged
@@ -1440,33 +1460,41 @@ akm registry remove my-team --yes # Skip the confirmation prompt
1440
1460
 
1441
1461
  ### migrate
1442
1462
 
1443
- Inspect or apply the explicit, one-way conversion of on-disk task sources to
1444
- task source v4, the only grammar normal task execution accepts. `akm migrate`
1445
- runs **both** migration generations in one pass task-v2 to task-v3, then
1446
- task-v3 to task source v4 against the resulting files each keeping its own
1447
- lock, backup, prevalidation, and rollback, so a file blocked in the first
1448
- generation does not stop the second generation from converting files that are
1449
- already `version: 3`. Database schema upgrades are additive and run
1450
- automatically when `state.db` opens; config and workflow formats have no
1451
- runtime compatibility migrator.
1463
+ Inspect or apply every pending migration in one plan. `akm migrate` is a thin
1464
+ wrapper over the standalone `akm-migrate` executable (installed alongside
1465
+ `akm`, and embedded in the release binary), which owns every historical shape
1466
+ akm has ever written so the CLI proper reads only current schemas. The steps,
1467
+ in order:
1468
+
1469
+ 1. legacy config `extraParams` keys lifted onto first-class engine fields
1470
+ (`configExtraParams`);
1471
+ 2. pending `state.db` migrations, historical-destructive ones included, with
1472
+ a verified sibling safety copy (`stateMigrations`) — the only path besides
1473
+ `akm upgrade` that admits released migration 018, which an ordinary
1474
+ command refuses;
1475
+ 3. task-v2 files to task v3, then task-v3 files to task source v4
1476
+ (`taskV3Migration`, `taskV4Migration`), each keeping its own lock, backup,
1477
+ prevalidation, and rollback, so a file blocked in the first generation does
1478
+ not stop the second from converting files already at `version: 3`;
1479
+ 4. superseded pre-0.9.0 `.akm` residue and stale filesystem transactions
1480
+ (`deadResidue`, `staleTxns`).
1452
1481
 
1453
1482
  ```sh
1454
1483
  akm migrate status
1455
1484
  akm migrate apply --dry-run
1456
1485
  akm migrate apply
1457
- ```
1458
-
1459
- `status` and `apply --dry-run` are read-only. Apply refuses blocked task
1460
- sources, backs up each changed file immediately before replacement, and
1461
- atomically publishes strict task source v4 YAML; it is idempotent per
1462
- generation, skipping a file already at that generation's target version
1463
- (`already-v3`, `already-v4`). To run only the second generation in isolation
1464
- (for example, previewing just the v3-to-v4 step against a tree that is
1465
- already all `version: 3`), the frozen migrator's standalone entry points
1466
- remain available as a separate executable: `akm-migrate task-v4-status` /
1467
- `akm-migrate task-v4-apply [--dry-run]`. See [Tasks: Migrating to task
1468
- source v4](tasks.md#migrating-to-task-source-v4) for the full blocked-reason
1469
- table and worked examples.
1486
+ akm-migrate apply # the same, without the akm wrapper
1487
+ ```
1488
+
1489
+ `status` and `apply --dry-run` are read-only. Apply skips blocked task
1490
+ sources rather than failing (and exits 1 while any remain), backs up each
1491
+ changed file immediately before replacement, and atomically publishes strict
1492
+ task source v4 YAML; it is idempotent, so a current installation is a no-op.
1493
+ `akm upgrade` runs `apply` after its install step. See [Tasks: Migrating to
1494
+ task source v4](tasks.md#migrating-to-task-source-v4) for the full
1495
+ blocked-reason table and worked examples, and
1496
+ [Bundling akm](../integration/bundling-akm.md) for the plan JSON shape and
1497
+ how to drive this from a container/image boot step.
1470
1498
 
1471
1499
  ### config
1472
1500
 
@@ -371,16 +371,10 @@ Two cases produce one today:
371
371
  v4 task is runnable manually with `akm task run` whether or not it has a
372
372
  `schedule:`. The migrated document simply has no `schedule:` key.
373
373
 
374
- If you only want to run one generation in isolation (for example, your
375
- tree is already all `version: 3` and you want to preview just the v4 step),
376
- the frozen migrator's standalone, single-generation entry points remain
377
- available as a separate executable:
378
-
379
- ```sh
380
- akm-migrate task-v4-status
381
- akm-migrate task-v4-apply --dry-run
382
- akm-migrate task-v4-apply
383
- ```
374
+ The migrator is also installed as its own executable, `akm-migrate`, with the
375
+ same `status` / `apply [--dry-run]` surface; `akm migrate` wraps it, and
376
+ `akm upgrade` runs `apply` after installing a release. A tree that is already
377
+ all `version: 3` simply reports the first generation as current.
384
378
 
385
379
  See the [0.9.1 to 0.9.2 migration guide](../migration/v0.9.1-to-v0.9.2.md#migrating-task-v3-to-task-source-v4)
386
380
  for full before/after examples and recovery guidance.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akm-cli",
3
- "version": "0.9.8",
3
+ "version": "0.9.9",
4
4
  "type": "module",
5
5
  "description": "akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cursor.",
6
6
  "keywords": [
@@ -51,6 +51,7 @@
51
51
  "docs/migration/v0.8-to-v0.9.md",
52
52
  "docs/migration/v0.9.0-troubleshooting.md",
53
53
  "docs/migration/v0.9.1-to-v0.9.2.md",
54
+ "docs/integration/bundling-akm.md",
54
55
  "docs/reference/bundle-types.md",
55
56
  "docs/reference/cli.md",
56
57
  "docs/reference/configuration.md",