akm-cli 0.10.261018-beta → 0.10.261020-beta

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.
@@ -875,9 +875,9 @@ function isMarkdownFenceClosing(line, fence) {
875
875
  * Version of what {@link projectMarkdownContent} indexes. It is part of every
876
876
  * directory's index fingerprint, so bumping it makes the next `akm index`
877
877
  * re-project every directory once instead of keeping the old text for
878
- * unchanged files. 2: identifiers from fenced code (akm#1260).
878
+ * unchanged files. 3: count each fenced identifier once per document (akm#1284).
879
879
  */
880
- export const CONTENT_PROJECTION_VERSION = 2;
880
+ export const CONTENT_PROJECTION_VERSION = 3;
881
881
  /** At or above this length a token with a digit reads as a key or hash, not a name. */
882
882
  const TOKEN_LIKE_MIN_LENGTH = 20;
883
883
  /**
@@ -1114,7 +1114,7 @@ export function projectMarkdownContent(body, truncationInfo) {
1114
1114
  const innerBlock = findInnerFrontmatterBlock(lines);
1115
1115
  const start = innerBlock && isFrontmatterShaped(lines, innerBlock) ? innerBlock.close + 1 : 0;
1116
1116
  const projected = [];
1117
- const fenced = [];
1117
+ const fenced = new Map();
1118
1118
  forEachVisibleMarkdownLine(lines, start, (_index, visible) => {
1119
1119
  let trimmed = visible.trim();
1120
1120
  if (!trimmed || /^(-{3,}|\*{3,}|_{3,}|=+)$/.test(trimmed))
@@ -1135,11 +1135,13 @@ export function projectMarkdownContent(body, truncationInfo) {
1135
1135
  projected.push(trimmed);
1136
1136
  }, (_index, fencedLine) => {
1137
1137
  const identifiers = fencedCodeIdentifiers(fencedLine);
1138
- if (identifiers)
1139
- fenced.push(identifiers);
1138
+ for (const identifier of identifiers.split(/\s+/)) {
1139
+ if (identifier)
1140
+ fenced.set(identifier.toLowerCase(), identifier);
1141
+ }
1140
1142
  });
1141
1143
  // Fenced identifiers follow the prose, so a head-truncated embedding input keeps the prose first.
1142
- const text = [...projected, ...fenced].join(" ").replace(/\s+/g, " ").trim();
1144
+ const text = [...projected, ...fenced.values()].join(" ").replace(/\s+/g, " ").trim();
1143
1145
  if (!text)
1144
1146
  return undefined;
1145
1147
  if (truncationInfo)
@@ -680,11 +680,15 @@ function formatUpgradePluginLines(r) {
680
680
  }
681
681
  const plugins = Array.isArray(r.plugins) ? r.plugins : [];
682
682
  for (const p of plugins) {
683
- if (p.outcome === "current" || p.outcome === "skipped")
683
+ if ((p.outcome === "current" || p.outcome === "skipped") &&
684
+ (r.updateAvailable === undefined || !p.channel) &&
685
+ !p.channelMismatch &&
686
+ !p.targetChannel)
684
687
  continue;
685
688
  const versions = p.from && p.to && p.from !== p.to ? ` v${p.from} → v${p.to}` : "";
689
+ const channel = p.channel ? ` [channel ${p.channel}${p.channelMismatch ? "; differs from CLI" : ""}]` : "";
686
690
  const note = p.message ? ` (${p.message})` : "";
687
- lines.push(`${HARNESS_LABELS[String(p.harness)] ?? p.harness} plugin ${p.outcome}${versions}${note}`);
691
+ lines.push(`${HARNESS_LABELS[String(p.harness)] ?? p.harness} plugin ${p.outcome}${versions}${channel}${note}`);
688
692
  }
689
693
  return lines;
690
694
  }
@@ -698,8 +702,11 @@ function formatUpgradeHead(r) {
698
702
  if (r.upgraded === true) {
699
703
  return `akm upgraded: v${r.currentVersion} → v${r.newVersion}`;
700
704
  }
705
+ if (r.targetChannel && r.latestVersion && r.latestVersion !== r.currentVersion && r.updateAvailable === false) {
706
+ return `akm v${r.currentVersion} → v${r.latestVersion} requires --allow-downgrade (run 'akm upgrade --channel ${r.targetChannel} --allow-downgrade' to install)`;
707
+ }
701
708
  if (r.updateAvailable === true) {
702
- return `akm v${r.currentVersion} → v${r.latestVersion} available (run 'akm upgrade${r.channel === "next" ? " --next" : ""}' to install)`;
709
+ return `akm v${r.currentVersion} → v${r.latestVersion} available (run 'akm upgrade${r.targetChannel ? ` --channel ${r.targetChannel}` : r.channel === "next" ? " --next" : ""}' to install)`;
703
710
  }
704
711
  if (r.updateAvailable === false && r.latestVersion) {
705
712
  return `akm v${r.currentVersion} is already the latest version`;
@@ -34812,7 +34812,7 @@ function projectMarkdownContent(body, truncationInfo) {
34812
34812
  const innerBlock = findInnerFrontmatterBlock(lines);
34813
34813
  const start = innerBlock && isFrontmatterShaped(lines, innerBlock) ? innerBlock.close + 1 : 0;
34814
34814
  const projected = [];
34815
- const fenced = [];
34815
+ const fenced = new Map;
34816
34816
  forEachVisibleMarkdownLine(lines, start, (_index, visible) => {
34817
34817
  let trimmed = visible.trim();
34818
34818
  if (!trimmed || /^(-{3,}|\*{3,}|_{3,}|=+)$/.test(trimmed))
@@ -34832,10 +34832,12 @@ function projectMarkdownContent(body, truncationInfo) {
34832
34832
  projected.push(trimmed);
34833
34833
  }, (_index, fencedLine) => {
34834
34834
  const identifiers = fencedCodeIdentifiers(fencedLine);
34835
- if (identifiers)
34836
- fenced.push(identifiers);
34835
+ for (const identifier of identifiers.split(/\s+/)) {
34836
+ if (identifier)
34837
+ fenced.set(identifier.toLowerCase(), identifier);
34838
+ }
34837
34839
  });
34838
- const text2 = [...projected, ...fenced].join(" ").replace(/\s+/g, " ").trim();
34840
+ const text2 = [...projected, ...fenced.values()].join(" ").replace(/\s+/g, " ").trim();
34839
34841
  if (!text2)
34840
34842
  return;
34841
34843
  if (truncationInfo)
@@ -70528,11 +70530,12 @@ function formatUpgradePluginLines(r) {
70528
70530
  }
70529
70531
  const plugins = Array.isArray(r.plugins) ? r.plugins : [];
70530
70532
  for (const p of plugins) {
70531
- if (p.outcome === "current" || p.outcome === "skipped")
70533
+ if ((p.outcome === "current" || p.outcome === "skipped") && (r.updateAvailable === undefined || !p.channel) && !p.channelMismatch && !p.targetChannel)
70532
70534
  continue;
70533
70535
  const versions = p.from && p.to && p.from !== p.to ? ` v${p.from} → v${p.to}` : "";
70536
+ const channel = p.channel ? ` [channel ${p.channel}${p.channelMismatch ? "; differs from CLI" : ""}]` : "";
70534
70537
  const note = p.message ? ` (${p.message})` : "";
70535
- lines.push(`${HARNESS_LABELS[String(p.harness)] ?? p.harness} plugin ${p.outcome}${versions}${note}`);
70538
+ lines.push(`${HARNESS_LABELS[String(p.harness)] ?? p.harness} plugin ${p.outcome}${versions}${channel}${note}`);
70536
70539
  }
70537
70540
  return lines;
70538
70541
  }
@@ -70547,8 +70550,11 @@ function formatUpgradeHead(r) {
70547
70550
  if (r.upgraded === true) {
70548
70551
  return `akm upgraded: v${r.currentVersion} → v${r.newVersion}`;
70549
70552
  }
70553
+ if (r.targetChannel && r.latestVersion && r.latestVersion !== r.currentVersion && r.updateAvailable === false) {
70554
+ return `akm v${r.currentVersion} → v${r.latestVersion} requires --allow-downgrade (run 'akm upgrade --channel ${r.targetChannel} --allow-downgrade' to install)`;
70555
+ }
70550
70556
  if (r.updateAvailable === true) {
70551
- return `akm v${r.currentVersion} → v${r.latestVersion} available (run 'akm upgrade${r.channel === "next" ? " --next" : ""}' to install)`;
70557
+ return `akm v${r.currentVersion} → v${r.latestVersion} available (run 'akm upgrade${r.targetChannel ? ` --channel ${r.targetChannel}` : r.channel === "next" ? " --next" : ""}' to install)`;
70552
70558
  }
70553
70559
  if (r.updateAvailable === false && r.latestVersion) {
70554
70560
  return `akm v${r.currentVersion} is already the latest version`;
@@ -34811,7 +34811,7 @@ function projectMarkdownContent(body, truncationInfo) {
34811
34811
  const innerBlock = findInnerFrontmatterBlock(lines);
34812
34812
  const start = innerBlock && isFrontmatterShaped(lines, innerBlock) ? innerBlock.close + 1 : 0;
34813
34813
  const projected = [];
34814
- const fenced = [];
34814
+ const fenced = new Map;
34815
34815
  forEachVisibleMarkdownLine(lines, start, (_index, visible) => {
34816
34816
  let trimmed = visible.trim();
34817
34817
  if (!trimmed || /^(-{3,}|\*{3,}|_{3,}|=+)$/.test(trimmed))
@@ -34831,10 +34831,12 @@ function projectMarkdownContent(body, truncationInfo) {
34831
34831
  projected.push(trimmed);
34832
34832
  }, (_index, fencedLine) => {
34833
34833
  const identifiers = fencedCodeIdentifiers(fencedLine);
34834
- if (identifiers)
34835
- fenced.push(identifiers);
34834
+ for (const identifier of identifiers.split(/\s+/)) {
34835
+ if (identifier)
34836
+ fenced.set(identifier.toLowerCase(), identifier);
34837
+ }
34836
34838
  });
34837
- const text2 = [...projected, ...fenced].join(" ").replace(/\s+/g, " ").trim();
34839
+ const text2 = [...projected, ...fenced.values()].join(" ").replace(/\s+/g, " ").trim();
34838
34840
  if (!text2)
34839
34841
  return;
34840
34842
  if (truncationInfo)
@@ -70526,11 +70528,12 @@ function formatUpgradePluginLines(r) {
70526
70528
  }
70527
70529
  const plugins = Array.isArray(r.plugins) ? r.plugins : [];
70528
70530
  for (const p of plugins) {
70529
- if (p.outcome === "current" || p.outcome === "skipped")
70531
+ if ((p.outcome === "current" || p.outcome === "skipped") && (r.updateAvailable === undefined || !p.channel) && !p.channelMismatch && !p.targetChannel)
70530
70532
  continue;
70531
70533
  const versions = p.from && p.to && p.from !== p.to ? ` v${p.from} \u2192 v${p.to}` : "";
70534
+ const channel = p.channel ? ` [channel ${p.channel}${p.channelMismatch ? "; differs from CLI" : ""}]` : "";
70532
70535
  const note = p.message ? ` (${p.message})` : "";
70533
- lines.push(`${HARNESS_LABELS[String(p.harness)] ?? p.harness} plugin ${p.outcome}${versions}${note}`);
70536
+ lines.push(`${HARNESS_LABELS[String(p.harness)] ?? p.harness} plugin ${p.outcome}${versions}${channel}${note}`);
70534
70537
  }
70535
70538
  return lines;
70536
70539
  }
@@ -70545,8 +70548,11 @@ function formatUpgradeHead(r) {
70545
70548
  if (r.upgraded === true) {
70546
70549
  return `akm upgraded: v${r.currentVersion} \u2192 v${r.newVersion}`;
70547
70550
  }
70551
+ if (r.targetChannel && r.latestVersion && r.latestVersion !== r.currentVersion && r.updateAvailable === false) {
70552
+ return `akm v${r.currentVersion} \u2192 v${r.latestVersion} requires --allow-downgrade (run 'akm upgrade --channel ${r.targetChannel} --allow-downgrade' to install)`;
70553
+ }
70548
70554
  if (r.updateAvailable === true) {
70549
- return `akm v${r.currentVersion} \u2192 v${r.latestVersion} available (run 'akm upgrade${r.channel === "next" ? " --next" : ""}' to install)`;
70555
+ return `akm v${r.currentVersion} \u2192 v${r.latestVersion} available (run 'akm upgrade${r.targetChannel ? ` --channel ${r.targetChannel}` : r.channel === "next" ? " --next" : ""}' to install)`;
70550
70556
  }
70551
70557
  if (r.updateAvailable === false && r.latestVersion) {
70552
70558
  return `akm v${r.currentVersion} is already the latest version`;
@@ -34,7 +34,7 @@ harness's `--version`, looks up the latest akm release on GitHub for `cli-versio
34
34
  Claude plugin is installed. With
35
35
  `--no-probe` none of these runs: the connection rows carry no `reachable` field and the
36
36
  message says reachability was not probed, and `plugin-version` reports `unknown` (its
37
- `AKM_VERSION_RANGE` check is local and still runs). The `plugin-version` and `cli-version`
37
+ `AKM_MIN_VERSION` (or legacy `AKM_VERSION_RANGE`) check is local and still runs). The `plugin-version` and `cli-version`
38
38
  lookups are best-effort and silent on failure; see `src/commands/health/plugin-staleness.ts`
39
39
  and `src/commands/health/version-drift.ts`.
40
40
 
@@ -62,7 +62,7 @@ and `src/commands/health/version-drift.ts`.
62
62
  | `type-directory-disagreement` | `warn`: an indexed asset's resolved type differs from the type its directory declares. Each line gives the file, both types, and the classifier signal that won (`winner`). | Review the listed files. Intended overrides (for example a `knowledge/` file containing `$ARGUMENTS`) are marked `known-good override`. |
63
63
  | `memory-cleanup-archive` | `pass` with size and file count when the working bundle has `.akm/memory-cleanup/archive` content and no git history (the purge does not run there). `warn` when a git-backed bundle has archived files the purge cannot remove (untracked, modified, or unverifiable in git). | Commit the archived files so the purge can remove them once they age out. |
64
64
  | `data-dir-usage` | `warn`: the data directory is over 1 GB and either more than 3 times the size of `state.db`, `index.db`, and `logs.db` (with their WAL and SHM files), or one top-level subdirectory holds more than half of it. The message names that subdirectory and its size. | Inspect the named subdirectory. `backups/tasks/` holds task-file snapshots written by `akm migrate apply`. |
65
- | `plugin-version` | One row per installed Claude Code harness plugin: its version and whether its declared `AKM_VERSION_RANGE` admits the running CLI. The newest version is read on the channel the marketplace clone follows: on the remote's default branch the newest stable tag, on any other branch (such as `next`) the release tag on that branch's head, and on a clone pinned to a tag that tag. The newest-tag lookup is `--probe`-gated: with `--no-probe` the row is `unknown` ("not probed") and only the local range check still runs. `warn`: the plugin is stale, or its range does not admit the running CLI. | Run the `claude plugin update <ref>` command in the message. |
65
+ | `plugin-version` | One row per native `claude plugin list --json` AKM installation, with scope, enabled state and install path; cached versions are never used to choose an installation. `AKM_MIN_VERSION` takes precedence over legacy `AKM_VERSION_RANGE`. Disabled and other-project installations are inert. Missing metadata or multiple applicable paths/versions produce `unknown`, since the native list does not identify which conflicting installation a running session loaded. The newest version is read on the channel the marketplace clone follows: on the remote's default branch the newest stable tag, on any other branch (such as `next`) the release tag on that branch's head, and on a clone pinned to a tag that tag. The newest-tag lookup is `--probe`-gated: with `--no-probe` the row is `unknown` ("not probed") and only the local range check still runs. `warn`: the plugin is stale, or its range does not admit the running CLI. | Run the `claude plugin update <ref>` command in the message. |
66
66
  | `opencode-plugin-version` | The same check for the OpenCode plugin's own bundled `akm-cli` (read from its `package.json`, never executed): it runs in-process and shares the host's databases, so its lag matters the same way. OpenCode 1 caches each spec in its own folder, so it reads the one folder of the spec the global OpenCode config names under `plugin` (`akm-opencode`, read as `akm-opencode@latest`, or `akm-opencode@next`), and for `akm-opencode@<version>` reports the plugin as pinned with no staleness verdict; with no entry, or no folder for it, it reads `packages/akm-opencode`, and it never scans the other cached specs. It reads local files only and is never `--probe`-gated. `warn`: the bundled version differs from the running CLI. Omitted when the OpenCode plugin is not installed. | Update the OpenCode plugin. |
67
67
  | `state-db-readable` | `fail`: `state.db` cannot be read, or is missing in a read-only run (which does not create it). Every other check is skipped, because none of them can read it. | Check the owner and mode of the data directory (or point `AKM_DATA_DIR` / `XDG_DATA_HOME` at one you own); run any akm command that records state, such as `akm health`, to create it. |
68
68
 
@@ -439,18 +439,24 @@ orchestration, checksum, and failure paths.
439
439
 
440
440
  `tests/integration/upgrade-rehearsal/` runs once per origin in
441
441
  `KNOWN_UPGRADE_ORIGINS` (`previous-release.ts`): `previous`, the highest stable
442
- release below the candidate's `package.json` version, and `0.9.15`, a fixed
443
- older release. For each origin it installs that release as a real global npm
442
+ release below the candidate's `package.json` version; `0.9.32`, which keeps the
443
+ full engines migration covered after `previous` advances to 0.10; and `0.9.15`,
444
+ which covers scheduler rows installed before host-local activation was recorded.
445
+ Each origin installs that release as a real global npm
444
446
  package (a real `npm pack` / `npm install --global` into a throwaway prefix, see
445
- `scripts/package-install.ts`), uses it to build a realistic home — filesystem,
447
+ `scripts/package-install.ts`). The `previous` and `0.9.32` origins build a full
448
+ home — filesystem,
446
449
  git (with an in-bundle symlink at its root), website, and npm bundles, scheduled
447
- tasks, a synced native (fake) crontab, and the 0.9 `engines` form the release
448
- itself writes (an llm engine with an `apiKeyFile`, an agent engine, a
449
- `models.json`, a command and a task source that name an engine) — then installs
450
+ tasks, a synced native (fake) crontab, and model configuration the release
451
+ itself writes: native connections/harnesses on 0.10+, or the full 0.9 `engines`
452
+ form (an llm engine with an `apiKeyFile`, agent engines, a `models.json`, and a
453
+ command and task that name an engine). The `0.9.15` origin uses a smaller home
454
+ with one directly installed scheduled task. Each then installs
450
455
  the CANDIDATE build OVER it in the SAME prefix: an in-place swap, the same thing
451
456
  a real `npm i -g akm-cli@…` or `bun add -g` upgrade does. It then runs the
452
457
  candidate against that home (`migrate status` / `apply`, which converts the
453
- `engines` form, files its key and rewrites the `engine:` keys, `bundle list`,
458
+ `engines` form, files its key and rewrites the `engine:` keys, or preserves
459
+ native connections/harnesses, `bundle list`,
454
460
  `search` / `show`, `task sync` dry-run and real — plain, no `--rebind`, as an
455
461
  upgrading user runs it — executing the generated cron command and confirming it
456
462
  ran the candidate, `task run`, `health`, `improve --dry-run`), and finally runs a
@@ -5,7 +5,7 @@ Upgrade guides and per-release migration notes.
5
5
  ## Current
6
6
 
7
7
  - [v0.9 -> v0.10 migration guide](v0.9-to-v0.10.md) -- Upgrade order for a 0.9.15+ install, every removed CLI spelling, strategy, config key and workflow feature with its replacement, changed `akm improve` defaults, the OpenCode plugins, and `0.10.YYMMNN` version numbers
8
- - [v0.10.0 release note](release-notes/0.10.0.md) -- Summary of the 0.10 series: the 0.9.15 version floor, `--shape`/`--target`/`help agents` removals, `default` and `consolidate` as the only built-in strategies, OpenCode 2, feedback-scaled ranking, and what is gone
8
+ - [v0.10.0 release note](release-notes/0.10.261019.md) -- Summary of the 0.10 series: the 0.9.15 version floor, `--shape`/`--target`/`help agents` removals, `default` and `consolidate` as the only built-in strategies, OpenCode 2, feedback-scaled ranking, and what is gone
9
9
  - [Release notes index](release-notes/README.md) -- The short per-release notes `akm help migrate <version>` prints, with links to every bundled note
10
10
 
11
11
  ## Historical
@@ -1,4 +1,4 @@
1
- # akm v0.10.0 migration notes
1
+ # akm v0.10 migration notes (0.10.261019, the first stable 0.10 release)
2
2
 
3
3
  0.10 is a clean-up and tuning release. It covers the daily alphas
4
4
  0.10.26101001 through 0.10.26101202 and the monthly releases that follow
@@ -87,7 +87,10 @@ not offer it.
87
87
  config keys, paths and dotted references such as `process.env.CI` in a fenced
88
88
  block now reach the `content` search text; quoted strings, assignment values,
89
89
  URLs and long token-like strings still do not. Search results, fragments and
90
- `akm show` are unchanged. On the first run after upgrading, `akm index` (and
90
+ `akm show` are unchanged. It lifts code lookups and keyword library search
91
+ in akm-eval and costs about 2 NDCG points on the SkillRet skill-selection
92
+ benchmark, where long, code-heavy skills now outrank short, code-free ones;
93
+ tuning is tracked in #1284. On the first run after upgrading, `akm index` (and
91
94
  the index `akm upgrade` runs, which `akm upgrade` waits for) re-reads every
92
95
  bundle directory and re-embeds only the entries whose embedded text changed.
93
96
  Text past `embedding.maxInputTokens` (default 512, about 2,048 characters) is
@@ -0,0 +1,22 @@
1
+ # akm v0.10.261020 migration notes
2
+
3
+ This beta fixes Claude plugin health reporting, adds coordinated channel
4
+ switching, and reduces repeated fenced identifiers in search content.
5
+
6
+ The next index pass refreshes existing content projections and changed embedding
7
+ inputs automatically. No manual database or configuration migration is required
8
+ when upgrading from 0.10.261019.
9
+
10
+ After installing this beta, `akm upgrade --channel next` switches the CLI and
11
+ installed Claude, Codex and OpenCode plugins together. The existing `--next`
12
+ flag keeps its prior behavior. To return to stable, use
13
+ `akm upgrade --channel latest`; if the target version is older, add
14
+ `--allow-downgrade`. Changed settings receive backups, and the command reports
15
+ any harness that could not finish switching. Restart the harness after updates.
16
+
17
+ Claude health now uses native installation metadata. It reports conflicting or
18
+ incomplete installation selections as unknown instead of choosing a cached
19
+ version. This can change a previous warning without changing the installation.
20
+
21
+ If upgrading from 0.9, first follow the
22
+ [0.9 to 0.10 migration guide](../v0.9-to-v0.10.md).
@@ -7,7 +7,8 @@ live one level up in `docs/migration/`.
7
7
 
8
8
  ## Available notes
9
9
 
10
- - [0.10.0](0.10.0.md) — The 0.10 series: 0.9.15 version floor, merged CLI spellings, `default`/`consolidate` strategies only, OpenCode 2, feedback-scaled ranking, and `0.10.YYMMNN` versions.
10
+ - [0.10.261020](0.10.261020.md) — Coordinated beta/stable channel switching, native Claude health, and automatic search-projection refresh.
11
+ - [0.10.261019](0.10.261019.md) — The 0.10 series: 0.9.15 version floor, merged CLI spellings, `default`/`consolidate` strategies only, OpenCode 2, feedback-scaled ranking, and `0.10.YYMMNN` versions.
11
12
  - [0.9.19](0.9.19.md) — Bundle-scoped improve planning, proposal reopen, and consolidate/distill guardrails.
12
13
  - [0.9.17](0.9.17.md) — Retire proposals, declared links replacing the LLM entity graph, and scoped improve planning.
13
14
  - [0.9.16](0.9.16.md) — Source-bound scheduler grants, local execution authority, and split unsafe overrides.
@@ -12,7 +12,7 @@ every change that can break a script, a task or a config.
12
12
  Checked against build 0.10.261013 with the connections and harnesses rework
13
13
  (#1244). The
14
14
  user-facing summary of the whole series is the
15
- [0.10.0 release note](release-notes/0.10.0.md); the per-build detail is in the
15
+ [0.10 release note](release-notes/0.10.261019.md); the per-build detail is in the
16
16
  [CHANGELOG](https://github.com/itlackey/akm/blob/main/CHANGELOG.md).
17
17
 
18
18
  ## What the upgrade preserves
@@ -801,7 +801,7 @@ below every daily alpha. Install the first monthly release explicitly once, for
801
801
  example `npm install -g akm-cli@0.10.261001-beta`; `akm upgrade` works normally
802
802
  from there.
803
803
 
804
- `akm help migrate 0.10.0` prints the [0.10.0 release note](release-notes/0.10.0.md).
804
+ `akm help migrate 0.10.261019` (or `akm help migrate latest`) prints the [0.10 release note](release-notes/0.10.261019.md).
805
805
  The loader looks a note up by its exact file name, so `akm help migrate latest`
806
806
  and a release number such as `0.10.261001` print the CHANGELOG section
807
807
  only.
@@ -1537,7 +1537,9 @@ computed, with a 256 MiB binary limit. Release/checksum metadata is capped at
1537
1537
  akm upgrade # Install a newer release if there is one, run every pending migration, then update the harness plugins
1538
1538
  akm upgrade --check # Report pending updates, CLI and plugins, without changing anything (no migration step)
1539
1539
  akm upgrade --force # Force the install even if already on latest
1540
- akm upgrade --next # Install the newest prerelease (@next) instead of the latest stable release
1540
+ akm upgrade --next # Include prereleases when selecting the newest CLI release
1541
+ akm upgrade --channel next # Switch CLI and installed harness plugins to next
1542
+ akm upgrade --channel latest # Return CLI and installed harness plugins to stable
1541
1543
  ```
1542
1544
 
1543
1545
  | Flag | Description |
@@ -1545,7 +1547,9 @@ akm upgrade --next # Install the newest prerelease (@next) instead of the
1545
1547
  | `--check` | Report pending updates (CLI and per-harness plugins) without changing anything |
1546
1548
  | `--force` | Force upgrade even if on latest version |
1547
1549
  | `--skip-post-upgrade` | Skip the post-upgrade index rebuild |
1548
- | `--next` | Follow the `next` prerelease channel: install the newest prerelease of akm (and of the OpenCode plugin, see the Prereleases section below). Works with `--check`, `--force` and `-q` |
1550
+ | `--channel latest\|next` | Switch the CLI and installed Claude, Codex and OpenCode plugins together; works with `--check` |
1551
+ | `--allow-downgrade` | Permit an older target version during an explicit `--channel` switch |
1552
+ | `--next` | Include prereleases when selecting the newest CLI release; retain configured harness channels (see below). Works with `--check`, `--force` and `-q` |
1549
1553
 
1550
1554
  Offline, or to migrate without a release check, run `akm migrate apply`
1551
1555
  directly: it is the same step.
@@ -1607,7 +1611,7 @@ target is the akm-cli that `akm-opencode@latest` depends on
1607
1611
  The result then carries `lockstep: { plugin, pinnedVersion, newestVersion, heldBack }`,
1608
1612
  `latestVersion` is the pinned version, the install names that exact version
1609
1613
  rather than `@latest`, and the text output says the CLI is held back. The CLI
1610
- is never moved backwards to meet the pin. With both OpenCode plugins present
1614
+ is never moved backwards to meet the pin unless an explicit channel switch also names `--allow-downgrade`. With both OpenCode plugins present
1611
1615
  (`akm-opencode` and `akm-opencode-v2`), the CLI is held to the older of their
1612
1616
  two pins, and `lockstep.plugin` names the one that sets it. Claude Code and Codex have no
1613
1617
  in-process copy and are not part of this rule.
@@ -1623,7 +1627,7 @@ retries.
1623
1627
  #### Prereleases (`--next`)
1624
1628
 
1625
1629
  `akm upgrade --next` means "newest available, prereleases included". The result
1626
- carries `channel: "next"` (`"latest"` otherwise); no other field changes.
1630
+ carries `channel: "next"` (`"latest"` otherwise), plus the CLI's inferred `currentChannel` and each installed plugin's configured `channel` and `channelMismatch`.
1627
1631
 
1628
1632
  - **CLI.** The target is the `next` dist-tag of `akm-cli` (read from the npm
1629
1633
  registry, so it works for binary installs too) when that is newer than the
@@ -1656,14 +1660,38 @@ carries `channel: "next"` (`"latest"` otherwise); no other field changes.
1656
1660
  - **Claude Code and Codex.** Their plugin comes from the `akm-plugins` git
1657
1661
  marketplace, and the channel is the ref the marketplace follows: the default
1658
1662
  branch is stable, the `next` branch carries prereleases. `akm upgrade` updates
1659
- whatever ref the marketplace follows and cannot switch it, with or without
1660
- `--next`. To follow prereleases, add the marketplace as `itlackey/akm-plugins@next`
1661
- in Claude Code (`extraKnownMarketplaces.akm-plugins.source.ref = "next"` in
1662
- `settings.json`), or run `codex plugin marketplace add itlackey/akm-plugins --ref next`
1663
- in Codex. Under `--next` their entries carry a note naming the ref followed.
1663
+ whatever ref the marketplace follows. `--next` alone leaves that ref unchanged;
1664
+ `--channel next` changes it. Under `--next` their entries carry a note naming
1665
+ the ref followed.
1664
1666
  The plugins need akm 0.10.261001 or newer; any newer akm passes.
1665
1667
  - `--check --next` reports all of this and changes nothing.
1666
1668
 
1669
+ #### Switching channels together (`--channel`)
1670
+
1671
+ `akm upgrade --channel next` selects the actual npm `next` CLI tag and changes
1672
+ installed harness plugins to their `next` branch or package tag.
1673
+ `--channel latest` selects stable and removes those prerelease refs. An explicit
1674
+ switch may replace a pinned plugin spec. The command checks target CLI and plugin
1675
+ versions first; if any would go backwards, repeat with `--allow-downgrade`.
1676
+ A failed CLI install or migration stops the plugin switch.
1677
+
1678
+ Claude and Codex retain their marketplace registrations, plugin enablement,
1679
+ `pluginConfigs`, and unrelated settings. akm edits only the marketplace ref,
1680
+ then uses the native updater. Claude's installed user, project and local scopes
1681
+ are updated explicitly. OpenCode changes only the installed plugin's spec,
1682
+ retaining tuple options and JSONC comments, then refreshes the desired cache.
1683
+ Every changed config file has a sibling `.akm-channel-*.bak` backup, whose path
1684
+ appears in that harness's `backups` result. Restart the harness to load updates.
1685
+
1686
+ `--check --channel next` shows `channel`, `targetChannel` and mismatches without
1687
+ writing settings or fetching Git marketplaces. The CLI's `currentChannel` is
1688
+ inferred from its version: prerelease means `next`, otherwise `latest`.
1689
+ A configured channel is distinct from a successful refresh: native update
1690
+ failures retain the configured target and backup paths but report `failed`.
1691
+ An installed plugin that cannot switch, including OpenCode awaiting shutdown,
1692
+ makes an explicit switch exit `1`; retry after resolving its reported cause.
1693
+ The switch is coordinated, not a transaction across independent harnesses.
1694
+
1667
1695
  #### Containers
1668
1696
 
1669
1697
  With the plugin step, a container entrypoint needs only:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akm-cli",
3
- "version": "0.10.261018-beta",
3
+ "version": "0.10.261020-beta",
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": [
@@ -119,9 +119,11 @@
119
119
  "citty": "^0.2.2",
120
120
  "dotenv": "^17.4.2",
121
121
  "fast-xml-parser": "^5.10.1",
122
+ "jsonc-parser": "^3.3.1",
122
123
  "marked": "^18.1.1",
123
124
  "node-html-parser": "^9.0.1",
124
125
  "semver": "^7.6.0",
126
+ "smol-toml": "^1.9.1",
125
127
  "turndown": "^7.2.4",
126
128
  "yaml": "^2.8.4",
127
129
  "zod": "^4.6.6"